diff --git a/src/amarokurls/AmarokUrlHandler.cpp b/src/amarokurls/AmarokUrlHandler.cpp index 5f8a1ecca0..db53cbde42 100644 --- a/src/amarokurls/AmarokUrlHandler.cpp +++ b/src/amarokurls/AmarokUrlHandler.cpp @@ -1,228 +1,228 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "AmarokUrlHandler.h" #include "GlobalCurrentTrackActions.h" #include "amarokurls/BookmarkMetaActions.h" #include "amarokurls/BookmarkModel.h" #include "amarokurls/ContextUrlGenerator.h" #include "amarokurls/NavigationUrlGenerator.h" #include "amarokurls/NavigationUrlRunner.h" #include "amarokurls/PlayUrlGenerator.h" #include "amarokurls/PlayUrlRunner.h" #include #include "core/meta/Meta.h" #include "core/support/Debug.h" #include "core-impl/storage/StorageManager.h" #include "core-impl/meta/timecode/TimecodeObserver.h" #include "playlist/PlaylistViewUrlGenerator.h" #include #include namespace The { static AmarokUrlHandler* s_AmarokUrlHandler_instance = 0; AmarokUrlHandler* amarokUrlHandler() { if( !s_AmarokUrlHandler_instance ) s_AmarokUrlHandler_instance = new AmarokUrlHandler(); return s_AmarokUrlHandler_instance; } } AmarokUrlHandler::AmarokUrlHandler() : QObject() , m_navigationRunner( 0 ) , m_playRunner ( 0 ) , m_timecodeObserver( 0 ) { DEBUG_BLOCK //init the bookmark model to make sure that db tables are created/updated if needed. BookmarkModel::instance(); //we init some of the default runners here. m_navigationRunner = new NavigationUrlRunner(); m_playlistViewRunner = new Playlist::ViewUrlRunner(); m_playRunner = new PlayUrlRunner(); m_timecodeObserver = new TimecodeObserver( this ); registerRunner( m_navigationRunner, m_navigationRunner->command() ); registerRunner( m_playRunner, m_playRunner->command() ); registerRunner( m_playlistViewRunner, m_playlistViewRunner->command() ); registerGenerator( ContextUrlGenerator::instance() ); registerGenerator( NavigationUrlGenerator::instance() ); registerGenerator( Playlist::ViewUrlGenerator::instance() ); registerGenerator( PlayUrlGenerator::instance() ); } AmarokUrlHandler::~AmarokUrlHandler() { delete m_navigationRunner; delete m_playlistViewRunner; } void AmarokUrlHandler::registerRunner( AmarokUrlRunnerBase * runner, const QString & command ) { m_registeredRunners.insert( command, runner ); } void AmarokUrlHandler::unRegisterRunner( AmarokUrlRunnerBase * runner ) { //get the key of the runner QString key = m_registeredRunners.key( runner, QString() ); if ( !key.isEmpty() ) m_registeredRunners.remove( key ); } void AmarokUrlHandler::registerGenerator( AmarokUrlGenerator * generator ) { if( !m_registeredGenerators.contains( generator ) ) m_registeredGenerators.append( generator ); } void AmarokUrlHandler::unRegisterGenerator( AmarokUrlGenerator * generator ) { m_registeredGenerators.removeAll( generator ); } bool AmarokUrlHandler::run( const AmarokUrl &url ) { DEBUG_BLOCK QString command = url.command(); debug() << "command: " << command; debug() << "registered commands: " << m_registeredRunners.keys(); if ( m_registeredRunners.contains( command ) ) return m_registeredRunners.value( command )->run( url ); else return false; } void AmarokUrlHandler::bookmarkAlbum( const Meta::AlbumPtr &album ) //slot { NavigationUrlGenerator::instance()->urlFromAlbum( album ).saveToDb(); BookmarkModel::instance()->reloadFromDb(); } void AmarokUrlHandler::bookmarkArtist( const Meta::ArtistPtr &artist ) //slot { NavigationUrlGenerator::instance()->urlFromArtist( artist ).saveToDb(); BookmarkModel::instance()->reloadFromDb(); } BookmarkList AmarokUrlHandler::urlsByCommand( const QString &command ) { DEBUG_BLOCK QString query = QStringLiteral("SELECT id, parent_id, name, url, description, custom FROM bookmarks where url like 'amarok://%1%' ORDER BY name;"); query = query.arg( command ); QStringList result = StorageManager::instance()->sqlStorage()->query( query ); debug() << "Result: " << result; int resultRows = result.count() / 6; BookmarkList resultList; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*6, 6 ); resultList << AmarokUrlPtr( new AmarokUrl( row ) ); } return resultList; } AmarokUrl AmarokUrlHandler::createBrowserViewBookmark() { return NavigationUrlGenerator::instance()->CreateAmarokUrl();; } AmarokUrl AmarokUrlHandler::createPlaylistViewBookmark() { return Playlist::ViewUrlGenerator::instance()->createUrl(); } AmarokUrl AmarokUrlHandler::createContextViewBookmark() { return ContextUrlGenerator::instance()->createContextBookmark(); } void AmarokUrlHandler::bookmarkCurrentBrowserView() { AmarokUrl url = createBrowserViewBookmark(); url.saveToDb(); BookmarkModel::instance()->reloadFromDb(); } void AmarokUrlHandler::bookmarkCurrentPlaylistView() { AmarokUrl url = createPlaylistViewBookmark(); url.saveToDb(); BookmarkModel::instance()->reloadFromDb(); } void AmarokUrlHandler::bookmarkCurrentContextView() { AmarokUrl url = createContextViewBookmark(); url.saveToDb(); BookmarkModel::instance()->reloadFromDb(); } QIcon AmarokUrlHandler::iconForCommand( const QString &command ) { if( m_registeredRunners.keys().contains( command ) ) return m_registeredRunners.value( command )->icon(); - return QIcon::fromTheme( "unknown" ); + return QIcon::fromTheme( QStringLiteral("unknown") ); } void AmarokUrlHandler::updateTimecodes(const QString* BookmarkName) { Q_EMIT timecodesUpdated( BookmarkName ); } void AmarokUrlHandler::paintNewTimecode( const QString &name, int pos ) { Q_EMIT timecodeAdded( name, pos ); } QString AmarokUrlHandler::prettyCommand( const QString &command ) { if( m_registeredRunners.keys().contains( command ) ) return m_registeredRunners.value( command )->prettyCommand(); return i18nc( "The command type of this url is not known", "Unknown" ); } diff --git a/src/amarokurls/ContextUrlGenerator.cpp b/src/amarokurls/ContextUrlGenerator.cpp index 29f87c93fa..f7030d3bc3 100644 --- a/src/amarokurls/ContextUrlGenerator.cpp +++ b/src/amarokurls/ContextUrlGenerator.cpp @@ -1,75 +1,75 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "ContextUrlGenerator.h" #include "AmarokUrl.h" #include "AmarokUrlHandler.h" #include "context/ContextView.h" #include ContextUrlGenerator * ContextUrlGenerator::s_instance = nullptr; ContextUrlGenerator * ContextUrlGenerator::instance() { if( s_instance == nullptr ) s_instance = new ContextUrlGenerator(); return s_instance; } ContextUrlGenerator::ContextUrlGenerator() { } ContextUrlGenerator::~ContextUrlGenerator() { The::amarokUrlHandler()->unRegisterGenerator( this ); } AmarokUrl ContextUrlGenerator::createContextBookmark() { QStringList pluginNames = Context::ContextView::self()->currentApplets(); QStringList appletNames = Context::ContextView::self()->currentAppletNames(); AmarokUrl url; url.setCommand( QStringLiteral("context") ); url.setArg( QStringLiteral("applets"), pluginNames.join( QStringLiteral(",") ) ); url.setName( i18n( "Context: %1", appletNames.join( QStringLiteral(",") ) ) ); return url; } QString ContextUrlGenerator::description() { return i18n( "Bookmark Context View Applets" ); } QIcon ContextUrlGenerator::icon() { - return QIcon::fromTheme( "x-media-podcast-amarok" ); + return QIcon::fromTheme( QStringLiteral("x-media-podcast-amarok") ); } AmarokUrl ContextUrlGenerator::createUrl() { return createContextBookmark(); } diff --git a/src/amarokurls/ContextUrlRunner.cpp b/src/amarokurls/ContextUrlRunner.cpp index 61098529af..c883d34650 100644 --- a/src/amarokurls/ContextUrlRunner.cpp +++ b/src/amarokurls/ContextUrlRunner.cpp @@ -1,76 +1,76 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "ContextUrlRunner.h" #include "MainWindow.h" #include "AmarokUrlHandler.h" #include "context/ContextView.h" #include "context/AppletModel.h" #include ContextUrlRunner::ContextUrlRunner() {} ContextUrlRunner::~ContextUrlRunner() { The::amarokUrlHandler()->unRegisterRunner ( this ); } QIcon ContextUrlRunner::icon() const { - return QIcon::fromTheme( "x-media-podcast-amarok" ); + return QIcon::fromTheme( QStringLiteral("x-media-podcast-amarok") ); } bool ContextUrlRunner::run( const AmarokUrl &url ) { DEBUG_BLOCK if( url.isNull() ) return false; if( url.command() != command() ) return false; QString appletsString = url.args().value( QStringLiteral("applets") ); debug() << "applet string: " << appletsString; QStringList appletList = appletsString.split( QLatin1Char(',') ); auto model = Context::ContextView::self()->appletModel(); if( model ) { model->clear(); foreach( const QString &appletPluginName, appletList ) { model->setAppletEnabled( appletPluginName, true ); } } The::mainWindow()->showDock( MainWindow::AmarokDockContext ); return true; } QString ContextUrlRunner::command() const { return QStringLiteral("context"); } QString ContextUrlRunner::prettyCommand() const { return i18nc( "A type of command that affects the context view", "Context" ); } diff --git a/src/amarokurls/NavigationUrlGenerator.cpp b/src/amarokurls/NavigationUrlGenerator.cpp index 539b3f280c..fad200546a 100644 --- a/src/amarokurls/NavigationUrlGenerator.cpp +++ b/src/amarokurls/NavigationUrlGenerator.cpp @@ -1,265 +1,265 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "NavigationUrlGenerator.h" #include "MainWindow.h" #include "amarokconfig.h" #include "amarokurls/AmarokUrl.h" #include "amarokurls/AmarokUrlHandler.h" #include "browsers/BrowserDock.h" #include "browsers/CollectionTreeItemModelBase.h" #include "browsers/collectionbrowser/CollectionWidget.h" #include "browsers/filebrowser/FileBrowser.h" #include "browsers/playlistbrowser/PlaylistBrowser.h" #include "browsers/servicebrowser/ServiceBrowser.h" #include "core/support/Debug.h" #include "core/capabilities/SourceInfoCapability.h" #include "core-impl/collections/db/sql/SqlMeta.h" #include "playlistmanager/PlaylistManager.h" NavigationUrlGenerator * NavigationUrlGenerator::s_instance = nullptr; NavigationUrlGenerator * NavigationUrlGenerator::instance() { if( s_instance == nullptr ) s_instance = new NavigationUrlGenerator(); return s_instance; } NavigationUrlGenerator::NavigationUrlGenerator() { } NavigationUrlGenerator::~NavigationUrlGenerator() { The::amarokUrlHandler()->unRegisterGenerator( this ); } AmarokUrl NavigationUrlGenerator::CreateAmarokUrl() { DEBUG_BLOCK AmarokUrl url; url.setCommand( QStringLiteral("navigate") ); //get the path QString path = The::mainWindow()->browserDock()->list()->path(); QStringList pathParts = path.split( QLatin1Char('/') ); //we don't use the "Home" part in navigation urls if ( pathParts.at( 0 ) == QLatin1String("root list") ) pathParts.removeFirst(); url.setPath( pathParts.join( QLatin1Char('/') ) ); QString filter = The::mainWindow()->browserDock()->list()->activeCategoryRecursive()->filter(); if ( !filter.isEmpty() ) url.setArg( QStringLiteral("filter"), filter ); QList levels = The::mainWindow()->browserDock()->list()->activeCategoryRecursive()->levels(); QString sortMode; foreach( CategoryId::CatMenuId level, levels ) { switch( level ) { case CategoryId::Genre: sortMode += QLatin1String("genre-"); break; case CategoryId::Artist: sortMode += QLatin1String("artist-"); break; case CategoryId::Album: sortMode += QLatin1String("album-"); break; case CategoryId::AlbumArtist: sortMode += QLatin1String("albumartist-"); break; case CategoryId::Composer: sortMode += QLatin1String("composer-"); break; case CategoryId::Year: sortMode += QLatin1String("year-"); break; default: break; } } //we have left a trailing '-' in there, get rid of it! if ( sortMode.size() > 0 ) sortMode = sortMode.left( sortMode.size() - 1 ); if ( !sortMode.isEmpty() ) url.setArg( QStringLiteral("levels"), sortMode ); //if in the local collection view, also store "show covers" and "show years" if( url.path().endsWith( QLatin1String("collections"), Qt::CaseInsensitive ) ) { debug() << "bookmarking in local collection"; if( AmarokConfig::showAlbumArt() ) url.setArg( QStringLiteral("show_cover"), QStringLiteral("true") ); else url.setArg( QStringLiteral("show_cover"), QStringLiteral("false") ); if( AmarokConfig::showYears() ) url.setArg( QStringLiteral("show_years"), QStringLiteral("true") ); else url.setArg( QStringLiteral("show_years"), QStringLiteral("false") ); } //come up with a default name for this url.. QString name = The::mainWindow()->browserDock()->list()->activeCategoryRecursive()->prettyName(); //if in the file browser, also store the file path if( url.path().endsWith( QLatin1String("files"), Qt::CaseInsensitive ) ) { //Give a proper name since it will return "/" as that is what is used in the breadcrumb. name = i18n( "Files" ); FileBrowser * fileBrowser = dynamic_cast( The::mainWindow()->browserDock()->list()->activeCategory() ); if( fileBrowser ) { url.setArg( QStringLiteral("path"), fileBrowser->currentDir() ); name = i18n( "Files (%1)", fileBrowser->currentDir() ); } } url.setName( name ); return url; } AmarokUrl NavigationUrlGenerator::urlFromAlbum( Meta::AlbumPtr album ) { AmarokUrl url; QScopedPointer btc( album->create() ); if( btc ) { if( btc->isBookmarkable() ) { QString albumName = album->prettyName(); url.setCommand( QStringLiteral("navigate") ); QString path = btc->browserName(); if ( !btc->collectionName().isEmpty() ) path += ( '/' + btc->collectionName() ); url.setPath( path ); QString filter; if ( btc->simpleFiltering() ) { filter = "\"" + albumName + "\""; } else { url.setArg( QStringLiteral("levels"), QStringLiteral("album") ); QString artistName; if ( album->albumArtist() ) artistName = album->albumArtist()->prettyName(); filter = "album:\"" + albumName + "\""; if ( !artistName.isEmpty() ) filter += ( " AND artist:\"" + artistName + "\"" ); } url.setArg( QStringLiteral("filter"), filter ); if ( !btc->collectionName().isEmpty() ) url.setName( i18n( "Album \"%1\" from %2", albumName, btc->collectionName() ) ); else url.setName( i18n( "Album \"%1\"", albumName ) ); } } //debug() << "got url: " << url.url(); return url; } AmarokUrl NavigationUrlGenerator::urlFromArtist( Meta::ArtistPtr artist ) { DEBUG_BLOCK AmarokUrl url; QScopedPointer btc( artist->create() ); if( btc ) { if( btc->isBookmarkable() ) { QString artistName = artist->prettyName(); url.setCommand( QStringLiteral("navigate") ); QString path = btc->browserName(); if ( !btc->collectionName().isEmpty() ) path += ( '/' + btc->collectionName() ); url.setPath( path ); //debug() << "Path: " << url.path(); QString filter; if ( btc->simpleFiltering() ) { //for services only supporting simple filtering, do not try to set the sorting mode filter = "\"" + artistName + "\""; } else { url.setArg( QStringLiteral("levels"), QStringLiteral("artist-album") ); filter = ( "artist:\"" + artistName + "\"" ); } url.setArg( QStringLiteral("filter"), filter ); if ( !btc->collectionName().isEmpty() ) url.setName( i18n( "Artist \"%1\" from %2", artistName, btc->collectionName() ) ); else url.setName( i18n( "Artist \"%1\"", artistName ) ); } } return url; } QString NavigationUrlGenerator::description() { return i18n( "Bookmark Media Sources View" ); } QIcon NavigationUrlGenerator::icon() { - return QIcon::fromTheme( "flag-amarok" ); + return QIcon::fromTheme( QStringLiteral("flag-amarok") ); } AmarokUrl NavigationUrlGenerator::createUrl() { return CreateAmarokUrl(); } diff --git a/src/amarokurls/NavigationUrlRunner.cpp b/src/amarokurls/NavigationUrlRunner.cpp index 380681247a..e5ec8f4739 100644 --- a/src/amarokurls/NavigationUrlRunner.cpp +++ b/src/amarokurls/NavigationUrlRunner.cpp @@ -1,139 +1,139 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "NavigationUrlRunner.h" #include "MainWindow.h" #include "amarokconfig.h" #include "amarokurls/AmarokUrlHandler.h" #include "browsers/BrowserDock.h" #include "browsers/CollectionTreeItemModelBase.h" #include "browsers/collectionbrowser/CollectionWidget.h" #include "browsers/filebrowser/FileBrowser.h" #include "browsers/playlistbrowser/PlaylistBrowser.h" #include "browsers/servicebrowser/ServiceBrowser.h" #include "core/support/Debug.h" #include "playlistmanager/PlaylistManager.h" #include "services/ServiceBase.h" NavigationUrlRunner::NavigationUrlRunner() : AmarokUrlRunnerBase() {} NavigationUrlRunner::~NavigationUrlRunner() { The::amarokUrlHandler()->unRegisterRunner( this ); } bool NavigationUrlRunner::run(const AmarokUrl &url ) { DEBUG_BLOCK; //get to the correct category debug() << "Navigate to path: " << url.path(); The::mainWindow()->browserDock()->list()->navigate( url.path() ); BrowserCategory * active = The::mainWindow()->browserDock()->list()->activeCategoryRecursive(); QMap args = url.args(); if ( args.keys().contains( QStringLiteral("levels") ) ) { QString levelsString = args.value( QStringLiteral("levels") ); QList levels; QStringList levelsStringList = levelsString.split( QLatin1Char('-') ); foreach( const QString &levelString, levelsStringList ) { if( levelString == QLatin1String("genre") ) levels.append( CategoryId::Genre ); else if( levelString == QLatin1String("artist") ) levels.append( CategoryId::Artist ); else if( levelString == QLatin1String("album") ) levels.append( CategoryId::Album ); else if( levelString == QLatin1String("albumartist") ) levels.append( CategoryId::AlbumArtist ); else if( levelString == QLatin1String("composer") ) levels.append( CategoryId::Composer ); else if( levelString == QLatin1String("year") ) levels.append( CategoryId::Year ); } active->setLevels( levels ); } //if we are activating the local collection, check if we need to restore "show cover" and "show year" //if in the local collection view, also store "show covers" and "show years" if( url.path().endsWith( QLatin1String("collections"), Qt::CaseInsensitive ) ) { if ( args.keys().contains( QStringLiteral("show_cover") ) ) { if( args.value( QStringLiteral("show_cover") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 ) AmarokConfig::setShowAlbumArt( true ); else if( args.value( QStringLiteral("show_cover") ).compare( QLatin1String("false"), Qt::CaseInsensitive ) == 0 ) AmarokConfig::setShowAlbumArt( false ); } if ( args.keys().contains( QStringLiteral("show_years") ) ) { if( args.value( QStringLiteral("show_years") ).compare( QLatin1String("true"), Qt::CaseInsensitive ) == 0 ) AmarokConfig::setShowYears( true ); else if( args.value( QStringLiteral("show_years") ).compare( QLatin1String("false"), Qt::CaseInsensitive ) == 0 ) AmarokConfig::setShowYears( false ); } } //also set the correct path if we are navigating to the file browser if( url.path().endsWith( QLatin1String("files"), Qt::CaseInsensitive ) ) { FileBrowser * fileBrowser = dynamic_cast( The::mainWindow()->browserDock()->list()->activeCategory() ); if( fileBrowser ) { if( args.keys().contains( QStringLiteral("path") ) ) { fileBrowser->setDir( QUrl::fromUserInput(args.value( QStringLiteral("path") )) ); } } } if ( args.keys().contains( QStringLiteral("filter") ) ) active->setFilter( QUrl::fromPercentEncoding(args.value( QStringLiteral("filter") ).toUtf8()) ); The::mainWindow()->showDock( MainWindow::AmarokDockNavigation ); return true; } QString NavigationUrlRunner::command() const { return QStringLiteral("navigate"); } QString NavigationUrlRunner::prettyCommand() const { return i18nc( "A type of command that affects the view in the browser category", "Navigate" ); } QIcon NavigationUrlRunner::icon() const { - return QIcon::fromTheme( "flag-amarok" ); + return QIcon::fromTheme( QStringLiteral("flag-amarok") ); } diff --git a/src/amarokurls/PlayUrlGenerator.cpp b/src/amarokurls/PlayUrlGenerator.cpp index f60362c5fe..4c9b226122 100644 --- a/src/amarokurls/PlayUrlGenerator.cpp +++ b/src/amarokurls/PlayUrlGenerator.cpp @@ -1,116 +1,116 @@ /**************************************************************************************** * Copyright (c) 2009 Casey Link * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "PlayUrlGenerator.h" #include "AmarokUrl.h" #include "AmarokUrlHandler.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaUtility.h" #include "core/support/Debug.h" #include "EngineController.h" #include "BookmarkModel.h" #include PlayUrlGenerator * PlayUrlGenerator::s_instance = nullptr; PlayUrlGenerator * PlayUrlGenerator::instance() { if( s_instance == nullptr) s_instance = new PlayUrlGenerator(); return s_instance; } PlayUrlGenerator::PlayUrlGenerator() { } PlayUrlGenerator::~PlayUrlGenerator() { The::amarokUrlHandler()->unRegisterGenerator( this ); } AmarokUrl PlayUrlGenerator::createCurrentTrackBookmark() { Meta::TrackPtr track = The::engineController()->currentTrack(); const qint64 miliseconds = The::engineController()->trackPositionMs(); return createTrackBookmark( track, miliseconds ); } AmarokUrl PlayUrlGenerator::createTrackBookmark( Meta::TrackPtr track, qint64 miliseconds, const QString &name ) { DEBUG_BLOCK const int seconds = miliseconds / 1000; const qreal accurateSeconds = (qreal) miliseconds / 1000.0; QString secondsString = QString::number( accurateSeconds ); AmarokUrl url; if( !track ) return url; const QString trackUrl = track->playableUrl().toEncoded().toBase64(); url.setCommand( QStringLiteral("play") ); url.setPath( trackUrl ); url.setArg( QStringLiteral("pos"), secondsString ); if( name.isEmpty() ) url.setName( track->prettyName() + " - " + Meta::secToPrettyTime( seconds ) ); else url.setName( name + " - " + Meta::secToPrettyTime( seconds ) ); debug() << "concocted url: " << url.url(); debug() << "pos: " << accurateSeconds; return url; } void PlayUrlGenerator::moveTrackBookmark( Meta::TrackPtr track, qint64 newMiliseconds, const QString &name ) { qreal seconds = qreal ( newMiliseconds ) / 1000; QString trackPosition; trackPosition.setNum( seconds ); QString trackName = track->prettyName(); QString newName = ( trackName + " - " + Meta::msToPrettyTime( newMiliseconds ) ); BookmarkModel::instance()->setBookmarkArg( name, QStringLiteral("pos"), trackPosition ); BookmarkModel::instance()->renameBookmark( name, newName ); } QString PlayUrlGenerator::description() { return i18n( "Bookmark Track Position" ); } QIcon PlayUrlGenerator::icon() { - return QIcon::fromTheme( "x-media-podcast-amarok" ); + return QIcon::fromTheme( QStringLiteral("x-media-podcast-amarok") ); } AmarokUrl PlayUrlGenerator::createUrl() { return createCurrentTrackBookmark(); } diff --git a/src/amarokurls/PlayUrlRunner.cpp b/src/amarokurls/PlayUrlRunner.cpp index 730399a0d2..2c69437be9 100644 --- a/src/amarokurls/PlayUrlRunner.cpp +++ b/src/amarokurls/PlayUrlRunner.cpp @@ -1,124 +1,124 @@ /**************************************************************************************** * Copyright (c) 2009 Casey Link * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "PlayUrlRunner.h" #include "core/support/Debug.h" #include "amarokconfig.h" #include "AmarokUrl.h" #include "AmarokUrlHandler.h" #include "core-impl/storage/StorageManager.h" #include "core-impl/collections/support/CollectionManager.h" #include "EngineController.h" #include "playlist/PlaylistController.h" #include "playlist/PlaylistModelStack.h" #include "playlist/proxymodels/AbstractModel.h" #include #include "core/meta/Meta.h" PlayUrlRunner::PlayUrlRunner() : AmarokUrlRunnerBase() { } PlayUrlRunner::~PlayUrlRunner() { The::amarokUrlHandler()->unRegisterRunner ( this ); } bool PlayUrlRunner::run (const AmarokUrl &url ) { DEBUG_BLOCK if ( url.isNull() ) return false; QUrl track_url = QUrl::fromEncoded ( QByteArray::fromBase64 ( url.path().toUtf8() ) ); debug() << "decoded track url: " << track_url.toString(); //get the position qint64 pos = 0; if ( url.args().keys().contains( QStringLiteral("pos") ) ) { pos = (qint64) ( url.args().value( QStringLiteral("pos") ).toDouble() * 1000.0 ); } debug() << "seek pos: " << pos; Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( track_url ); if ( !track ) return false; The::engineController()->play ( track, pos ); Playlist::AbstractModel *model = The::playlist(); int row = model->firstRowForTrack( track ); if( row != -1 ) model->setActiveRow( row ); else { row = AmarokConfig::dynamicMode() ? model->activeRow() + 1 : model->qaim()->rowCount(); The::playlistController()->insertTrack( row, track ); model->setActiveRow( row ); } return true; } QString PlayUrlRunner::command() const { return QStringLiteral("play"); } QString PlayUrlRunner::prettyCommand() const { return i18nc( "A type of command that starts playing at a specific position in a track", "Play" ); } BookmarkList PlayUrlRunner::bookmarksFromUrl( const QUrl &url ) { BookmarkList list; //See PlayUrlGenerator for the description of a 'play' amarokurl QString track_encoded = url.toEncoded().toBase64(); // The last character of a base64 encoded string is always '=', which // chokes the SQL. Since we are using a substring like text comparison // and every url in the database will have the '=', just chop it off. // some tracks even seem to have multiple '='s at the end... chop them all off! while( track_encoded.endsWith( '=' ) ) track_encoded.chop ( 1 ); // Queries the database for bookmarks where the url field contains // the base64 encoded url (minus the '='). QString query = QStringLiteral("SELECT id, parent_id, name, url, description, custom FROM bookmarks WHERE url LIKE '%%1%'"); query = query.arg ( track_encoded ); QStringList result = StorageManager::instance()->sqlStorage()->query ( query ); int resultRows = result.count() / 6; for ( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid ( i*6, 6 ); list << AmarokUrlPtr ( new AmarokUrl ( row ) ); } return list; } QIcon PlayUrlRunner::icon() const { - return QIcon::fromTheme( "x-media-podcast-amarok" ); + return QIcon::fromTheme( QStringLiteral("x-media-podcast-amarok") ); } diff --git a/src/browsers/BrowserBreadcrumbItem.cpp b/src/browsers/BrowserBreadcrumbItem.cpp index 3e8a047580..fd9083d8df 100644 --- a/src/browsers/BrowserBreadcrumbItem.cpp +++ b/src/browsers/BrowserBreadcrumbItem.cpp @@ -1,175 +1,175 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "BrowserBreadcrumbItem.h" #include "browsers/BrowserCategoryList.h" #include "browsers/filebrowser/FileBrowser.h" #include "core/support/Debug.h" #include "widgets/BreadcrumbItemButton.h" #include #include #include #include #include BrowserBreadcrumbItem::BrowserBreadcrumbItem( BrowserCategory *category, QWidget *parent ) : BoxWidget( false, parent ) - , m_menuButton( 0 ) + , m_menuButton( nullptr ) { //figure out if we want to add a menu to this item. A menu allows you to select //any of the _sibling_ items. (yes, I know, this is different from how Dolphin //does it, but I find the Dolphin way amazingly unintuitive and I always get it //wrong when using it...) BrowserCategoryList * parentList = category->parentList(); if( parentList ) { m_menuButton = new BreadcrumbItemMenuButton( this ); QMenu *menu = new QMenu( this ); //see QMenu docs: it's still a top-level widget. //parent is only for memory management. QMap siblingMap = parentList->categories(); const QStringList siblingNames = siblingMap.keys(); foreach( const QString &siblingName, siblingNames ) { //no point in adding ourselves to this menu if ( siblingName == category->name() ) continue; BrowserCategory *siblingCategory = siblingMap.value( siblingName ); QAction *action = menu->addAction( siblingCategory->icon(), siblingCategory->prettyName() ); connect( action, &QAction::triggered, siblingMap.value( siblingName ), &BrowserCategory::activate ); } m_menuButton->setMenu( menu ); } m_mainButton = new BreadcrumbItemButton( category->icon(), category->prettyName(), this ); if( category->prettyName().isEmpty() ) { // root item m_mainButton->setToolTip( i18n( "Media Sources Home" ) ); m_mainButton->setIcon( QIcon::fromTheme( QStringLiteral("user-home") ) ); } connect( m_mainButton, &BreadcrumbItemButton::sizePolicyChanged, this, &BrowserBreadcrumbItem::updateSizePolicy ); //if this is a list, make clicking on this item cause us //to navigate to its home. BrowserCategoryList *list = qobject_cast( category ); if ( list ) { connect( m_mainButton, &QAbstractButton::clicked, list, &BrowserCategoryList::home ); } else { connect( m_mainButton, &QAbstractButton::clicked, category, &BrowserCategory::reActivate ); } adjustSize(); m_nominalWidth = width(); hide(); updateSizePolicy(); } BrowserBreadcrumbItem::BrowserBreadcrumbItem( const QString &name, const QString &callback, const BreadcrumbSiblingList &childItems, FileBrowser *handler, QWidget *parent ) : BoxWidget( false, parent ) , m_menuButton( nullptr ) , m_callback( callback ) { if ( !childItems.isEmpty() ) { m_menuButton = new BreadcrumbItemMenuButton( this ); QMenu *menu = new QMenu( this ); int i = 0; foreach( const BreadcrumbSibling &sibling, childItems ) { QString visibleName = sibling.name; visibleName.replace( '&', QLatin1String("&&") ); // prevent bug 244817 QAction *action = menu->addAction( sibling.icon, visibleName ); action->setProperty( "callback", sibling.callback ); // the current action should be bolded if( sibling.name == name ) { QFont font = action->font(); font.setBold( true ); action->setFont( font ); } connect( action, &QAction::triggered, this, &BrowserBreadcrumbItem::activateSibling ); i++; } m_menuButton->setMenu( menu ); } m_mainButton = new BreadcrumbItemButton( name, this ); connect( m_mainButton, &BreadcrumbItemButton::sizePolicyChanged, this, &BrowserBreadcrumbItem::updateSizePolicy ); connect( m_mainButton, &QAbstractButton::clicked, this, &BrowserBreadcrumbItem::activate ); connect( this, &BrowserBreadcrumbItem::activated, handler, &FileBrowser::addItemActivated ); adjustSize(); m_nominalWidth = width(); hide(); updateSizePolicy(); } BrowserBreadcrumbItem::~BrowserBreadcrumbItem() { } void BrowserBreadcrumbItem::setActive( bool active ) { m_mainButton->setActive( active ); } QSizePolicy BrowserBreadcrumbItem::sizePolicy() const { return m_mainButton->sizePolicy(); } void BrowserBreadcrumbItem::updateSizePolicy() { setSizePolicy( m_mainButton->sizePolicy() ); } void BrowserBreadcrumbItem::activate() { Q_EMIT activated( m_callback ); } void BrowserBreadcrumbItem::activateSibling() { QAction *action = qobject_cast( sender() ); if( action ) Q_EMIT activated( action->property( "callback" ).toString() ); } int BrowserBreadcrumbItem::nominalWidth() const { return m_nominalWidth; } diff --git a/src/browsers/BrowserBreadcrumbWidget.cpp b/src/browsers/BrowserBreadcrumbWidget.cpp index 037f728f7d..6c0499ab48 100644 --- a/src/browsers/BrowserBreadcrumbWidget.cpp +++ b/src/browsers/BrowserBreadcrumbWidget.cpp @@ -1,243 +1,243 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "BrowserBreadcrumbWidget" #include "BrowserBreadcrumbWidget.h" #include "amarokurls/AmarokUrl.h" #include "BrowserBreadcrumbItem.h" #include "BrowserCategoryList.h" #include "browsers/filebrowser/FileBrowser.h" #include "MainWindow.h" #include "widgets/BreadcrumbItemButton.h" #include #include #include #include #include #include BrowserBreadcrumbWidget::BrowserBreadcrumbWidget( QWidget * parent ) : BoxWidget( false, parent) - , m_rootList( 0 ) - , m_childMenuButton( 0 ) + , m_rootList( nullptr ) + , m_childMenuButton( nullptr ) { setFixedHeight( 28 ); setContentsMargins( 3, 0, 3, 0 ); m_breadcrumbArea = new BoxWidget( false, this ); m_breadcrumbArea->setContentsMargins( 0, 0, 0, 0 ); static_cast( layout() )->setStretchFactor( m_breadcrumbArea, 10 ); new BreadcrumbUrlMenuButton( QStringLiteral("navigate"), this ); - m_spacer = new QWidget( 0 ); + m_spacer = new QWidget( nullptr ); } BrowserBreadcrumbWidget::~BrowserBreadcrumbWidget() { clearCrumbs(); } void BrowserBreadcrumbWidget::clearCrumbs() { foreach( BrowserBreadcrumbItem *item, m_items ) { item->hide(); item->deleteLater(); } m_items.clear(); //if we have a final menu button, also delete it. delete m_childMenuButton; m_childMenuButton = 0; } void BrowserBreadcrumbWidget::setRootList( BrowserCategoryList * rootList ) { m_rootList = rootList; //update the breadcrumbs every time the view changes. connect( m_rootList, &BrowserCategoryList::viewChanged, this, &BrowserBreadcrumbWidget::updateBreadcrumbs ); updateBreadcrumbs(); } void BrowserBreadcrumbWidget::updateBreadcrumbs() { if( !m_rootList ) return; clearCrumbs(); m_spacer->setParent( this ); addLevel( m_rootList ); m_breadcrumbArea->layout()->addWidget( m_spacer ); showAsNeeded(); } void BrowserBreadcrumbWidget::addLevel( BrowserCategoryList *list ) { BrowserBreadcrumbItem *item = list->breadcrumb(); addBreadCrumbItem( item ); m_items.append( item ); BrowserCategory *childCategory = list->activeCategory(); if( childCategory ) { item->setActive( false ); //check if this is also a list BrowserCategoryList *childList = qobject_cast( childCategory ); if( childList ) { addLevel( childList ); } else { BrowserBreadcrumbItem *leaf = childCategory->breadcrumb(); addBreadCrumbItem( leaf ); m_items.append( leaf ); const QList additionalItems = childCategory->additionalItems(); //no children, but check if there are additional breadcrumb levels (for internal navigation in the category) that should be added anyway. foreach( BrowserBreadcrumbItem *addItem, additionalItems ) { //hack to ensure that we have not already added it to the front of the breadcrumb... addBreadCrumbItem( addItem ); } if( !additionalItems.isEmpty() ) additionalItems.last()->setActive( true ); else leaf->setActive( true ); } } else { //if this item has children, add a menu button for selecting these. BrowserCategoryList *childList = qobject_cast( list ); if( childList ) { m_childMenuButton = new BreadcrumbItemMenuButton( m_breadcrumbArea ); QMenu *menu = new QMenu( item ); menu->hide(); QMap childMap = childList->categories(); QStringList childNames = childMap.keys(); foreach( const QString &siblingName, childNames ) { //no point in adding ourselves to this menu if ( siblingName == list->name() ) continue; BrowserCategory * siblingCategory = childMap.value( siblingName ); QAction * action = menu->addAction( siblingCategory->icon(), siblingCategory->prettyName() ); connect( action, &QAction::triggered, childMap.value( siblingName ), &BrowserCategory::activate ); } m_childMenuButton->setMenu( menu ); //do a little magic to line up items in the menu with the current item int offset = 6; menu->setContentsMargins( offset, 1, 1, 2 ); } item->setActive( true ); } } void BrowserBreadcrumbWidget::addBreadCrumbItem( BrowserBreadcrumbItem *item ) { item->hide(); item->setParent( this ); // may be already shown, we want it to be last, so reparent m_breadcrumbArea->layout()->addWidget( item ); } void BrowserBreadcrumbWidget::resizeEvent( QResizeEvent *event ) { Q_UNUSED( event ) // we need to postpone the call, because hideAsNeeded() itself may trigger resizeEvent QTimer::singleShot( 0 , this, &BrowserBreadcrumbWidget::showAsNeeded ); } void BrowserBreadcrumbWidget::showAsNeeded() { /* we need to check if there is enough space for all items, if not, we start hiding * items from the left (excluding the home item) until they fit (we never hide the * rightmost item) we also add the hidden levels to the drop down menu of the last * item so they are accessible. */ //make a temp list that includes both regular items and add items QList allItems; allItems.append( m_items ); if( m_rootList->activeCategory() ) allItems.append( m_rootList->activeCategory()->additionalItems() ); // filter-out leftover items not parented to m_breadcrumbArea (bug 285712): QMutableListIterator it( allItems ); while( it.hasNext() ) { if( it.next()->parent() != m_breadcrumbArea ) it.remove(); } if( allItems.isEmpty() ) return; int sizeOfFirst = allItems.first()->nominalWidth(); int sizeOfLast = allItems.last()->nominalWidth(); int spaceLeft = width() - ( sizeOfFirst + sizeOfLast + 28 ); allItems.first()->show(); allItems.last()->show(); int numberOfItems = allItems.count(); for( int i = numberOfItems - 2; i > 0; i-- ) { if( allItems.at( i )->nominalWidth() <= spaceLeft ) { allItems.at( i )->show(); spaceLeft -= allItems.at( i )->nominalWidth(); } else { //set spaceLeft to 0 so no items further to the left are shown spaceLeft = 0; allItems.at( i )->hide(); } } } diff --git a/src/browsers/BrowserCategoryList.cpp b/src/browsers/BrowserCategoryList.cpp index e2c5508f0e..4984228f0b 100644 --- a/src/browsers/BrowserCategoryList.cpp +++ b/src/browsers/BrowserCategoryList.cpp @@ -1,420 +1,420 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "BrowserCategoryList" #include "BrowserCategoryList.h" #include "App.h" #include "core/support/Debug.h" #include "InfoProxy.h" #include "PaletteHandler.h" #include "widgets/PrettyTreeView.h" #include "widgets/PrettyTreeDelegate.h" #include "widgets/SearchWidget.h" #include #include #include #include #include #include #include BrowserCategoryList::BrowserCategoryList( const QString &name, QWidget* parent, bool sort ) : BrowserCategory( name, parent ) , m_categoryListModel( new BrowserCategoryListModel( this ) ) , m_sorting( sort ) { // -- the widget stack m_widgetStack = new QStackedWidget( this ); QWidget* mainWidget = new QWidget( m_widgetStack ); QVBoxLayout* vLayout = new QVBoxLayout( mainWidget ); mainWidget->setLayout( vLayout ); // -- the search widget m_searchWidget = new SearchWidget( this, false ); m_searchWidget->setClickMessage( i18n( "Filter Music Sources" ) ); vLayout->addWidget( m_searchWidget ); connect( m_searchWidget, &SearchWidget::filterChanged, this, &BrowserCategoryList::setFilter ); // -- the main list view m_categoryListView = new Amarok::PrettyTreeView(); m_categoryListView->setFrameShape( QFrame::NoFrame ); m_proxyModel = new BrowserCategoryListSortFilterProxyModel( this ); m_proxyModel->setSourceModel( m_categoryListModel ); m_categoryListView->setItemDelegate( new PrettyTreeDelegate( m_categoryListView ) ); m_categoryListView->setHeaderHidden( true ); m_categoryListView->setRootIsDecorated( false ); m_categoryListView->setModel( m_proxyModel ); m_categoryListView->setMouseTracking ( true ); if( sort ) { m_proxyModel->setSortRole( Qt::DisplayRole ); m_categoryListView->setSortingEnabled( true ); m_categoryListView->sortByColumn( 0 ); } connect( m_categoryListView, &Amarok::PrettyTreeView::activated, this, &BrowserCategoryList::categoryActivated ); connect( m_categoryListView, &Amarok::PrettyTreeView::entered, this, &BrowserCategoryList::categoryEntered ); vLayout->addWidget( m_categoryListView ); m_widgetStack->addWidget( mainWidget ); } BrowserCategoryList::~BrowserCategoryList() { } void BrowserCategoryList::categoryActivated( const QModelIndex &index ) { DEBUG_BLOCK BrowserCategory * category = 0; if( index.data( CustomCategoryRoles::CategoryRole ).canConvert() ) category = index.data( CustomCategoryRoles::CategoryRole ).value(); else return; if( category ) { debug() << "Show service: " << category->name(); setActiveCategory( category ); } } void BrowserCategoryList::home() { DEBUG_BLOCK if( activeCategory() ) { BrowserCategoryList *childList = qobject_cast( activeCategory() ); if( childList ) childList->home(); activeCategory()->clearAdditionalItems(); m_widgetStack->setCurrentIndex( 0 ); Q_EMIT( viewChanged() ); } } QMap BrowserCategoryList::categories() { return m_categories; } void BrowserCategoryList::addCategory( BrowserCategory *category ) { Q_ASSERT( category ); category->setParentList( this ); //insert service into service map category->setParent( this ); m_categories[category->name()] = category; m_categoryListModel->addCategory( category ); m_widgetStack->addWidget( category ); //if this is also a category list, watch it for changes as we need to report //these down the tree BrowserCategoryList *childList = qobject_cast( category ); if ( childList ) connect( childList, &BrowserCategoryList::viewChanged, this, &BrowserCategoryList::childViewChanged ); category->polish(); // service categories do an additional construction in polish if( m_sorting ) { m_proxyModel->sort( 0 ); } Q_EMIT( viewChanged() ); } void BrowserCategoryList::removeCategory( BrowserCategory *category ) { Q_ASSERT( category ); if( m_widgetStack->indexOf( category ) == -1 ) return; // no such category if( m_widgetStack->currentWidget() == category ) home(); m_categories.remove( category->name() ); m_categoryListModel->removeCategory( category ); m_widgetStack->removeWidget( category ); delete category; m_categoryListView->reset(); Q_EMIT( viewChanged() ); } BrowserCategory* BrowserCategoryList::activeCategory() const { return qobject_cast(m_widgetStack->currentWidget()); } void BrowserCategoryList::setActiveCategory( BrowserCategory* category ) { DEBUG_BLOCK; if( m_widgetStack->indexOf( category ) == -1 ) return; // no such category if( !category || activeCategory() == category ) return; // nothing to do if( activeCategory() ) activeCategory()->clearAdditionalItems(); category->setupAddItems(); m_widgetStack->setCurrentWidget( category ); Q_EMIT( viewChanged() ); } void BrowserCategoryList::back() { DEBUG_BLOCK BrowserCategoryList *childList = qobject_cast( activeCategory() ); if( childList ) { if( childList->activeCategory() != 0 ) { childList->back(); return; } } home(); } void BrowserCategoryList::childViewChanged() { DEBUG_BLOCK Q_EMIT( viewChanged() ); } QString BrowserCategoryList::navigate( const QString & target ) { DEBUG_BLOCK debug() << "target: " << target; QStringList categories = target.split( QLatin1Char('/') ); if ( categories.isEmpty() ) return QString(); //remove our own name if present, before passing on... if ( categories.at( 0 ) == name() ) { debug() << "removing own name (" << categories.at( 0 ) << ") from path"; categories.removeFirst(); if ( categories.isEmpty() ) { //nothing else left, make sure this category is visible home(); return QString(); } } QString childName = categories.at( 0 ); debug() << "looking for child category " << childName; if ( !m_categories.contains( childName ) ) return target; debug() << "got it!"; setActiveCategory( m_categories[childName] ); //check if this category is also BrowserCategoryList.target BrowserCategoryList *childList = qobject_cast( activeCategory() ); - if ( childList == 0 ) + if ( childList == nullptr ) { debug() << "child is not a list..."; if ( categories.size() > 1 ) { categories.removeFirst(); QString leftover = categories.join( QLatin1Char('/') ); return leftover; } return QString(); } //check if there are more arguments in the navigate string. if ( categories.size() == 1 ) { debug() << "Child is a list but path ends here..."; //only one name, but since the category we switched to is also //a category list, make sure that it is reset to home childList->home(); return QString(); } categories.removeFirst(); debug() << "passing remaining path to child: " << categories.join( QLatin1Char('/') ); return childList->navigate( categories.join( QLatin1Char('/') ) ); } QString BrowserCategoryList::path() { DEBUG_BLOCK QString pathString = name(); BrowserCategoryList *childList = qobject_cast( activeCategory() ); if( childList ) pathString += '/' + childList->path(); else if( activeCategory() ) pathString += '/' + activeCategory()->name(); debug() << "path: " << pathString; return pathString; } void BrowserCategoryList::categoryEntered( const QModelIndex & index ) { //get the long description for this item and pass it to info proxy. - BrowserCategory *category = 0; + BrowserCategory *category = nullptr; if ( index.data( CustomCategoryRoles::CategoryRole ).canConvert() ) category = index.data( CustomCategoryRoles::CategoryRole ).value(); else return; if( category ) { //instead of just throwing out raw text, let's format the long description and the //icon into a nice html page. if ( m_infoHtmlTemplate.isEmpty() ) { QString dataPath = QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/data/"), QStandardPaths::LocateDirectory ); //load html QString htmlPath = dataPath + "/hover_info_template.html"; QFile file( htmlPath ); if ( !file.open( QIODevice::ReadOnly | QIODevice::Text) ) { debug() << "error opening file:" << file.fileName() << "Error: " << file.error(); return; } m_infoHtmlTemplate = file.readAll(); file.close(); m_infoHtmlTemplate.replace( QLatin1String("{background_color}"), The::paletteHandler()->highlightColor().lighter( 150 ).name() ); m_infoHtmlTemplate.replace( QLatin1String("{border_color}"), The::paletteHandler()->highlightColor().lighter( 150 ).name() ); m_infoHtmlTemplate.replace( QLatin1String("{text_color}"), pApp->palette().brush( QPalette::Text ).color().name() ); QColor highlight( pApp->palette().highlight().color() ); highlight.setHsvF( highlight.hueF(), 0.3, .95, highlight.alphaF() ); m_infoHtmlTemplate.replace( QLatin1String("{header_background_color}"), highlight.name() ); } QString currentHtml = m_infoHtmlTemplate; currentHtml.replace( QLatin1String("%%NAME%%"), category->prettyName() ); currentHtml.replace( QLatin1String("%%DESCRIPTION%%"), category->longDescription() ); currentHtml.replace( QLatin1String("%%IMAGE_PATH%%"), "file://" + category->imagePath() ); QVariantMap variantMap; variantMap[QStringLiteral("main_info")] = QVariant( currentHtml ); The::infoProxy()->setInfo( variantMap ); } } QString BrowserCategoryList::css() { QString style = ""; return style; } BrowserCategory *BrowserCategoryList::activeCategoryRecursive() { BrowserCategory *category = activeCategory(); if( !category ) return this; BrowserCategoryList *childList = qobject_cast( category ); if( childList ) return childList->activeCategoryRecursive(); return category; } void BrowserCategoryList::setFilter( const QString &filter ) { m_proxyModel->setFilterFixedString( filter ); } diff --git a/src/browsers/CollectionTreeItemModelBase.cpp b/src/browsers/CollectionTreeItemModelBase.cpp index ecbd568983..05f29dcc55 100644 --- a/src/browsers/CollectionTreeItemModelBase.cpp +++ b/src/browsers/CollectionTreeItemModelBase.cpp @@ -1,1324 +1,1324 @@ /**************************************************************************************** * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2007-2009 Maximilian Kossick * * Copyright (c) 2007 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "CollectionTreeItemModelBase" #include "CollectionTreeItemModelBase.h" #include "AmarokMimeData.h" #include "FileType.h" #include "SvgHandler.h" #include "amarokconfig.h" #include "browsers/CollectionTreeItem.h" #include "core/collections/Collection.h" #include "core/collections/QueryMaker.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaConstants.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/support/TextualQueryFilter.h" #include "widgets/PrettyTreeRoles.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Meta; class TrackLoaderJob : public ThreadWeaver::Job { public: TrackLoaderJob( const QModelIndex &index, const Meta::AlbumPtr &album, CollectionTreeItemModelBase *model ) : m_index( index ) , m_album( album ) , m_model( model ) , m_abortRequested( false ) { if( !m_model || !m_album || !m_index.isValid() ) requestAbort(); } void requestAbort() override { m_abortRequested = true; } protected: void run( ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread ) override { Q_UNUSED( self ) Q_UNUSED( thread ) if( m_abortRequested || !m_model ) return; const auto tracks = m_album->tracks(); if( m_model && !m_abortRequested ) { auto slot = std::bind( &CollectionTreeItemModelBase::tracksLoaded, m_model, m_album, m_index, tracks ); QTimer::singleShot( 0, m_model, slot ); } } private: QModelIndex m_index; Meta::AlbumPtr m_album; QPointer m_model; bool m_abortRequested; }; inline uint qHash( const Meta::DataPtr &data ) { return qHash( data.data() ); } /** * This set determines which collection browser levels should have shown Various Artists * item under them. AlbumArtist is certain, (Track)Artist is questionable. */ static const QSet variousArtistCategories = QSet() << CategoryId::AlbumArtist; CollectionTreeItemModelBase::CollectionTreeItemModelBase( ) : QAbstractItemModel() , m_rootItem( 0 ) , m_animFrame( 0 ) , m_loading1( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/loading1.png") ) ) ) , m_loading2( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/loading2.png") ) ) ) , m_currentAnimPixmap( m_loading1 ) , m_autoExpand( false ) { m_timeLine = new QTimeLine( 10000, this ); m_timeLine->setFrameRange( 0, 20 ); m_timeLine->setLoopCount ( 0 ); connect( m_timeLine, &QTimeLine::frameChanged, this, &CollectionTreeItemModelBase::loadingAnimationTick ); } CollectionTreeItemModelBase::~CollectionTreeItemModelBase() { KConfigGroup config = Amarok::config( QStringLiteral("Collection Browser") ); QList levelNumbers; foreach( CategoryId::CatMenuId category, levels() ) levelNumbers.append( category ); config.writeEntry( "TreeCategory", levelNumbers ); if( m_rootItem ) m_rootItem->deleteLater(); } Qt::ItemFlags CollectionTreeItemModelBase::flags(const QModelIndex & index) const { Qt::ItemFlags flags = 0; if( index.isValid() ) { flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; } return flags; } bool CollectionTreeItemModelBase::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_UNUSED( role ) if( !index.isValid() ) return false; CollectionTreeItem *item = static_cast( index.internalPointer() ); Meta::DataPtr data = item->data(); if( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( data ) ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) { ec->setTitle( value.toString() ); Q_EMIT dataChanged( index, index ); return true; } } else if( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( data ) ) { Meta::TrackList tracks = album->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setAlbum( value.toString() ); } Q_EMIT dataChanged( index, index ); return true; } } else if( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( data ) ) { Meta::TrackList tracks = artist->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setArtist( value.toString() ); } Q_EMIT dataChanged( index, index ); return true; } } else if( Meta::GenrePtr genre = Meta::GenrePtr::dynamicCast( data ) ) { Meta::TrackList tracks = genre->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setGenre( value.toString() ); } Q_EMIT dataChanged( index, index ); return true; } } else if( Meta::YearPtr year = Meta::YearPtr::dynamicCast( data ) ) { Meta::TrackList tracks = year->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setYear( value.toInt() ); } Q_EMIT dataChanged( index, index ); return true; } } else if( Meta::ComposerPtr composer = Meta::ComposerPtr::dynamicCast( data ) ) { Meta::TrackList tracks = composer->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setComposer( value.toString() ); } Q_EMIT dataChanged( index, index ); return true; } } return false; } QVariant CollectionTreeItemModelBase::dataForItem( CollectionTreeItem *item, int role, int level ) const { if( level == -1 ) level = item->level(); if( item->isTrackItem() ) { Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( item->data() ); switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: case PrettyTreeRoles::FilterRole: { QString name = track->prettyName(); Meta::AlbumPtr album = track->album(); Meta::ArtistPtr artist = track->artist(); if( album && artist && album->isCompilation() ) name.prepend( QStringLiteral("%1 - ").arg(artist->prettyName()) ); if( AmarokConfig::showTrackNumbers() ) { int trackNum = track->trackNumber(); if( trackNum > 0 ) name.prepend( QStringLiteral("%1 - ").arg(trackNum) ); } // Check empty after track logic and before album logic if( name.isEmpty() ) name = i18nc( "The Name is not known", "Unknown" ); return name; } case Qt::DecorationRole: return QIcon::fromTheme( QStringLiteral("media-album-track") ); case PrettyTreeRoles::SortRole: return track->sortableName(); } } else if( item->isAlbumItem() ) { Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( item->data() ); switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: { QString name = album->prettyName(); // add years for named albums (if enabled) if( AmarokConfig::showYears() ) { if( m_years.contains( album.data() ) ) { int year = m_years.value( album.data() ); if( year > 0 ) name.prepend( QStringLiteral("%1 - ").arg( year ) ); } else if( !album->name().isEmpty() ) { if( !m_loadingAlbums.contains( album ) ) { m_loadingAlbums.insert( album ); auto nonConstThis = const_cast( this ); auto job = QSharedPointer::create( itemIndex( item ), album, nonConstThis ); ThreadWeaver::Queue::instance()->enqueue( job ); } } } return name; } case Qt::DecorationRole: if( AmarokConfig::showAlbumArt() ) { QStyle *style = QApplication::style(); const int largeIconSize = style->pixelMetric( QStyle::PM_LargeIconSize ); return The::svgHandler()->imageWithBorder( album, largeIconSize, 2 ); } else return iconForLevel( level ); case PrettyTreeRoles::SortRole: return album->sortableName(); case PrettyTreeRoles::HasCoverRole: return AmarokConfig::showAlbumArt(); case PrettyTreeRoles::YearRole: { if( m_years.contains( album.data() ) ) return m_years.value( album.data() ); else if( !album->name().isEmpty() ) { if( !m_loadingAlbums.contains( album ) ) { m_loadingAlbums.insert( album ); auto nonConstThis = const_cast( this ); auto job = QSharedPointer::create( itemIndex( item ), album, nonConstThis ); ThreadWeaver::Queue::instance()->enqueue( job ); } } return -1; } } } else if( item->isDataItem() ) { switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: case PrettyTreeRoles::FilterRole: { QString name = item->data()->prettyName(); if( name.isEmpty() ) name = i18nc( "The Name is not known", "Unknown" ); return name; } case Qt::DecorationRole: { if( m_childQueries.values().contains( item ) ) { if( level < m_levelType.count() ) return m_currentAnimPixmap; } return iconForLevel( level ); } case PrettyTreeRoles::SortRole: return item->data()->sortableName(); } } else if( item->isVariousArtistItem() ) { switch( role ) { case Qt::DecorationRole: return QIcon::fromTheme( QStringLiteral("similarartists-amarok") ); case Qt::DisplayRole: return i18n( "Various Artists" ); case PrettyTreeRoles::SortRole: return QString(); // so that we can compare it against other strings } } // -- all other roles are handled by item return item->data( role ); } QVariant CollectionTreeItemModelBase::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) return m_headerText; } return QVariant(); } QModelIndex CollectionTreeItemModelBase::index(int row, int column, const QModelIndex & parent) const { //ensure sanity of parameters //we are a tree model, there are no columns if( row < 0 || column != 0 ) return QModelIndex(); CollectionTreeItem *parentItem; if (!parent.isValid()) parentItem = m_rootItem; else parentItem = static_cast(parent.internalPointer()); CollectionTreeItem *childItem = parentItem->child(row); if( childItem ) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex CollectionTreeItemModelBase::parent(const QModelIndex & index) const { if( !index.isValid() ) return QModelIndex(); CollectionTreeItem *childItem = static_cast(index.internalPointer()); CollectionTreeItem *parentItem = childItem->parent(); return itemIndex( parentItem ); } int CollectionTreeItemModelBase::rowCount(const QModelIndex & parent) const { CollectionTreeItem *parentItem; if( !parent.isValid() ) parentItem = m_rootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } int CollectionTreeItemModelBase::columnCount(const QModelIndex & parent) const { Q_UNUSED( parent ) return 1; } QStringList CollectionTreeItemModelBase::mimeTypes() const { QStringList types; types << AmarokMimeData::TRACK_MIME; return types; } QMimeData* CollectionTreeItemModelBase::mimeData( const QModelIndexList &indices ) const { if ( indices.isEmpty() ) return 0; // first, filter out duplicate entries that may arise when both parent and child are selected QSet indexSet = QSet::fromList( indices ); QMutableSetIterator it( indexSet ); while( it.hasNext() ) { it.next(); // we go up in parent hierarchy searching whether some parent indices are already in set QModelIndex parentIndex = it.value(); while( parentIndex.isValid() ) // leave the root (top, invalid) index intact { parentIndex = parentIndex.parent(); // yes, we start from the parent of current index if( indexSet.contains( parentIndex ) ) { it.remove(); // parent already in selected set, remove child break; // no need to continue inner loop, already deleted } } } QList items; foreach( const QModelIndex &index, indexSet ) { if( index.isValid() ) items << static_cast( index.internalPointer() ); } return mimeData( items ); } QMimeData* CollectionTreeItemModelBase::mimeData( const QList &items ) const { if ( items.isEmpty() ) return 0; Meta::TrackList tracks; QList queries; foreach( CollectionTreeItem *item, items ) { if( item->allDescendentTracksLoaded() ) { tracks << item->descendentTracks(); } else { Collections::QueryMaker *qm = item->queryMaker(); for( CollectionTreeItem *tmp = item; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); queries.append( qm ); } } qStableSort( tracks.begin(), tracks.end(), Meta::Track::lessThan ); AmarokMimeData *mimeData = new AmarokMimeData(); mimeData->setTracks( tracks ); mimeData->setQueryMakers( queries ); mimeData->startQueries(); return mimeData; } bool CollectionTreeItemModelBase::hasChildren ( const QModelIndex & parent ) const { if( !parent.isValid() ) return true; // must be root item! CollectionTreeItem *item = static_cast(parent.internalPointer()); //we added the collection level so we have to be careful with the item level return !item->isDataItem() || item->level() + levelModifier() <= m_levelType.count(); } void CollectionTreeItemModelBase::ensureChildrenLoaded( CollectionTreeItem *item ) { //only start a query if necessary and we are not querying for the item's children already if ( item->requiresUpdate() && !m_runningQueries.contains( item ) ) { listForLevel( item->level() + levelModifier(), item->queryMaker(), item ); } } CollectionTreeItem * CollectionTreeItemModelBase::treeItem( const QModelIndex &index ) const { if( !index.isValid() || index.model() != this ) return 0; return static_cast( index.internalPointer() ); } QModelIndex CollectionTreeItemModelBase::itemIndex( CollectionTreeItem *item ) const { if( !item || item == m_rootItem ) return QModelIndex(); return createIndex( item->row(), 0, item ); } void CollectionTreeItemModelBase::listForLevel(int level, Collections::QueryMaker * qm, CollectionTreeItem * parent) { if( qm && parent ) { // this check should not hurt anyone... needs to check if single... needs it if( m_runningQueries.contains( parent ) ) return; // following special cases are handled extra - right when the parent is added if( level > m_levelType.count() || parent->isVariousArtistItem() || parent->isNoLabelItem() ) { qm->deleteLater(); return; } // - the last level are always the tracks if ( level == m_levelType.count() ) qm->setQueryType( Collections::QueryMaker::Track ); // - all other levels are more complicate else { Collections::QueryMaker::QueryType nextLevel; if( level + 1 >= m_levelType.count() ) nextLevel = Collections::QueryMaker::Track; else nextLevel = mapCategoryToQueryType( m_levelType.value( level + 1 ) ); qm->setQueryType( mapCategoryToQueryType( m_levelType.value( level ) ) ); CategoryId::CatMenuId category = m_levelType.value( level ); if( category == CategoryId::Album ) { // restrict query to normal albums if the previous level // was the AlbumArtist category. In that case we handle compilations below if( levelCategory( level - 1 ) == CategoryId::AlbumArtist ) qm->setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums ); } else if( variousArtistCategories.contains( category ) ) // we used to handleCompilations() only if nextLevel is Album, but I cannot // tell any reason why we should have done this --- strohel handleCompilations( nextLevel, parent ); else if( category == CategoryId::Label ) handleTracksWithoutLabels( nextLevel, parent ); } for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); addQueryMaker( parent, qm ); m_childQueries.insert( qm, parent ); qm->run(); //some very quick queries may be done so fast that the loading //animation creates an unnecessary flicker, therefore delay it for a bit QTimer::singleShot( 150, this, &CollectionTreeItemModelBase::startAnimationTick ); } } void CollectionTreeItemModelBase::setLevels( const QList &levelType ) { if( m_levelType == levelType ) return; m_levelType = levelType; updateHeaderText(); m_expandedItems.clear(); m_expandedSpecialNodes.clear(); m_runningQueries.clear(); m_childQueries.clear(); m_compilationQueries.clear(); filterChildren(); } Collections::QueryMaker::QueryType CollectionTreeItemModelBase::mapCategoryToQueryType( int levelType ) const { Collections::QueryMaker::QueryType type; switch( levelType ) { case CategoryId::Album: type = Collections::QueryMaker::Album; break; case CategoryId::Artist: type = Collections::QueryMaker::Artist; break; case CategoryId::AlbumArtist: type = Collections::QueryMaker::AlbumArtist; break; case CategoryId::Composer: type = Collections::QueryMaker::Composer; break; case CategoryId::Genre: type = Collections::QueryMaker::Genre; break; case CategoryId::Label: type = Collections::QueryMaker::Label; break; case CategoryId::Year: type = Collections::QueryMaker::Year; break; default: type = Collections::QueryMaker::None; break; } return type; } void CollectionTreeItemModelBase::tracksLoaded( const Meta::AlbumPtr &album, const QModelIndex &index, const Meta::TrackList& tracks ) { DEBUG_BLOCK if( !album ) return; m_loadingAlbums.remove( album ); if( !index.isValid() ) return; int year = 0; if( !tracks.isEmpty() ) { Meta::YearPtr yearPtr = tracks.first()->year(); if( yearPtr ) year = yearPtr->year(); debug() << "Valid album year found:" << year; } if( !m_years.contains( album.data() ) || m_years.value( album.data() ) != year ) { m_years[ album.data() ] = year; Q_EMIT dataChanged( index, index ); } } void CollectionTreeItemModelBase::addQueryMaker( CollectionTreeItem* item, Collections::QueryMaker *qm ) const { connect( qm, &Collections::QueryMaker::newTracksReady, this, &CollectionTreeItemModelBase::newTracksReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newArtistsReady, this, &CollectionTreeItemModelBase::newArtistsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &CollectionTreeItemModelBase::newAlbumsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newGenresReady, this, &CollectionTreeItemModelBase::newGenresReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newComposersReady, this, &CollectionTreeItemModelBase::newComposersReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newYearsReady, this, &CollectionTreeItemModelBase::newYearsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newLabelsReady, this, &CollectionTreeItemModelBase::newLabelsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newDataReady, this, &CollectionTreeItemModelBase::newDataReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionTreeItemModelBase::queryDone, Qt::QueuedConnection ); m_runningQueries.insert( item, qm ); } void CollectionTreeItemModelBase::queryDone() { Collections::QueryMaker *qm = qobject_cast( sender() ); if( !qm ) return; CollectionTreeItem* item = 0; if( m_childQueries.contains( qm ) ) item = m_childQueries.take( qm ); else if( m_compilationQueries.contains( qm ) ) item = m_compilationQueries.take( qm ); else if( m_noLabelsQueries.contains( qm ) ) item = m_noLabelsQueries.take( qm ); if( item ) m_runningQueries.remove( item, qm ); //reset icon for this item if( item && item != m_rootItem ) { Q_EMIT dataChanged( itemIndex( item ), itemIndex( item ) ); } //stop timer if there are no more animations active if( m_runningQueries.isEmpty() ) { Q_EMIT allQueriesFinished( m_autoExpand ); m_autoExpand = false; // reset to default value m_timeLine->stop(); } qm->deleteLater(); } // TODO /** Small helper function to convert a list of e.g. tracks to a list of DataPtr */ template static Meta::DataList convertToDataList( const QList& list ) { Meta::DataList data; for( const auto &p : list ) data << Meta::DataPtr::staticCast( p ); return data; } void CollectionTreeItemModelBase::newTracksReady( const Meta::TrackList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newArtistsReady( const Meta::ArtistList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newAlbumsReady( const Meta::AlbumList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newGenresReady( const Meta::GenreList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newComposersReady( const Meta::ComposerList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newYearsReady( const Meta::YearList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newLabelsReady( const Meta::LabelList &res ) { newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newDataReady( const Meta::DataList &data ) { //if we are expanding an item, we'll find the sender in childQueries //otherwise we are filtering all collections Collections::QueryMaker *qm = qobject_cast( sender() ); if( !qm ) return; if( m_childQueries.contains( qm ) ) handleNormalQueryResult( qm, data ); else if( m_compilationQueries.contains( qm ) ) handleSpecialQueryResult( CollectionTreeItem::VariousArtist, qm, data ); else if( m_noLabelsQueries.contains( qm ) ) handleSpecialQueryResult( CollectionTreeItem::NoLabel, qm, data ); } void CollectionTreeItemModelBase::handleSpecialQueryResult( CollectionTreeItem::Type type, Collections::QueryMaker *qm, const Meta::DataList &dataList ) { CollectionTreeItem *parent = nullptr; if( type == CollectionTreeItem::VariousArtist ) parent = m_compilationQueries.value( qm ); else if( type == CollectionTreeItem::NoLabel ) parent = m_noLabelsQueries.value( qm ); if( parent ) { QModelIndex parentIndex = itemIndex( parent ); //if the special query did not return a result we have to remove the //the special node itself if( dataList.isEmpty() ) { for( int i = 0; i < parent->childCount(); i++ ) { CollectionTreeItem *cti = parent->child( i ); if( cti->type() == type ) { //found the special node beginRemoveRows( parentIndex, cti->row(), cti->row() ); cti = 0; //will be deleted; parent->removeChild( i ); endRemoveRows(); break; } } //we have removed the special node if it existed return; } CollectionTreeItem *specialNode = 0; if( parent->childCount() == 0 ) { //we only insert the special node beginInsertRows( parentIndex, 0, 0 ); specialNode = new CollectionTreeItem( type, dataList, parent, this ); //set requiresUpdate, otherwise we will query for the children of specialNode again! specialNode->setRequiresUpdate( false ); endInsertRows(); } else { for( int i = 0; i < parent->childCount(); i++ ) { CollectionTreeItem *cti = parent->child( i ); if( cti->type() == type ) { //found the special node specialNode = cti; break; } } if( !specialNode ) { //we only insert the special node beginInsertRows( parentIndex, 0, 0 ); specialNode = new CollectionTreeItem( type, dataList, parent, this ); //set requiresUpdate, otherwise we will query for the children of specialNode again! specialNode->setRequiresUpdate( false ); endInsertRows(); } else { //only call populateChildren for the special node if we have not //created it in this method call. The special node ctor takes care //of that itself populateChildren( dataList, specialNode, itemIndex( specialNode ) ); } //populate children will call setRequiresUpdate on vaNode //but as the special query is based on specialNode's parent, //we have to call setRequiresUpdate on the parent too //yes, this will mean we will call setRequiresUpdate twice parent->setRequiresUpdate( false ); for( int count = specialNode->childCount(), i = 0; i < count; ++i ) { CollectionTreeItem *item = specialNode->child( i ); if ( m_expandedItems.contains( item->data() ) ) //item will always be a data item { listForLevel( item->level() + levelModifier(), item->queryMaker(), item ); } } } //if the special node exists, check if it has to be expanded if( specialNode ) { if( m_expandedSpecialNodes.contains( parent->parentCollection() ) ) { Q_EMIT expandIndex( createIndex( 0, 0, specialNode ) ); //we have just inserted the vaItem at row 0 } } } } void CollectionTreeItemModelBase::handleNormalQueryResult( Collections::QueryMaker *qm, const Meta::DataList &dataList ) { CollectionTreeItem *parent = m_childQueries.value( qm ); if( parent ) { QModelIndex parentIndex = itemIndex( parent ); populateChildren( dataList, parent, parentIndex ); if ( parent->isDataItem() ) { if ( m_expandedItems.contains( parent->data() ) ) Q_EMIT expandIndex( parentIndex ); else //simply insert the item, nothing will change if it is already in the set m_expandedItems.insert( parent->data() ); } } } void CollectionTreeItemModelBase::populateChildren( const DataList &dataList, CollectionTreeItem *parent, const QModelIndex &parentIndex ) { CategoryId::CatMenuId childCategory = levelCategory( parent->level() ); // add new rows after existing ones here (which means all artists nodes // will be inserted after the "Various Artists" node) // figure out which children of parent have to be removed, // which new children have to be added, and preemptively Q_EMIT dataChanged for the rest // have to check how that influences performance... const QSet dataSet = dataList.toSet(); QSet childrenSet; foreach( CollectionTreeItem *child, parent->children() ) { // we don't add null children, these are special-cased below if( !child->data() ) continue; childrenSet.insert( child->data() ); } const QSet dataToBeAdded = dataSet - childrenSet; const QSet dataToBeRemoved = childrenSet - dataSet; // first remove all rows that have to be removed // walking through the children in reverse order does not screw up the order for( int i = parent->childCount() - 1; i >= 0; i-- ) { CollectionTreeItem *child = parent->child( i ); // should this child be removed? bool toBeRemoved; if( child->isDataItem() ) toBeRemoved = dataToBeRemoved.contains( child->data() ); else if( child->isVariousArtistItem() ) toBeRemoved = !variousArtistCategories.contains( childCategory ); else if( child->isNoLabelItem() ) toBeRemoved = childCategory != CategoryId::Label; else { warning() << "Unknown child type encountered in populateChildren(), removing"; toBeRemoved = true; } if( toBeRemoved ) { beginRemoveRows( parentIndex, i, i ); parent->removeChild( i ); endRemoveRows(); } else { // the remaining child items may be dirty, so refresh them if( child->isDataItem() && child->data() && m_expandedItems.contains( child->data() ) ) ensureChildrenLoaded( child ); // tell the view that the existing children may have changed QModelIndex idx = index( i, 0, parentIndex ); Q_EMIT dataChanged( idx, idx ); } } // add the new rows if( !dataToBeAdded.isEmpty() ) { int lastRow = parent->childCount() - 1; //the above check ensures that Qt does not crash on beginInsertRows ( because lastRow+1 > lastRow+0) beginInsertRows( parentIndex, lastRow + 1, lastRow + dataToBeAdded.count() ); foreach( Meta::DataPtr data, dataToBeAdded ) { new CollectionTreeItem( data, parent, this ); } endInsertRows(); } parent->setRequiresUpdate( false ); } void CollectionTreeItemModelBase::updateHeaderText() { m_headerText.clear(); for( int i=0; i< m_levelType.count(); ++i ) m_headerText += nameForLevel( i ) + " / "; m_headerText.chop( 3 ); } QIcon CollectionTreeItemModelBase::iconForCategory( CategoryId::CatMenuId category ) { switch( category ) { case CategoryId::Album : - return QIcon::fromTheme( "media-optical-amarok" ); + return QIcon::fromTheme( QStringLiteral("media-optical-amarok") ); case CategoryId::Artist : - return QIcon::fromTheme( "view-media-artist-amarok" ); + return QIcon::fromTheme( QStringLiteral("view-media-artist-amarok") ); case CategoryId::AlbumArtist : - return QIcon::fromTheme( "view-media-artist-amarok" ); + return QIcon::fromTheme( QStringLiteral("view-media-artist-amarok") ); case CategoryId::Composer : - return QIcon::fromTheme( "filename-composer-amarok" ); + return QIcon::fromTheme( QStringLiteral("filename-composer-amarok") ); case CategoryId::Genre : - return QIcon::fromTheme( "favorite-genres-amarok" ); + return QIcon::fromTheme( QStringLiteral("favorite-genres-amarok") ); case CategoryId::Year : - return QIcon::fromTheme( "clock" ); + return QIcon::fromTheme( QStringLiteral("clock") ); case CategoryId::Label : - return QIcon::fromTheme( "label-amarok" ); + return QIcon::fromTheme( QStringLiteral("label-amarok") ); case CategoryId::None: default: - return QIcon::fromTheme( "image-missing" ); + return QIcon::fromTheme( QStringLiteral("image-missing") ); } } QIcon CollectionTreeItemModelBase::iconForLevel( int level ) const { return iconForCategory( m_levelType.value( level ) ); } QString CollectionTreeItemModelBase::nameForCategory( CategoryId::CatMenuId category, bool showYears ) { switch( category ) { case CategoryId::Album: return showYears ? i18n( "Year - Album" ) : i18n( "Album" ); case CategoryId::Artist: return i18n( "Track Artist" ); case CategoryId::AlbumArtist: return i18n( "Album Artist" ); case CategoryId::Composer: return i18n( "Composer" ); case CategoryId::Genre: return i18n( "Genre" ); case CategoryId::Year: return i18n( "Year" ); case CategoryId::Label: return i18n( "Label" ); case CategoryId::None: return i18n( "None" ); default: return QString(); } } QString CollectionTreeItemModelBase::nameForLevel( int level ) const { return nameForCategory( m_levelType.value( level ), AmarokConfig::showYears() ); } void CollectionTreeItemModelBase::handleCompilations( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const { // this method will be called when we retrieve a list of artists from the database. // we have to query for all compilations, and then add a "Various Artists" node if at least // one compilation exists Collections::QueryMaker *qm = parent->queryMaker(); qm->setQueryType( queryType ); qm->setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations ); for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); addQueryMaker( parent, qm ); m_compilationQueries.insert( qm, parent ); qm->run(); } void CollectionTreeItemModelBase::handleTracksWithoutLabels( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const { Collections::QueryMaker *qm = parent->queryMaker(); qm->setQueryType( queryType ); qm->setLabelQueryMode( Collections::QueryMaker::OnlyWithoutLabels ); for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); addQueryMaker( parent, qm ); m_noLabelsQueries.insert( qm, parent ); qm->run(); } void CollectionTreeItemModelBase::startAnimationTick() { //start animation if( ( m_timeLine->state() != QTimeLine::Running ) && !m_runningQueries.isEmpty() ) m_timeLine->start(); } void CollectionTreeItemModelBase::loadingAnimationTick() { if ( m_animFrame == 0 ) m_currentAnimPixmap = m_loading2; else m_currentAnimPixmap = m_loading1; m_animFrame = 1 - m_animFrame; //trigger an update of all items being populated at the moment; QList< CollectionTreeItem * > items = m_runningQueries.uniqueKeys(); foreach ( CollectionTreeItem* item, items ) { if( item == m_rootItem ) continue; Q_EMIT dataChanged( itemIndex( item ), itemIndex( item ) ); } } QString CollectionTreeItemModelBase::currentFilter() const { return m_currentFilter; } void CollectionTreeItemModelBase::setCurrentFilter( const QString &filter ) { m_currentFilter = filter; slotFilter( /* autoExpand */ true ); } void CollectionTreeItemModelBase::slotFilter( bool autoExpand ) { m_autoExpand = autoExpand; filterChildren(); // following is not auto-expansion, it is restoring the state before filtering foreach( Collections::Collection *expanded, m_expandedCollections ) { CollectionTreeItem *expandedItem = m_collections.value( expanded->collectionId() ).second; if( expandedItem ) Q_EMIT expandIndex( itemIndex( expandedItem ) ); } } void CollectionTreeItemModelBase::slotCollapsed( const QModelIndex &index ) { if ( index.isValid() ) //probably unnecessary, but let's be safe { CollectionTreeItem *item = static_cast( index.internalPointer() ); switch( item->type() ) { case CollectionTreeItem::Root: break; // nothing to do case CollectionTreeItem::Collection: m_expandedCollections.remove( item->parentCollection() ); break; case CollectionTreeItem::VariousArtist: case CollectionTreeItem::NoLabel: m_expandedSpecialNodes.remove( item->parentCollection() ); break; case CollectionTreeItem::Data: m_expandedItems.remove( item->data() ); break; } } } void CollectionTreeItemModelBase::slotExpanded( const QModelIndex &index ) { if( !index.isValid() ) return; CollectionTreeItem *item = static_cast( index.internalPointer() ); // we are really only interested in the special nodes here. // we have to remember whether the user expanded a various artists/no labels node or not. // otherwise we won't be able to automatically expand the special node after filtering again // there is exactly one special node per type per collection, so use the collection to store that information // we also need to store collection expansion state here as they are no longer // added to th expanded set in handleNormalQueryResult() switch( item->type() ) { case CollectionTreeItem::VariousArtist: case CollectionTreeItem::NoLabel: m_expandedSpecialNodes.insert( item->parentCollection() ); break; case CollectionTreeItem::Collection: m_expandedCollections.insert( item->parentCollection() ); default: break; } } void CollectionTreeItemModelBase::markSubTreeAsDirty( CollectionTreeItem *item ) { //tracks are the leaves so they are never dirty if( !item->isTrackItem() ) item->setRequiresUpdate( true ); for( int i = 0; i < item->childCount(); i++ ) { markSubTreeAsDirty( item->child( i ) ); } } void CollectionTreeItemModelBase::itemAboutToBeDeleted( CollectionTreeItem *item ) { // also all the children will be deleted foreach( CollectionTreeItem *child, item->children() ) itemAboutToBeDeleted( child ); if( !m_runningQueries.contains( item ) ) return; // TODO: replace this hack with QWeakPointer now than we depend on Qt >= 4.8 foreach(Collections::QueryMaker *qm, m_runningQueries.values( item )) { m_childQueries.remove( qm ); m_compilationQueries.remove( qm ); m_noLabelsQueries.remove( qm ); m_runningQueries.remove(item, qm); //Disconnect all signals from the QueryMaker so we do not get notified about the results qm->disconnect(); qm->abortQuery(); //Nuke it qm->deleteLater(); } } void CollectionTreeItemModelBase::setDragSourceCollections( const QSet &collections ) { m_dragSourceCollections = collections; } bool CollectionTreeItemModelBase::hasRunningQueries() const { return !m_runningQueries.isEmpty(); } CategoryId::CatMenuId CollectionTreeItemModelBase::levelCategory( const int level ) const { const int actualLevel = level + levelModifier(); if( actualLevel >= 0 && actualLevel < m_levelType.count() ) return m_levelType.at( actualLevel ); return CategoryId::None; } diff --git a/src/browsers/filebrowser/FileView.cpp b/src/browsers/filebrowser/FileView.cpp index d74109cb7f..f9f313b860 100644 --- a/src/browsers/filebrowser/FileView.cpp +++ b/src/browsers/filebrowser/FileView.cpp @@ -1,609 +1,609 @@ /**************************************************************************************** * Copyright (c) 2010 Nikolaj Hald Nielsen * * Copyright (c) 2010 Casey Link * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "FileView" #include "FileView.h" #include "EngineController.h" #include "PaletteHandler.h" #include "PopupDropperFactory.h" #include "SvgHandler.h" #include "context/ContextView.h" #include "core/playlists/PlaylistFormat.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/collections/support/FileCollectionLocation.h" #include "core-impl/meta/file/File.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core-impl/support/TrackLoader.h" #include "dialogs/TagDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FileView::FileView( QWidget *parent ) : Amarok::PrettyTreeView( parent ) , m_appendAction( 0 ) , m_loadAction( 0 ) , m_editAction( 0 ) , m_moveToTrashAction( 0 ) , m_deleteAction( 0 ) , m_pd( 0 ) , m_ongoingDrag( false ) { setFrameStyle( QFrame::NoFrame ); setItemsExpandable( false ); setRootIsDecorated( false ); setAlternatingRowColors( true ); setUniformRowHeights( true ); setEditTriggers( EditKeyPressed ); The::paletteHandler()->updateItemView( this ); connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &FileView::newPalette ); } void FileView::contextMenuEvent( QContextMenuEvent *e ) { if( !model() ) return; //trying to do fancy stuff while showing places only leads to tears! if( model()->objectName() == "PLACESMODEL" ) { e->accept(); return; } QModelIndexList indices = selectedIndexes(); // Abort if nothing is selected if( indices.isEmpty() ) return; QMenu menu; foreach( QAction *action, actionsForIndices( indices, PlaylistAction ) ) menu.addAction( action ); menu.addSeparator(); // Create Copy/Move to menu items // ported from old filebrowser QList writableCollections; QHash hash = CollectionManager::instance()->collections(); QHash::const_iterator it = hash.constBegin(); while( it != hash.constEnd() ) { Collections::Collection *coll = it.key(); if( coll && coll->isWritable() ) writableCollections.append( coll ); ++it; } if( !writableCollections.isEmpty() ) { - QMenu *copyMenu = new QMenu( i18n( "Copy to Collection" ), &menu ); - copyMenu->setIcon( QIcon::fromTheme( "edit-copy" ) ); + QMenu *copyMenu = new QMenu( i18n( ("Copy to Collection") ), &menu ); + copyMenu->setIcon( QIcon::fromTheme( QStringLiteral("edit-copy") ) ); foreach( Collections::Collection *coll, writableCollections ) { CollectionAction *copyAction = new CollectionAction( coll, &menu ); connect( copyAction, &QAction::triggered, this, &FileView::slotPrepareCopyTracks ); copyMenu->addAction( copyAction ); } menu.addMenu( copyMenu ); QMenu *moveMenu = new QMenu( i18n( "Move to Collection" ), &menu ); - moveMenu->setIcon( QIcon::fromTheme( "go-jump" ) ); + moveMenu->setIcon( QIcon::fromTheme( QStringLiteral("go-jump") ) ); foreach( Collections::Collection *coll, writableCollections ) { CollectionAction *moveAction = new CollectionAction( coll, &menu ); connect( moveAction, &QAction::triggered, this, &FileView::slotPrepareMoveTracks ); moveMenu->addAction( moveAction ); } menu.addMenu( moveMenu ); } foreach( QAction *action, actionsForIndices( indices, OrganizeAction ) ) menu.addAction( action ); menu.addSeparator(); foreach( QAction *action, actionsForIndices( indices, EditAction ) ) menu.addAction( action ); menu.exec( e->globalPos() ); } void FileView::mouseReleaseEvent( QMouseEvent *event ) { QModelIndex index = indexAt( event->pos() ); if( !index.isValid() ) { PrettyTreeView::mouseReleaseEvent( event ); return; } if( state() == QAbstractItemView::NoState && event->button() == Qt::MidButton ) { addIndexToPlaylist( index, Playlist::OnMiddleClickOnSelectedItems ); event->accept(); return; } KFileItem file = index.data( KDirModel::FileItemRole ).value(); if( state() == QAbstractItemView::NoState && event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier && style()->styleHint( QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this ) && ( file.isDir() || file.isNull() ) ) { Q_EMIT navigateToDirectory( index ); event->accept(); return; } PrettyTreeView::mouseReleaseEvent( event ); } void FileView::mouseDoubleClickEvent( QMouseEvent *event ) { QModelIndex index = indexAt( event->pos() ); if( !index.isValid() ) { event->accept(); return; } // swallow middle-button double-clicks if( event->button() == Qt::MidButton ) { event->accept(); return; } if( event->button() == Qt::LeftButton ) { KFileItem file = index.data( KDirModel::FileItemRole ).value(); QUrl url = file.url(); if( !file.isNull() && ( Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) ) addIndexToPlaylist( index, Playlist::OnDoubleClickOnSelectedItems ); else Q_EMIT navigateToDirectory( index ); event->accept(); return; } PrettyTreeView::mouseDoubleClickEvent( event ); } void FileView::keyPressEvent( QKeyEvent *event ) { QModelIndex index = currentIndex(); if( !index.isValid() ) return; switch( event->key() ) { case Qt::Key_Enter: case Qt::Key_Return: { KFileItem file = index.data( KDirModel::FileItemRole ).value(); QUrl url = file.url(); if( !file.isNull() && ( Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) ) // right, we test the current item, but then add the selection to playlist addSelectionToPlaylist( Playlist::OnReturnPressedOnSelectedItems ); else Q_EMIT navigateToDirectory( index ); return; } case Qt::Key_Delete: slotMoveToTrash( Qt::NoButton, event->modifiers() ); break; case Qt::Key_F5: Q_EMIT refreshBrowser(); break; default: break; } QTreeView::keyPressEvent( event ); } void FileView::slotAppendToPlaylist() { addSelectionToPlaylist( Playlist::OnAppendToPlaylistAction ); } void FileView::slotReplacePlaylist() { addSelectionToPlaylist( Playlist::OnReplacePlaylistAction ); } void FileView::slotEditTracks() { Meta::TrackList tracks = tracksForEdit(); if( !tracks.isEmpty() ) { TagDialog *dialog = new TagDialog( tracks, this ); dialog->show(); } } void FileView::slotPrepareMoveTracks() { if( m_moveDestinationCollection ) return; CollectionAction *action = dynamic_cast( sender() ); if( !action ) return; m_moveDestinationCollection = action->collection(); const KFileItemList list = selectedItems(); if( list.isEmpty() ) return; // prevent bug 313003, require full metadata TrackLoader* dl = new TrackLoader( TrackLoader::FullMetadataRequired ); // auto-deletes itself connect( dl, &TrackLoader::finished, this, &FileView::slotMoveTracks ); dl->init( list.urlList() ); } void FileView::slotPrepareCopyTracks() { if( m_copyDestinationCollection ) return; CollectionAction *action = dynamic_cast( sender() ); if( !action ) return; m_copyDestinationCollection = action->collection(); const KFileItemList list = selectedItems(); if( list.isEmpty() ) return; // prevent bug 313003, require full metadata TrackLoader* dl = new TrackLoader( TrackLoader::FullMetadataRequired ); // auto-deletes itself connect( dl, &TrackLoader::finished, this, &FileView::slotMoveTracks ); dl->init( list.urlList() ); } void FileView::slotCopyTracks( const Meta::TrackList& tracks ) { if( !m_copyDestinationCollection ) return; QSet collections; foreach( const Meta::TrackPtr &track, tracks ) { collections.insert( track->collection() ); } if( collections.count() == 1 ) { Collections::Collection *sourceCollection = collections.values().first(); Collections::CollectionLocation *source; if( sourceCollection ) source = sourceCollection->location(); else source = new Collections::FileCollectionLocation(); Collections::CollectionLocation *destination = m_copyDestinationCollection->location(); source->prepareCopy( tracks, destination ); } else warning() << "Cannot handle copying tracks from multiple collections, doing nothing to be safe"; m_copyDestinationCollection.clear(); } void FileView::slotMoveTracks( const Meta::TrackList& tracks ) { if( !m_moveDestinationCollection ) return; QSet collections; foreach( const Meta::TrackPtr &track, tracks ) { collections.insert( track->collection() ); } if( collections.count() == 1 ) { Collections::Collection *sourceCollection = collections.values().first(); Collections::CollectionLocation *source; if( sourceCollection ) source = sourceCollection->location(); else source = new Collections::FileCollectionLocation(); Collections::CollectionLocation *destination = m_moveDestinationCollection->location(); source->prepareMove( tracks, destination ); } else warning() << "Cannot handle moving tracks from multiple collections, doing nothing to be safe"; m_moveDestinationCollection.clear(); } QList FileView::actionsForIndices( const QModelIndexList &indices, ActionType type ) { QList actions; if( indices.isEmpty() ) return actions; // get out of here! if( !m_appendAction ) { m_appendAction = new QAction( QIcon::fromTheme( "media-track-add-amarok" ), i18n( "&Add to Playlist" ), this ); m_appendAction->setProperty( "popupdropper_svg_id", "append" ); connect( m_appendAction, &QAction::triggered, this, &FileView::slotAppendToPlaylist ); } if( type & PlaylistAction ) actions.append( m_appendAction ); if( !m_loadAction ) { m_loadAction = new QAction( i18nc( "Replace the currently loaded tracks with these", "&Replace Playlist" ), this ); m_loadAction->setProperty( "popupdropper_svg_id", "load" ); connect( m_loadAction, &QAction::triggered, this, &FileView::slotReplacePlaylist ); } if( type & PlaylistAction ) actions.append( m_loadAction ); if( !m_moveToTrashAction ) { m_moveToTrashAction = new QAction( QIcon::fromTheme( "user-trash" ), i18n( "&Move to Trash" ), this ); m_moveToTrashAction->setProperty( "popupdropper_svg_id", "delete_file" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_moveToTrashAction->setShortcut( Qt::Key_Delete ); connect( m_moveToTrashAction, &QAction::triggered, this, &FileView::slotMoveToTrashWithoutModifiers ); } if( type & OrganizeAction ) actions.append( m_moveToTrashAction ); if( !m_deleteAction ) { m_deleteAction = new QAction( QIcon::fromTheme( "remove-amarok" ), i18n( "&Delete" ), this ); m_deleteAction->setProperty( "popupdropper_svg_id", "delete_file" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_deleteAction->setShortcut( Qt::SHIFT + Qt::Key_Delete ); connect( m_deleteAction, &QAction::triggered, this, &FileView::slotDelete ); } if( type & OrganizeAction ) actions.append( m_deleteAction ); if( !m_editAction ) { m_editAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ), i18n( "&Edit Track Details" ), this ); m_editAction->setProperty( "popupdropper_svg_id", "edit" ); connect( m_editAction, &QAction::triggered, this, &FileView::slotEditTracks ); } if( type & EditAction ) { actions.append( m_editAction ); Meta::TrackList tracks = tracksForEdit(); m_editAction->setVisible( !tracks.isEmpty() ); } return actions; } void FileView::addSelectionToPlaylist( Playlist::AddOptions options ) { addIndicesToPlaylist( selectedIndexes(), options ); } void FileView::addIndexToPlaylist( const QModelIndex &idx, Playlist::AddOptions options ) { addIndicesToPlaylist( QModelIndexList() << idx, options ); } void FileView::addIndicesToPlaylist( QModelIndexList indices, Playlist::AddOptions options ) { if( indices.isEmpty() ) return; // let tracks & playlists appear in playlist as they are shown in the view: qSort( indices ); QList urls; foreach( const QModelIndex &index, indices ) { KFileItem file = index.data( KDirModel::FileItemRole ).value(); QUrl url = file.url(); if( file.isDir() || Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) { urls << file.url(); } } The::playlistController()->insertOptioned( urls, options ); } void FileView::startDrag( Qt::DropActions supportedActions ) { //setSelectionMode( QAbstractItemView::NoSelection ); // When a parent item is dragged, startDrag() is called a bunch of times. Here we prevent that: m_dragMutex.lock(); if( m_ongoingDrag ) { m_dragMutex.unlock(); return; } m_ongoingDrag = true; m_dragMutex.unlock(); if( !m_pd ) m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() ); if( m_pd && m_pd->isHidden() ) { QModelIndexList indices = selectedIndexes(); QList actions = actionsForIndices( indices ); QFont font; font.setPointSize( 16 ); font.setBold( true ); foreach( QAction *action, actions ) m_pd->addItem( The::popupDropperFactory()->createItem( action ) ); m_pd->show(); } QTreeView::startDrag( supportedActions ); if( m_pd ) { connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear ); m_pd->hide(); } m_dragMutex.lock(); m_ongoingDrag = false; m_dragMutex.unlock(); } KFileItemList FileView::selectedItems() const { KFileItemList items; QModelIndexList indices = selectedIndexes(); if( indices.isEmpty() ) return items; foreach( const QModelIndex& index, indices ) { KFileItem item = index.data( KDirModel::FileItemRole ).value(); items << item; } return items; } Meta::TrackList FileView::tracksForEdit() const { Meta::TrackList tracks; QModelIndexList indices = selectedIndexes(); if( indices.isEmpty() ) return tracks; foreach( const QModelIndex &index, indices ) { KFileItem item = index.data( KDirModel::FileItemRole ).value(); Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( item.url() ); if( track ) tracks << track; } return tracks; } void FileView::slotMoveToTrash( Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ) { Q_UNUSED( buttons ) DEBUG_BLOCK QModelIndexList indices = selectedIndexes(); if( indices.isEmpty() ) return; const bool deleting = modifiers.testFlag( Qt::ShiftModifier ); QString caption; QString labelText; if( deleting ) { caption = i18nc( "@title:window", "Confirm Delete" ); labelText = i18np( "Are you sure you want to delete this item?", "Are you sure you want to delete these %1 items?", indices.count() ); } else { caption = i18nc( "@title:window", "Confirm Move to Trash" ); labelText = i18np( "Are you sure you want to move this item to trash?", "Are you sure you want to move these %1 items to trash?", indices.count() ); } QList urls; QStringList filepaths; foreach( const QModelIndex& index, indices ) { KFileItem file = index.data( KDirModel::FileItemRole ).value(); filepaths << file.localPath(); urls << file.url(); } KGuiItem confirmButton = deleting ? KStandardGuiItem::del() : KStandardGuiItem::remove(); if( KMessageBox::warningContinueCancelList( this, labelText, filepaths, caption, confirmButton ) != KMessageBox::Continue ) return; if( deleting ) { KIO::del( urls, KIO::HideProgressInfo ); return; } KIO::trash( urls, KIO::HideProgressInfo ); } void FileView::slotDelete() { slotMoveToTrash( Qt::NoButton, Qt::ShiftModifier ); } diff --git a/src/browsers/playlistbrowser/DynamicBiasDelegate.cpp b/src/browsers/playlistbrowser/DynamicBiasDelegate.cpp index c35c6d7bd2..945bd71448 100644 --- a/src/browsers/playlistbrowser/DynamicBiasDelegate.cpp +++ b/src/browsers/playlistbrowser/DynamicBiasDelegate.cpp @@ -1,108 +1,108 @@ /**************************************************************************************** * Copyright (c) 2008 Daniel Caleb Jones * * Copyright (c) 2011 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "DynamicBiasDelegate.h" #include "dynamic/DynamicModel.h" #include "App.h" // #include "Bias.h" // #include "core/support/Debug.h" #include #include PlaylistBrowserNS::DynamicBiasDelegate::DynamicBiasDelegate( QWidget* parent ) : QStyledItemDelegate( parent ) { m_smallFont.setPointSize( m_smallFont.pointSize() - 1 ); m_normalFm = new QFontMetrics( m_normalFont ); m_smallFm = new QFontMetrics( m_smallFont ); } PlaylistBrowserNS::DynamicBiasDelegate::~DynamicBiasDelegate() { delete m_normalFm; delete m_smallFm; } void PlaylistBrowserNS::DynamicBiasDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { Dynamic::AbstractBias* bias = nullptr; QVariant v; if( index.isValid() ) { v = index.model()->data( index, Dynamic::DynamicModel::BiasRole ); if( v.isValid() ) bias = qobject_cast(v.value() ); } // for a bias paint the operator (e.g. a small progress bar for the part bias) in front of it if( bias ) { QModelIndex parentIndex = index.parent(); Dynamic::AbstractBias* parentBias = nullptr; - const bool isRTL = QApplication::isRightToLeft(); - Q_UNUSED( isRTL ); + //const bool isRTL = QApplication::isRightToLeft(); + //Q_UNUSED( isRTL ); v = parentIndex.model()->data( parentIndex, Dynamic::DynamicModel::BiasRole ); if( v.isValid() ) parentBias = qobject_cast(v.value() ); if( parentBias ) { // sub-biases have a operator drawn in front of them. const int operatorWidth = m_smallFm->boundingRect( QStringLiteral("mmmm") ).width(); // draw the selection QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter ); // TODO: isRTL // -- paint the operator QRect operatorRect( option.rect.x(), option.rect.y(), operatorWidth, option.rect.height() ); painter->setFont( m_smallFont ); parentBias->paintOperator( painter, operatorRect, bias ); // -- paint the normal text QRect textRect( option.rect.x() + operatorWidth, option.rect.y(), option.rect.width() - operatorWidth, option.rect.height() ); painter->setFont( m_normalFont ); const QString text = index.data( Qt::DisplayRole ).toString(); painter->drawText( textRect, Qt::TextWordWrap, text ); } else { QStyledItemDelegate::paint( painter, option, index ); } } else { QStyledItemDelegate::paint( painter, option, index ); } } diff --git a/src/core-impl/collections/db/DatabaseCollection.cpp b/src/core-impl/collections/db/DatabaseCollection.cpp index b6c5b71603..1082845852 100644 --- a/src/core-impl/collections/db/DatabaseCollection.cpp +++ b/src/core-impl/collections/db/DatabaseCollection.cpp @@ -1,250 +1,250 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Casey Link * * Copyright (c) 2010 Jeff Mitchell * * Copyright (c) 2013 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "DatabaseCollection" #include #include "DatabaseCollection.h" #include "core/support/Debug.h" #include "scanner/GenericScanManager.h" #include "MountPointManager.h" using namespace Collections; DatabaseCollection::DatabaseCollection() : Collection() , m_mpm( 0 ) , m_scanManager( 0 ) , m_blockUpdatedSignalCount( 0 ) , m_updatedSignalRequested( false ) { } DatabaseCollection::~DatabaseCollection() { delete m_mpm; } QString DatabaseCollection::collectionId() const { return QLatin1String( "localCollection" ); } QString DatabaseCollection::prettyName() const { return i18n( "Local Collection" ); } QIcon DatabaseCollection::icon() const { - return QIcon::fromTheme("drive-harddisk"); + return QIcon::fromTheme(QStringLiteral("drive-harddisk")); } GenericScanManager* DatabaseCollection::scanManager() const { return m_scanManager; } MountPointManager* DatabaseCollection::mountPointManager() const { Q_ASSERT( m_mpm ); return m_mpm; } void DatabaseCollection::setMountPointManager( MountPointManager *mpm ) { Q_ASSERT( mpm ); if( m_mpm ) { disconnect( mpm, &MountPointManager::deviceAdded, this, &DatabaseCollection::slotDeviceAdded ); disconnect( mpm, &MountPointManager::deviceRemoved, this, &DatabaseCollection::slotDeviceRemoved ); } m_mpm = mpm; connect( mpm, &MountPointManager::deviceAdded, this, &DatabaseCollection::slotDeviceAdded ); connect( mpm, &MountPointManager::deviceRemoved, this, &DatabaseCollection::slotDeviceRemoved ); } QStringList DatabaseCollection::collectionFolders() const { return mountPointManager()->collectionFolders(); } void DatabaseCollection::setCollectionFolders( const QStringList &folders ) { mountPointManager()->setCollectionFolders( folders ); } void DatabaseCollection::blockUpdatedSignal() { QMutexLocker locker( &m_mutex ); m_blockUpdatedSignalCount ++; } void DatabaseCollection::unblockUpdatedSignal() { QMutexLocker locker( &m_mutex ); Q_ASSERT( m_blockUpdatedSignalCount > 0 ); m_blockUpdatedSignalCount --; // check if meanwhile somebody had updated the collection if( m_blockUpdatedSignalCount == 0 && m_updatedSignalRequested ) { m_updatedSignalRequested = false; locker.unlock(); Q_EMIT updated(); } } void DatabaseCollection::collectionUpdated() { QMutexLocker locker( &m_mutex ); if( m_blockUpdatedSignalCount == 0 ) { m_updatedSignalRequested = false; locker.unlock(); Q_EMIT updated(); } else { m_updatedSignalRequested = true; } } bool DatabaseCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::CollectionImport: case Capabilities::Capability::CollectionScan: return true; default: return Collection::hasCapabilityInterface( type ); } } Capabilities::Capability* DatabaseCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::CollectionImport: return new DatabaseCollectionImportCapability( this ); case Capabilities::Capability::CollectionScan: return new DatabaseCollectionScanCapability( this ); default: return Collection::createCapabilityInterface( type ); } } // --------- DatabaseCollectionScanCapability ------------- DatabaseCollectionScanCapability::DatabaseCollectionScanCapability( DatabaseCollection* collection ) : m_collection( collection ) { Q_ASSERT( m_collection ); } DatabaseCollectionScanCapability::~DatabaseCollectionScanCapability() { } void DatabaseCollectionScanCapability::startFullScan() { QList urls; foreach( const QString& path, m_collection->mountPointManager()->collectionFolders() ) urls.append( QUrl::fromLocalFile( path ) ); m_collection->scanManager()->requestScan( urls, GenericScanManager::FullScan ); } void DatabaseCollectionScanCapability::startIncrementalScan( const QString &directory ) { if( directory.isEmpty() ) { QList urls; foreach( const QString& path, m_collection->mountPointManager()->collectionFolders() ) urls.append( QUrl::fromLocalFile( path ) ); m_collection->scanManager()->requestScan( urls, GenericScanManager::UpdateScan ); } else { QList urls; urls.append( QUrl::fromLocalFile( directory ) ); m_collection->scanManager()->requestScan( urls, GenericScanManager::PartialUpdateScan ); } } void DatabaseCollectionScanCapability::stopScan() { m_collection->scanManager()->abort(); } // --------- DatabaseCollectionImportCapability ------------- DatabaseCollectionImportCapability::DatabaseCollectionImportCapability( DatabaseCollection* collection ) : m_collection( collection ) { Q_ASSERT( m_collection ); } DatabaseCollectionImportCapability::~DatabaseCollectionImportCapability() { } void DatabaseCollectionImportCapability::import( QIODevice *input, QObject *listener ) { DEBUG_BLOCK if( listener ) { // TODO: change import capability to collection action // TODO: why have listeners here and not for the scan capability // TODO: showMessage does not longer work like this, the scan result processor is doing this connect( m_collection->scanManager(), SIGNAL(succeeded()), listener, SIGNAL(importSucceeded()) ); connect( m_collection->scanManager(), SIGNAL(failed(QString)), listener, SIGNAL(showMessage(QString)) ); } m_collection->scanManager()->requestImport( input ); } diff --git a/src/core-impl/collections/mtpcollection/MtpCollection.h b/src/core-impl/collections/mtpcollection/MtpCollection.h index 57a3bf8427..94d989926d 100644 --- a/src/core-impl/collections/mtpcollection/MtpCollection.h +++ b/src/core-impl/collections/mtpcollection/MtpCollection.h @@ -1,65 +1,65 @@ /**************************************************************************************** * Copyright (c) 2008 Alejandro Wainzinger * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef MTPCOLLECTION_H #define MTPCOLLECTION_H #include "MtpHandler.h" #include "MediaDeviceCollection.h" #include "core/support/Debug.h" #include #include class MediaDeviceInfo; namespace Collections { class MtpCollection; class MtpCollectionFactory : public MediaDeviceCollectionFactory { Q_PLUGIN_METADATA(IID AmarokPluginFactory_iid FILE "amarok_collection-mtpcollection.json") Q_INTERFACES(Plugins::PluginFactory) Q_OBJECT public: MtpCollectionFactory(); virtual ~MtpCollectionFactory(); }; class MtpCollection : public MediaDeviceCollection { Q_OBJECT public: explicit MtpCollection( MediaDeviceInfo* ); virtual ~MtpCollection(); QString collectionId() const override; QString prettyName() const override; - QIcon icon() const override { return QIcon::fromTheme("multimedia-player"); } + QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("multimedia-player")); } //void writeDatabase(); }; } //namespace Collections #endif diff --git a/src/core/transcoding/formats/TranscodingAacFormat.cpp b/src/core/transcoding/formats/TranscodingAacFormat.cpp index d159a4ec14..da6dfd2244 100644 --- a/src/core/transcoding/formats/TranscodingAacFormat.cpp +++ b/src/core/transcoding/formats/TranscodingAacFormat.cpp @@ -1,101 +1,101 @@ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "TranscodingAacFormat.h" #include #include using namespace Transcoding; AacFormat::AacFormat() { m_encoder = AAC; m_fileExtension = QStringLiteral("m4a"); QString description1 = i18n( "The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
" "The encoder used by Amarok operates better with a constant bitrate.
" "VBR is experimental and likely to get even worse results than the CBR.
" "For this reason, the bitrate measure in this slider is a pretty accurate estimate " "of the bitrate of the encoded track.
" "The encoder is transparent at 128kbps for most samples tested with artifacts only appearing in extreme cases."); QStringList valueLabels; QByteArray cbr = "CBR %1kb/s"; valueLabels << i18n( cbr, 32 ) << i18n( cbr, 64 ) << i18n( cbr, 96 ) << i18n( cbr, 128 ) << i18n( cbr, 160 ) << i18n( cbr, 192 ) << i18n( cbr, 224 ) << i18n( cbr, 256 ); m_propertyList << Property::Tradeoff( "bitrate", i18n( "Bitrate target for constant bitrate encoding" ), description1, i18n( "Smaller file" ), i18n( "Better sound quality"), valueLabels, 3 ); } QString AacFormat::prettyName() const { return i18n( "AAC" ); } QString AacFormat::description() const { return i18nc( "Feel free to redirect the english Wikipedia link to a local version, if " "it exists.", "Advanced Audio " "Coding (AAC) is a patented lossy codec for digital audio.
AAC " "generally achieves better sound quality than MP3 at similar bit rates. " "It is a reasonable choice for the iPod and some other portable music players." ); } QIcon AacFormat::icon() const { - return QIcon::fromTheme( "audio-ac3" ); //TODO: get a *real* icon! + return QIcon::fromTheme( QStringLiteral("audio-ac3") ); //TODO: get a *real* icon! } QStringList AacFormat::ffmpegParameters( const Configuration &configuration ) const { QStringList parameters; parameters << QStringLiteral("-acodec") << QStringLiteral("aac") << QStringLiteral("-strict") << QStringLiteral("-2"); foreach( const Property &property, m_propertyList ) { if( !configuration.property( property.name() ).isNull() && configuration.property( property.name() ).type() == property.variantType() ) { if( property.name() == "bitrate" ) { parameters << QStringLiteral("-b:a") << QString::number( ( configuration.property( "bitrate" ).toInt() + 1 ) * 32000); } } } parameters << QStringLiteral("-vn"); // no album art, writing it to m4a is not supported by ffmpeg return parameters; } bool AacFormat::verifyAvailability( const QString &ffmpegOutput ) const { return ffmpegOutput.contains( QRegExp( "^ .EA... aac +" ) ); } diff --git a/src/core/transcoding/formats/TranscodingAlacFormat.cpp b/src/core/transcoding/formats/TranscodingAlacFormat.cpp index b0d200989a..097db3dcab 100644 --- a/src/core/transcoding/formats/TranscodingAlacFormat.cpp +++ b/src/core/transcoding/formats/TranscodingAlacFormat.cpp @@ -1,67 +1,67 @@ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "TranscodingAlacFormat.h" #include using namespace Transcoding; AlacFormat::AlacFormat() { m_encoder = ALAC; m_fileExtension = QStringLiteral("m4a"); //ALAC seems to have absolutely no configurable options whatsoever. Gnomes would love it. } QString AlacFormat::prettyName() const { return i18n( "Apple Lossless" ); } QString AlacFormat::description() const { return i18nc( "Feel free to redirect the english Wikipedia link to a local version, if " "it exists.", "Apple Lossless " "(ALAC) is an audio codec for lossless compression of digital music.
" "Recommended only for Apple music players and players that do not support " "FLAC." ); } QIcon AlacFormat::icon() const { - return QIcon::fromTheme( "audio-x-flac" ); //TODO: get a *real* icon! + return QIcon::fromTheme( QStringLiteral("audio-x-flac") ); //TODO: get a *real* icon! } QStringList AlacFormat::ffmpegParameters( const Configuration &configuration ) const { Q_UNUSED( configuration ) QStringList parameters; parameters << QStringLiteral("-acodec") << QStringLiteral("alac"); parameters << QStringLiteral("-vn"); // no album art, writing it to m4a is not supported by ffmpeg return parameters; } bool AlacFormat::verifyAvailability( const QString &ffmpegOutput ) const { return ffmpegOutput.contains( QRegExp( QStringLiteral("^ .EA... alac +") ) ); } diff --git a/src/core/transcoding/formats/TranscodingFlacFormat.cpp b/src/core/transcoding/formats/TranscodingFlacFormat.cpp index 3ac06e9e70..295e160a06 100644 --- a/src/core/transcoding/formats/TranscodingFlacFormat.cpp +++ b/src/core/transcoding/formats/TranscodingFlacFormat.cpp @@ -1,94 +1,94 @@ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "TranscodingFlacFormat.h" #include #include using namespace Transcoding; FlacFormat::FlacFormat() { m_encoder = FLAC; m_fileExtension = QStringLiteral("flac"); const QString description1 = i18n( "The " "compression level is an integer value between 0 and 8 that represents " "the tradeoff between file size and compression speed while encoding with FLAC.
" "Setting the compression level to 0 yields the shortest compression time but " "generates a comparably big file
" "On the other hand, a compression level of 8 makes compression quite slow but " "produces the smallest file.
" "Note that since FLAC is by definition a lossless codec, the audio quality " "of the output is exactly the same regardless of the compression level.
" "Also, levels above 5 dramatically increase compression time but create an only " "slightly smaller file, and are not recommended." ); m_propertyList << Property::Tradeoff( "level", i18n( "Compression level" ), description1, i18n( "Faster compression" ), i18n( "Smaller file" ), 0, 8, 5 ); } QString FlacFormat::prettyName() const { return i18n( "FLAC" ); } QString FlacFormat::description() const { return i18nc( "Feel free to redirect the english Wikipedia link to a local version, if " "it exists.", "Free " "Lossless Audio Codec (FLAC) is an open and royalty-free codec for " "lossless compression of digital music.
If you wish to store your music " "without compromising on audio quality, FLAC is an excellent choice." ); } QIcon FlacFormat::icon() const { - return QIcon::fromTheme( "audio-x-flac" ); //TODO: get a *real* icon! + return QIcon::fromTheme( QStringLiteral("audio-x-flac") ); //TODO: get a *real* icon! } QStringList FlacFormat::ffmpegParameters( const Configuration &configuration ) const { QStringList parameters; parameters << QStringLiteral("-acodec") << QStringLiteral("flac"); foreach( const Property &property, m_propertyList ) { if( !configuration.property( property.name() ).isNull() && configuration.property( property.name() ).type() == property.variantType() ) { if( property.name() == "level" ) { parameters << QStringLiteral("-compression_level") << QString::number( configuration.property( "level" ).toInt() ); } } } parameters << QStringLiteral("-vn"); // no album art, writing it to flac is not supported by ffmpeg return parameters; } bool FlacFormat::verifyAvailability( const QString &ffmpegOutput ) const { return ffmpegOutput.contains( QRegExp( QStringLiteral("^ .EA... flac +") ) ); } diff --git a/src/core/transcoding/formats/TranscodingOpusFormat.cpp b/src/core/transcoding/formats/TranscodingOpusFormat.cpp index 848b7b121b..8568cd9312 100644 --- a/src/core/transcoding/formats/TranscodingOpusFormat.cpp +++ b/src/core/transcoding/formats/TranscodingOpusFormat.cpp @@ -1,121 +1,121 @@ /**************************************************************************************** * Copyright (c) 2013 Martin Brodbeck * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "TranscodingOpusFormat.h" #include using namespace Transcoding; OpusFormat::OpusFormat() { m_encoder = OPUS; m_fileExtension = QStringLiteral("opus"); const QString description1 = i18n( "The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
The Opus encoder used by Amarok supports " "a variable bitrate (VBR) " "setting, which means that the bitrate value fluctuates along the track " "based on the complexity of the audio content. More complex intervals of " "data are encoded with a higher bitrate than less complex ones; this " "approach yields overall better quality and a smaller file than having a " "constant bitrate throughout the track.
" "For this reason, the bitrate measure in this slider is just an estimate " "of the average bitrate of the encoded track.
" "128kb/s is a good choice for music listening on a portable player.
" "Anything below 100kb/s might be unsatisfactory for music and anything above " "256kb/s is probably overkill."); QStringList valueLabels; QByteArray vbr = "VBR ~%1kb/s"; valueLabels << i18n( vbr, 32 ) << i18n( vbr, 64 ) << i18n( vbr, 96 ) << i18n( vbr, 128 ) << i18n( vbr, 160 ) << i18n( vbr, 192 ) << i18n( vbr, 256 ) << i18n( vbr, 320 ) << i18n( vbr, 360 ); m_validBitrates << 32 << 64 << 96 << 128 << 160 << 192 << 256 << 320 << 360; m_propertyList << Property::Tradeoff( "bitrate", i18n( "Expected average bitrate for variable bitrate encoding" ), description1, i18n( "Smaller file" ), i18n( "Better sound quality" ), valueLabels, 4 ); } QString OpusFormat::prettyName() const { return i18n( "Opus" ); } QString OpusFormat::description() const { return i18nc( "Feel free to redirect the english Wikipedia link to a local version, if " "it exists.", "Opus is " "a patent-free digital audio codec using a form of lossy data compression."); } QIcon OpusFormat::icon() const { - return QIcon::fromTheme( "audio-x-generic" ); //TODO: get a *real* icon! + return QIcon::fromTheme( QStringLiteral("audio-x-generic") ); //TODO: get a *real* icon! } QStringList OpusFormat::ffmpegParameters( const Configuration &configuration ) const { QStringList parameters; parameters << QStringLiteral("-acodec") << QStringLiteral("libopus"); foreach( const Property &property, m_propertyList ) { if( !configuration.property( property.name() ).isNull() && configuration.property( property.name() ).type() == property.variantType() ) { if( property.name() == "bitrate" ) { int ffmpegBitrate = toFfmpegBitrate( configuration.property( "bitrate" ).toInt() ); parameters << QStringLiteral("-ab") << QString::number( ffmpegBitrate ); } } } parameters << QStringLiteral("-vn"); // no video stream or album art return parameters; } int OpusFormat::toFfmpegBitrate( int setting ) const { return m_validBitrates[ setting ] * 1000; } bool OpusFormat::verifyAvailability( const QString &ffmpegOutput ) const { return ffmpegOutput.contains( QRegExp( QStringLiteral("^ .EA... opus +.*libopus") ) ); } diff --git a/src/core/transcoding/formats/TranscodingVorbisFormat.cpp b/src/core/transcoding/formats/TranscodingVorbisFormat.cpp index 87aad6da50..2e0f30de73 100644 --- a/src/core/transcoding/formats/TranscodingVorbisFormat.cpp +++ b/src/core/transcoding/formats/TranscodingVorbisFormat.cpp @@ -1,114 +1,114 @@ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "TranscodingVorbisFormat.h" #include using namespace Transcoding; VorbisFormat::VorbisFormat() { m_encoder = VORBIS; m_fileExtension = QStringLiteral("ogg"); const QString description1 = i18n( "The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
The Vorbis encoder used by Amarok supports " "a variable bitrate " "(VBR) setting, which means that the bitrate value fluctuates along the track " "based on the complexity of the audio content. More complex intervals of " "data are encoded with a higher bitrate than less complex ones; this " "approach yields overall better quality and a smaller file than having a " "constant bitrate throughout the track.
" "The Vorbis encoder uses a quality rating \"-q parameter\" between -1 and 10 to define " "a certain expected audio quality level. The bitrate measure in this slider is " "just a rough estimate (provided by Vorbis) of the average bitrate of the encoded " "track given a q value. In fact, with newer and more efficient Vorbis versions the " "actual bitrate is even lower.
" "-q5 is a good choice for music listening on a portable player.
" "Anything below -q3 might be unsatisfactory for music and anything above " "-q8 is probably overkill."); QStringList valueLabels; const QByteArray vbr = "-q%1 ~%2kb/s"; valueLabels << i18n( vbr, -1, 45 ) << i18n( vbr, 0, 64 ) << i18n( vbr, 1, 80 ) << i18n( vbr, 2, 96 ) << i18n( vbr, 3, 112 ) << i18n( vbr, 4, 128 ) << i18n( vbr, 5, 160 ) << i18n( vbr, 6, 192 ) << i18n( vbr, 7, 224 ) << i18n( vbr, 8, 256 ) << i18n( vbr, 9, 320 ) << i18n( vbr, 10, 500 ); m_propertyList << Property::Tradeoff( "quality", i18n( "Quality rating for variable bitrate encoding" ), description1, i18n( "Smaller file" ), i18n( "Better sound quality" ), valueLabels, 7 ); } QString VorbisFormat::prettyName() const { return i18n( "Ogg Vorbis" ); } QString VorbisFormat::description() const { return i18nc( "Feel free to redirect the english Wikipedia link to a local version, if " "it exists.", "Ogg Vorbis is an open " "and royalty-free audio codec for lossy audio compression.
It produces " "smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an " "all-around excellent choice, especially for portable music players that " "support it." ); } QIcon VorbisFormat::icon() const { - return QIcon::fromTheme( "audio-x-wav" ); //TODO: get a *real* icon! + return QIcon::fromTheme( QStringLiteral("audio-x-wav") ); //TODO: get a *real* icon! } QStringList VorbisFormat::ffmpegParameters( const Configuration &configuration ) const { QStringList parameters; parameters << QStringLiteral("-acodec") << QStringLiteral("libvorbis"); //libvorbis is better than FFmpeg's //vorbis implementation in many ways foreach( const Property &property, m_propertyList ) { if( !configuration.property( property.name() ).isNull() && configuration.property( property.name() ).type() == property.variantType() ) { if( property.name() == "quality" ) { int ffmpegQuality = configuration.property( "quality" ).toInt() - 1; parameters << QStringLiteral("-aq") << QString::number( ffmpegQuality ); } } } parameters << QStringLiteral("-vn"); // no video stream or album art, some devices can't handle that return parameters; } bool VorbisFormat::verifyAvailability( const QString &ffmpegOutput ) const { return ffmpegOutput.contains( QRegExp( QStringLiteral("^ .EA... vorbis +.*libvorbis") ) ); } diff --git a/src/covermanager/CoverFetchingActions.cpp b/src/covermanager/CoverFetchingActions.cpp index fe081fb9d5..11a350fe03 100644 --- a/src/covermanager/CoverFetchingActions.cpp +++ b/src/covermanager/CoverFetchingActions.cpp @@ -1,207 +1,207 @@ /**************************************************************************************** * Copyright (c) 2008 Seb Ruiz * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "CoverFetchingActions" #include "CoverFetchingActions.h" #include "core/support/Debug.h" #include "MainWindow.h" #include "CoverFetcher.h" #include "CoverManager.h" #include "CoverViewDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include ///////////////////////////////////// // FetchCoverAction ///////////////////////////////////// void FetchCoverAction::init() { setText( i18np("Fetch Cover", "Fetch Covers", m_albums.count()) ); setIcon( QIcon::fromTheme(QStringLiteral("insert-image")) ); setToolTip( i18np("Fetch the artwork for this album", "Fetch artwork for %1 albums", m_albums.count()) ); bool enabled = !m_albums.isEmpty(); foreach( Meta::AlbumPtr album, m_albums ) { enabled &= album->canUpdateImage(); } setEnabled( enabled ); } void FetchCoverAction::slotTriggered() { // Queuing multiple albums causes the fetcher to automatically assign values without asking // Such as case would be the CoverManager's Fetch All Missing feature if( m_albums.size() == 1 ) CoverFetcher::instance()->manualFetch( m_albums.first() ); else CoverFetcher::instance()->queueAlbums( m_albums ); } ///////////////////////////////////// // DisplayCoverAction ///////////////////////////////////// void DisplayCoverAction::init() { setText( i18n("Display Cover") ); - setIcon( QIcon::fromTheme("zoom-original") ); + setIcon( QIcon::fromTheme(QStringLiteral("zoom-original")) ); setToolTip( i18n("Display artwork for this album") ); Meta::AlbumPtr album = m_albums.first(); if( album ) setEnabled( album->hasImage() ); } void DisplayCoverAction::slotTriggered() { ( new CoverViewDialog( m_albums.first(), The::mainWindow() ) )->show(); } ///////////////////////////////////// // UnsetCoverAction ///////////////////////////////////// void UnsetCoverAction::init() { setText( i18np("Unset Cover", "Unset Covers", m_albums.count()) ); - setIcon( QIcon::fromTheme("list-remove") ); + setIcon( QIcon::fromTheme(QStringLiteral("list-remove")) ); setToolTip( i18np("Remove artwork for this album", "Remove artwork for %1 albums", m_albums.count()) ); // this action is enabled if any one of the albums has an image and can be updated bool enabled = false; foreach( Meta::AlbumPtr album, m_albums ) enabled |= ( album->hasImage() && album->canUpdateImage() ); setEnabled( enabled ); } void UnsetCoverAction::slotTriggered() { int button = KMessageBox::warningContinueCancel( qobject_cast( parent() ), i18np( "Are you sure you want to remove this cover from the Collection?", "Are you sure you want to delete these %1 covers from the Collection?", m_albums.count() ), QString(), KStandardGuiItem::del() ); if ( button == KMessageBox::Continue ) { foreach( Meta::AlbumPtr album, m_albums ) { if( album && album->canUpdateImage() ) album->removeImage(); } qApp->processEvents(); } } ///////////////////////////////////// // SetCustomCoverAction ///////////////////////////////////// void SetCustomCoverAction::init() { setText( i18n("Set Custom Cover") ); - setIcon( QIcon::fromTheme("document-open") ); + setIcon( QIcon::fromTheme(QStringLiteral("document-open")) ); setToolTip( i18np("Set custom artwork for this album", "Set custom artwork for these %1 albums", m_albums.count()) ); // this action is enabled if any one of the albums can be updated bool enabled = false; foreach( Meta::AlbumPtr album, m_albums ) if( album ) enabled |= album->canUpdateImage(); setEnabled( enabled ); } void SetCustomCoverAction::slotTriggered() { if( m_albums.isEmpty() || m_albums.first()->tracks().isEmpty() ) return; const QString& startPath = m_albums.first()->tracks().first()->playableUrl().adjusted(QUrl::RemoveFilename).path(); const auto mt( QImageReader::supportedMimeTypes() ); QStringList mimetypes; for( const auto &mimetype : mt ) mimetypes << QString( mimetype ); QFileDialog dlg; dlg.setDirectory( startPath ); dlg.setAcceptMode( QFileDialog::AcceptOpen ); dlg.setFileMode( QFileDialog::ExistingFile ); dlg.setMimeTypeFilters( mimetypes ); dlg.setWindowTitle( i18n("Select Cover Image File") ); dlg.exec(); QUrl file = dlg.selectedUrls().value( 0 ); if( !file.isEmpty() ) { QImage image; if( file.isLocalFile() ) { image.load( file.path() ); } else { debug() << "Custom Cover Fetch: " << file.toDisplayString(); QTemporaryDir tempDir; tempDir.setAutoRemove( true ); const QString coverDownloadPath = tempDir.path() + QLatin1Char('/') + file.fileName(); auto copyJob = KIO::copy( file, QUrl::fromLocalFile( coverDownloadPath ) ); bool ret = copyJob->exec(); if( ret ) image.load( coverDownloadPath ); } if( !image.isNull() ) { foreach( Meta::AlbumPtr album, m_albums ) { if( album && album->canUpdateImage() ) album->setImage( image ); } } } } diff --git a/src/covermanager/CoverManager.cpp b/src/covermanager/CoverManager.cpp index 72e0ca5c4d..869c018ce3 100644 --- a/src/covermanager/CoverManager.cpp +++ b/src/covermanager/CoverManager.cpp @@ -1,896 +1,896 @@ /**************************************************************************************** * Copyright (c) 2004 Pierpaolo Di Panfilo * * Copyright (c) 2005 Isaiah Damron * * Copyright (c) 2007 Dan Meltzer * * Copyright (c) 2008 Seb Ruiz * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "CoverManager" #include "CoverManager.h" #include "amarokconfig.h" #include #include "core/capabilities/ActionsCapability.h" #include "core/collections/Collection.h" #include "core/collections/QueryMaker.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "covermanager/CoverFetchingActions.h" #include "covermanager/CoverViewDialog.h" #include "playlist/PlaylistController.h" #include "statusbar/CompoundProgressBar.h" #include "widgets/LineEdit.h" #include "widgets/PixmapViewer.h" #include #include #include #include #include //showCoverMenu() #include #include #include #include #include #include //search filter timer #include #include #include #include #include #include #include #include //status label #include static QString artistToSelectInInitFunction; CoverManager *CoverManager::s_instance = nullptr; class ArtistItem : public QTreeWidgetItem { public: ArtistItem( QTreeWidget *parent, Meta::ArtistPtr artist ) : QTreeWidgetItem( parent ) , m_artist( artist ) { setText( 0, artist->prettyName() ); } ArtistItem( const QString &text, QTreeWidget *parent = nullptr ) : QTreeWidgetItem( parent ) , m_artist( 0 ) { setText( 0, text ); } Meta::ArtistPtr artist() const { return m_artist; } private: Meta::ArtistPtr m_artist; }; CoverManager::CoverManager( QWidget *parent ) : QDialog( parent ) , m_currentView( AllAlbums ) , m_timer( new QTimer( this ) ) //search filter timer , m_fetchingCovers( false ) , m_coversFetched( 0 ) , m_coverErrors( 0 ) , m_isLoadingCancelled( false ) { DEBUG_BLOCK setObjectName( "TheCoverManager" ); s_instance = this; // Sets caption and icon correctly (needed e.g. for GNOME) //kapp->setTopWidget( this ); QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Close, this ); QVBoxLayout *mainLayout = new QVBoxLayout(this); connect(buttonBox, &QDialogButtonBox::accepted, this, &CoverManager::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &CoverManager::reject); setWindowTitle( i18n("Cover Manager") ); setAttribute( Qt::WA_DeleteOnClose ); // TODO: There is no hidden signal in QDialog. Needs porting to QT5. // connect( this, &CoverManager::hidden, this, &CoverManager::delayedDestruct ); connect( buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, this, &CoverManager::delayedDestruct ); m_splitter = new QSplitter( this ); mainLayout->addWidget(m_splitter); mainLayout->addWidget(buttonBox); //artist listview m_artistView = new QTreeWidget( m_splitter ); m_artistView->setHeaderLabel( i18n( "Albums By" ) ); m_artistView->setSortingEnabled( false ); m_artistView->setTextElideMode( Qt::ElideRight ); m_artistView->setMinimumWidth( 200 ); m_artistView->setColumnCount( 1 ); m_artistView->setAlternatingRowColors( true ); m_artistView->setUniformRowHeights( true ); m_artistView->setSelectionMode( QAbstractItemView::ExtendedSelection ); ArtistItem *item = 0; item = new ArtistItem( i18n( "All Artists" ) ); item->setIcon(0, SmallIcon( "media-optical-audio-amarok" ) ); m_items.append( item ); Collections::Collection *coll = CollectionManager::instance()->primaryCollection(); Collections::QueryMaker *qm = coll->queryMaker(); qm->setAutoDelete( true ); qm->setQueryType( Collections::QueryMaker::Artist ); qm->setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums ); qm->orderBy( Meta::valArtist ); connect( qm, &Collections::QueryMaker::newArtistsReady, this, &CoverManager::slotArtistQueryResult ); connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotContinueConstruction ); qm->run(); } void CoverManager::slotArtistQueryResult( Meta::ArtistList artists ) //SLOT { DEBUG_BLOCK foreach( Meta::ArtistPtr artist, artists ) m_artistList << artist; } void CoverManager::slotContinueConstruction() //SLOT { DEBUG_BLOCK foreach( Meta::ArtistPtr artist, m_artistList ) { ArtistItem* item = new ArtistItem( m_artistView, artist ); item->setIcon( 0, SmallIcon( "view-media-artist-amarok" ) ); m_items.append( item ); } m_artistView->insertTopLevelItems( 0, m_items ); BoxWidget *vbox = new BoxWidget( true, m_splitter ); BoxWidget *hbox = new BoxWidget( false, vbox ); vbox->layout()->setSpacing( 4 ); hbox->layout()->setSpacing( 4 ); { // m_searchEdit = new Amarok::LineEdit( hbox ); m_searchEdit->setPlaceholderText( i18n( "Enter search terms here" ) ); m_searchEdit->setFrame( true ); m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum); m_searchEdit->setClearButtonEnabled( true ); static_cast( hbox->layout() )->setStretchFactor( m_searchEdit, 1 ); } // // view menu m_viewButton = new QPushButton( hbox ); m_viewMenu = new QMenu( m_viewButton ); m_selectAllAlbums = m_viewMenu->addAction( i18n("All Albums"), this, &CoverManager::slotShowAllAlbums ); m_selectAlbumsWithCover = m_viewMenu->addAction( i18n("Albums With Cover"), this, &CoverManager::slotShowAlbumsWithCover ); m_selectAlbumsWithoutCover = m_viewMenu->addAction( i18n("Albums Without Cover"), this, &CoverManager::slotShowAlbumsWithoutCover ); QActionGroup *viewGroup = new QActionGroup( m_viewButton ); viewGroup->setExclusive( true ); viewGroup->addAction( m_selectAllAlbums ); viewGroup->addAction( m_selectAlbumsWithCover ); viewGroup->addAction( m_selectAlbumsWithoutCover ); m_viewButton->setMenu( m_viewMenu ); - m_viewButton->setIcon( QIcon::fromTheme( "filename-album-amarok" ) ); + m_viewButton->setIcon( QIcon::fromTheme( QStringLiteral("filename-album-amarok") ) ); connect( m_viewMenu, &QMenu::triggered, this, &CoverManager::slotAlbumFilterTriggered ); //fetch missing covers button m_fetchButton = new QPushButton( QIcon( "get-hot-new-stuff-amarok" ), i18n("Fetch Missing Covers"), hbox ); connect( m_fetchButton, &QAbstractButton::clicked, this, &CoverManager::fetchMissingCovers ); m_selectAllAlbums->setChecked( true ); m_selectAllAlbums->trigger(); //cover view m_coverView = new CoverView( vbox ); m_coverViewSpacer = new CoverView( vbox ); m_coverViewSpacer->hide(); //status bar QStatusBar *statusBar = new QStatusBar( vbox ); m_statusLabel = new KSqueezedTextLabel( statusBar ); m_statusLabel->setIndent( 3 ); m_progress = new CompoundProgressBar( statusBar ); statusBar->addWidget( m_statusLabel, 4 ); statusBar->addPermanentWidget( m_progress, 1 ); connect( m_progress, &CompoundProgressBar::allDone, this, &CoverManager::progressAllDone ); QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5; QSize sz = Amarok::config( "Cover Manager" ).readEntry( "Window Size", size ); resize( sz.width(), sz.height() ); m_splitter->setStretchFactor( m_splitter->indexOf( m_artistView ), 1 ); m_splitter->setStretchFactor( m_splitter->indexOf( vbox ), 4 ); m_fetcher = The::coverFetcher(); QTreeWidgetItem *item = 0; int i = 0; if ( !artistToSelectInInitFunction.isEmpty() ) { for( item = m_artistView->invisibleRootItem()->child( 0 ); i < m_artistView->invisibleRootItem()->childCount(); item = m_artistView->invisibleRootItem()->child( i++ ) ) { if ( item->text( 0 ) == artistToSelectInInitFunction ) break; } } // signals and slots connections connect( m_artistView, &QTreeWidget::itemSelectionChanged, this, &CoverManager::slotArtistSelected ); connect( m_coverView, &CoverView::itemActivated, this, &CoverManager::coverItemClicked ); connect( m_timer, &QTimer::timeout, this, &CoverManager::slotSetFilter ); connect( m_searchEdit, &Amarok::LineEdit::textChanged, this, &CoverManager::slotSetFilterTimeout ); if( item == 0 ) item = m_artistView->invisibleRootItem()->child( 0 ); item->setSelected( true ); show(); } CoverManager::~CoverManager() { Amarok::config( "Cover Manager" ).writeEntry( "Window Size", size() ); qDeleteAll( m_coverItems ); delete m_coverView; m_coverView = 0; s_instance = nullptr; } void CoverManager::viewCover( const Meta::AlbumPtr &album, QWidget *parent ) //static { //QDialog means "escape" works as expected QDialog *dialog = new CoverViewDialog( album, parent ); dialog->show(); } void CoverManager::metadataChanged(const Meta::AlbumPtr &album ) { const QString albumName = album->name(); foreach( CoverViewItem *item, m_coverItems ) { if( albumName == item->albumPtr()->name() ) item->loadCover(); } updateStatusBar(); } void CoverManager::fetchMissingCovers() //SLOT { m_fetchCovers.clear(); for( int i = 0, coverCount = m_coverView->count(); i < coverCount; ++i ) { QListWidgetItem *item = m_coverView->item( i ); CoverViewItem *coverItem = static_cast( item ); if( !coverItem->hasCover() ) m_fetchCovers += coverItem->albumPtr(); } debug() << QString( "Fetching %1 missing covers" ).arg( m_fetchCovers.size() ); ProgressBar *fetchProgressBar = new ProgressBar( this ); fetchProgressBar->setDescription( i18n( "Fetching" ) ); fetchProgressBar->setMaximum( m_fetchCovers.size() ); m_progress->addProgressBar( fetchProgressBar, m_fetcher ); m_progress->show(); m_fetcher->queueAlbums( m_fetchCovers ); m_fetchingCovers = true; updateStatusBar(); m_fetchButton->setEnabled( false ); connect( m_fetcher, &CoverFetcher::finishedSingle, this, &CoverManager::updateFetchingProgress ); } void CoverManager::showOnce( const QString &artist, QWidget* parent ) { if( !s_instance ) { artistToSelectInInitFunction = artist; new CoverManager( parent ); } else { s_instance->activateWindow(); s_instance->raise(); } } void CoverManager::slotArtistSelected() //SLOT { DEBUG_BLOCK // delete cover items before clearing cover view qDeleteAll( m_coverItems ); m_coverItems.clear(); m_coverView->clear(); //this can be a bit slow QApplication::setOverrideCursor( Qt::WaitCursor ); Collections::Collection *coll = CollectionManager::instance()->primaryCollection(); Collections::QueryMaker *qm = coll->queryMaker(); qm->setAutoDelete( true ); qm->setQueryType( Collections::QueryMaker::Album ); qm->orderBy( Meta::valAlbum ); qm->beginOr(); const QList< QTreeWidgetItem* > items = m_artistView->selectedItems(); foreach( const QTreeWidgetItem *item, items ) { const ArtistItem *artistItem = static_cast< const ArtistItem* >( item ); if( artistItem != m_artistView->invisibleRootItem()->child( 0 ) ) qm->addFilter( Meta::valArtist, artistItem->artist()->name(), true, true ); else qm->excludeFilter( Meta::valAlbum, QString(), true, true ); } qm->endAndOr(); // do not show albums with no name, i.e. tracks not belonging to any album qm->beginAnd(); qm->excludeFilter( Meta::valAlbum, QString(), true, true ); qm->endAndOr(); connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &CoverManager::slotAlbumQueryResult ); connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotArtistQueryDone ); qm->run(); } void CoverManager::slotAlbumQueryResult( const Meta::AlbumList &albums ) //SLOT { m_albumList = albums; } void CoverManager::slotAlbumFilterTriggered( QAction *action ) //SLOT { m_viewButton->setText( action->text() ); } void CoverManager::slotArtistQueryDone() //SLOT { DEBUG_BLOCK QApplication::restoreOverrideCursor(); const int albumCount = m_albumList.count(); ProgressBar *coverLoadProgressBar = new ProgressBar( this ); coverLoadProgressBar->setDescription( i18n( "Loading" ) ); coverLoadProgressBar->setMaximum( albumCount ); connect( coverLoadProgressBar, &ProgressBar::cancelled, this, &CoverManager::cancelCoverViewLoading ); m_progress->addProgressBar( coverLoadProgressBar, m_coverView ); m_progress->show(); uint x = 0; debug() << "Loading covers for selected artist(s)"; //the process events calls below causes massive flickering in the m_albumList //so we hide this view and only show it when all items has been inserted. This //also provides quite a massive speed improvement when loading covers. m_coverView->hide(); m_coverViewSpacer->show(); foreach( const Meta::AlbumPtr &album, m_albumList ) { qApp->processEvents( QEventLoop::ExcludeSocketNotifiers ); if( isHidden() ) { m_progress->endProgressOperation( m_coverView ); return; } /* * Loading is stopped if cancelled by the user, or the number of albums * has changed. The latter occurs when the artist selection changes. */ if( m_isLoadingCancelled || albumCount != m_albumList.count() ) { m_isLoadingCancelled = false; break; } CoverViewItem *item = new CoverViewItem( m_coverView, album ); m_coverItems.append( item ); if( ++x % 10 == 0 ) { m_progress->setProgress( m_coverView, x ); } } m_progress->endProgressOperation( m_coverView ); // makes sure View is retained when artist selection changes changeView( m_currentView, true ); m_coverViewSpacer->hide(); m_coverView->show(); updateStatusBar(); } void CoverManager::cancelCoverViewLoading() { m_isLoadingCancelled = true; } // called when a cover item is clicked void CoverManager::coverItemClicked( QListWidgetItem *item ) //SLOT { #define item static_cast(item) if( !item ) return; item->setSelected( true ); if ( item->hasCover() ) viewCover( item->albumPtr(), this ); else m_fetcher->manualFetch( item->albumPtr() ); #undef item } void CoverManager::slotSetFilter() //SLOT { m_filter = m_searchEdit->text(); m_coverView->clearSelection(); uint i = 0; QListWidgetItem *item = m_coverView->item( i ); while ( item ) { QListWidgetItem *tmp = m_coverView->item( i + 1 ); m_coverView->takeItem( i ); item = tmp; } foreach( QListWidgetItem *item, m_coverItems ) { CoverViewItem *coverItem = static_cast(item); if( coverItem->album().contains( m_filter, Qt::CaseInsensitive ) || coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) ) m_coverView->insertItem( m_coverView->count() - 1, item ); } // makes sure View is retained when filter text has changed changeView( m_currentView, true ); updateStatusBar(); } void CoverManager::slotSetFilterTimeout() //SLOT { if ( m_timer->isActive() ) m_timer->stop(); m_timer->setSingleShot( true ); m_timer->start( 180 ); } void CoverManager::changeView( CoverManager::View id, bool force ) //SLOT { if( !force && m_currentView == id ) return; //clear the iconview without deleting items m_coverView->clearSelection(); int itemsCount = m_coverView->count(); while( itemsCount-- > 0 ) m_coverView->takeItem( 0 ); foreach( QListWidgetItem *item, m_coverItems ) { bool show = false; CoverViewItem *coverItem = static_cast(item); if( !m_filter.isEmpty() ) { if( !coverItem->album().contains( m_filter, Qt::CaseInsensitive ) && !coverItem->artist().contains( m_filter, Qt::CaseInsensitive ) ) continue; } if( id == AllAlbums ) //show all albums show = true; else if( id == AlbumsWithCover && coverItem->hasCover() ) //show only albums with cover show = true; else if( id == AlbumsWithoutCover && !coverItem->hasCover() )//show only albums without cover show = true; if( show ) m_coverView->insertItem( m_coverView->count() - 1, item ); } m_currentView = id; } void CoverManager::updateFetchingProgress( int state ) { switch( static_cast< CoverFetcher::FinishState >( state ) ) { case CoverFetcher::Success: m_coversFetched++; break; case CoverFetcher::Cancelled: case CoverFetcher::Error: case CoverFetcher::NotFound: default: m_coverErrors++; break; } m_progress->incrementProgress( m_fetcher ); updateStatusBar(); } void CoverManager::stopFetching() { DEBUG_FUNC_INFO m_fetchCovers.clear(); m_fetchingCovers = false; m_progress->endProgressOperation( m_fetcher ); updateStatusBar(); } void CoverManager::loadCover( const QString &artist, const QString &album ) { foreach( QListWidgetItem *item, m_coverItems ) { CoverViewItem *coverItem = static_cast(item); if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) ) { coverItem->loadCover(); return; } } } void CoverManager::progressAllDone() { m_progress->hide(); } void CoverManager::updateStatusBar() { QString text; //cover fetching info if( m_fetchingCovers ) { //update the status text if( m_coversFetched + m_coverErrors >= m_fetchCovers.size() ) { //fetching finished text = i18nc( "The fetching is done.", "Finished." ); if( m_coverErrors ) text += i18np( " Cover not found", " %1 covers not found", m_coverErrors ); //reset counters m_coversFetched = 0; m_coverErrors = 0; m_fetchCovers.clear(); m_fetchingCovers = false; m_progress->endProgressOperation( m_fetcher ); disconnect( m_fetcher, &CoverFetcher::finishedSingle, this, &CoverManager::updateFetchingProgress ); QTimer::singleShot( 2000, this, &CoverManager::updateStatusBar ); } if( m_fetchCovers.size() == 1 ) { foreach( Meta::AlbumPtr album, m_fetchCovers ) { if( album->hasAlbumArtist() && !album->albumArtist()->prettyName().isEmpty() ) { text = i18n( "Fetching cover for %1 - %2...", album->albumArtist()->prettyName(), album->prettyName() ); } else { text = i18n( "Fetching cover for %1..." , album->prettyName() ); } } } else { text = i18np( "Fetching 1 cover: ", "Fetching %1 covers... : ", m_fetchCovers.size() ); if( m_coversFetched ) text += i18np( "1 fetched", "%1 fetched", m_coversFetched ); if( m_coverErrors ) { if( m_coversFetched ) text += i18n(" - "); text += i18np( "1 not found", "%1 not found", m_coverErrors ); } if( m_coversFetched + m_coverErrors == 0 ) text += i18n( "Connecting..." ); } } else { m_coversFetched = 0; m_coverErrors = 0; uint totalCounter = 0, missingCounter = 0; //album info for( int i = 0, coverCount = m_coverView->count(); i < coverCount; ++i ) { totalCounter++; QListWidgetItem *item = m_coverView->item( i ); if( !static_cast( item )->hasCover() ) missingCounter++; //counter for albums without cover } const QList< QTreeWidgetItem* > selected = m_artistView->selectedItems(); if( !m_filter.isEmpty() ) { text = i18np( "1 result for \"%2\"", "%1 results for \"%2\"", totalCounter, m_filter ); } else if( selected.count() > 0 ) { text = i18np( "1 album", "%1 albums", totalCounter ); // showing albums by selected artist(s) if( selected.first() != m_artistView->invisibleRootItem()->child( 0 ) ) { QStringList artists; foreach( const QTreeWidgetItem *item, selected ) { QString artist = item->text( 0 ); Amarok::manipulateThe( artist, false ); artists.append( artist ); } text = i18n( "%1 by %2", text, artists.join( i18nc("Separator for artists", ", ")) ); } } if( missingCounter ) text = i18np("%2 - ( 1 without cover )", "%2 - ( %1 without cover )", missingCounter, text ); m_fetchButton->setEnabled( missingCounter ); } m_statusLabel->setText( text ); } void CoverManager::delayedDestruct() { if ( isVisible() ) hide(); deleteLater(); } void CoverManager::setStatusText( const QString &text ) { m_oldStatusText = m_statusLabel->text(); m_statusLabel->setText( text ); } ////////////////////////////////////////////////////////////////////// // CLASS CoverView ///////////////////////////////////////////////////////////////////// CoverView::CoverView( QWidget *parent, const char *name, Qt::WindowFlags f ) : QListWidget( parent ) { DEBUG_BLOCK setObjectName( name ); setWindowFlags( f ); setViewMode( QListView::IconMode ); setMovement( QListView::Static ); setResizeMode( QListView::Adjust ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setWrapping( true ); setWordWrap( true ); setIconSize( QSize(100, 100) ); setGridSize( QSize(120, 160) ); setTextElideMode( Qt::ElideRight ); setContextMenuPolicy( Qt::DefaultContextMenu ); setMouseTracking( true ); // required for setting status text when itemEntered signal is emitted connect( this, &CoverView::itemEntered, this, &CoverView::setStatusText ); connect( this, &CoverView::viewportEntered, CoverManager::instance(), &CoverManager::updateStatusBar ); } void CoverView::contextMenuEvent( QContextMenuEvent *event ) { QList items = selectedItems(); const int itemsCount = items.count(); QMenu menu; menu.addSection( i18n( "Cover Image" ) ); if( itemsCount == 1 ) { // only one item selected: get all custom actions this album is capable of. CoverViewItem *item = dynamic_cast( items.first() ); QList actions; Meta::AlbumPtr album = item->albumPtr(); if( album ) { QScopedPointer ac( album->create() ); if( ac ) { actions = ac->actions(); foreach( QAction *action, actions ) menu.addAction( action ); } } menu.exec( event->globalPos() ); } else if( itemsCount > 1 ) { // multiple albums selected: only unset cover and fetch cover actions // make sense here, and perhaps (un)setting compilation flag (TODO). Meta::AlbumList unsetAlbums; Meta::AlbumList fetchAlbums; foreach( QListWidgetItem *item, items ) { CoverViewItem *cvItem = dynamic_cast(item); Meta::AlbumPtr album = cvItem->albumPtr(); if( album ) { QScopedPointer ac( album->create() ); if( ac ) { QList actions = ac->actions(); foreach( QAction *action, actions ) { if( qobject_cast(action) ) fetchAlbums << album; else if( qobject_cast(action) ) unsetAlbums << album; } } } } if( itemsCount == fetchAlbums.count() ) { FetchCoverAction *fetchAction = new FetchCoverAction( this, fetchAlbums ); menu.addAction( fetchAction ); } if( itemsCount == unsetAlbums.count() ) { UnsetCoverAction *unsetAction = new UnsetCoverAction( this, unsetAlbums ); menu.addAction( unsetAction ); } menu.exec( event->globalPos() ); } else QListView::contextMenuEvent( event ); // TODO: Play, Load and Append to playlist actions } void CoverView::setStatusText( QListWidgetItem *item ) { #define itemmacro static_cast( item ) if ( !itemmacro ) return; const QString artist = itemmacro->albumPtr()->isCompilation() ? i18n( "Various Artists" ) : itemmacro->artist(); const QString tipContent = i18n( "%1 - %2", artist , itemmacro->album() ); CoverManager::instance()->setStatusText( tipContent ); #undef item } ////////////////////////////////////////////////////////////////////// // CLASS CoverViewItem ///////////////////////////////////////////////////////////////////// CoverViewItem::CoverViewItem( QListWidget *parent, Meta::AlbumPtr album ) : QListWidgetItem( parent ) , m_albumPtr( album) { m_album = album->prettyName(); if( album->hasAlbumArtist() ) m_artist = album->albumArtist()->prettyName(); else m_artist = i18n( "No Artist" ); setText( album->prettyName() ); loadCover(); CoverManager::instance()->subscribeTo( album ); } CoverViewItem::~CoverViewItem() {} bool CoverViewItem::hasCover() const { return albumPtr()->hasImage(); } void CoverViewItem::loadCover() { const bool isSuppressing = m_albumPtr->suppressImageAutoFetch(); m_albumPtr->setSuppressImageAutoFetch( true ); setIcon( QPixmap::fromImage( m_albumPtr->image( 100 ) ) ); m_albumPtr->setSuppressImageAutoFetch( isSuppressing ); } void CoverViewItem::dragEntered() { setSelected( true ); } void CoverViewItem::dragLeft() { setSelected( false ); } diff --git a/src/dialogs/EqualizerDialog.cpp b/src/dialogs/EqualizerDialog.cpp index 3819fe2fb4..e13fbc7608 100644 --- a/src/dialogs/EqualizerDialog.cpp +++ b/src/dialogs/EqualizerDialog.cpp @@ -1,328 +1,328 @@ /**************************************************************************************** * Copyright (c) 2004-2009 Mark Kretschmann * * Copyright (c) 2009 Artur Szymiec * * Copyright (c) 2013 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "EqualizerDialog.h" #include "amarokconfig.h" #include "EngineController.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" EqualizerDialog * EqualizerDialog::s_instance = nullptr; EqualizerDialog::EqualizerDialog( QWidget* parent ) : QDialog( parent ) { DEBUG_BLOCK setWindowTitle( i18n( "Configure Equalizer" ) ); setupUi( this ); EqualizerController *equalizer = The::engineController()->equalizerController(); // Check if equalizer is supported - disable controls if not if( !equalizer->isEqSupported() ) { EqualizerWidget->setDisabled( true ); activeCheckBox->setEnabled( false ); activeCheckBox->setChecked( false ); } connect(buttonBox, &QDialogButtonBox::accepted, this, &EqualizerDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &EqualizerDialog::restoreOriginalSettings); // Assign slider items to vectors m_bands.append( eqPreampSlider ); m_bands.append( eqBand0Slider ); m_bands.append( eqBand1Slider ); m_bands.append( eqBand2Slider ); m_bands.append( eqBand3Slider ); m_bands.append( eqBand4Slider ); m_bands.append( eqBand5Slider ); m_bands.append( eqBand6Slider ); m_bands.append( eqBand7Slider ); m_bands.append( eqBand8Slider ); m_bands.append( eqBand9Slider ); m_bandValues.append( eqPreampValue ); m_bandValues.append( eqBand0Value ); m_bandValues.append( eqBand1Value ); m_bandValues.append( eqBand2Value ); m_bandValues.append( eqBand3Value ); m_bandValues.append( eqBand4Value ); m_bandValues.append( eqBand5Value ); m_bandValues.append( eqBand6Value ); m_bandValues.append( eqBand7Value ); m_bandValues.append( eqBand8Value ); m_bandValues.append( eqBand9Value ); m_bandLabels.append( eqPreampLabel ); m_bandLabels.append( eqBand0Label ); m_bandLabels.append( eqBand1Label ); m_bandLabels.append( eqBand2Label ); m_bandLabels.append( eqBand3Label ); m_bandLabels.append( eqBand4Label ); m_bandLabels.append( eqBand5Label ); m_bandLabels.append( eqBand6Label ); m_bandLabels.append( eqBand7Label ); m_bandLabels.append( eqBand8Label ); m_bandLabels.append( eqBand9Label ); // Ask engine for maximum gain value and compute scale to display values mValueScale = equalizer->eqMaxGain(); const QString mlblText = i18n( "%0\ndB" ).arg( QString::number( mValueScale, 'f', 1 ) ); eqMaxEq->setText( QStringLiteral("+") + mlblText ); eqMinEq->setText( QStringLiteral("-") + mlblText ); // Ask engine for band frequencies and set labels const QStringList equalizerBandFreq = equalizer->eqBandsFreq(); QStringListIterator i( equalizerBandFreq ); // Check if preamp is supported by Phonon backend if( equalizerBandFreq.size() == s_equalizerBandsNum ) { // Preamp not supported, so hide its slider eqPreampLabel->hide(); eqPreampSlider->hide(); eqPreampValue->hide(); } else if( i.hasNext() ) // If preamp is present then skip its label as it is hard-coded in UI i.next(); foreach( QLabel* mLabel, m_bandLabels ) if( mLabel->objectName() != "eqPreampLabel" ) mLabel->setText( i.hasNext() ? i.next() : "N/A" ); updatePresets(); activeCheckBox->setChecked( equalizer->enabled() ); equalizer->applyEqualizerPresetByIndex( AmarokConfig::equalizerMode() - 1 ); equalizer->setGains( equalizer->gains() ); updateUi(); connect( equalizer, &EqualizerController::presetsChanged, this, &EqualizerDialog::presetsChanged ); connect( equalizer, &EqualizerController::gainsChanged, this, &EqualizerDialog::gainsChanged ); connect( equalizer, &EqualizerController::presetApplied, this, &EqualizerDialog::presetApplied ); // Configure signal and slots to handle presets connect( activeCheckBox, &QCheckBox::toggled, this, &EqualizerDialog::toggleEqualizer ); connect( eqPresets, QOverload::of(&QComboBox::currentIndexChanged), equalizer, &EqualizerController::applyEqualizerPresetByIndex ); connect( eqPresets, &QComboBox::editTextChanged, this, &EqualizerDialog::updateUi ); foreach( QSlider* mSlider, m_bands ) connect( mSlider, &QSlider::valueChanged, this, &EqualizerDialog::bandsChanged ); - eqPresetSaveBtn->setIcon( QIcon::fromTheme( "document-save" ) ); + eqPresetSaveBtn->setIcon( QIcon::fromTheme( QStringLiteral("document-save") ) ); connect( eqPresetSaveBtn, &QAbstractButton::clicked, this, &EqualizerDialog::savePreset ); - eqPresetDeleteBtn->setIcon( QIcon::fromTheme( "edit-delete" ) ); + eqPresetDeleteBtn->setIcon( QIcon::fromTheme( QStringLiteral("edit-delete") ) ); connect( eqPresetDeleteBtn, &QAbstractButton::clicked, this, &EqualizerDialog::deletePreset ); - eqPresetResetBtn->setIcon( QIcon::fromTheme( "edit-undo" ) ); + eqPresetResetBtn->setIcon( QIcon::fromTheme( QStringLiteral("edit-undo") ) ); connect( eqPresetResetBtn, &QAbstractButton::clicked, this, &EqualizerDialog::restorePreset ); } EqualizerDialog::~EqualizerDialog() { } void EqualizerDialog::showOnce( QWidget *parent ) { DEBUG_BLOCK if( s_instance == nullptr ) s_instance = new EqualizerDialog( parent ); s_instance->activateWindow(); s_instance->show(); s_instance->raise(); s_instance->storeOriginalSettings(); } QList EqualizerDialog::gains() const { QList result; foreach( QSlider* mSlider, m_bands ) result << mSlider->value(); return result; } void EqualizerDialog::gainsChanged( const QList &eqGains ) { for( int i = 0; i < m_bands.count() && i < eqGains.count(); i++ ) { // Update slider values with signal blocking to prevent circular loop m_bands[i]->blockSignals( true ); m_bands[i]->setValue( eqGains[ i ] ); m_bands[i]->blockSignals( false ); } updateToolTips(); updateLabels(); updateUi(); } void EqualizerDialog::storeOriginalSettings() { m_originalActivated = activeCheckBox->isChecked(); m_originalPreset = selectedPresetName(); m_originalGains = gains(); } void EqualizerDialog::restoreOriginalSettings() { // Only restore original settings if the equalizer was originally enabled // or if the equalizer is currently enabled. This prevents a reset of the // equalizer when cancel button is clicked with equalizer toggle off. if( m_originalActivated || activeCheckBox->isChecked() ) { activeCheckBox->setChecked( m_originalActivated ); int originalPresetIndex = EqualizerPresets::eqGlobalList().indexOf( m_originalPreset ); The::engineController()->equalizerController()->applyEqualizerPresetByIndex( originalPresetIndex ); eqPresets->setEditText( m_originalPreset ); The::engineController()->equalizerController()->setGains( m_originalGains ); } this->reject(); } void EqualizerDialog::presetApplied( int index ) //SLOT { if( index < 0 ) return; // if not called from the eqPreset->indexChanged signal we need // to update the combo box too. if( eqPresets->currentIndex() != index ) { eqPresets->blockSignals( true ); eqPresets->setCurrentIndex( index ); eqPresets->blockSignals( false ); } } void EqualizerDialog::bandsChanged() //SLOT { updateToolTips(); updateLabels(); updateUi(); // The::engineController()->equalizerController()->blockSignals( true ); The::engineController()->equalizerController()->setGains( gains() ); // The::engineController()->equalizerController()->blockSignals( false ); } void EqualizerDialog::updateUi() // SLOT { const QString currentName = selectedPresetName(); const bool enabledState = activeCheckBox->isChecked(); const bool userState = EqualizerPresets::eqUserList().contains( currentName ); const bool modified = ( EqualizerPresets::eqCfgGetPresetVal( currentName ) != gains() ); const bool nameModified = ! EqualizerPresets::eqGlobalList().contains( currentName ); const bool resetable = EqualizerPresets::eqCfgCanRestorePreset( currentName ); eqPresets->setEnabled( enabledState ); eqBandsGroupBox->setEnabled( enabledState ); eqPresetSaveBtn->setEnabled( enabledState && ( modified || nameModified ) ); eqPresetDeleteBtn->setEnabled( enabledState && userState ); eqPresetResetBtn->setEnabled( enabledState && ( resetable || modified ) ); } void EqualizerDialog::updatePresets() { const QString currentName = selectedPresetName(); eqPresets->blockSignals( true ); eqPresets->clear(); eqPresets->addItems( EqualizerPresets::eqGlobalTranslatedList() ); const int index = EqualizerPresets::eqGlobalList().indexOf( currentName ); if( index >= 0 ) eqPresets->setCurrentIndex( index ); eqPresets->blockSignals( false ); } void EqualizerDialog::presetsChanged( const QString &name ) { Q_UNUSED( name ) updatePresets(); if( EqualizerPresets::eqGlobalList().indexOf( selectedPresetName() ) == -1 ) presetApplied( 0 ); updateUi(); } void EqualizerDialog::savePreset() //SLOT { The::engineController()->equalizerController()->savePreset( selectedPresetName(), gains() ); } void EqualizerDialog::deletePreset() //SLOT { The::engineController()->equalizerController()->deletePreset( selectedPresetName() ); } QString EqualizerDialog::selectedPresetName() const { const QString currentText = eqPresets->currentText(); const int index = EqualizerPresets::eqGlobalTranslatedList().indexOf( currentText ); if( index < 0 ) return currentText; return EqualizerPresets::eqGlobalList().at( index ); } void EqualizerDialog::restorePreset() //SLOT { DEBUG_BLOCK EqualizerPresets::eqCfgRestorePreset( selectedPresetName() ); The::engineController()->equalizerController()->setGains( EqualizerPresets::eqCfgGetPresetVal( selectedPresetName() ) ); } void EqualizerDialog::updateToolTips() { foreach( QSlider* mSlider, m_bands ) mSlider->setToolTip( QString::number( mSlider->value()*mValueScale/100.0, 'f', 1 ) ); } void EqualizerDialog::updateLabels() { for( int i = 0; i < m_bandValues.count() && i < m_bands.count(); i++ ) m_bandValues[i]->setText( QString::number( m_bands[i]->value() * mValueScale / 100.0, 'f', 1 ) ); } void EqualizerDialog::toggleEqualizer( bool enabled ) { DEBUG_BLOCK EqualizerController *eq = The::engineController()->equalizerController(); if( !enabled ) eq->applyEqualizerPresetByIndex( -1 ); else eq->applyEqualizerPresetByIndex( eqPresets->currentIndex() ); } diff --git a/src/dialogs/TagDialog.cpp b/src/dialogs/TagDialog.cpp index 360632f3ac..66cb1f6b65 100644 --- a/src/dialogs/TagDialog.cpp +++ b/src/dialogs/TagDialog.cpp @@ -1,1406 +1,1406 @@ /**************************************************************************************** * Copyright (c) 2004 Mark Kretschmann * * Copyright (c) 2004 Pierpaolo Di Panfilo * * Copyright (c) 2005-2006 Alexandre Pereira de Oliveira * * Copyright (c) 2008 Téo Mrnjavac * * Copyright (c) 2008 Leo Franchi * * Copyright (c) 2009 Daniel Dewald * * Copyright (c) 2009 Pierre Dumuid * * Copyright (c) 2011 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "TagDialog" #include "TagDialog.h" #include "MainWindow.h" #include "SvgHandler.h" #include "core/collections/QueryMaker.h" #include "core/logger/Logger.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaUtility.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "covermanager/CoverFetchingActions.h" #include "dialogs/MusicBrainzTagger.h" #include "widgets/CoverLabel.h" #include "widgets/FilenameLayoutWidget.h" #include "ui_TagDialogBase.h" // needs to be after including CoverLabel, silly #include "TagGuesserDialog.h" #include #include #include #include #include namespace Meta { namespace Field { const QString LABELS = "labels"; const QString LYRICS = "lyrics"; const QString TYPE = "type"; const QString COLLECTION = "collection"; const QString NOTE = "note"; } } TagDialog::TagDialog( const Meta::TrackList &tracks, QWidget *parent ) : QDialog( parent ) , m_perTrack( true ) , m_currentTrackNum( 0 ) , m_changed( false ) , m_queryMaker( 0 ) , ui( new Ui::TagDialogBase() ) { DEBUG_BLOCK foreach( Meta::TrackPtr track, tracks ) addTrack( track ); ui->setupUi( this ); resize( minimumSizeHint() ); initUi(); setCurrentTrack( 0 ); } TagDialog::TagDialog( Meta::TrackPtr track, QWidget *parent ) : QDialog( parent ) , m_perTrack( true ) , m_currentTrackNum( 0 ) , m_changed( false ) , m_queryMaker( 0 ) , ui( new Ui::TagDialogBase() ) { DEBUG_BLOCK addTrack( track ); ui->setupUi( this ); resize( minimumSizeHint() ); initUi(); setCurrentTrack( 0 ); QTimer::singleShot( 0, this, &TagDialog::show ); } TagDialog::TagDialog( Collections::QueryMaker *qm ) : QDialog( The::mainWindow() ) , m_perTrack( true ) , m_currentTrackNum( 0 ) , m_changed( false ) , m_queryMaker( qm ) , ui( new Ui::TagDialogBase() ) { DEBUG_BLOCK ui->setupUi( this ); resize( minimumSizeHint() ); qm->setQueryType( Collections::QueryMaker::Track ); connect( qm, &Collections::QueryMaker::newArtistsReady, this, &TagDialog::artistsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newTracksReady, this, &TagDialog::tracksReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &TagDialog::albumsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newComposersReady, this, &TagDialog::composersReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newGenresReady, this, &TagDialog::genresReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newLabelsReady, this, &TagDialog::labelsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::queryDone, this, &TagDialog::queryDone, Qt::QueuedConnection ); qm->run(); } TagDialog::~TagDialog() { DEBUG_BLOCK Amarok::config( "TagDialog" ).writeEntry( "CurrentTab", ui->qTabWidget->currentIndex() ); if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); delete ui; } void TagDialog::metadataChanged( const Meta::AlbumPtr &album ) { if( m_currentAlbum ) return; // If the metadata of the current album has changed, reload the cover if( album == m_currentAlbum ) updateCover(); // TODO: if the lyrics changed: should we show a warning and ask the user // if he wants to use the new lyrics? } //////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS //////////////////////////////////////////////////////////////////////////////// void TagDialog::addTrack( Meta::TrackPtr &track ) { if( !m_tracks.contains( track ) ) { m_tracks.append( track ); m_storedTags.insert( track, getTagsFromTrack( track ) ); } } void TagDialog::tracksReady( const Meta::TrackList &tracks ) { foreach( Meta::TrackPtr track, tracks ) addTrack( track ); } void TagDialog::queryDone() { delete m_queryMaker; if( !m_tracks.isEmpty() ) { initUi(); setCurrentTrack( 0 ); QTimer::singleShot( 0, this, &TagDialog::show ); } else { deleteLater(); } } void TagDialog::albumsReady( const Meta::AlbumList &albums ) { foreach( const Meta::AlbumPtr &album, albums ) { if( !album->name().isEmpty() ) m_albums << album->name(); if( album->hasAlbumArtist() && !album->albumArtist()->name().isEmpty() ) m_albumArtists << album->albumArtist()->name(); } } void TagDialog::artistsReady( const Meta::ArtistList &artists ) { foreach( const Meta::ArtistPtr &artist, artists ) { if( !artist->name().isEmpty() ) m_artists << artist->name(); } } void TagDialog::composersReady( const Meta::ComposerList &composers ) { foreach( const Meta::ComposerPtr &composer, composers ) { if( !composer->name().isEmpty() ) m_composers << composer->name(); } } void TagDialog::genresReady( const Meta::GenreList &genres ) { foreach( const Meta::GenrePtr &genre, genres ) { if( !genre->name().isEmpty() ) // Where the heck do the empty genres come from? m_genres << genre->name(); } } void TagDialog::labelsReady( const Meta::LabelList &labels ) { foreach( const Meta::LabelPtr &label, labels ) { if( !label->name().isEmpty() ) m_allLabels << label->name(); } } void TagDialog::dataQueryDone() { // basically we want to ignore the fact that the fields are being // edited because we do it not the user, so it results in empty // tags being saved to files---data loss is BAD! bool oldChanged = m_changed; //we simply clear the completion data of all comboboxes //then load the current track again. that's more work than necessary //but the performance impact should be negligible // we do this because if we insert items and the contents of the textbox // are not in the list, it clears the textbox. which is bad --lfranchi 2.22.09 QString saveText( ui->kComboBox_artist->lineEdit()->text() ); QStringList artists = m_artists.toList(); artists.sort(); ui->kComboBox_artist->clear(); ui->kComboBox_artist->insertItems( 0, artists ); ui->kComboBox_artist->completionObject()->setItems( artists ); ui->kComboBox_artist->lineEdit()->setText( saveText ); saveText = ui->kComboBox_album->lineEdit()->text(); QStringList albums = m_albums.toList(); albums.sort(); ui->kComboBox_album->clear(); ui->kComboBox_album->insertItems( 0, albums ); ui->kComboBox_album->completionObject()->setItems( albums ); ui->kComboBox_album->lineEdit()->setText( saveText ); saveText = ui->kComboBox_albumArtist->lineEdit()->text(); QStringList albumArtists = m_albumArtists.toList(); albumArtists.sort(); ui->kComboBox_albumArtist->clear(); ui->kComboBox_albumArtist->insertItems( 0, albumArtists ); ui->kComboBox_albumArtist->completionObject()->setItems( albumArtists ); ui->kComboBox_albumArtist->lineEdit()->setText( saveText ); saveText = ui->kComboBox_composer->lineEdit()->text(); QStringList composers = m_composers.toList(); composers.sort(); ui->kComboBox_composer->clear(); ui->kComboBox_composer->insertItems( 0, composers ); ui->kComboBox_composer->completionObject()->setItems( composers ); ui->kComboBox_composer->lineEdit()->setText( saveText ); saveText = ui->kComboBox_genre->lineEdit()->text(); QStringList genres = m_genres.toList(); genres.sort(); ui->kComboBox_genre->clear(); ui->kComboBox_genre->insertItems( 0, genres ); ui->kComboBox_genre->completionObject()->setItems( genres ); ui->kComboBox_genre->lineEdit()->setText( saveText ); saveText = ui->kComboBox_label->lineEdit()->text(); QStringList labels = m_allLabels.toList(); labels.sort(); ui->kComboBox_label->clear(); ui->kComboBox_label->insertItems( 0, labels ); ui->kComboBox_label->completionObject()->setItems( labels ); ui->kComboBox_label->lineEdit()->setText( saveText ); m_changed = oldChanged; } void TagDialog::removeLabelPressed() //SLOT { if( ui->labelsList->selectionModel()->hasSelection() ) { QModelIndexList idxList = ui->labelsList->selectionModel()->selectedRows(); QStringList selection; for( int x = 0; x < idxList.size(); ++x ) { QString label = idxList.at(x).data( Qt::DisplayRole ).toString(); selection.append( label ); } m_labelModel->removeLabels( selection ); ui->labelsList->selectionModel()->reset(); labelSelected(); checkChanged(); } } void TagDialog::addLabelPressed() //SLOT { QString label = ui->kComboBox_label->currentText(); if( !label.isEmpty() ) { m_labelModel->addLabel( label ); ui->kComboBox_label->setCurrentIndex( -1 ); ui->kComboBox_label->completionObject()->insertItems( QStringList( label ) ); if ( !ui->kComboBox_label->contains( label ) ) ui->kComboBox_label->addItem( label ); checkChanged(); } } void TagDialog::cancelPressed() //SLOT { QApplication::restoreOverrideCursor(); // restore the cursor before closing the dialog (The musicbrainz dialog might have set it) reject(); } void TagDialog::accept() //SLOT { ui->pushButton_ok->setEnabled( false ); //visual feedback saveTags(); QDialog::accept(); } inline void TagDialog::openPressed() //SLOT { new KRun( QUrl::fromLocalFile(m_path), this ); } inline void TagDialog::previousTrack() //SLOT { setCurrentTrack( m_currentTrackNum - 1 ); } inline void TagDialog::nextTrack() //SLOT { setCurrentTrack( m_currentTrackNum + 1 ); } inline void TagDialog::perTrack( bool enabled ) //SLOT { if( enabled == m_perTrack ) return; setTagsToTrack(); setPerTrack( enabled ); setTagsToUi(); } void TagDialog::checkChanged() //SLOT { QVariantMap oldTags; if( m_perTrack ) oldTags = m_storedTags.value( m_currentTrack ); else oldTags = getTagsFromMultipleTracks(); QVariantMap newTags = getTagsFromUi( oldTags ); ui->pushButton_ok->setEnabled( m_changed || !newTags.isEmpty() ); } inline void TagDialog::labelModified() //SLOT { ui->addButton->setEnabled( ui->kComboBox_label->currentText().length()>0 ); } inline void TagDialog::labelSelected() //SLOT { ui->removeButton->setEnabled( ui->labelsList->selectionModel()->hasSelection() ); } //creates a QDialog and executes the FilenameLayoutWidget. Grabs a filename scheme, extracts tags (via TagGuesser) from filename and fills the appropriate fields on TagDialog. void TagDialog::guessFromFilename() //SLOT { TagGuesserDialog dialog( m_currentTrack->playableUrl().path(), this ); if( dialog.exec() == QDialog::Accepted ) { dialog.onAccept(); int cur = 0; QMap tags = dialog.guessedTags(); if( !tags.isEmpty() ) { if( tags.contains( Meta::valTitle ) ) ui->kLineEdit_title->setText( tags[Meta::valTitle] ); if( tags.contains( Meta::valArtist ) ) { cur = ui->kComboBox_artist->currentIndex(); ui->kComboBox_artist->setItemText( cur, tags[Meta::valArtist] ); } if( tags.contains( Meta::valAlbum ) ) { cur = ui->kComboBox_album->currentIndex(); ui->kComboBox_album->setItemText( cur, tags[Meta::valAlbum] ); } if( tags.contains( Meta::valAlbumArtist ) ) { cur = ui->kComboBox_albumArtist->currentIndex(); ui->kComboBox_albumArtist->setItemText( cur, tags[Meta::valAlbumArtist] ); } if( tags.contains( Meta::valTrackNr ) ) ui->qSpinBox_track->setValue( tags[Meta::valTrackNr].toInt() ); if( tags.contains( Meta::valComment ) ) ui->qPlainTextEdit_comment->setPlainText( tags[Meta::valComment] ); if( tags.contains( Meta::valYear ) ) ui->qSpinBox_year->setValue( tags[Meta::valYear].toInt() ); if( tags.contains( Meta::valComposer ) ) { cur = ui->kComboBox_composer->currentIndex(); ui->kComboBox_composer->setItemText( cur, tags[Meta::valComposer] ); } if( tags.contains( Meta::valGenre ) ) { cur = ui->kComboBox_genre->currentIndex(); ui->kComboBox_genre->setItemText( cur, tags[Meta::valGenre] ); } if( tags.contains( Meta::valDiscNr ) ) { ui->qSpinBox_discNumber->setValue( tags[Meta::valDiscNr].toInt() ); } } else { debug() << "guessing tags from filename failed" << endl; } } } //////////////////////////////////////////////////////////////////////////////// // PRIVATE //////////////////////////////////////////////////////////////////////////////// void TagDialog::initUi() { DEBUG_BLOCK // delete itself when closing setAttribute( Qt::WA_DeleteOnClose ); KConfigGroup config = Amarok::config( "TagDialog" ); ui->qTabWidget->addTab( ui->summaryTab , i18n( "Summary" ) ); ui->qTabWidget->addTab( ui->tagsTab , i18n( "Tags" ) ); ui->qTabWidget->addTab( ui->lyricsTab , i18n( "Lyrics" ) ); ui->qTabWidget->addTab( ui->labelsTab , i18n( "Labels" ) ); ui->kComboBox_label->completionObject()->setIgnoreCase( true ); ui->kComboBox_label->setCompletionMode( KCompletion::CompletionPopup ); m_labelModel = new LabelListModel( QStringList(), this ); ui->labelsList->setModel( m_labelModel ); ui->labelsTab->setEnabled( true ); ui->qTabWidget->setCurrentIndex( config.readEntry( "CurrentTab", 0 ) ); ui->kComboBox_artist->completionObject()->setIgnoreCase( true ); ui->kComboBox_artist->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_album->completionObject()->setIgnoreCase( true ); ui->kComboBox_album->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_albumArtist->completionObject()->setIgnoreCase( true ); ui->kComboBox_albumArtist->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_composer->completionObject()->setIgnoreCase( true ); ui->kComboBox_composer->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_genre->completionObject()->setIgnoreCase( true ); ui->kComboBox_genre->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_label->completionObject()->setIgnoreCase( true ); ui->kComboBox_label->setCompletionMode( KCompletion::CompletionPopup ); ui->addButton->setEnabled( false ); ui->removeButton->setEnabled( false ); // set an icon for the open-in-konqui button - ui->pushButton_open->setIcon( QIcon::fromTheme( "folder-amarok" ) ); + ui->pushButton_open->setIcon( QIcon::fromTheme( QStringLiteral("folder-amarok") ) ); connect( ui->pushButton_guessTags, &QAbstractButton::clicked, this, &TagDialog::guessFromFilename ); // Connects for modification check // only set to overwrite-on-save if the text has changed connect( ui->kLineEdit_title, &QLineEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_composer, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_composer, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_artist, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_artist, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_album, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_album, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_albumArtist, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_albumArtist, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_genre, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_genre, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kLineEdit_Bpm, &QLineEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->ratingWidget, QOverload::of(&KRatingWidget::ratingChanged), this, &TagDialog::checkChanged ); connect( ui->qSpinBox_track, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->qSpinBox_year, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->qSpinBox_score, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->qPlainTextEdit_comment, &QPlainTextEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->kRichTextEdit_lyrics, &QTextEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->qSpinBox_discNumber, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->pushButton_cancel, &QAbstractButton::clicked, this, &TagDialog::cancelPressed ); connect( ui->pushButton_ok, &QAbstractButton::clicked, this, &TagDialog::accept ); connect( ui->pushButton_open, &QAbstractButton::clicked, this, &TagDialog::openPressed ); connect( ui->pushButton_previous, &QAbstractButton::clicked, this, &TagDialog::previousTrack ); connect( ui->pushButton_next, &QAbstractButton::clicked, this, &TagDialog::nextTrack ); connect( ui->checkBox_perTrack, &QCheckBox::toggled, this, &TagDialog::perTrack ); connect( ui->addButton, &QAbstractButton::clicked, this, &TagDialog::addLabelPressed ); connect( ui->removeButton, &QAbstractButton::clicked, this, &TagDialog::removeLabelPressed ); connect( ui->kComboBox_label, QOverload::of(&KComboBox::activated), this, &TagDialog::labelModified ); connect( ui->kComboBox_label, &QComboBox::editTextChanged, this, &TagDialog::labelModified ); connect( ui->kComboBox_label, QOverload<>::of(&KComboBox::returnPressed), this, &TagDialog::addLabelPressed ); connect( ui->kComboBox_label, QOverload<>::of(&KComboBox::returnPressed), this, &TagDialog::checkChanged ); connect( ui->labelsList, &QListView::pressed, this, &TagDialog::labelSelected ); ui->pixmap_cover->setContextMenuPolicy( Qt::CustomContextMenu ); connect( ui->pixmap_cover, &CoverLabel::customContextMenuRequested, this, &TagDialog::showCoverMenu ); connect( ui->pushButton_musicbrainz, &QAbstractButton::clicked, this, &TagDialog::musicbrainzTagger ); if( m_tracks.count() > 1 ) setPerTrack( false ); else setPerTrack( true ); ui->pushButton_ok->setEnabled( false ); startDataQueries(); } void TagDialog::setCurrentTrack( int num ) { if( num < 0 || num >= m_tracks.count() ) return; if( m_currentTrack ) // even in multiple tracks mode we don't want to write back setTagsToTrack(); // there is a logical problem here. // if the track itself changes (e.g. because it get's a new album) // then we don't re-subscribe if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); m_currentTrack = m_tracks.at( num ); m_currentAlbum = m_currentTrack->album(); m_currentTrackNum = num; if( m_currentAlbum ) subscribeTo( m_currentAlbum ); setControlsAccessability(); updateButtons(); setTagsToUi(); } void TagDialog::startDataQuery( Collections::QueryMaker::QueryType type, const QMetaMethod &signal, const QMetaMethod &slot ) { Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker(); qm->setQueryType( type ); connect( qm, &Collections::QueryMaker::queryDone, this, &TagDialog::dataQueryDone, Qt::QueuedConnection ); connect( qm, signal, this, slot, Qt::QueuedConnection ); qm->setAutoDelete( true ); qm->run(); } void TagDialog::startDataQueries() { startDataQuery( Collections::QueryMaker::Artist, QMetaMethod::fromSignal(&Collections::QueryMaker::newArtistsReady), QMetaMethod::fromSignal(&TagDialog::artistsReady) ); startDataQuery( Collections::QueryMaker::Album, QMetaMethod::fromSignal(&Collections::QueryMaker::newAlbumsReady), QMetaMethod::fromSignal(&TagDialog::albumsReady) ); startDataQuery( Collections::QueryMaker::Composer, QMetaMethod::fromSignal(&Collections::QueryMaker::newComposersReady), QMetaMethod::fromSignal(&TagDialog::composersReady) ); startDataQuery( Collections::QueryMaker::Genre, QMetaMethod::fromSignal(&Collections::QueryMaker::newGenresReady), QMetaMethod::fromSignal(&TagDialog::genresReady) ); startDataQuery( Collections::QueryMaker::Label, QMetaMethod::fromSignal(&Collections::QueryMaker::newLabelsReady), QMetaMethod::fromSignal(&TagDialog::labelsReady) ); } inline const QString TagDialog::unknownSafe( const QString &s ) const { return ( s.isNull() || s.isEmpty() || s == "?" || s == "-" ) ? i18nc( "The value for this tag is not known", "Unknown" ) : s; } inline const QString TagDialog::unknownSafe( int i ) const { return ( i == 0 ) ? i18nc( "The value for this tag is not known", "Unknown" ) : QString::number( i ); } void TagDialog::showCoverMenu( const QPoint &pos ) { if( !m_currentAlbum ) return; // TODO: warning or something? QAction *displayCoverAction = new DisplayCoverAction( this, m_currentAlbum ); QAction *unsetCoverAction = new UnsetCoverAction( this, m_currentAlbum ); if( !m_currentAlbum->hasImage() ) { displayCoverAction->setEnabled( false ); unsetCoverAction->setEnabled( false ); } QMenu *menu = new QMenu( this ); menu->addAction( displayCoverAction ); menu->addAction( new FetchCoverAction( this, m_currentAlbum ) ); menu->addAction( new SetCustomCoverAction( this, m_currentAlbum ) ); menu->addAction( unsetCoverAction ); menu->exec( ui->pixmap_cover->mapToGlobal(pos) ); delete menu; } void TagDialog::setTagsToUi( const QVariantMap &tags ) { bool oldChanged = m_changed; // -- the windows title if( m_perTrack ) { setWindowTitle( i18n("Track Details: %1 by %2", m_currentTrack->name(), m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString() ) ); } else { setWindowTitle( i18ncp( "The amount of tracks being edited", "1 Track", "Information for %1 Tracks", m_tracks.count() ) ); } // -- the title in the summary tab if( m_perTrack ) { QString niceTitle; const QFontMetrics fnt = ui->trackArtistAlbumLabel->fontMetrics(); const int len = ui->trackArtistAlbumLabel->width(); QString curTrackAlbName; QString curArtistName; QString curTrackName = fnt.elidedText( m_currentTrack->name().toHtmlEscaped(), Qt::ElideRight, len ); QString curTrackPretName = fnt.elidedText( m_currentTrack->prettyName().toHtmlEscaped(), Qt::ElideRight, len ); if( m_currentAlbum ) curTrackAlbName = fnt.elidedText( m_currentAlbum->name().toHtmlEscaped(), Qt::ElideRight, len ); if( m_currentTrack->artist() ) curArtistName = fnt.elidedText( m_currentTrack->artist()->name().toHtmlEscaped(), Qt::ElideRight, len ); if( m_currentAlbum && m_currentAlbum->name().isEmpty() ) { if( !m_currentTrack->name().isEmpty() ) { if( !m_currentTrack->artist()->name().isEmpty() ) niceTitle = i18n( "%1 by %2", curTrackName, curArtistName ); else niceTitle = i18n( "%1", curTrackName ); } else niceTitle = curTrackPretName; } else if( m_currentAlbum ) niceTitle = i18n( "%1 by %2 on %3" , curTrackName, curArtistName, curTrackAlbName ); else if( m_currentTrack->artist() ) niceTitle = i18n( "%1 by %2" , curTrackName, curArtistName ); else niceTitle = i18n( "%1" , curTrackName ); ui->trackArtistAlbumLabel->setText( niceTitle ); } else { ui->trackArtistAlbumLabel->setText( i18np( "Editing 1 file", "Editing %1 files", m_tracks.count() ) ); } // -- the rest ui->kLineEdit_title->setText( tags.value( Meta::Field::TITLE ).toString() ); selectOrInsertText( tags.value( Meta::Field::ARTIST ).toString(), ui->kComboBox_artist ); selectOrInsertText( tags.value( Meta::Field::ALBUM ).toString(), ui->kComboBox_album ); selectOrInsertText( tags.value( Meta::Field::ALBUMARTIST ).toString(), ui->kComboBox_albumArtist ); selectOrInsertText( tags.value( Meta::Field::COMPOSER ).toString(), ui->kComboBox_composer ); ui->qPlainTextEdit_comment->setPlainText( tags.value( Meta::Field::COMMENT ).toString() ); selectOrInsertText( tags.value( Meta::Field::GENRE ).toString(), ui->kComboBox_genre ); ui->qSpinBox_track->setValue( tags.value( Meta::Field::TRACKNUMBER ).toInt() ); ui->qSpinBox_discNumber->setValue( tags.value( Meta::Field::DISCNUMBER ).toInt() ); ui->qSpinBox_year->setValue( tags.value( Meta::Field::YEAR ).toInt() ); ui->kLineEdit_Bpm->setText( tags.value( Meta::Field::BPM ).toString() ); ui->qLabel_length->setText( unknownSafe( Meta::msToPrettyTime( tags.value( Meta::Field::LENGTH ).toLongLong() ) ) ); ui->qLabel_bitrate->setText( Meta::prettyBitrate( tags.value( Meta::Field::BITRATE ).toInt() ) ); ui->qLabel_samplerate->setText( unknownSafe( tags.value( Meta::Field::SAMPLERATE ).toInt() ) ); ui->qLabel_size->setText( Meta::prettyFilesize( tags.value( Meta::Field::FILESIZE ).toLongLong() ) ); ui->qLabel_format->setText( unknownSafe( tags.value( Meta::Field::TYPE ).toString() ) ); ui->qSpinBox_score->setValue( tags.value( Meta::Field::SCORE ).toInt() ); ui->ratingWidget->setRating( tags.value( Meta::Field::RATING ).toInt() ); ui->ratingWidget->setMaxRating( 10 ); int playcount = tags.value( Meta::Field::PLAYCOUNT ).toInt(); ui->qLabel_playcount->setText( unknownSafe( playcount ) ); QDateTime firstPlayed = tags.value( Meta::Field::FIRST_PLAYED ).toDateTime(); ui->qLabel_firstPlayed->setText( Amarok::verboseTimeSince( firstPlayed ) ); QDateTime lastPlayed = tags.value( Meta::Field::LAST_PLAYED ).toDateTime(); ui->qLabel_lastPlayed->setText( Amarok::verboseTimeSince( lastPlayed ) ); ui->qLabel_collection->setText( tags.contains( Meta::Field::COLLECTION ) ? tags.value( Meta::Field::COLLECTION ).toString() : i18nc( "The collection this track is part of", "None") ); // special handling - we want to hide this if empty if( tags.contains( Meta::Field::NOTE ) ) { ui->noteLabel->show(); ui->qLabel_note->setText( tags.value( Meta::Field::NOTE ).toString() ); ui->qLabel_note->show(); } else { ui->noteLabel->hide(); ui->qLabel_note->hide(); } ui->kRichTextEdit_lyrics->setTextOrHtml( tags.value( Meta::Field::LYRICS ).toString() ); m_labelModel->setLabels( tags.value( Meta::Field::LABELS ).toStringList() ); ui->labelsList->update(); updateCover(); setControlsAccessability(); // If it's a local file, write the directory to m_path, else disable the "open in konqui" button QString urlString = tags.value( Meta::Field::URL ).toString(); QUrl url( urlString ); //pathOrUrl will give localpath or proper url for remote. ui->kLineEdit_location->setText( url.toDisplayString() ); if( url.isLocalFile() ) { ui->locationLabel->show(); ui->kLineEdit_location->show(); QFileInfo fi( urlString ); m_path = fi.isDir() ? urlString : url.adjusted(QUrl::RemoveFilename).path(); ui->pushButton_open->setEnabled( true ); } else { m_path.clear(); ui->pushButton_open->setEnabled( false ); } m_changed = oldChanged; ui->pushButton_ok->setEnabled( m_changed ); } void TagDialog::setTagsToUi() { if( m_perTrack ) setTagsToUi( m_storedTags.value( m_currentTrack ) ); else setTagsToUi( getTagsFromMultipleTracks() ); } QVariantMap TagDialog::getTagsFromUi( const QVariantMap &tags ) const { QVariantMap map; if( ui->kLineEdit_title->text() != tags.value( Meta::Field::TITLE ).toString() ) map.insert( Meta::Field::TITLE, ui->kLineEdit_title->text() ); if( ui->kComboBox_artist->currentText() != tags.value( Meta::Field::ARTIST ).toString() ) map.insert( Meta::Field::ARTIST, ui->kComboBox_artist->currentText() ); if( ui->kComboBox_album->currentText() != tags.value( Meta::Field::ALBUM ).toString() ) map.insert( Meta::Field::ALBUM, ui->kComboBox_album->currentText() ); if( ui->kComboBox_albumArtist->currentText() != tags.value( Meta::Field::ALBUMARTIST ).toString() ) map.insert( Meta::Field::ALBUMARTIST, ui->kComboBox_albumArtist->currentText() ); if( ui->kComboBox_composer->currentText() != tags.value( Meta::Field::COMPOSER ).toString() ) map.insert( Meta::Field::COMPOSER, ui->kComboBox_composer->currentText() ); if( ui->qPlainTextEdit_comment->toPlainText() != tags.value( Meta::Field::COMMENT ).toString() ) map.insert( Meta::Field::COMMENT, ui->qPlainTextEdit_comment->toPlainText() ); if( ui->kComboBox_genre->currentText() != tags.value( Meta::Field::GENRE ).toString() ) map.insert( Meta::Field::GENRE, ui->kComboBox_genre->currentText() ); if( ui->qSpinBox_track->value() != tags.value( Meta::Field::TRACKNUMBER ).toInt() ) map.insert( Meta::Field::TRACKNUMBER, ui->qSpinBox_track->value() ); if( ui->qSpinBox_discNumber->value() != tags.value( Meta::Field::DISCNUMBER ).toInt() ) map.insert( Meta::Field::DISCNUMBER, ui->qSpinBox_discNumber->value() ); if( ui->kLineEdit_Bpm->text().toDouble() != tags.value( Meta::Field::BPM ).toReal() ) map.insert( Meta::Field::BPM, ui->kLineEdit_Bpm->text() ); if( ui->qSpinBox_year->value() != tags.value( Meta::Field::YEAR ).toInt() ) map.insert( Meta::Field::YEAR, ui->qSpinBox_year->value() ); if( ui->qSpinBox_score->value() != tags.value( Meta::Field::SCORE ).toInt() ) map.insert( Meta::Field::SCORE, ui->qSpinBox_score->value() ); if( ui->ratingWidget->rating() != tags.value( Meta::Field::RATING ).toUInt() ) map.insert( Meta::Field::RATING, ui->ratingWidget->rating() ); if( !m_tracks.count() || m_perTrack ) { //ignore these on MultipleTracksMode if ( ui->kRichTextEdit_lyrics->textOrHtml() != tags.value( Meta::Field::LYRICS ).toString() ) map.insert( Meta::Field::LYRICS, ui->kRichTextEdit_lyrics->textOrHtml() ); } QSet uiLabels = m_labelModel->labels().toSet(); QSet oldLabels = tags.value( Meta::Field::LABELS ).toStringList().toSet(); if( uiLabels != oldLabels ) map.insert( Meta::Field::LABELS, QVariant( uiLabels.toList() ) ); return map; } QVariantMap TagDialog::getTagsFromTrack( const Meta::TrackPtr &track ) const { QVariantMap map; if( !track ) return map; // get the shared pointers now to ensure that they don't get freed Meta::AlbumPtr album = track->album(); Meta::ArtistPtr artist = track->artist(); Meta::GenrePtr genre = track->genre(); Meta::ComposerPtr composer = track->composer(); Meta::YearPtr year = track->year(); if( !track->name().isEmpty() ) map.insert( Meta::Field::TITLE, track->name() ); if( artist && !artist->name().isEmpty() ) map.insert( Meta::Field::ARTIST, artist->name() ); if( album && !track->album()->name().isEmpty() ) { map.insert( Meta::Field::ALBUM, album->name() ); if( album->hasAlbumArtist() && !album->albumArtist()->name().isEmpty() ) map.insert( Meta::Field::ALBUMARTIST, album->albumArtist()->name() ); } if( composer && !composer->name().isEmpty() ) map.insert( Meta::Field::COMPOSER, composer->name() ); if( !track->comment().isEmpty() ) map.insert( Meta::Field::COMMENT, track->comment() ); if( genre && !genre->name().isEmpty() ) map.insert( Meta::Field::GENRE, genre->name() ); if( track->trackNumber() ) map.insert( Meta::Field::TRACKNUMBER, track->trackNumber() ); if( track->discNumber() ) map.insert( Meta::Field::DISCNUMBER, track->discNumber() ); if( year && year->year() ) map.insert( Meta::Field::YEAR, year->year() ); if( track->bpm() > 0.0) map.insert( Meta::Field::BPM, track->bpm() ); if( track->length() ) map.insert( Meta::Field::LENGTH, track->length() ); if( track->bitrate() ) map.insert( Meta::Field::BITRATE, track->bitrate() ); if( track->sampleRate() ) map.insert( Meta::Field::SAMPLERATE, track->sampleRate() ); if( track->filesize() ) map.insert( Meta::Field::FILESIZE, track->filesize() ); Meta::ConstStatisticsPtr statistics = track->statistics(); map.insert( Meta::Field::SCORE, statistics->score() ); map.insert( Meta::Field::RATING, statistics->rating() ); map.insert( Meta::Field::PLAYCOUNT, statistics->playCount() ); map.insert( Meta::Field::FIRST_PLAYED, statistics->firstPlayed() ); map.insert( Meta::Field::LAST_PLAYED, statistics->lastPlayed() ); map.insert( Meta::Field::URL, track->prettyUrl() ); map.insert( Meta::Field::TYPE, track->type() ); if( track->inCollection() ) map.insert( Meta::Field::COLLECTION, track->collection()->prettyName() ); if( !track->notPlayableReason().isEmpty() ) map.insert( Meta::Field::NOTE, i18n( "The track is not playable. %1", track->notPlayableReason() ) ); QStringList labelNames; foreach( const Meta::LabelPtr &label, track->labels() ) { labelNames << label->name(); } map.insert( Meta::Field::LABELS, labelNames ); map.insert( Meta::Field::LYRICS, track->cachedLyrics() ); return map; } QVariantMap TagDialog::getTagsFromMultipleTracks() const { QVariantMap map; if( m_tracks.isEmpty() ) return map; //Check which fields are the same for all selected tracks QSet mismatchingTags; Meta::TrackPtr first = m_tracks.first(); map = getTagsFromTrack( first ); QString directory = first->playableUrl().adjusted(QUrl::RemoveFilename).path(); int scoreCount = 0; double scoreSum = map.value( Meta::Field::SCORE ).toDouble(); if( map.value( Meta::Field::SCORE ).toDouble() ) scoreCount++; int ratingCount = 0; int ratingSum = map.value( Meta::Field::RATING ).toInt(); if( map.value( Meta::Field::RATING ).toInt() ) ratingCount++; QDateTime firstPlayed = first->statistics()->firstPlayed(); QDateTime lastPlayed = first->statistics()->lastPlayed(); qint64 length = first->length(); qint64 size = first->filesize(); QStringList validLabels = map.value( Meta::Field::LABELS ).toStringList(); for( int i = 1; i < m_tracks.count(); i++ ) { Meta::TrackPtr track = m_tracks[i]; QVariantMap tags = m_storedTags.value( track ); // -- figure out which tags do not match. // - occur not in every file mismatchingTags |= map.keys().toSet() - tags.keys().toSet(); mismatchingTags |= tags.keys().toSet() - map.keys().toSet(); // - not the same in every file foreach( const QString &key, (map.keys().toSet() & tags.keys().toSet()) ) { if( map.value( key ) != tags.value( key ) ) mismatchingTags.insert( key ); } // -- special handling for values // go up in the directories until we find a common one QString newDirectory = track->playableUrl().adjusted(QUrl::RemoveFilename).path(); while( newDirectory != directory ) { if( newDirectory.length() > directory.length() ) { QDir up( newDirectory ); up.cdUp(); QString d = up.path(); if( d == newDirectory ) // nothing changed { directory.clear(); break; } newDirectory = d; } else { QDir up( directory ); up.cdUp(); QString d = up.path(); if( d == directory ) // nothing changed { directory.clear(); break; } directory = d; } } if( !track->playableUrl().isLocalFile() ) directory.clear(); // score and rating (unrated if rating == 0) scoreSum += tags.value( Meta::Field::SCORE ).toDouble(); if( tags.value( Meta::Field::SCORE ).toDouble() ) scoreCount++; ratingSum += tags.value( Meta::Field::RATING ).toInt(); if( tags.value( Meta::Field::RATING ).toInt() ) ratingCount++; Meta::StatisticsPtr statistics = track->statistics(); if( statistics->firstPlayed().isValid() && (!firstPlayed.isValid() || statistics->firstPlayed() < firstPlayed) ) firstPlayed = statistics->firstPlayed(); if( statistics->lastPlayed().isValid() && (!lastPlayed.isValid() || statistics->lastPlayed() > lastPlayed) ) lastPlayed = statistics->lastPlayed(); length += track->length(); size += track->filesize(); // Only show labels present in all of the tracks QStringList labels = tags.value( Meta::Field::LABELS ).toStringList(); for ( int x = 0; x < validLabels.count(); x++ ) { if ( !labels.contains( validLabels.at( x ) ) ) validLabels.removeAt( x ); } } foreach( const QString &key, mismatchingTags ) map.remove( key ); map.insert( Meta::Field::URL, directory ); if( scoreCount > 0 ) map.insert( Meta::Field::SCORE, scoreSum / scoreCount ); if( ratingCount > 0 ) // the extra fuzz is for emulating rounding to nearest integer map.insert( Meta::Field::RATING, ( ratingSum + ratingCount / 2 ) / ratingCount ); map.insert( Meta::Field::FIRST_PLAYED, firstPlayed ); map.insert( Meta::Field::LAST_PLAYED, lastPlayed ); map.insert( Meta::Field::LENGTH, length ); map.insert( Meta::Field::FILESIZE, size ); map.insert( Meta::Field::LABELS, validLabels ); return map; } void TagDialog::setTagsToTrack( const Meta::TrackPtr &track, const QVariantMap &tags ) { foreach( const QString &key, tags.keys() ) { m_storedTags[ track ].insert( key, tags.value( key ) ); } } void TagDialog::setTagsToMultipleTracks( QVariantMap tags ) { tags.remove( Meta::Field::LABELS ); foreach( const Meta::TrackPtr &track, m_tracks ) { setTagsToTrack( track, tags ); } } void TagDialog::setTagsToTrack() { QVariantMap oldTags; if( m_perTrack ) oldTags = m_storedTags.value( m_currentTrack ); else oldTags = getTagsFromMultipleTracks(); QVariantMap newTags = getTagsFromUi( oldTags ); if( !newTags.isEmpty() ) { m_changed = true; if( m_perTrack ) setTagsToTrack( m_currentTrack, newTags ); else { setTagsToMultipleTracks( newTags ); // -- special handling for labels if( newTags.contains( Meta::Field::LABELS ) ) { // determine the differences QSet oldLabelsSet = oldTags.value( Meta::Field::LABELS ).toStringList().toSet(); QSet newLabelsSet = newTags.value( Meta::Field::LABELS ).toStringList().toSet(); QSet labelsToRemove = oldLabelsSet - newLabelsSet; QSet labelsToAdd = newLabelsSet - oldLabelsSet; // apply the differences for each track foreach( const Meta::TrackPtr &track, m_tracks ) { QSet labelsSet = m_storedTags[track].value( Meta::Field::LABELS ).toStringList().toSet(); labelsSet += labelsToAdd; labelsSet -= labelsToRemove; m_storedTags[ track ].insert( Meta::Field::LABELS, QVariant( labelsSet.toList() ) ); } } } } } void TagDialog::setPerTrack( bool isEnabled ) { debug() << "setPerTrack" << m_tracks.count() << isEnabled; if( m_tracks.count() < 2 ) isEnabled = true; /* force an update so that we can use this function in the initialization if( m_perTrack == isEnabled ) return; */ m_perTrack = isEnabled; setControlsAccessability(); updateButtons(); } void TagDialog::updateButtons() { ui->pushButton_ok->setEnabled( m_changed ); ui->checkBox_perTrack->setVisible( m_tracks.count() > 1 ); ui->pushButton_previous->setVisible( m_tracks.count() > 1 ); ui->pushButton_next->setVisible( m_tracks.count() > 1 ); ui->checkBox_perTrack->setChecked( m_perTrack ); ui->pushButton_previous->setEnabled( m_perTrack && m_currentTrackNum > 0 ); ui->pushButton_next->setEnabled( m_perTrack && m_currentTrackNum < m_tracks.count()-1 ); } void TagDialog::updateCover() { DEBUG_BLOCK if( !m_currentTrack ) return; // -- get the album Meta::AlbumPtr album = m_currentAlbum; if( !m_perTrack ) { foreach( Meta::TrackPtr track, m_tracks ) { if( track->album() != album ) album = nullptr; } } // -- set the ui const int s = 100; // Image preview size ui->pixmap_cover->setMinimumSize( s, s ); ui->pixmap_cover->setMaximumSize( s, s ); if( !album ) { ui->pixmap_cover->setVisible( false ); } else { ui->pixmap_cover->setVisible( true ); ui->pixmap_cover->setPixmap( The::svgHandler()->imageWithBorder( album, s ) ); QString artist = m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString(); ui->pixmap_cover->setInformation( artist, album->name() ); } } void TagDialog::setControlsAccessability() { bool editable = m_currentTrack ? bool( m_currentTrack->editor() ) : true; ui->qTabWidget->setTabEnabled( ui->qTabWidget->indexOf(ui->lyricsTab), m_perTrack ); ui->kLineEdit_title->setEnabled( m_perTrack && editable ); ui->kLineEdit_title->setClearButtonEnabled( m_perTrack && editable ); #define enableOrDisable( X ) \ ui->X->setEnabled( editable ); \ qobject_cast(ui->X->lineEdit())->setClearButtonEnabled( editable ) enableOrDisable( kComboBox_artist ); enableOrDisable( kComboBox_albumArtist ); enableOrDisable( kComboBox_composer ); enableOrDisable( kComboBox_album ); enableOrDisable( kComboBox_genre ); #undef enableOrDisable ui->qSpinBox_track->setEnabled( m_perTrack && editable ); ui->qSpinBox_discNumber->setEnabled( editable ); ui->qSpinBox_year->setEnabled( editable ); ui->kLineEdit_Bpm->setEnabled( editable ); ui->kLineEdit_Bpm->setClearButtonEnabled( editable ); ui->qPlainTextEdit_comment->setEnabled( editable ); ui->pushButton_guessTags->setEnabled( m_perTrack && editable ); ui->pushButton_musicbrainz->setEnabled( editable ); } void TagDialog::saveTags() { setTagsToTrack(); for( auto &track : m_tracks ) { QVariantMap data = m_storedTags[ track ]; //there is really no need to write to the file if only info m_stored in the db has changed if( !data.isEmpty() ) { debug() << "File info changed...."; auto lambda = [=] () mutable { if( data.contains( Meta::Field::SCORE ) ) track->statistics()->setScore( data.value( Meta::Field::SCORE ).toInt() ); if( data.contains( Meta::Field::RATING ) ) track->statistics()->setRating( data.value( Meta::Field::RATING ).toInt() ); if( data.contains( Meta::Field::LYRICS ) ) track->setCachedLyrics( data.value( Meta::Field::LYRICS ).toString() ); QStringList labels = data.value( Meta::Field::LABELS ).toStringList(); QHash labelMap; for( const auto &label : track->labels() ) labelMap.insert( label->name(), label ); // labels to remove for( const auto &label : labelMap.keys().toSet() - labels.toSet() ) track->removeLabel( labelMap.value( label ) ); // labels to add for( const auto &label : labels.toSet() - labelMap.keys().toSet() ) track->addLabel( label ); Meta::TrackEditorPtr ec = track->editor(); if( !ec ) { debug() << "Track" << track->prettyUrl() << "does not have Meta::TrackEditor. Skipping."; return; } ec->beginUpdate(); if( data.contains( Meta::Field::TITLE ) ) ec->setTitle( data.value( Meta::Field::TITLE ).toString() ); if( data.contains( Meta::Field::COMMENT ) ) ec->setComment( data.value( Meta::Field::COMMENT ).toString() ); if( data.contains( Meta::Field::ARTIST ) ) ec->setArtist( data.value( Meta::Field::ARTIST ).toString() ); if( data.contains( Meta::Field::ALBUM ) ) ec->setAlbum( data.value( Meta::Field::ALBUM ).toString() ); if( data.contains( Meta::Field::GENRE ) ) ec->setGenre( data.value( Meta::Field::GENRE ).toString() ); if( data.contains( Meta::Field::COMPOSER ) ) ec->setComposer( data.value( Meta::Field::COMPOSER ).toString() ); if( data.contains( Meta::Field::YEAR ) ) ec->setYear( data.value( Meta::Field::YEAR ).toInt() ); if( data.contains( Meta::Field::TRACKNUMBER ) ) ec->setTrackNumber( data.value( Meta::Field::TRACKNUMBER ).toInt() ); if( data.contains( Meta::Field::DISCNUMBER ) ) ec->setDiscNumber( data.value( Meta::Field::DISCNUMBER ).toInt() ); if( data.contains( Meta::Field::BPM ) ) ec->setBpm( data.value( Meta::Field::BPM ).toDouble() ); if( data.contains( Meta::Field::ALBUMARTIST ) ) ec->setAlbumArtist( data.value( Meta::Field::ALBUMARTIST ).toString() ); ec->endUpdate(); // note: the track should by itself Q_EMIT a collectionUpdated signal if needed }; std::thread thread( lambda ); thread.detach(); } } } void TagDialog::selectOrInsertText( const QString &text, QComboBox *comboBox ) { int index = comboBox->findText( text ); if( index == -1 ) { comboBox->insertItem( 0, text ); //insert at the beginning comboBox->setCurrentIndex( 0 ); } else { comboBox->setCurrentIndex( index ); } } void TagDialog::musicbrainzTagger() { DEBUG_BLOCK MusicBrainzTagger *dialog = new MusicBrainzTagger( m_tracks, this ); dialog->setWindowTitle( i18n( "MusicBrainz Tagger" ) ); connect( dialog, &MusicBrainzTagger::sendResult, this, &TagDialog::musicbrainzTaggerResult ); dialog->show(); } void TagDialog::musicbrainzTaggerResult( const QMap &result ) { if( result.isEmpty() ) return; foreach( Meta::TrackPtr track, result.keys() ) { setTagsToTrack( track, result.value( track ) ); } m_changed = true; if( m_perTrack ) setTagsToUi( m_storedTags.value( m_currentTrack ) ); else setTagsToUi( getTagsFromMultipleTracks() ); } diff --git a/src/importers/amarok/AmarokManager.cpp b/src/importers/amarok/AmarokManager.cpp index 18ceda21af..794ba0f94d 100644 --- a/src/importers/amarok/AmarokManager.cpp +++ b/src/importers/amarok/AmarokManager.cpp @@ -1,64 +1,64 @@ /**************************************************************************************** * Copyright (c) 2013 Konrad Zemek * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "AmarokManager.h" #include "AmarokConfigWidget.h" #include "AmarokProvider.h" using namespace StatSyncing; AmarokManager::AmarokManager() : ImporterManager() { } QString AmarokManager::type() const { return QStringLiteral("AmarokImporter"); } QString AmarokManager::prettyName() const { return i18n( "Amarok 2.x" ); } QString AmarokManager::description() const { return i18n( "Amarok 2.x Statistics Importer" ); } QIcon AmarokManager::icon() const { - return QIcon::fromTheme( "amarok" ); + return QIcon::fromTheme( QStringLiteral("amarok") ); } ProviderConfigWidget* AmarokManager::configWidget( const QVariantMap &config ) { return new AmarokConfigWidget( config ); } ImporterProviderPtr AmarokManager::newInstance( const QVariantMap &config ) { return ImporterProviderPtr( new AmarokProvider( config, this ) ); } diff --git a/src/importers/fastforward/FastForwardManager.cpp b/src/importers/fastforward/FastForwardManager.cpp index b0a4c87755..bf4199c607 100644 --- a/src/importers/fastforward/FastForwardManager.cpp +++ b/src/importers/fastforward/FastForwardManager.cpp @@ -1,31 +1,31 @@ /**************************************************************************************** * Copyright (c) 2013 Konrad Zemek * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "importers/SimpleImporterManager.h" #include "FastForwardConfigWidget.h" #include "FastForwardProvider.h" AMAROK_EXPORT_SIMPLE_IMPORTER_PLUGIN( FastForwardImporterFactory, "amarok_importer-fastforward.json", "FastForwardImporter", i18n( "Amarok 1.4 (FastForward)" ), i18n( "Amarok 1.4 Statistics Importer" ), - QIcon::fromTheme( "amarok" ), + QIcon::fromTheme( QStringLiteral("amarok") ), StatSyncing::FastForwardConfigWidget, StatSyncing::FastForwardProvider ) #include diff --git a/src/playlistmanager/sql/SqlUserPlaylistProvider.h b/src/playlistmanager/sql/SqlUserPlaylistProvider.h index 50ef39e35e..148aaebe60 100644 --- a/src/playlistmanager/sql/SqlUserPlaylistProvider.h +++ b/src/playlistmanager/sql/SqlUserPlaylistProvider.h @@ -1,88 +1,88 @@ /**************************************************************************************** * Copyright (c) 2008 Bart Cerneels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_COLLECTION_SQLUSERPLAYLISTPROVIDER_H #define AMAROK_COLLECTION_SQLUSERPLAYLISTPROVIDER_H #include "core-impl/playlists/providers/user/UserPlaylistProvider.h" #include "SqlPlaylist.h" #include "SqlPlaylistGroup.h" #include #include class QAction; namespace Playlists { class AMAROK_EXPORT SqlUserPlaylistProvider : public UserPlaylistProvider { Q_OBJECT public: /** * SqlUserPlaylistProvider constructor * @param debug used for unit testing; enabling means skipping * confirmation dialogs when deleting or renaming playlists. */ explicit SqlUserPlaylistProvider( bool debug = false ); ~SqlUserPlaylistProvider(); /* PlaylistProvider functions */ QString prettyName() const override { return i18n( "Amarok Database" ); } virtual QString description() const { return i18n( "Local playlists stored in the database" ); } - QIcon icon() const override { return QIcon::fromTheme( "server-database" ); } + QIcon icon() const override { return QIcon::fromTheme( QStringLiteral("server-database") ); } int playlistCount() const override; Playlists::PlaylistList playlists() override; virtual Playlists::PlaylistPtr save( const Meta::TrackList &tracks ); Playlists::PlaylistPtr save( const Meta::TrackList &tracks, const QString& name ) override; /* UserPlaylistProvider functions */ bool isWritable() override; bool deletePlaylists( const Playlists::PlaylistList &playlistlist ) override; void renamePlaylist(Playlists::PlaylistPtr playlist, const QString &newName ) override; Playlists::SqlPlaylistGroupPtr group( const QString &name ); static Playlists::SqlPlaylistList toSqlPlaylists( Playlists::PlaylistList playlists ); private: void reloadFromDb(); Playlists::SqlPlaylistGroupPtr m_root; void createTables(); void deleteTables(); void checkTables(); /** * removes COLUMN "description" from "playlists" */ void upgradeVersion2to3(); void loadFromDb(); bool deleteSqlPlaylists( Playlists::SqlPlaylistList playlistlist ); Playlists::SqlPlaylistList selectedPlaylists() const { return m_selectedPlaylists; } Playlists::SqlPlaylistList m_selectedPlaylists; const bool m_debug; }; } //namespace Playlists #endif diff --git a/src/services/gpodder/GpodderService.cpp b/src/services/gpodder/GpodderService.cpp index 67cfcc5bbb..6ead72cc74 100644 --- a/src/services/gpodder/GpodderService.cpp +++ b/src/services/gpodder/GpodderService.cpp @@ -1,274 +1,274 @@ /**************************************************************************************** * Copyright (c) 2010 - 2011 Stefan Derkits * * Copyright (c) 2010 - 2011 Christian Wagner * * Copyright (c) 2010 - 2011 Felix Winter * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "GpodderService" #include "GpodderService.h" #include "core/podcasts/PodcastProvider.h" #include "core/support/Debug.h" #include "GpodderPodcastTreeItem.h" #include "GpodderServiceConfig.h" #include "GpodderServiceModel.h" #include "GpodderServiceView.h" #include "GpodderSortFilterProxyModel.h" #include #include #include "playlistmanager/PlaylistManager.h" #include "widgets/SearchWidget.h" #include #include #include GpodderServiceFactory::GpodderServiceFactory() : ServiceFactory() {} GpodderServiceFactory::~GpodderServiceFactory() {} void GpodderServiceFactory::init() { ServiceBase *service = createGpodderService(); if( service ) { m_initialized = true; emit newService( service ); } } QString GpodderServiceFactory::name() { return QStringLiteral("gpodder.net"); } KConfigGroup GpodderServiceFactory::config() { return Amarok::config( GpodderServiceConfig::configSectionName() ); } void GpodderServiceFactory::slotCreateGpodderService() { //Until we can remove a service when networking gets disabled, only create it the first time. if( !m_initialized ) { ServiceBase *service = createGpodderService(); if( service ) { m_initialized = true; emit newService( service ); } } } void GpodderServiceFactory::slotRemoveGpodderService() { if( activeServices().isEmpty() ) return; m_initialized = false; emit removeService( activeServices().first() ); } ServiceBase * GpodderServiceFactory::createGpodderService() { ServiceBase *service = new GpodderService( this, QLatin1String( "gpodder" ) ); return service; } GpodderService::GpodderService( GpodderServiceFactory *parent, const QString &name ) : ServiceBase( name, parent, false ) , m_inited( false ) - , m_apiRequest( 0 ) - , m_podcastProvider( 0 ) - , m_proxyModel( 0 ) - , m_subscribeButton( 0 ) - , m_selectionModel( 0 ) + , m_apiRequest( nullptr ) + , m_podcastProvider( nullptr ) + , m_proxyModel( nullptr ) + , m_subscribeButton( nullptr ) + , m_selectionModel( nullptr ) { DEBUG_BLOCK setShortDescription( i18n( "gpodder.net: Podcast Directory Service" ) ); - setIcon( QIcon::fromTheme( "view-services-gpodder-amarok" ) ); + setIcon( QIcon::fromTheme( QStringLiteral("view-services-gpodder-amarok") ) ); setLongDescription( i18n( "gpodder.net is an online Podcast Directory & Synchonisation Service." ) ); - setImagePath( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/mygpo.png" ) ); + setImagePath( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/mygpo.png") ) ); init(); } GpodderService::~GpodderService() { DEBUG_BLOCK if( m_podcastProvider ) { //Remove the provider The::playlistManager()->removeProvider( m_podcastProvider ); delete m_podcastProvider; } if ( m_apiRequest ) delete m_apiRequest; } //This Method should only contain the most necessary things for initilazing the Service void GpodderService::init() { DEBUG_BLOCK GpodderServiceConfig config; const QString &username = config.username(); const QString &password = config.password(); if ( m_apiRequest ) delete m_apiRequest; //We have to check this here too, since KWallet::openWallet() doesn't //guarantee that it will always return a wallet. //Notice that LastFm service does the same verification. if ( !config.isDataLoaded() ) { debug() << "Failed to read gpodder credentials."; m_apiRequest = new mygpo::ApiRequest( The::networkAccessManager() ); } else { if( config.enableProvider() ) { m_apiRequest = new mygpo::ApiRequest( username, password, The::networkAccessManager() ); if( m_podcastProvider ) delete m_podcastProvider; enableGpodderProvider( username ); } else m_apiRequest = new mygpo::ApiRequest( The::networkAccessManager() ); } setServiceReady( true ); m_inited = true; } //This Method should contain the rest of the Service Initialization (not soo necessary things, that //can be done after the Object was created) void GpodderService::polish() { DEBUG_BLOCK generateWidgetInfo(); if( m_polished ) return; //do not allow this content to get added to the playlist. At least not for now setPlayableTracks( false ); GpodderServiceView *view = new GpodderServiceView( this ); view->setHeaderHidden( true ); view->setFrameShape( QFrame::NoFrame ); // Was set true in OpmlDirectoryService, but I think we won't need this on true view->setDragEnabled( false ); view->setItemsExpandable( true ); view->setSortingEnabled( false ); view->setEditTriggers( QAbstractItemView::NoEditTriggers ); view->setDragDropMode( QAbstractItemView::NoDragDrop ); setView( view ); GpodderServiceModel *sourceModel = new GpodderServiceModel( m_apiRequest, this ); m_proxyModel = new GpodderSortFilterProxyModel( this ); m_proxyModel->setDynamicSortFilter( true ); m_proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); m_proxyModel->setSourceModel( sourceModel ); setModel( m_proxyModel ); m_selectionModel = view->selectionModel(); m_subscribeButton = new QPushButton(); m_subscribeButton->setParent( m_bottomPanel ); m_subscribeButton->setText( i18n( "Subscribe" ) ); m_subscribeButton->setObjectName( "subscribeButton" ); - m_subscribeButton->setIcon( QIcon::fromTheme( "get-hot-new-stuff-amarok" ) ); + m_subscribeButton->setIcon( QIcon::fromTheme( QStringLiteral("get-hot-new-stuff-amarok") ) ); m_subscribeButton->setEnabled( true ); connect( m_subscribeButton, &QPushButton::clicked, this, &GpodderService::subscribe ); connect( m_searchWidget, &SearchWidget::filterChanged, m_proxyModel, &QSortFilterProxyModel::setFilterWildcard ); m_polished = true; } void GpodderService::itemSelected( CollectionTreeItem * selectedItem ) { Q_UNUSED( selectedItem ) DEBUG_BLOCK return; } void GpodderService::subscribe() { QModelIndex index = m_proxyModel->mapToSource( m_selectionModel->currentIndex() ); GpodderTreeItem *treeItem = static_cast( index.internalPointer() ); if( GpodderPodcastTreeItem *podcastTreeItem = qobject_cast( treeItem ) ) { Podcasts::PodcastProvider *podcastProvider = The::playlistManager()->defaultPodcasts(); QUrl kUrl( podcastTreeItem->podcast()->url() ); podcastProvider->addPodcast( kUrl ); } } void GpodderService::enableGpodderProvider( const QString &username ) { DEBUG_BLOCK QString deviceName = QLatin1String( "amarok-" ) % QHostInfo::localHostName(); debug() << QString( "Enabling GpodderProvider( Username: %1 - Device: %1 )" ) .arg( username ) .arg( deviceName ); m_podcastProvider = new Podcasts::GpodderProvider( username, deviceName, m_apiRequest ); //Add the gpodder's provider to the playlist manager The::playlistManager()->addProvider( m_podcastProvider, PlaylistManager::PodcastChannel ); } diff --git a/src/services/opmldirectory/OpmlDirectoryService.cpp b/src/services/opmldirectory/OpmlDirectoryService.cpp index 3649d3649c..739617c643 100644 --- a/src/services/opmldirectory/OpmlDirectoryService.cpp +++ b/src/services/opmldirectory/OpmlDirectoryService.cpp @@ -1,195 +1,195 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "OpmlDirectoryService.h" #include "amarokurls/AmarokUrlHandler.h" #include "core/support/Debug.h" #include "core/support/Components.h" #include "core/logger/Logger.h" #include "browsers/CollectionTreeItem.h" #include "browsers/SingleCollectionTreeItemModel.h" #include "OpmlDirectoryInfoParser.h" #include "OpmlDirectoryModel.h" #include "OpmlDirectoryView.h" #include "playlistmanager/PlaylistManager.h" #include "core/podcasts/PodcastProvider.h" #include "ServiceSqlRegistry.h" #include "widgets/SearchWidget.h" #include #include using namespace Meta; OpmlDirectoryServiceFactory::OpmlDirectoryServiceFactory() : ServiceFactory() {} OpmlDirectoryServiceFactory::~OpmlDirectoryServiceFactory() {} void OpmlDirectoryServiceFactory::init() { ServiceBase* service = new OpmlDirectoryService( this, "OpmlDirectory", i18n( "Podcast Directory" ) ); m_initialized = true; Q_EMIT newService( service ); } QString OpmlDirectoryServiceFactory::name() { return "OpmlDirectory"; } KConfigGroup OpmlDirectoryServiceFactory::config() { return Amarok::config( "Service_OpmlDirectory" ); } OpmlDirectoryService::OpmlDirectoryService( OpmlDirectoryServiceFactory* parent, const QString &name, const QString &prettyName ) : ServiceBase( name, parent, false, prettyName ) { setShortDescription( i18n( "A large listing of podcasts" ) ); setIcon( QIcon::fromTheme( "view-services-opml-amarok" ) ); setLongDescription( i18n( "A comprehensive list of searchable podcasts that you can subscribe to directly from within Amarok." ) ); KIconLoader loader; setImagePath( loader.iconPath( "view-services-opml-amarok", -128, true ) ); The::amarokUrlHandler()->registerRunner( this, command() ); setServiceReady( true ); } OpmlDirectoryService::~OpmlDirectoryService() { } void OpmlDirectoryService::polish() { generateWidgetInfo(); if ( m_polished ) return; //do not allow this content to get added to the playlist. At least not for now setPlayableTracks( false ); //TODO: implement searching m_searchWidget->setVisible( false ); OpmlDirectoryView* opmlView = new OpmlDirectoryView( this ); opmlView->setHeaderHidden( true ); opmlView->setFrameShape( QFrame::NoFrame ); opmlView->setDragEnabled ( true ); opmlView->setSortingEnabled( false ); opmlView->setSelectionMode( QAbstractItemView::ExtendedSelection ); opmlView->setDragDropMode ( QAbstractItemView::DragOnly ); opmlView->setEditTriggers( QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed ); setView( opmlView ); QString opmlLocation = Amarok::saveLocation() + "podcast_directory.opml"; if( !QFile::exists( opmlLocation ) ) { //copy from the standard data dir QString schippedOpmlLocation = QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/data/podcast_directory.opml" ); if( !QFile::copy( schippedOpmlLocation, opmlLocation ) ) { debug() << QString( "Failed to copy from %1 to %2" ) .arg( schippedOpmlLocation, opmlLocation ); //TODO: error box drawn in the view's area. return; } } setModel( new OpmlDirectoryModel( QUrl::fromLocalFile( opmlLocation ), this ) ); m_subscribeButton = new QPushButton( m_bottomPanel ); m_subscribeButton->setText( i18n( "Subscribe" ) ); m_subscribeButton->setObjectName( "subscribeButton" ); - m_subscribeButton->setIcon( QIcon::fromTheme( "get-hot-new-stuff-amarok" ) ); + m_subscribeButton->setIcon( QIcon::fromTheme( QStringLiteral("get-hot-new-stuff-amarok") ) ); m_subscribeButton->setEnabled( false ); connect( m_subscribeButton, &QPushButton::clicked, this, &OpmlDirectoryService::subscribe ); m_addOpmlButton = new QPushButton( m_bottomPanel ); m_addOpmlButton->setText( i18n( "Add OPML" ) ); - m_addOpmlButton->setObjectName( "addOpmlButton" ); - m_addOpmlButton->setIcon( QIcon::fromTheme( "list-add-amarok" ) ); + m_addOpmlButton->setObjectName( QStringLiteral("addOpmlButton") ); + m_addOpmlButton->setIcon( QIcon::fromTheme( QStringLiteral("list-add-amarok") ) ); connect( m_addOpmlButton, &QPushButton::clicked, dynamic_cast( model() ), &OpmlDirectoryModel::slotAddOpmlAction ); connect( view()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &OpmlDirectoryService::slotSelectionChanged ); setInfoParser( new OpmlDirectoryInfoParser() ); m_polished = true; } QString OpmlDirectoryService::command() const { return "service-podcastdirectory"; } QString OpmlDirectoryService::prettyCommand() const { return i18n( "Add an OPML file to the list." ); } bool OpmlDirectoryService::run(const AmarokUrl &url ) { //make sure this category is shown. AmarokUrl( "amarok://navigate/internet/OpmlDirectory" ).run(); if( url.path() == QLatin1String( "addOpml" ) ) { OpmlDirectoryModel *opmlModel = qobject_cast( model() ); Q_ASSERT_X(opmlModel, "OpmlDirectoryService::run()", "fix if a proxy is used"); opmlModel->slotAddOpmlAction(); return true; } return false; } void OpmlDirectoryService::subscribe() { OpmlDirectoryModel * opmlModel = dynamic_cast( model() ); Q_ASSERT( opmlModel ); opmlModel->subscribe( view()->selectionModel()->selectedIndexes() ); } void OpmlDirectoryService::slotSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected ) { Q_UNUSED(selected) Q_UNUSED(deselected) m_subscribeButton->setEnabled( !view()->selectionModel()->selectedIndexes().isEmpty() ); } diff --git a/src/services/opmldirectory/OpmlDirectoryService.h b/src/services/opmldirectory/OpmlDirectoryService.h index 5c11d9c0f8..3bddca9d0e 100644 --- a/src/services/opmldirectory/OpmlDirectoryService.h +++ b/src/services/opmldirectory/OpmlDirectoryService.h @@ -1,85 +1,85 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef OPMLDIRECTORYSERVICE_H #define OPMLDIRECTORYSERVICE_H #include "amarokurls/AmarokUrlRunnerBase.h" #include "../ServiceBase.h" #include "OpmlDirectoryDatabaseHandler.h" #include "ServiceSqlCollection.h" #include "core/support/Amarok.h" #include class OpmlOutline; class OpmlDirectoryServiceFactory: public ServiceFactory { Q_PLUGIN_METADATA(IID AmarokPluginFactory_iid FILE "amarok_service_opmldirectory.json") Q_INTERFACES(Plugins::PluginFactory) Q_OBJECT public: OpmlDirectoryServiceFactory(); virtual ~OpmlDirectoryServiceFactory(); void init() override; QString name() override; KConfigGroup config() override; }; /** A service for displaying, previewing and downloading music from OpmlDirectory.com @author */ class OpmlDirectoryService : public ServiceBase, public AmarokUrlRunnerBase { Q_OBJECT public: OpmlDirectoryService( OpmlDirectoryServiceFactory* parent, const QString &name, const QString &prettyName ); ~OpmlDirectoryService() override; void polish() override; Collections::Collection * collection() override { return nullptr; } /* UrlRunnerBase methods */ QString command() const override; QString prettyCommand() const override; bool run( const AmarokUrl &url ) override; - QIcon icon() const override { return QIcon::fromTheme( "view-services-opml-amarok" ); } + QIcon icon() const override { return QIcon::fromTheme( QStringLiteral("view-services-opml-amarok") ); } private Q_SLOTS: void subscribe(); void slotSelectionChanged( const QItemSelection &, const QItemSelection & ); private: QPushButton *m_addOpmlButton; QPushButton *m_subscribeButton; int m_currentCategoryId; int m_numberOfFeeds; int m_numberOfCategories; }; #endif diff --git a/src/toolbar/VolumePopupButton.cpp b/src/toolbar/VolumePopupButton.cpp index a37c5ae5ff..d764b8d32f 100644 --- a/src/toolbar/VolumePopupButton.cpp +++ b/src/toolbar/VolumePopupButton.cpp @@ -1,158 +1,158 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * Copyright (c) 2009 Mark Kretschmann * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "VolumePopupButton.h" #include "ActionClasses.h" #include "EngineController.h" #include "core/support/Amarok.h" #include "widgets/BoxWidget.h" #include "widgets/SliderWidget.h" #include #include #include #include #include #include #include #include VolumePopupButton::VolumePopupButton( QWidget * parent ) : QToolButton( parent ) { //create the volume popup m_volumeMenu = new QMenu( this ); BoxWidget * mainBox = new BoxWidget( true, this ); m_volumeLabel= new QLabel( mainBox ); m_volumeLabel->setAlignment( Qt::AlignHCenter ); BoxWidget *sliderBox = new BoxWidget( false, mainBox ); m_volumeSlider = new Amarok::VolumeSlider( Amarok::VOLUME_MAX, sliderBox, false ); m_volumeSlider->setFixedHeight( 170 ); mainBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); sliderBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); EngineController* ec = The::engineController(); QWidgetAction * sliderActionWidget = new QWidgetAction( this ); sliderActionWidget->setDefaultWidget( mainBox ); connect( m_volumeSlider, &Amarok::VolumeSlider::sliderMoved, ec, &EngineController::setVolume ); connect( m_volumeSlider, &Amarok::VolumeSlider::sliderReleased, ec, &EngineController::setVolume ); QToolBar *muteBar = new QToolBar( QString(), mainBox ); muteBar->setContentsMargins( 0, 0, 0, 0 ); muteBar->setIconSize( QSize( 16, 16 ) ); m_muteAction = new QAction( QIcon::fromTheme( "audio-volume-muted" ), QString(), muteBar ); m_muteAction->setCheckable ( true ); m_muteAction->setChecked( ec->isMuted() ); connect( m_muteAction, &QAction::toggled, ec, &EngineController::setMuted ); m_volumeMenu->addAction( sliderActionWidget ); muteBar->addAction( m_muteAction ); //set correct icon and label initially volumeChanged( ec->volume() ); connect( ec, &EngineController::volumeChanged, this, &VolumePopupButton::volumeChanged ); connect( ec, &EngineController::muteStateChanged, this, &VolumePopupButton::muteStateChanged ); } void VolumePopupButton::volumeChanged( int newVolume ) { if ( newVolume < 34 ) - setIcon( QIcon::fromTheme( "audio-volume-low" ) ); + setIcon( QIcon::fromTheme( QStringLiteral("audio-volume-low") ) ); else if ( newVolume < 67 ) - setIcon( QIcon::fromTheme( "audio-volume-medium" ) ); + setIcon( QIcon::fromTheme( QStringLiteral("audio-volume-medium") ) ); else - setIcon( QIcon::fromTheme( "audio-volume-high" ) ); + setIcon( QIcon::fromTheme( QStringLiteral("audio-volume-high") ) ); m_volumeLabel->setText( QString::number( newVolume ) + '%' ); if( newVolume != m_volumeSlider->value() ) m_volumeSlider->setValue( newVolume ); //make sure to uncheck mute toolbar when moving slider if ( newVolume ) m_muteAction->setChecked( false ); setToolTip( m_muteAction->isChecked() ? i18n( "Volume: %1% (muted)", newVolume ) : i18n( "Volume: %1%", newVolume )); } void VolumePopupButton::muteStateChanged( bool muted ) { const int volume = The::engineController()->volume(); if ( muted ) { - setIcon( QIcon::fromTheme( "audio-volume-muted" ) ); + setIcon( QIcon::fromTheme( QStringLiteral("audio-volume-muted") ) ); setToolTip( i18n( "Volume: %1% (muted)", volume ) ); } else { volumeChanged( volume ); } m_muteAction->setChecked( muted ); } void VolumePopupButton::mouseReleaseEvent( QMouseEvent * event ) { if( event->button() == Qt::LeftButton ) { if ( m_volumeMenu->isVisible() ) m_volumeMenu->hide(); else { const QPoint pos( 0, height() ); m_volumeMenu->exec( mapToGlobal( pos ) ); } } else if( event->button() == Qt::MidButton ) { The::engineController()->toggleMute(); } QToolButton::mouseReleaseEvent( event ); } void VolumePopupButton::wheelEvent( QWheelEvent * event ) { //debug() << "delta: " << event->delta(); event->accept(); EngineController* const ec = The::engineController(); const int volume = qBound( 0, ec->volume() + event->delta() / 40 , 100 ); ec->setVolume( volume ); } diff --git a/src/widgets/TokenWithLayout.cpp b/src/widgets/TokenWithLayout.cpp index 217eb458f3..9c151bff51 100644 --- a/src/widgets/TokenWithLayout.cpp +++ b/src/widgets/TokenWithLayout.cpp @@ -1,292 +1,292 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * Copyright (c) 2009 Roman Jarosz * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "TokenWithLayout.h" #include "core/support/Debug.h" #include "TokenDropTarget.h" #include "playlist/layouts/LayoutEditDialog.h" #include #include #include #include #include #include #include #include Wrench::Wrench( QWidget *parent ) : QLabel( parent ) { setCursor( Qt::ArrowCursor ); - setPixmap( QIcon::fromTheme( "configure" ).pixmap( 64 ) ); + setPixmap( QIcon::fromTheme( QStringLiteral("configure") ).pixmap( 64 ) ); setScaledContents( true ); setMargin( 4 ); } void Wrench::enterEvent( QEvent * ) { setMargin( 1 ); update(); } void Wrench::leaveEvent( QEvent * ) { setMargin( 4 ); update(); } void Wrench::mousePressEvent( QMouseEvent * ) { setMargin( 4 ); update(); Q_EMIT clicked(); } void Wrench::mouseReleaseEvent( QMouseEvent * ) { setMargin( 1 ); update(); Q_EMIT clicked(); } void Wrench::paintEvent( QPaintEvent *pe ) { QPainter p( this ); QColor c = palette().color( backgroundRole() ); p.setPen( Qt::NoPen ); c = palette().color( backgroundRole() ); c.setAlpha( 212 ); p.setBrush( c ); p.setRenderHint( QPainter::Antialiasing ); p.drawEllipse( rect() ); p.end(); QLabel::paintEvent( pe ); } const QString ActionBoldName = QLatin1String( "ActionBold" ); const QString ActionItalicName = QLatin1String( "ActionItalic" ); const QString ActionAlignLeftName = QLatin1String( "ActionAlignLeft" ); const QString ActionAlignCenterName = QLatin1String( "ActionAlignCenter" ); const QString ActionAlignRightName = QLatin1String( "ActionAlignRight" ); Token * TokenWithLayoutFactory::createToken( const QString &text, const QString &iconName, qint64 value, QWidget *parent ) const { return new TokenWithLayout( text, iconName, value, parent ); } QPointer TokenWithLayout::m_dialog; TokenWithLayout::TokenWithLayout( const QString &text, const QString &iconName, qint64 value, QWidget *parent ) : Token( text, iconName, value, parent ) , m_width( 0.0 ), m_wrenchTimer( 0 ) { m_alignment = Qt::AlignCenter; m_bold = false; m_italic = false; m_underline = false; m_wrench = new Wrench( this ); m_wrench->installEventFilter( this ); m_wrench->hide(); connect ( m_wrench, &Wrench::clicked, this, &TokenWithLayout::showConfig ); setFocusPolicy( Qt::ClickFocus ); } TokenWithLayout::~TokenWithLayout() { delete m_wrench; } void TokenWithLayout::enterEvent( QEvent *e ) { QWidget *win = window(); const int sz = 2*height(); QPoint pt = mapTo( win, rect().topLeft() ); m_wrench->setParent( win ); m_wrench->setFixedSize( sz, sz ); m_wrench->move( pt - QPoint( m_wrench->width()/3, m_wrench->height()/3 ) ); m_wrench->setCursor( Qt::PointingHandCursor ); m_wrench->raise(); m_wrench->show(); Token::enterEvent( e ); } bool TokenWithLayout::eventFilter( QObject *o, QEvent *e ) { if ( e->type() == QEvent::Leave && o == m_wrench ) { if ( m_wrenchTimer ) killTimer( m_wrenchTimer ); m_wrenchTimer = startTimer( 40 ); } return false; } void TokenWithLayout::leaveEvent( QEvent *e ) { Token::leaveEvent( e ); if ( m_wrenchTimer ) killTimer( m_wrenchTimer ); m_wrenchTimer = startTimer( 40 ); } void TokenWithLayout::showConfig() { if( !m_dialog ) m_dialog = new LayoutEditDialog( window() ); m_dialog->setToken( this ); if( !m_dialog->isVisible() ) { m_dialog->adjustSize(); QPoint pt = mapToGlobal( rect().bottomLeft() ); pt.setY( pt.y() + 9 ); if ( parentWidget() ) pt.setX( parentWidget()->mapToGlobal( QPoint( 0, 0 ) ).x() + ( parentWidget()->width() - m_dialog->QDialog::width() ) / 2 ); m_dialog->move( pt ); } m_dialog->show(); // ensures raise in doubt QTimerEvent te( m_wrenchTimer ); timerEvent( &te ); // it's not like we'd get a leave event when the child dialog pops in between... } void TokenWithLayout::timerEvent( QTimerEvent *te ) { if ( te->timerId() == m_wrenchTimer ) { killTimer( m_wrenchTimer ); m_wrenchTimer = 0; QRegion rgn; rgn |= QRect( mapToGlobal( QPoint( 0, 0 ) ), QWidget::size() ); rgn |= QRect( m_wrench->mapToGlobal( QPoint( 0, 0 ) ), m_wrench->size() ); if ( !rgn.contains( QCursor::pos() ) ) m_wrench->hide(); } Token::timerEvent( te ); } Qt::Alignment TokenWithLayout::alignment() { return m_alignment; } void TokenWithLayout::setAlignment( Qt::Alignment alignment ) { if ( m_alignment == alignment ) return; m_alignment = alignment; m_label->setAlignment( alignment ); Q_EMIT changed(); } bool TokenWithLayout::bold() const { return m_bold; } void TokenWithLayout::setBold( bool bold ) { if ( m_bold == bold ) return; m_bold = bold; QFont font = m_label->font(); font.setBold( bold ); m_label->setFont( font ); Q_EMIT changed(); } void TokenWithLayout::setPrefix( const QString& string ) { if ( m_prefix == string ) return; if ( string == i18n( "[prefix]" ) ) m_prefix.clear(); else m_prefix = string; Q_EMIT changed(); } void TokenWithLayout::setSuffix( const QString& string ) { if ( m_suffix == string ) return; if ( string == i18n( "[suffix]" ) ) m_suffix.clear(); else m_suffix = string; Q_EMIT changed(); } void TokenWithLayout::setWidth( int size ) { m_width = qMax( qMin( 1.0, size/100.0 ), 0.0 ) ; Q_EMIT changed(); } qreal TokenWithLayout::width() const { return m_width; } bool TokenWithLayout::italic() const { return m_italic; } bool TokenWithLayout::underline() const { return m_underline; } void TokenWithLayout::setItalic( bool italic ) { if ( m_italic == italic ) return; m_italic = italic; QFont font = m_label->font(); font.setItalic( italic ); m_label->setFont( font ); Q_EMIT changed(); } void TokenWithLayout::setUnderline( bool underline ) { if( m_underline == underline ) return; m_underline = underline; QFont font = m_label->font(); font.setUnderline( underline ); m_label->setFont( font ); Q_EMIT changed(); }