No OneTemporary

File Metadata

Created
Wed, May 1, 3:26 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/ActionClasses.cpp b/src/ActionClasses.cpp
index bb67daeefa..8f549aa6be 100644
--- a/src/ActionClasses.cpp
+++ b/src/ActionClasses.cpp
@@ -1,503 +1,503 @@
/****************************************************************************************
* Copyright (c) 2004 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2008 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ActionClasses"
#include "ActionClasses.h"
#include "App.h"
#include "EngineController.h"
#include "MainWindow.h"
#include "amarokconfig.h"
#include <config.h>
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "widgets/Osd.h"
#include "KNotificationBackend.h"
#include <KAuthorized>
#include <KHelpMenu>
#include <KLocale>
#include <KToolBar>
#include <KGlobalAccel>
#include <QKeySequence>
extern OcsData ocsData;
namespace Amarok
{
bool favorNone() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::Off; }
bool favorScores() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherScores; }
bool favorRatings() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherRatings; }
bool favorLastPlay() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::LessRecentlyPlayed; }
bool entireAlbums() { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum; }
bool repeatEnabled() { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist; }
bool randomEnabled() { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum; }
}
using namespace Amarok;
KHelpMenu *Menu::s_helpMenu = 0;
static void
safePlug( KActionCollection *ac, const char *name, QWidget *w )
{
if( ac )
{
QAction *a = (QAction*) ac->action( name );
if( a && w ) w->addAction( a );
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// MenuAction && Menu
// KActionMenu doesn't work very well, so we derived our own
//////////////////////////////////////////////////////////////////////////////////////////
MenuAction::MenuAction( KActionCollection *ac, QObject *parent )
: QAction( parent )
{
setText(i18n( "Amarok Menu" ));
ac->addAction("amarok_menu", this);
setShortcutConfigurable ( false ); //FIXME disabled as it doesn't work, should use QCursor::pos()
}
void MenuAction::setShortcutConfigurable(bool b)
{
setProperty("isShortcutConfigurable", b);
}
Menu* Menu::s_instance = 0;
Menu::Menu( QWidget* parent )
: QMenu( parent )
{
s_instance = this;
KActionCollection *ac = Amarok::actionCollection();
safePlug( ac, "repeat", this );
safePlug( ac, "random_mode", this );
addSeparator();
safePlug( ac, "playlist_playmedia", this );
addSeparator();
safePlug( ac, "cover_manager", this );
safePlug( ac, "queue_manager", this );
safePlug( ac, "script_manager", this );
addSeparator();
safePlug( ac, "update_collection", this );
safePlug( ac, "rescan_collection", this );
#ifndef Q_WS_MAC
addSeparator();
safePlug( ac, KStandardAction::name(KStandardAction::ShowMenubar), this );
#endif
addSeparator();
safePlug( ac, KStandardAction::name(KStandardAction::ConfigureToolbars), this );
safePlug( ac, KStandardAction::name(KStandardAction::KeyBindings), this );
// safePlug( ac, "options_configure_globals", this ); //we created this one
safePlug( ac, KStandardAction::name(KStandardAction::Preferences), this );
addSeparator();
addMenu( helpMenu( this ) );
addSeparator();
safePlug( ac, KStandardAction::name(KStandardAction::Quit), this );
}
Menu*
Menu::instance()
{
return s_instance ? s_instance : new Menu( The::mainWindow() );
}
QMenu*
Menu::helpMenu( QWidget *parent ) //STATIC
{
if ( s_helpMenu == 0 )
s_helpMenu = new KHelpMenu( parent, KAboutData::applicationData(), Amarok::actionCollection() );
QMenu* menu = s_helpMenu->menu();
// "What's This" isn't currently defined for anything in Amarok, so let's remove it
s_helpMenu->action( KHelpMenu::menuWhatsThis )->setVisible( false );
// Hide the default "About App" dialog, as we replace it with a custom one
s_helpMenu->action( KHelpMenu::menuAboutApp )->setVisible( false );
return menu;
}
//////////////////////////////////////////////////////////////////////////////////////////
// PlayPauseAction
//////////////////////////////////////////////////////////////////////////////////////////
PlayPauseAction::PlayPauseAction( KActionCollection *ac, QObject *parent )
: KToggleAction( parent )
{
ac->addAction( "play_pause", this );
setText( i18n( "Play/Pause" ) );
setShortcut( Qt::Key_Space );
EngineController *engine = The::engineController();
if( engine->isPaused() )
paused();
else if( engine->isPlaying() )
playing();
else
stopped();
- connect( this, SIGNAL(triggered()),
- engine, SLOT(playPause()) );
+ connect( this, &PlayPauseAction::triggered,
+ engine, &EngineController::playPause );
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped()) );
- connect( engine, SIGNAL(paused()),
- this, SLOT(paused()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(playing()) );
+ connect( engine, &EngineController::stopped,
+ this, &PlayPauseAction::stopped );
+ connect( engine, &EngineController::paused,
+ this, &PlayPauseAction::paused );
+ connect( engine, &EngineController::trackPlaying,
+ this, &PlayPauseAction::playing );
}
void
PlayPauseAction::stopped()
{
setChecked( false );
setIcon( QIcon::fromTheme("media-playback-start-amarok") );
}
void
PlayPauseAction::paused()
{
setChecked( true );
setIcon( QIcon::fromTheme("media-playback-start-amarok") );
}
void
PlayPauseAction::playing()
{
setChecked( false );
setIcon( QIcon::fromTheme("media-playback-pause-amarok") );
}
//////////////////////////////////////////////////////////////////////////////////////////
// ToggleAction
//////////////////////////////////////////////////////////////////////////////////////////
ToggleAction::ToggleAction( const QString &text, void ( *f ) ( bool ), KActionCollection* const ac, const char *name, QObject *parent )
: KToggleAction( parent )
, m_function( f )
{
setText(text);
ac->addAction(name, this);
}
void ToggleAction::setChecked( bool b )
{
const bool announce = b != isChecked();
m_function( b );
KToggleAction::setChecked( b );
AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing
if( announce ) emit toggled( b ); //KToggleAction doesn't do this for us. How gay!
}
void ToggleAction::setEnabled( bool b )
{
const bool announce = b != isEnabled();
KToggleAction::setEnabled( b );
AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing
if( announce ) emit QAction::triggered( b );
}
//////////////////////////////////////////////////////////////////////////////////////////
// SelectAction
//////////////////////////////////////////////////////////////////////////////////////////
SelectAction::SelectAction( const QString &text, void ( *f ) ( int ), KActionCollection* const ac, const char *name, QObject *parent )
: KSelectAction( parent )
, m_function( f )
{
PERF_LOG( "In SelectAction" );
setText(text);
ac->addAction(name, this);
}
void SelectAction::setCurrentItem( int n )
{
const bool announce = n != currentItem();
debug() << "setCurrentItem: " << n;
m_function( n );
KSelectAction::setCurrentItem( n );
AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing
if( announce ) emit triggered( n );
}
void SelectAction::actionTriggered( QAction *a )
{
m_function( currentItem() );
AmarokConfig::self()->writeConfig();
KSelectAction::actionTriggered( a );
}
void SelectAction::setEnabled( bool b )
{
const bool announce = b != isEnabled();
KSelectAction::setEnabled( b );
AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing
if( announce ) emit QAction::triggered( b );
}
void SelectAction::setIcons( QStringList icons )
{
m_icons = icons;
foreach( QAction *a, selectableActionGroup()->actions() )
{
a->setIcon( QIcon::fromTheme(icons.takeFirst()) );
}
}
QStringList SelectAction::icons() const { return m_icons; }
QString SelectAction::currentIcon() const
{
if( m_icons.count() )
return m_icons.at( currentItem() );
return QString();
}
QString SelectAction::currentText() const {
return KSelectAction::currentText() + "<br /><br />" + i18n("Click to change");
}
void
RandomAction::setCurrentItem( int n )
{
// Porting
//if( QAction *a = parentCollection()->action( "favor_tracks" ) )
// a->setEnabled( n );
SelectAction::setCurrentItem( n );
}
//////////////////////////////////////////////////////////////////////////////////////////
// ReplayGainModeAction
//////////////////////////////////////////////////////////////////////////////////////////
ReplayGainModeAction::ReplayGainModeAction( KActionCollection *ac, QObject *parent ) :
SelectAction( i18n( "&Replay Gain Mode" ), &AmarokConfig::setReplayGainMode, ac, "replay_gain_mode", parent )
{
setItems( QStringList() << i18nc( "Replay Gain state, as in, disabled", "&Off" )
<< i18nc( "Item, as in, music", "&Track" )
<< i18n( "&Album" ) );
EngineController *engine = EngineController::instance();
Q_ASSERT( engine );
if( engine->supportsGainAdjustments() )
setCurrentItem( AmarokConfig::replayGainMode() );
else
{
// Note: it would be nice to set a tooltip that would explain why this is disabled
// to users, but tooltips aren't shown in meny anyway :-(
actions().at( 1 )->setEnabled( false );
actions().at( 2 )->setEnabled( false );
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// BurnMenuAction
//////////////////////////////////////////////////////////////////////////////////////////
BurnMenuAction::BurnMenuAction( KActionCollection *ac, QObject *parent )
: QAction( parent )
{
setText(i18n( "Burn" ));
ac->addAction("burn_menu", this);
}
QWidget*
BurnMenuAction::createWidget( QWidget *w )
{
KToolBar *bar = dynamic_cast<KToolBar*>(w);
if( bar && KAuthorized::authorizeKAction( objectName() ) )
{
//const int id = QAction::getToolButtonID();
//addContainer( bar, id );
w->addAction( this );
- connect( bar, SIGNAL(destroyed()), SLOT(slotDestroyed()) );
+ //connect( bar, &KToolBar::destroyed, this, &BurnMenuAction::slotDestroyed );
//bar->insertButton( QString::null, id, true, i18n( "Burn" ), index );
//KToolBarButton* button = bar->getButton( id );
//button->setPopup( Amarok::BurnMenu::instance() );
//button->setObjectName( "toolbutton_burn_menu" );
//button->setIcon( "k3b" );
//return associatedWidgets().count() - 1;
return 0;
}
//else return -1;
else return 0;
}
BurnMenu* BurnMenu::s_instance = 0;
BurnMenu::BurnMenu( QWidget* parent )
: QMenu( parent )
{
s_instance = this;
- addAction( i18n("Current Playlist"), this, SLOT(slotBurnCurrentPlaylist()) );
- addAction( i18n("Selected Tracks"), this, SLOT(slotBurnSelectedTracks()) );
+ addAction( i18n("Current Playlist"), this, &BurnMenu::slotBurnCurrentPlaylist );
+ addAction( i18n("Selected Tracks"), this, &BurnMenu::slotBurnSelectedTracks );
//TODO add "album" and "all tracks by artist"
}
QMenu*
BurnMenu::instance()
{
return s_instance ? s_instance : new BurnMenu( The::mainWindow() );
}
void
BurnMenu::slotBurnCurrentPlaylist() //SLOT
{
//K3bExporter::instance()->exportCurrentPlaylist();
}
void
BurnMenu::slotBurnSelectedTracks() //SLOT
{
//K3bExporter::instance()->exportSelectedTracks();
}
//////////////////////////////////////////////////////////////////////////////////////////
// StopAction
//////////////////////////////////////////////////////////////////////////////////////////
StopAction::StopAction( KActionCollection *ac, QObject *parent )
: QAction( parent )
{
ac->addAction( "stop", this );
setText( i18n( "Stop" ) );
setIcon( QIcon::fromTheme("media-playback-stop-amarok") );
KGlobalAccel::setGlobalShortcut(this, QKeySequence() );
- connect( this, SIGNAL(triggered()), this, SLOT(stop()) );
+ connect( this, &StopAction::triggered, this, &StopAction::stop );
EngineController *engine = The::engineController();
if( engine->isStopped() )
stopped();
else
playing();
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(playing()) );
+ connect( engine, &EngineController::stopped,
+ this, &StopAction::stopped );
+ connect( engine, &EngineController::trackPlaying,
+ this, &StopAction::playing );
}
void
StopAction::stopped()
{
setEnabled( false );
}
void
StopAction::playing()
{
setEnabled( true );
}
void
StopAction::stop()
{
The::engineController()->stop();
}
//////////////////////////////////////////////////////////////////////////////////////////
// StopPlayingAfterCurrentTrackAction
//////////////////////////////////////////////////////////////////////////////////////////
StopPlayingAfterCurrentTrackAction::StopPlayingAfterCurrentTrackAction( KActionCollection *ac, QObject *parent )
: QAction( parent )
{
ac->addAction( "stop_after_current", this );
setText( i18n( "Stop after current Track" ) );
setIcon( QIcon::fromTheme("media-playback-stop-amarok") );
KGlobalAccel::setGlobalShortcut(this, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_V ) );
- connect( this, SIGNAL(triggered()), SLOT(stopPlayingAfterCurrentTrack()) );
+ connect( this, &StopPlayingAfterCurrentTrackAction::triggered, this, &StopPlayingAfterCurrentTrackAction::stopPlayingAfterCurrentTrack );
}
void
StopPlayingAfterCurrentTrackAction::stopPlayingAfterCurrentTrack()
{
QString text;
quint64 activeTrack = Playlist::ModelStack::instance()->bottom()->activeId();
if( activeTrack )
{
if( The::playlistActions()->willStopAfterTrack( activeTrack ) )
{
The::playlistActions()->stopAfterPlayingTrack( 0 );
text = i18n( "Stop after current track: Off" );
}
else
{
The::playlistActions()->stopAfterPlayingTrack( activeTrack );
text = i18n( "Stop after current track: On" );
}
}
else
text = i18n( "No track playing" );
Amarok::OSD::instance()->OSDWidget::show( text );
if( Amarok::KNotificationBackend::instance()->isEnabled() )
Amarok::KNotificationBackend::instance()->show( i18n( "Amarok" ), text );
}
diff --git a/src/AmarokMimeData.cpp b/src/AmarokMimeData.cpp
index 52c6d32b9f..5b3cca0d9a 100644
--- a/src/AmarokMimeData.cpp
+++ b/src/AmarokMimeData.cpp
@@ -1,394 +1,395 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokMimeData.h"
#include "core/support/Debug.h"
#include <QCoreApplication>
#include <QList>
#include <QTimer>
#include <QUrl>
const QString AmarokMimeData::TRACK_MIME = "application/x-amarok-tracks";
const QString AmarokMimeData::PLAYLIST_MIME = "application/x-amarok-playlists";
const QString AmarokMimeData::PLAYLISTBROWSERGROUP_MIME = "application/x-amarok-playlistbrowsergroup";
const QString AmarokMimeData::PODCASTCHANNEL_MIME = "application/x-amarok-podcastchannel";
const QString AmarokMimeData::PODCASTEPISODE_MIME = "application/x-amarok-podcastepisode";
const QString AmarokMimeData::AMAROKURL_MIME = "application/x-amarok-amarokurl";
const QString AmarokMimeData::BOOKMARKGROUP_MIME = "application/x-amarok-bookmarkgroup";
class AmarokMimeData::Private
{
public:
Private() : deleteQueryMakers( true ), completedQueries( 0 )
{}
~Private()
{
if( deleteQueryMakers )
qDeleteAll( queryMakers );
}
Meta::TrackList tracks;
Playlists::PlaylistList playlists;
QStringList playlistGroups;
Podcasts::PodcastChannelList m_podcastChannels;
Podcasts::PodcastEpisodeList m_podcastEpisodes;
QList<Collections::QueryMaker*> queryMakers;
QMap<Collections::QueryMaker*, Meta::TrackList> trackMap;
QMap<Collections::QueryMaker*, Playlists::PlaylistList> playlistMap;
BookmarkList bookmarks;
BookmarkGroupList bookmarkGroups;
bool deleteQueryMakers;
int completedQueries;
};
AmarokMimeData::AmarokMimeData()
: QMimeData()
, d( new Private() )
{
//nothing to do
}
AmarokMimeData::~AmarokMimeData()
{
delete d;
}
QStringList
AmarokMimeData::formats() const
{
QStringList formats( QMimeData::formats() );
if( !d->tracks.isEmpty() || !d->queryMakers.isEmpty() || !d->playlistGroups.isEmpty() || !d->bookmarks.isEmpty() || !d->bookmarkGroups.isEmpty() )
{
formats.append( TRACK_MIME );
formats.append( PLAYLIST_MIME );
formats.append( PLAYLISTBROWSERGROUP_MIME );
formats.append( PODCASTCHANNEL_MIME );
formats.append( PODCASTEPISODE_MIME );
formats.append( BOOKMARKGROUP_MIME );
formats.append( AMAROKURL_MIME );
if( !formats.contains( "text/uri-list" ) )
formats.append( "text/uri-list" );
if( !formats.contains( "text/plain" ) )
formats.append( "text/plain" );
}
return formats;
}
bool
AmarokMimeData::hasFormat( const QString &mimeType ) const
{
if( mimeType == TRACK_MIME )
return !d->tracks.isEmpty() || !d->queryMakers.isEmpty();
else if( mimeType == PLAYLIST_MIME )
return !d->playlists.isEmpty() || !d->queryMakers.isEmpty();
else if( mimeType == PLAYLISTBROWSERGROUP_MIME )
return !d->playlistGroups.isEmpty();
else if( mimeType == PODCASTCHANNEL_MIME )
return !d->m_podcastChannels.isEmpty();
else if( mimeType == PODCASTEPISODE_MIME )
return !d->m_podcastEpisodes.isEmpty();
else if( mimeType == BOOKMARKGROUP_MIME )
return !d->bookmarkGroups.isEmpty();
else if( mimeType == AMAROKURL_MIME )
return !d->bookmarks.isEmpty();
else if( mimeType == "text/uri-list" || mimeType == "text/plain" )
return !d->tracks.isEmpty() || !d->playlists.isEmpty()
|| !d->m_podcastChannels.isEmpty() || !d->m_podcastEpisodes.isEmpty()
|| !d->queryMakers.isEmpty();
else
return QMimeData::hasFormat( mimeType );
}
Meta::TrackList
AmarokMimeData::tracks() const
{
while( d->completedQueries < d->queryMakers.count() )
{
QCoreApplication::instance()->processEvents( QEventLoop::ExcludeUserInputEvents );
}
Meta::TrackList result = d->tracks;
foreach( Collections::QueryMaker *qm, d->queryMakers )
{
if( d->trackMap.contains( qm ) )
result << d->trackMap.value( qm );
}
return result;
}
void
AmarokMimeData::setTracks( const Meta::TrackList &tracks )
{
d->tracks = tracks;
}
void
AmarokMimeData::addTracks( const Meta::TrackList &tracks )
{
d->tracks << tracks;
}
void
AmarokMimeData::getTrackListSignal() const
{
if( d->completedQueries < d->queryMakers.count() )
{
- QTimer::singleShot( 0, const_cast<AmarokMimeData*>( this ), SLOT(getTrackListSignal()) );
+ QTimer::singleShot( 0, this, &AmarokMimeData::getTrackListSignal );
return;
}
else
{
Meta::TrackList result = d->tracks;
foreach( Collections::QueryMaker *qm, d->queryMakers )
{
if( d->trackMap.contains( qm ) )
result << d->trackMap.value( qm );
}
emit trackListSignal( result );
}
}
Playlists::PlaylistList
AmarokMimeData::playlists() const
{
while( d->completedQueries < d->queryMakers.count() )
{
QCoreApplication::instance()->processEvents( QEventLoop::AllEvents );
}
Playlists::PlaylistList result = d->playlists;
return result;
}
void
AmarokMimeData::setPlaylists( const Playlists::PlaylistList &playlists )
{
d->playlists = playlists;
}
void
AmarokMimeData::addPlaylists( const Playlists::PlaylistList &playlists )
{
d->playlists << playlists;
}
QStringList
AmarokMimeData::playlistGroups() const
{
return d->playlistGroups;
}
void
AmarokMimeData::setPlaylistGroups( const QStringList &groups )
{
d->playlistGroups = groups;
}
void
AmarokMimeData::addPlaylistGroup( const QString &group )
{
d->playlistGroups << group;
}
Podcasts::PodcastChannelList
AmarokMimeData::podcastChannels() const
{
return d->m_podcastChannels;
}
void
AmarokMimeData::setPodcastChannels( const Podcasts::PodcastChannelList &channels )
{
d->m_podcastChannels = channels;
}
void
AmarokMimeData::addPodcastChannels( const Podcasts::PodcastChannelList &channels )
{
d->m_podcastChannels << channels;
}
Podcasts::PodcastEpisodeList
AmarokMimeData::podcastEpisodes() const
{
return d->m_podcastEpisodes;
}
void
AmarokMimeData::setPodcastEpisodes( const Podcasts::PodcastEpisodeList &episodes )
{
d->m_podcastEpisodes = episodes;
}
void
AmarokMimeData::addPodcastEpisodes( const Podcasts::PodcastEpisodeList &episodes )
{
d->m_podcastEpisodes << episodes;
}
QList<Collections::QueryMaker*>
AmarokMimeData::queryMakers()
{
d->deleteQueryMakers = false;
return d->queryMakers;
}
void
AmarokMimeData::addQueryMaker( Collections::QueryMaker *queryMaker )
{
d->queryMakers.append( queryMaker );
}
void
AmarokMimeData::setQueryMakers( const QList<Collections::QueryMaker*> &queryMakers )
{
d->queryMakers << queryMakers;
}
BookmarkList AmarokMimeData::bookmarks() const
{
return d->bookmarks;
}
void AmarokMimeData::setBookmarks( const BookmarkList &bookmarks )
{
d->bookmarks = bookmarks;
}
void AmarokMimeData::addBookmarks( const BookmarkList &bookmarks )
{
d->bookmarks << bookmarks;
}
BookmarkGroupList AmarokMimeData::bookmarkGroups() const
{
return d->bookmarkGroups;
}
void AmarokMimeData::setBookmarkGroups( const BookmarkGroupList &groups )
{
d->bookmarkGroups = groups;
}
void AmarokMimeData::addBookmarkGroups( const BookmarkGroupList &groups )
{
d->bookmarkGroups << groups;
}
QVariant
AmarokMimeData::retrieveData( const QString &mimeType, QVariant::Type type ) const
{
Meta::TrackList tracks = this->tracks();
Playlists::PlaylistList playlists = this->playlists();
Podcasts::PodcastChannelList channels = this->podcastChannels();
Podcasts::PodcastEpisodeList episodes = this->podcastEpisodes();
if( !tracks.isEmpty() )
{
if( mimeType == "text/uri-list" && (type == QVariant::List || type == QVariant::ByteArray) )
{
QList<QVariant> list;
foreach( Meta::TrackPtr track, tracks )
{
list.append( QVariant( QUrl( track->playableUrl() ) ) );
}
foreach( Podcasts::PodcastEpisodePtr episode, episodes )
{
list.append( QVariant( QUrl( episode->playableUrl() ) ) );
}
foreach( Playlists::PlaylistPtr playlist, playlists )
{
list.append( QVariant( QUrl( playlist->uidUrl() ) ) );
}
foreach( Podcasts::PodcastChannelPtr channel, channels )
{
list.append( QVariant( QUrl( channel->url() ) ) );
}
return QVariant( list );
}
if( mimeType == "text/plain" && (type == QVariant::String || type == QVariant::ByteArray) )
{
QString result;
foreach( Meta::TrackPtr track, tracks )
{
if( !result.isEmpty() )
result += '\n';
result += track->artist()->prettyName();
result += " - ";
result += track->prettyName();
}
foreach( Podcasts::PodcastEpisodePtr episode, episodes )
{
if( !result.isEmpty() )
result += '\n';
result += episode->prettyName();
result += " - ";
result += episode->channel()->prettyName();
}
foreach( Playlists::PlaylistPtr playlist, playlists )
{
if( !result.isEmpty() )
result += '\n';
result += playlist->prettyName();
}
foreach( Podcasts::PodcastChannelPtr channel, channels )
{
if( !result.isEmpty() )
result += '\n';
result += channel->prettyName();
}
return QVariant( result );
}
}
return QMimeData::retrieveData( mimeType, type );
}
void
AmarokMimeData::startQueries()
{
foreach( Collections::QueryMaker *qm, d->queryMakers )
{
qm->setQueryType( Collections::QueryMaker::Track );
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(newResultReady(Meta::TrackList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(queryDone()), this, SLOT(queryDone()), Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::newTracksReady,
+ this, &AmarokMimeData::newResultReady, Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &AmarokMimeData::queryDone, Qt::QueuedConnection );
qm->run();
}
}
void
AmarokMimeData::newResultReady( const Meta::TrackList &tracks )
{
Collections::QueryMaker *qm = dynamic_cast<Collections::QueryMaker*>( sender() );
if( qm )
{
d->trackMap.insert( qm, tracks );
}
else
d->tracks << tracks;
}
void
AmarokMimeData::queryDone()
{
d->completedQueries++;
}
diff --git a/src/AmarokProcess.cpp b/src/AmarokProcess.cpp
index 3b8f167b48..ccaf36e1d7 100644
--- a/src/AmarokProcess.cpp
+++ b/src/AmarokProcess.cpp
@@ -1,147 +1,148 @@
/****************************************************************************************
* Copyright (c) 2007 Shane King <kde@dontletsstart.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokProcess.h"
#include "core/support/Debug.h"
#include <QTextCodec>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
AmarokProcess::AmarokProcess(QObject *parent)
: KProcess(parent), lowPriority(false)
{
- connect( this, SIGNAL(finished(int)), this, SLOT(finished()) );
- connect( this, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()) );
- connect( this, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()) );
+ connect( this, QOverload<int>::of(&QProcess::finished),
+ this, QOverload<>::of(&AmarokProcess::finished) );
+ connect( this, &QProcess::readyReadStandardOutput, this, &AmarokProcess::readyReadStandardOutput );
+ connect( this, &QProcess::readyReadStandardError, this, &AmarokProcess::readyReadStandardError );
}
/**
* Due to xine-lib, we have to make KProcess close all fds, otherwise we get "device is busy" messages
* exploiting setupChildProcess(), a virtual method that
* happens to be called in the forked process
* See bug #103750 for more information.
*/
void
AmarokProcess::setupChildProcess()
{
KProcess::setupChildProcess();
#ifdef Q_OS_UNIX
// can't get at the fds that QProcess needs to keep around to do its status
// tracking , but fortunately it sets them to close on exec anyway, so if
// we do likewise to everything then we should be ok.
for(int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i--)
fcntl(i, F_SETFD, FD_CLOEXEC);
if( lowPriority )
setpriority( PRIO_PROCESS, 0, 19 );
#endif
}
void
AmarokProcess::start()
{
KProcess::start();
#ifdef Q_OS_WIN32
if( lowPriority )
SetPriorityClass( QProcess::pid()->hProcess, IDLE_PRIORITY_CLASS );
#endif
}
void
AmarokProcess::finished() // SLOT
{
emit processExited( this );
}
void
AmarokProcess::readyReadStandardOutput() // SLOT
{
emit receivedStdout( this );
}
void
AmarokProcess::readyReadStandardError() // SLOT
{
emit receivedStderr( this );
}
// AmarokProcIO
AmarokProcIO::AmarokProcIO ( QObject *parent )
: AmarokProcess( parent ), codec( QTextCodec::codecForName( "UTF-8" ) )
{
}
bool
AmarokProcIO::writeStdin (const QString &line)
{
return write( codec->fromUnicode( line + '\n' ) ) > 0;
}
int
AmarokProcIO::readln (QString &line)
{
QByteArray bytes = readLine();
if (bytes.length() == 0)
{
return -1;
}
else
{
// convert and remove \n
line = codec->toUnicode( bytes.data(), bytes.length() - 1);
return line.length();
}
}
void
AmarokProcIO::start()
{
- connect (this, SIGNAL (readyReadStandardOutput()), this, SLOT (readyReadStandardOutput()));
+ connect (this, &AmarokProcIO::readyReadStandardOutput, this, &AmarokProcIO::readyReadStandardOutput);
KProcess::start ();
}
void
AmarokProcIO::readyReadStandardOutput()
{
if( canReadLine() )
emit readReady( this );
}
// AmarokShellProcess
AmarokShellProcess &
AmarokShellProcess::operator<<(const QString& arg)
{
if( program().isEmpty() )
setShellCommand( arg );
else
AmarokProcess::operator<<( arg );
return *this;
}
AmarokShellProcess &
AmarokShellProcess::operator<<(const QStringList& args)
{
foreach( const QString &arg, args )
*this << arg;
return *this;
}
diff --git a/src/App.cpp b/src/App.cpp
index 76385d6413..811bcb0272 100644
--- a/src/App.cpp
+++ b/src/App.cpp
@@ -1,647 +1,669 @@
/****************************************************************************************
* Copyright (c) 2002 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "App.h"
#include <config.h>
#include "EngineController.h"
#include "KNotificationBackend.h"
#include "PluginManager.h"
#include "scripting/scriptmanager/ScriptManager.h"
#include "TrayIcon.h"
#include "amarokconfig.h"
#include "amarokurls/AmarokUrl.h"
#include "configdialog/ConfigDialog.h"
#include "configdialog/dialogs/PlaybackConfig.h"
#include "core/capabilities/SourceInfoCapability.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/meta/support/MetaUtility.h"
#include "core/playlists/Playlist.h"
#include "core/playlists/PlaylistFormat.h"
#include "core/podcasts/PodcastProvider.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/transcoding/TranscodingController.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "core-impl/storage/StorageManager.h"
#include "covermanager/CoverCache.h"
#include "covermanager/CoverFetcher.h"
#include "dbus/CollectionDBusHandler.h"
#include "dbus/mpris1/PlayerHandler.h"
#include "dbus/mpris1/RootHandler.h"
#include "dbus/mpris1/TrackListHandler.h"
#include "dbus/mpris2/Mpris2.h"
#include "network/NetworkAccessManagerProxy.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistController.h"
#include "playlist/PlaylistModelStack.h"
#include "playlistmanager/PlaylistManager.h"
#include "services/ServicePluginManager.h"
#include "scripting/scriptconsole/ScriptConsole.h"
#include "statemanagement/ApplicationController.h"
#include "statemanagement/DefaultApplicationController.h"
#include "statsyncing/Controller.h"
#include "widgets/Osd.h"
#include <iostream>
#include <KCalendarSystem>
#include <KCmdLineArgs> //initCliArgs()
#include <KDirLister>
#include <KEditToolBar> //slotConfigToolbars()
#include <KGlobalSettings>
#include <KIO/CopyJob>
#include <KJob>
#include <KJobUiDelegate>
#include <KLocalizedString>
#include <KMessageBox>
#include <KShortcutsDialog> //slotConfigShortcuts()
#include <KStandardDirs>
#include <KGlobal>
#include <QAction>
#include <QByteArray>
#include <QDesktopServices>
#include <QFile>
#include <QStringList>
#include <QTextDocument> // for Qt::escape()
#include <QTimer> //showHyperThreadingWarning()
#include <QtDBus>
#ifdef Q_WS_MAC
#include <CoreFoundation/CoreFoundation.h>
extern void setupEventHandler_mac(SRefCon);
#endif
QStringList App::s_delayedAmarokUrls = QStringList();
AMAROK_EXPORT OcsData ocsData( "opendesktop" );
App::App(int &argc, char **argv)
: QApplication(argc, argv)
, m_args(nullptr)
, m_tray(nullptr)
{
DEBUG_BLOCK
PERF_LOG( "Begin Application ctor" )
// required for last.fm plugin to grab app version
setApplicationVersion( AMAROK_VERSION );
qRegisterMetaType<Meta::DataPtr>();
qRegisterMetaType<Meta::DataList>();
qRegisterMetaType<Meta::TrackPtr>();
qRegisterMetaType<Meta::TrackList>();
qRegisterMetaType<Meta::AlbumPtr>();
qRegisterMetaType<Meta::AlbumList>();
qRegisterMetaType<Meta::ArtistPtr>();
qRegisterMetaType<Meta::ArtistList>();
qRegisterMetaType<Meta::GenrePtr>();
qRegisterMetaType<Meta::GenreList>();
qRegisterMetaType<Meta::ComposerPtr>();
qRegisterMetaType<Meta::ComposerList>();
qRegisterMetaType<Meta::YearPtr>();
qRegisterMetaType<Meta::YearList>();
qRegisterMetaType<Meta::LabelPtr>();
qRegisterMetaType<Meta::LabelList>();
qRegisterMetaType<Playlists::PlaylistPtr>();
qRegisterMetaType<Playlists::PlaylistList>();
#ifdef Q_WS_MAC
// this is inspired by OpenSceneGraph: osgDB/FilePath.cpp
// Start with the Bundle PlugIns directory.
// Get the main bundle first. No need to retain or release it since
// we are not keeping a reference
CFBundleRef myBundle = CFBundleGetMainBundle();
if( myBundle )
{
// CFBundleGetMainBundle will return a bundle ref even if
// the application isn't part of a bundle, so we need to
// check
// if the path to the bundle ends in ".app" to see if it is
// a
// proper application bundle. If it is, the plugins path is
// added
CFURLRef urlRef = CFBundleCopyBundleURL(myBundle);
if(urlRef)
{
char bundlePath[1024];
if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) )
{
QByteArray bp( bundlePath );
size_t len = bp.length();
if( len > 4 && bp.right( 4 ) == ".app" )
{
bp.append( "/Contents/MacOS" );
QByteArray path = qgetenv( "PATH" );
if( path.length() > 0 )
{
path.prepend( ":" );
}
path.prepend( bp );
debug() << "setting PATH=" << path;
setenv("PATH", path, 1);
}
}
// docs say we are responsible for releasing CFURLRef
CFRelease(urlRef);
}
}
setupEventHandler_mac(this);
#endif
PERF_LOG( "Done App ctor" )
}
App::~App()
{
DEBUG_BLOCK
//delete m_args;
CollectionManager::instance()->stopScan();
// Hiding the OSD before exit prevents crash
Amarok::OSD::instance()->hide();
// This following can't go in the PlaylistModel destructor, because by the time that
// happens, the Config has already been written.
// Use the bottom model because that provides the most dependable/invariable row
// number to save in an external file.
AmarokConfig::setLastPlaying( Playlist::ModelStack::instance()->bottom()->activeRow() );
if ( AmarokConfig::resumePlayback() )
{
Meta::TrackPtr engineTrack = The::engineController()->currentTrack();
if( engineTrack )
{
AmarokConfig::setResumeTrack( engineTrack->playableUrl().toDisplayString() );
AmarokConfig::setResumeTime( The::engineController()->trackPositionMs() );
AmarokConfig::setResumePaused( The::engineController()->isPaused() );
}
else
AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time!
}
The::engineController()->endSession(); //records final statistics
#ifndef Q_WS_MAC
// do even if trayicon is not shown, it is safe
Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() );
AmarokConfig::self()->writeConfig();
#else
// for some reason on OS X the main window always reports being hidden
// this means if you have the tray icon enabled, amarok will always open minimized
Amarok::config().writeEntry( "HiddenOnExit", false );
AmarokConfig::self()->writeConfig();
#endif
ScriptManager::destroy();
// this must be deleted before the connection to the Xserver is
// severed, or we risk a crash when the QApplication is exited,
// I asked Trolltech! *smug*
Amarok::OSD::destroy();
Amarok::KNotificationBackend::destroy();
AmarokConfig::self()->writeConfig();
//mainWindow()->deleteBrowsers();
delete mainWindow();
Playlist::Controller::destroy();
Playlist::ModelStack::destroy();
Playlist::Actions::destroy();
PlaylistManager::destroy();
CoverFetcher::destroy();
CoverCache::destroy();
ServicePluginManager::destroy();
CollectionManager::destroy();
StorageManager::destroy();
NetworkAccessManagerProxy::destroy();
Plugins::PluginManager::destroy();
//this should be moved to App::quit() I guess
Amarok::Components::applicationController()->shutdown();
#ifdef Q_WS_WIN
// work around for KUniqueApplication being not completely implemented on windows
QDBusConnectionInterface* dbusService;
if (QDBusConnection::sessionBus().isConnected() && (dbusService = QDBusConnection::sessionBus().interface()))
{
dbusService->unregisterService("org.mpris.amarok");
dbusService->unregisterService("org.mpris.MediaPlayer2.amarok");
}
#endif
}
void
App::handleCliArgs(const QString &cwd)
{
DEBUG_BLOCK
//TODO Resolve positional arguments using cwd
if( m_args->isSet( "cwd" ) ) {
m_cwd = m_args->value( "cwd" );
} else {
m_cwd = cwd;
}
bool haveArgs = true; // assume having args in first place
if( !m_args->positionalArguments().isEmpty() )
{
QList<QUrl> list;
for( int i = 0; i < m_args->positionalArguments().count() ; i++ )
{
QUrl url( m_args->positionalArguments().at( i ) );
//TODO:PORTME
if( Podcasts::PodcastProvider::couldBeFeed( url.url() ) )
{
QUrl feedUrl = Podcasts::PodcastProvider::toFeedUrl( url.url() );
The::playlistManager()->defaultPodcasts()->addPodcast( feedUrl );
}
else if( url.scheme() == "amarok" )
s_delayedAmarokUrls.append( url.url() );
else
list << url;
}
Playlist::AddOptions options;
if( m_args->isSet( "queue" ) )
options = Playlist::OnQueueToPlaylistAction;
else if( m_args->isSet( "append" ) )
options = Playlist::OnAppendToPlaylistAction;
else if( m_args->isSet( "load" ) )
options = Playlist::OnReplacePlaylistAction;
else
options = Playlist::OnPlayMediaAction;
The::playlistController()->insertOptioned( list, options );
}
else if ( m_args->isSet( "cdplay" ) )
The::mainWindow()->playAudioCd();
//we shouldn't let the user specify two of these since it is pointless!
//so we prioritise, pause > stop > play > next > prev
//thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!)
//then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log
//I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it
else if ( m_args->isSet( "pause" ) )
The::engineController()->pause();
else if ( m_args->isSet( "stop" ) )
The::engineController()->stop();
else if ( m_args->isSet( "play-pause" ) )
The::engineController()->playPause();
else if ( m_args->isSet( "play" ) ) //will restart if we are playing
The::engineController()->play();
else if ( m_args->isSet( "next" ) )
The::playlistActions()->next();
else if ( m_args->isSet( "previous" ) )
The::playlistActions()->back();
else // no args given
haveArgs = false;
static bool firstTime = true;
//allows debugging on OS X. Bundles have to be started with "open". Therefore it is not possible to pass an argument
const bool forceDebug = Amarok::config().readEntry( "Force Debug", false );
if( firstTime && !Debug::debugEnabled() && !forceDebug )
{
qDebug() << "**********************************************************************************************";
qDebug() << "** AMAROK WAS STARTED IN NORMAL MODE. IF YOU WANT TO SEE DEBUGGING INFORMATION, PLEASE USE: **";
qDebug() << "** amarok --debug **";
qDebug() << "**********************************************************************************************";
}
if( !firstTime && !haveArgs )
{
// mainWindow() can be 0 if another instance is loading, see https://bugs.kde.org/show_bug.cgi?id=202713
if( pApp->mainWindow() )
pApp->mainWindow()->activate();
}
firstTime = false;
m_args->clearPositionalArguments(); //free up memory
}
/////////////////////////////////////////////////////////////////////////////////////
// INIT
/////////////////////////////////////////////////////////////////////////////////////
void
App::initCliArgs(QCommandLineParser *parser)
{
m_args = parser;
// Update main.cpp (below KUniqueApplication::start() wrt instanceOptions) aswell if needed!
QList<QCommandLineOption> options;
options.append(QCommandLineOption("+[URL(s)]", i18n( "Files/URLs to open" )));
options.append(QCommandLineOption("cdplay", i18n("Immediately start playing an audio cd")));
options.append(QCommandLineOption(QStringList() << "r" << "previous", i18n( "Skip backwards in playlist" )));
options.append(QCommandLineOption(QStringList() << "p" << "play", i18n( "Start playing current playlist" )));
options.append(QCommandLineOption(QStringList() << "t" << "play-pause", i18n( "Play if stopped, pause if playing" )));
options.append(QCommandLineOption("pause", i18n( "Pause playback" )));
options.append(QCommandLineOption(QStringList() << "s" << "stop", i18n( "Stop playback" )));
options.append(QCommandLineOption(QStringList() << "f" << "next", i18n( "Skip forwards in playlist" )
+ "\n\n\n"
+ i18n("Additional options:")));
options.append(QCommandLineOption(QStringList() << "a" << "append", i18n( "Append files/URLs to playlist" )));
options.append(QCommandLineOption("queue", i18n("Queue URLs after the currently playing track")));
options.append(QCommandLineOption(QStringList() << "l" << "load", i18n("Load URLs, replacing current playlist")));
options.append(QCommandLineOption(QStringList() << "d" << "debug", i18n("Print verbose debugging information")));
options.append(QCommandLineOption("debug-audio", i18n("Print verbose debugging information from the audio system")));
options.append(QCommandLineOption(QStringList() << "c" << "coloroff", i18n("Disable colorization for debug output.")));
options.append(QCommandLineOption(QStringList() << "m" << "multipleinstances", i18n("Allow running multiple Amarok instances")));
options.append(QCommandLineOption("cwd", i18n( "Base for relative filenames/URLs" )));
parser->addOptions(options); //add our own options
}
/////////////////////////////////////////////////////////////////////////////////////
// METHODS
/////////////////////////////////////////////////////////////////////////////////////
//SLOT
-void App::applySettings( bool firstTime )
+void App::applySettings()
{
- ///Called when the configDialog is closed with OK or Apply
-
DEBUG_BLOCK
if( AmarokConfig::showTrayIcon() && ! m_tray )
{
m_tray = new Amarok::TrayIcon( m_mainWindow.data() );
}
else if( !AmarokConfig::showTrayIcon() && m_tray )
{
delete m_tray;
m_tray = 0;
}
- if( !firstTime ) // prevent OSD from popping up during startup
- Amarok::OSD::instance()->applySettings();
+ Amarok::OSD::instance()->applySettings();
+
+ emit settingsChanged();
+
+ if( AmarokConfig::enableScriptConsole() && !m_scriptConsole )
+ m_scriptConsole = ScriptConsoleNS::ScriptConsole::instance();
+ else if( !AmarokConfig::enableScriptConsole() && m_scriptConsole )
+ m_scriptConsole.data()->deleteLater();}
- if( !firstTime )
- emit settingsChanged();
+//SLOT
+void App::applySettingsFirstTime()
+{
+ DEBUG_BLOCK
+
+ if( AmarokConfig::showTrayIcon() && ! m_tray )
+ {
+ m_tray = new Amarok::TrayIcon( m_mainWindow.data() );
+ }
+ else if( !AmarokConfig::showTrayIcon() && m_tray )
+ {
+ delete m_tray;
+ m_tray = 0;
+ }
if( AmarokConfig::enableScriptConsole() && !m_scriptConsole )
m_scriptConsole = ScriptConsoleNS::ScriptConsole::instance();
else if( !AmarokConfig::enableScriptConsole() && m_scriptConsole )
m_scriptConsole.data()->deleteLater();
}
//SLOT
void
App::continueInit()
{
DEBUG_BLOCK
PERF_LOG( "Begin App::continueInit" )
newInstance();
const bool restoreSession = m_args->positionalArguments().isEmpty() || m_args->isSet( "append" )
|| m_args->isSet( "queue" )
|| Amarok::config().readEntry( "AppendAsDefault", false );
new Amarok::DefaultApplicationController( this );
Amarok::Components::applicationController()->start();
// Instantiate statistics synchronization controller. Needs to be before creating
// MainWindow as MainWindow connects a signal to StatSyncing::Controller.
Amarok::Components::setStatSyncingController( new StatSyncing::Controller( this ) );
PERF_LOG( "Creating MainWindow" )
m_mainWindow = new MainWindow();
PERF_LOG( "Done creating MainWindow" )
if( AmarokConfig::showTrayIcon() )
m_tray = new Amarok::TrayIcon( mainWindow() );
PERF_LOG( "Creating DBus handlers" )
new Mpris1::RootHandler();
new Mpris1::PlayerHandler();
new Mpris1::TrackListHandler();
QDBusConnection::sessionBus().registerService("org.mpris.amarok");
new CollectionDBusHandler( this );
new Amarok::Mpris2( this );
PERF_LOG( "Done creating DBus handlers" )
//DON'T DELETE THIS NEXT LINE or the app crashes when you click the X (unless we reimplement closeEvent)
//Reason: in ~App we have to call the deleteBrowsers method or else we run afoul of refcount foobar in KHTMLPart
//But if you click the X (not Action->Quit) it automatically kills MainWindow because KMainWindow sets this
//for us as default (bad KMainWindow)
mainWindow()->setAttribute( Qt::WA_DeleteOnClose, false );
//init playlist window as soon as the database is guaranteed to be usable
// Create engine, show TrayIcon etc.
- applySettings( true );
+ applySettingsFirstTime();
// Must be created _after_ MainWindow.
PERF_LOG( "Starting ScriptManager" )
ScriptManager::instance();
PERF_LOG( "ScriptManager started" )
The::engineController()->setVolume( AmarokConfig::masterVolume() );
The::engineController()->setMuted( AmarokConfig::muteState() );
Amarok::KNotificationBackend::instance()->setEnabled( AmarokConfig::kNotifyEnabled() );
Amarok::OSD::instance()->applySettings(); // Create after setting volume (don't show OSD for that)
// Restore keyboard shortcuts etc from config
Amarok::actionCollection()->readSettings();
//on startup we need to show the window, but only if it wasn't hidden on exit
//and always if the trayicon isn't showing
if( !Amarok::config().readEntry( "HiddenOnExit", false ) || !AmarokConfig::showTrayIcon() )
{
PERF_LOG( "showing main window again" )
m_mainWindow.data()->show();
PERF_LOG( "after showing mainWindow" )
}
//Instantiate the Transcoding::Controller, this fires up an asynchronous KProcess with
//FFmpeg which should not take more than ~200msec.
Amarok::Components::setTranscodingController( new Transcoding::Controller( this ) );
PERF_LOG( "App init done" )
// check that the amarok sql configuration is valid.
if( !StorageManager::instance()->getLastErrors().isEmpty() )
{
QMessageBox::critical( The::mainWindow(), i18n( "Database Error" ),
i18n( "The amarok database reported the following errors:"
"\n%1\nIn most cases you will need to resolve these errors "
"before Amarok will run properly." ).
arg( StorageManager::instance()->getLastErrors().join( "\n" ) ) );
StorageManager::instance()->clearLastErrors();
slotConfigAmarok( "DatabaseConfig" );
}
else
{
handleFirstRun();
}
if( AmarokConfig::resumePlayback() && restoreSession && m_args->isSet( "stop" ) ) {
//restore session as long as the user didn't specify media to play etc.
//do this after applySettings() so OSD displays correctly
The::engineController()->restoreSession();
}
//and now we can run any amarokurls provided on startup, as all components should be initialized by now!
foreach( const QString& urlString, s_delayedAmarokUrls )
{
AmarokUrl aUrl( urlString );
aUrl.run();
}
s_delayedAmarokUrls.clear();
}
//SLOT
void App::activateRequested(const QStringList &arguments, const QString & cwd)
{
qDebug() << "activateRequested";
if (!arguments.isEmpty()) {
m_args->parse(arguments);
handleCliArgs(cwd);
} else {
newInstance( );
}
}
void App::slotConfigAmarok( const QString& page )
{
KConfigDialog *dialog = KConfigDialog::exists( "settings" );
if( !dialog )
{
//KConfigDialog didn't find an instance of this dialog, so lets create it :
dialog = new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() );
- connect( dialog, SIGNAL(settingsChanged(QString)), SLOT(applySettings()) );
+ connect( dialog, &KConfigDialog::settingsChanged,
+ this, &App::applySettings );
}
static_cast<Amarok2ConfigDialog*>( dialog )->show( page );
}
+void App::slotConfigAmarokWithEmptyPage()
+{
+ slotConfigAmarok( QString() );
+}
+
void App::slotConfigShortcuts()
{
KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, mainWindow() );
AmarokConfig::self()->writeConfig();
}
KIO::Job *App::trashFiles( const QList<QUrl> &files )
{
KIO::Job *job = KIO::trash( files );
Amarok::Components::logger()->newProgressOperation( job, i18n("Moving files to trash") );
- connect( job, SIGNAL(result(KJob*)), this, SLOT(slotTrashResult(KJob*)) );
+ connect( job, &KIO::Job::result, this, &App::slotTrashResult );
return job;
}
void App::slotTrashResult( KJob *job )
{
if( job->error() )
job->uiDelegate()->showErrorMessage();
}
void App::quit()
{
DEBUG_BLOCK
The::playlistManager()->completePodcastDownloads();
// Following signal is relayed to scripts, which may block quitting for a while
emit prepareToQuit();
QApplication::quit();
}
bool App::event( QEvent *event )
{
switch( event->type() )
{
//allows Amarok to open files from the finder on OS X
case QEvent::FileOpen:
{
QString file = static_cast<QFileOpenEvent*>( event )->file();
The::playlistController()->insertOptioned( QUrl( file ), Playlist::OnPlayMediaAction );
return true;
}
default:
return QApplication::event( event );
}
}
int App::newInstance()
{
DEBUG_BLOCK
static bool first = true;
if ( isSessionRestored() && first )
{
first = false;
return 0;
}
first = false;
handleCliArgs(QDir::currentPath());
return 0;
}
void App::handleFirstRun()
{
KConfigGroup config = KGlobal::config()->group( "General" );
if( !config.readEntry( "First Run", true ) )
return;
const QUrl musicUrl = QUrl::fromUserInput(QDesktopServices::storageLocation( QDesktopServices::MusicLocation ));
const QString musicDir = musicUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QDir dir( musicDir );
int result = KMessageBox::No;
if( dir.exists() && dir.isReadable() )
{
result = KMessageBox::questionYesNoCancel( mainWindow(), i18n( "A music path, "
"%1, is set in System Settings.\nWould you like to use that as a "
"collection folder?", musicDir ) );
}
switch( result )
{
case KMessageBox::Yes:
{
Collections::Collection *coll = CollectionManager::instance()->primaryCollection();
if( coll )
{
coll->setProperty( "collectionFolders", QStringList() << musicDir );
CollectionManager::instance()->startFullScan();
}
break;
}
case KMessageBox::No:
slotConfigAmarok( "CollectionConfig" );
break;
default:
break;
}
config.writeEntry( "First Run", false );
}
diff --git a/src/App.h b/src/App.h
index 34776cc310..166ffbd964 100644
--- a/src/App.h
+++ b/src/App.h
@@ -1,111 +1,113 @@
/****************************************************************************************
* Copyright (c) 2002 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_APP_H
#define AMAROK_APP_H
#include "MainWindow.h"
#include "amarok_export.h"
#include <config.h>
#include "aboutdialog/OcsData.h"
#include <KAboutData>
#include <KUniqueApplication> //baseclass
#include <QUrl>
#include <QHash>
#include <QCommandLineParser>
#include <QWeakPointer>
#include <QString>
namespace Amarok {
class TrayIcon;
}
namespace ScriptConsoleNS{
class ScriptConsole;
}
class OcsData;
namespace KIO { class Job; }
class KJob;
class MediaDeviceManager;
class AMAROK_EXPORT App : public QApplication
{
Q_OBJECT
public:
App(int &argc, char **argv);
~App();
static App *instance() { return static_cast<App*>( qApp ); }
void setUniqueInstance( bool isUnique ) { m_isUniqueInstance = isUnique; }
bool isNonUniqueInstance() const { return m_isUniqueInstance; }
void continueInit();
Amarok::TrayIcon* trayIcon() const { return m_tray; }
void handleCliArgs(const QString &cwd);
void initCliArgs(QCommandLineParser *parsers);
virtual int newInstance();
inline MainWindow *mainWindow() const { return m_mainWindow.data(); }
// FRIENDS
friend class MainWindow; //requires access to applySettings()
Q_SIGNALS:
void prepareToQuit();
void settingsChanged();
private Q_SLOTS:
public Q_SLOTS:
void activateRequested(const QStringList & arguments, const QString & cwd);
- void applySettings( bool firstTime = false );
+ void applySettings();
+ void applySettingsFirstTime();
void slotConfigAmarok( const QString& page = QString() );
+ void slotConfigAmarokWithEmptyPage();
void slotConfigShortcuts();
KIO::Job *trashFiles( const QList<QUrl> &files );
void quit();
protected:
virtual bool event( QEvent *event );
private Q_SLOTS:
void slotTrashResult( KJob *job );
private:
void handleFirstRun();
// ATTRIBUTES
bool m_isUniqueInstance;
QWeakPointer<MainWindow> m_mainWindow;
Amarok::TrayIcon *m_tray;
MediaDeviceManager *m_mediaDeviceManager;
QWeakPointer<ScriptConsoleNS::ScriptConsole> m_scriptConsole;
QCommandLineParser *m_args;
QString m_cwd;
static QStringList s_delayedAmarokUrls;
};
#define pApp static_cast<App*>(qApp)
#endif // AMAROK_APP_H
diff --git a/src/EngineController.cpp b/src/EngineController.cpp
index fa61efa0d7..237d0c1f10 100644
--- a/src/EngineController.cpp
+++ b/src/EngineController.cpp
@@ -1,1379 +1,1375 @@
/****************************************************************************************
* Copyright (c) 2004 Frederik Holljen <fh@ez.no> *
* Copyright (c) 2004,2005 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2004-2013 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2006,2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "EngineController"
#include "EngineController.h"
#include "MainWindow.h"
#include "amarokconfig.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core/capabilities/MultiPlayableCapability.h"
#include "core/capabilities/MultiSourceCapability.h"
#include "core/capabilities/SourceInfoCapability.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "playback/DelayedDoers.h"
#include "playback/Fadeouter.h"
#include "playback/PowerManager.h"
#include "playlist/PlaylistActions.h"
#include <KLocale>
#include <Phonon/AudioOutput>
#include <Phonon/BackendCapabilities>
#include <Phonon/MediaObject>
#include <Phonon/VolumeFaderEffect>
#include <QCoreApplication>
#include <QTextDocument> // for Qt::escape
#include <qmath.h>
// for slotMetaDataChanged()
typedef QPair<Phonon::MetaData, QString> FieldPair;
namespace The {
EngineController* engineController() { return EngineController::instance(); }
}
EngineController *
EngineController::instance()
{
return Amarok::Components::engineController();
}
EngineController::EngineController()
: m_boundedPlayback( 0 )
, m_multiPlayback( 0 )
, m_multiSource( 0 )
, m_playWhenFetched( true )
, m_volume( 0 )
, m_currentAudioCdTrack( 0 )
, m_pauseTimer( new QTimer( this ) )
, m_lastStreamStampPosition( -1 )
, m_ignoreVolumeChangeAction ( false )
, m_ignoreVolumeChangeObserve ( false )
, m_tickInterval( 0 )
, m_lastTickPosition( -1 )
, m_lastTickCount( 0 )
, m_mutex( QMutex::Recursive )
{
DEBUG_BLOCK
// ensure this object is created in a main thread
Q_ASSERT( thread() == QCoreApplication::instance()->thread() );
- connect( this, SIGNAL(fillInSupportedMimeTypes()), SLOT(slotFillInSupportedMimeTypes()) );
- connect( this, SIGNAL(trackFinishedPlaying(Meta::TrackPtr,double)),
- SLOT(slotTrackFinishedPlaying(Meta::TrackPtr,double)) );
+ connect( this, &EngineController::fillInSupportedMimeTypes, &EngineController::slotFillInSupportedMimeTypes );
+ connect( this, &EngineController::trackFinishedPlaying, &EngineController::slotTrackFinishedPlaying );
new PowerManager( this ); // deals with inhibiting suspend etc.
m_pauseTimer->setSingleShot( true );
- connect( m_pauseTimer, SIGNAL(timeout()), SLOT(slotPause() ) );
+ connect( m_pauseTimer, &QTimer::timeout, this, &EngineController::slotPause );
m_equalizerController = new EqualizerController( this );
}
EngineController::~EngineController()
{
DEBUG_BLOCK //we like to know when singletons are destroyed
// don't do any of the after-processing that normally happens when
// the media is stopped - that's what endSession() is for
if( m_media )
{
m_media.data()->blockSignals(true);
m_media.data()->stop();
}
delete m_boundedPlayback;
m_boundedPlayback = 0;
delete m_multiPlayback; // need to get a new instance of multi if played again
m_multiPlayback = 0;
delete m_media.data();
delete m_audio.data();
delete m_audioDataOutput.data();
}
void
EngineController::initializePhonon()
{
DEBUG_BLOCK
m_path.disconnect();
m_dataPath.disconnect();
// QWeakPointers reset themselves to null if the object is deleted
delete m_media.data();
delete m_controller.data();
delete m_audio.data();
delete m_audioDataOutput.data();
delete m_preamp.data();
delete m_fader.data();
using namespace Phonon;
PERF_LOG( "EngineController: loading phonon objects" )
m_media = new MediaObject( this );
// Enable zeitgeist support on linux
//TODO: make this configurable by the user.
m_media.data()->setProperty( "PlaybackTracking", true );
m_audio = new AudioOutput( MusicCategory, this );
m_audioDataOutput = new AudioDataOutput( this );
m_audioDataOutput.data()->setDataSize( DATAOUTPUT_DATA_SIZE ); // The number of samples that Phonon sends per signal
m_path = createPath( m_media.data(), m_audio.data() );
m_controller = new MediaController( m_media.data() );
m_equalizerController->initialize( m_path );
// HACK we turn off replaygain manually on OSX, until the phonon coreaudio backend is fixed.
// as the default is specified in the .cfg file, we can't just tell it to be a different default on OSX
#ifdef Q_WS_MAC
AmarokConfig::setReplayGainMode( AmarokConfig::EnumReplayGainMode::Off );
AmarokConfig::setFadeoutOnStop( false );
#endif
// we now try to create pre-amp unconditionally, however we check that it is valid.
// So now m_preamp is null equals not available at all
QScopedPointer<VolumeFaderEffect> preamp( new VolumeFaderEffect( this ) );
if( preamp->isValid() )
{
m_preamp = preamp.take();
m_path.insertEffect( m_preamp.data() );
}
QScopedPointer<VolumeFaderEffect> fader( new VolumeFaderEffect( this ) );
if( fader->isValid() )
{
fader->setFadeCurve( VolumeFaderEffect::Fade9Decibel );
m_fader = fader.take();
m_path.insertEffect( m_fader.data() );
m_dataPath = createPath( m_fader.data(), m_audioDataOutput.data() );
}
else
m_dataPath = createPath( m_media.data(), m_audioDataOutput.data() );
m_media.data()->setTickInterval( 100 );
m_tickInterval = m_media.data()->tickInterval();
debug() << "Tick Interval (actual): " << m_tickInterval;
PERF_LOG( "EngineController: loaded phonon objects" )
// Get the next track when there is 2 seconds left on the current one.
m_media.data()->setPrefinishMark( 2000 );
- connect( m_media.data(), SIGNAL(finished()), SLOT(slotFinished()));
- connect( m_media.data(), SIGNAL(aboutToFinish()), SLOT(slotAboutToFinish()) );
- connect( m_media.data(), SIGNAL(metaDataChanged()), SLOT(slotMetaDataChanged()) );
- connect( m_media.data(), SIGNAL(stateChanged(Phonon::State,Phonon::State)),
- SLOT(slotStateChanged(Phonon::State,Phonon::State)) );
- connect( m_media.data(), SIGNAL(tick(qint64)), SLOT(slotTick(qint64)) );
- connect( m_media.data(), SIGNAL(totalTimeChanged(qint64)),
- SLOT(slotTrackLengthChanged(qint64)) );
- connect( m_media.data(), SIGNAL(currentSourceChanged(Phonon::MediaSource)),
- SLOT(slotNewTrackPlaying(Phonon::MediaSource)) );
- connect( m_media.data(), SIGNAL(seekableChanged(bool)),
- SLOT(slotSeekableChanged(bool)) );
- connect( m_audio.data(), SIGNAL(volumeChanged(qreal)),
- SLOT(slotVolumeChanged(qreal)) );
- connect( m_audio.data(), SIGNAL(mutedChanged(bool)),
- SLOT(slotMutedChanged(bool)) );
- connect( m_audioDataOutput.data(),
- SIGNAL(dataReady(QMap<Phonon::AudioDataOutput::Channel,QVector<qint16> >)),
- SIGNAL(audioDataReady(QMap<Phonon::AudioDataOutput::Channel,QVector<qint16> >))
- );
-
- connect( m_controller.data(), SIGNAL(titleChanged(int)),
- SLOT(slotTitleChanged(int)) );
+ connect( m_media.data(), &MediaObject::finished, this, &EngineController::slotFinished );
+ connect( m_media.data(), &MediaObject::aboutToFinish, this, &EngineController::slotAboutToFinish );
+ connect( m_media.data(), &MediaObject::metaDataChanged, this, &EngineController::slotMetaDataChanged );
+ connect( m_media.data(), &MediaObject::stateChanged, this, &EngineController::slotStateChanged );
+ connect( m_media.data(), &MediaObject::tick, this, &EngineController::slotTick );
+ connect( m_media.data(), &MediaObject::totalTimeChanged, this, &EngineController::slotTrackLengthChanged );
+ connect( m_media.data(), &MediaObject::currentSourceChanged, this, &EngineController::slotNewTrackPlaying );
+ connect( m_media.data(), &MediaObject::seekableChanged, this, &EngineController::slotSeekableChanged );
+ connect( m_audio.data(), &AudioOutput::volumeChanged, this, &EngineController::slotVolumeChanged );
+ connect( m_audio.data(), &AudioOutput::mutedChanged, this, &EngineController::slotMutedChanged );
+ connect( m_audioDataOutput.data(), &AudioDataOutput::dataReady, this, &EngineController::audioDataReady );
+
+ connect( m_controller.data(), &MediaController::titleChanged, this, &EngineController::slotTitleChanged );
// Read the volume from phonon
m_volume = qBound<qreal>( 0, qRound(m_audio.data()->volume()*100), 100 );
if( m_currentTrack )
{
unsubscribeFrom( m_currentTrack );
m_currentTrack.clear();
}
if( m_currentAlbum )
{
unsubscribeFrom( m_currentAlbum );
m_currentAlbum.clear();
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC
//////////////////////////////////////////////////////////////////////////////////////////
QStringList
EngineController::supportedMimeTypes()
{
// this ensures that slotFillInSupportedMimeTypes() is called in the main thread. It
// will be called directly if we are called in the main thread (so that no deadlock
// can occur) and indirectly if we are called in non-main thread.
emit fillInSupportedMimeTypes();
// ensure slotFillInSupportedMimeTypes() called above has already finished:
m_supportedMimeTypesSemaphore.acquire();
return m_supportedMimeTypes;
}
void
EngineController::slotFillInSupportedMimeTypes()
{
// we assume non-empty == already filled in
if( !m_supportedMimeTypes.isEmpty() )
{
// unblock waiting for the semaphore in supportedMimeTypes():
m_supportedMimeTypesSemaphore.release();
return;
}
QRegExp avFilter( "^(audio|video)/", Qt::CaseInsensitive );
m_supportedMimeTypes = Phonon::BackendCapabilities::availableMimeTypes().filter( avFilter );
// Add whitelist hacks
// MP4 Audio Books have a different extension that KFileItem/Phonon don't grok
if( !m_supportedMimeTypes.contains( "audio/x-m4b" ) )
m_supportedMimeTypes << "audio/x-m4b";
// technically, "audio/flac" is not a valid mimetype (not on IANA list), but some things expect it
if( m_supportedMimeTypes.contains( "audio/x-flac" ) && !m_supportedMimeTypes.contains( "audio/flac" ) )
m_supportedMimeTypes << "audio/flac";
// technically, "audio/mp4" is the official mime type, but sometimes Phonon returns audio/x-m4a
if( m_supportedMimeTypes.contains( "audio/x-m4a" ) && !m_supportedMimeTypes.contains( "audio/mp4" ) )
m_supportedMimeTypes << "audio/mp4";
// unblock waiting for the semaphore in supportedMimeTypes(). We can over-shoot
// resource number so that next call to supportedMimeTypes won't have to
// wait for main loop; this is however just an optimization and we could have safely
// released just one resource. Note that this code-path is reached only once, so
// overflow cannot happen.
m_supportedMimeTypesSemaphore.release( 100000 );
}
void
EngineController::restoreSession()
{
//here we restore the session
//however, do note, this is always done, KDE session management is not involved
if( AmarokConfig::resumePlayback() )
{
const QUrl url = QUrl::fromUserInput(AmarokConfig::resumeTrack());
Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url );
// Only give a resume time for local files, because resuming remote protocols can have weird side effects.
// See: http://bugs.kde.org/show_bug.cgi?id=172897
if( url.isLocalFile() )
play( track, AmarokConfig::resumeTime(), AmarokConfig::resumePaused() );
else
play( track, 0, AmarokConfig::resumePaused() );
}
}
void
EngineController::endSession()
{
//only update song stats, when we're not going to resume it
if ( !AmarokConfig::resumePlayback() && m_currentTrack )
{
emit stopped( trackPositionMs(), m_currentTrack->length() );
unsubscribeFrom( m_currentTrack );
if( m_currentAlbum )
unsubscribeFrom( m_currentAlbum );
emit trackChanged( Meta::TrackPtr( 0 ) );
}
emit sessionEnded( AmarokConfig::resumePlayback() && m_currentTrack );
}
EqualizerController*
EngineController::equalizerController() const
{
return m_equalizerController;
}
//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC SLOTS
//////////////////////////////////////////////////////////////////////////////////////////
void
EngineController::play() //SLOT
{
DEBUG_BLOCK
if( isPlaying() )
return;
if( isPaused() )
{
if( m_currentTrack && m_currentTrack->type() == "stream" )
{
debug() << "This is a stream that cannot be resumed after pausing. Restarting instead.";
play( m_currentTrack );
return;
}
else
{
m_pauseTimer->stop();
if( supportsFadeout() )
m_fader.data()->setVolume( 1.0 );
m_media.data()->play();
emit trackPlaying( m_currentTrack );
return;
}
}
The::playlistActions()->play();
}
void
EngineController::play( Meta::TrackPtr track, uint offset, bool startPaused )
{
DEBUG_BLOCK
if( !track ) // Guard
return;
// clear the current track without sending playbackEnded or trackChangeNotify yet
stop( /* forceInstant */ true, /* playingWillContinue */ true );
// we grant exclusive access to setting new m_currentTrack to newTrackPlaying()
m_nextTrack = track;
debug() << "play: bounded is "<<m_boundedPlayback<<"current"<<track->name();
m_boundedPlayback = track->create<Capabilities::BoundedPlaybackCapability>();
m_multiPlayback = track->create<Capabilities::MultiPlayableCapability>();
track->prepareToPlay();
m_nextUrl = track->playableUrl();
if( m_multiPlayback )
{
- connect( m_multiPlayback, SIGNAL(playableUrlFetched(QUrl)),
- SLOT(slotPlayableUrlFetched(QUrl)) );
+ connect( m_multiPlayback, &Capabilities::MultiPlayableCapability::playableUrlFetched,
+ this, &EngineController::slotPlayableUrlFetched );
m_multiPlayback->fetchFirst();
}
else if( m_boundedPlayback )
{
debug() << "Starting bounded playback of url " << track->playableUrl() << " at position " << m_boundedPlayback->startPosition();
playUrl( track->playableUrl(), m_boundedPlayback->startPosition(), startPaused );
}
else
{
debug() << "Just a normal, boring track... :-P";
playUrl( track->playableUrl(), offset, startPaused );
}
}
void
EngineController::replay() // slot
{
DEBUG_BLOCK
seekTo( 0 );
emit trackPositionChanged( 0, true );
}
void
EngineController::playUrl( const QUrl &url, uint offset, bool startPaused )
{
DEBUG_BLOCK
m_media.data()->stop();
debug() << "URL: " << url << url.url();
debug() << "Offset: " << offset;
m_currentAudioCdTrack = 0;
if( url.scheme() == "audiocd" )
{
QStringList pathItems = url.path().split( '/', QString::KeepEmptyParts );
if( pathItems.count() != 3 )
{
error() << __PRETTY_FUNCTION__ << url.url() << "is not in expected format";
return;
}
bool ok = false;
int trackNumber = pathItems.at( 2 ).toInt( &ok );
if( !ok || trackNumber <= 0 )
{
error() << __PRETTY_FUNCTION__ << "failed to get positive track number from" << url.url();
return;
}
QString device = QUrlQuery(url).queryItemValue( "device" );
m_media.data()->setCurrentSource( Phonon::MediaSource( Phonon::Cd, device ) );
m_currentAudioCdTrack = trackNumber;
}
else
{
// keep in sync with setNextTrack(), slotPlayableUrlFetched()
if( url.isLocalFile() )
m_media.data()->setCurrentSource( url.toLocalFile() );
else
m_media.data()->setCurrentSource( url );
}
m_media.data()->clearQueue();
if( m_currentAudioCdTrack )
{
// call to play() is asynchronous and ->setCurrentTitle() can be only called on
// playing, buffering or paused media.
m_media.data()->pause();
DelayedTrackChanger *trackChanger = new DelayedTrackChanger( m_media.data(),
m_controller.data(), m_currentAudioCdTrack, offset, startPaused );
- connect( trackChanger, SIGNAL(trackPositionChanged(qint64,bool)),
- SIGNAL(trackPositionChanged(qint64,bool)) );
+ connect( trackChanger, &DelayedTrackChanger::trackPositionChanged,
+ this, &EngineController::trackPositionChanged );
}
else if( offset )
{
// call to play() is asynchronous and ->seek() can be only called on playing,
// buffering or paused media. Calling play() would lead to audible glitches,
// so call pause() that doesn't suffer from such problem.
m_media.data()->pause();
DelayedSeeker *seeker = new DelayedSeeker( m_media.data(), offset, startPaused );
- connect( seeker, SIGNAL(trackPositionChanged(qint64,bool)),
- SIGNAL(trackPositionChanged(qint64,bool)) );
+ connect( seeker, &DelayedSeeker::trackPositionChanged,
+ this, &EngineController::trackPositionChanged );
}
else
{
if( startPaused )
{
m_media.data()->pause();
}
else
{
m_pauseTimer->stop();
if( supportsFadeout() )
m_fader.data()->setVolume( 1.0 );
m_media.data()->play();
}
}
}
void
EngineController::pause() //SLOT
{
if( supportsFadeout() && AmarokConfig::fadeoutOnPause() )
{
m_fader.data()->fadeOut( AmarokConfig::fadeoutLength() );
m_pauseTimer->start( AmarokConfig::fadeoutLength() + 500 );
return;
}
slotPause();
}
void
EngineController::slotPause()
{
if( supportsFadeout() && AmarokConfig::fadeoutOnPause() )
{
// Reset VolumeFaderEffect to full volume
m_fader.data()->setVolume( 1.0 );
// Wait a bit before pausing the pipeline. Necessary for the new fader setting to take effect.
- QTimer::singleShot( 1000, m_media.data(), SLOT(pause()) );
+ QTimer::singleShot( 1000, m_media.data(), &Phonon::MediaObject::pause );
}
else
{
m_media.data()->pause();
}
emit paused();
}
void
EngineController::stop( bool forceInstant, bool playingWillContinue ) //SLOT
{
DEBUG_BLOCK
/* Only do fade-out when all conditions are met:
* a) instant stop is not requested
* b) we aren't already in a fadeout
* c) we are currently playing (not paused etc.)
* d) Amarok is configured to fadeout at all
* e) configured fadeout length is positive
* f) Phonon fader to do it is actually available
*/
bool doFadeOut = !forceInstant
&& !m_fadeouter
&& m_media.data()->state() == Phonon::PlayingState
&& AmarokConfig::fadeoutOnStop()
&& AmarokConfig::fadeoutLength() > 0
&& m_fader;
// let Amarok know that the previous track is no longer playing; if we will fade-out
// ::stop() is called after the fade by Fadeouter.
if( m_currentTrack && !doFadeOut )
{
unsubscribeFrom( m_currentTrack );
if( m_currentAlbum )
unsubscribeFrom( m_currentAlbum );
const qint64 pos = trackPositionMs();
// updateStreamLength() intentionally not here, we're probably in the middle of a track
const qint64 length = trackLength();
emit trackFinishedPlaying( m_currentTrack, pos / qMax<double>( length, pos ) );
m_currentTrack = 0;
m_currentAlbum = 0;
if( !playingWillContinue )
{
emit stopped( pos, length );
emit trackChanged( m_currentTrack );
}
}
{
QMutexLocker locker( &m_mutex );
delete m_boundedPlayback;
m_boundedPlayback = 0;
delete m_multiPlayback; // need to get a new instance of multi if played again
m_multiPlayback = 0;
m_multiSource.reset();
m_nextTrack.clear();
m_nextUrl.clear();
m_media.data()->clearQueue();
}
if( doFadeOut )
{
m_fadeouter = new Fadeouter( m_media, m_fader, AmarokConfig::fadeoutLength() );
// even though we don't pass forceInstant, doFadeOut will be false because
// m_fadeouter will be still valid
- connect( m_fadeouter.data(), SIGNAL(fadeoutFinished()), SLOT(stop()) );
+ connect( m_fadeouter.data(), &Fadeouter::fadeoutFinished,
+ this, &EngineController::regularStop );
}
else
{
m_media.data()->stop();
m_media.data()->setCurrentSource( Phonon::MediaSource() );
}
}
+void
+EngineController::regularStop()
+{
+ stop( false, false );
+}
+
bool
EngineController::isPaused() const
{
return m_media.data()->state() == Phonon::PausedState;
}
bool
EngineController::isPlaying() const
{
return !isPaused() && !isStopped();
}
bool
EngineController::isStopped() const
{
return
m_media.data()->state() == Phonon::StoppedState ||
m_media.data()->state() == Phonon::LoadingState ||
m_media.data()->state() == Phonon::ErrorState;
}
void
EngineController::playPause() //SLOT
{
DEBUG_BLOCK
debug() << "PlayPause: EngineController state" << m_media.data()->state();
if( isPlaying() )
pause();
else
play();
}
void
EngineController::seekTo( int ms ) //SLOT
{
DEBUG_BLOCK
if( m_media.data()->isSeekable() )
{
debug() << "seek to: " << ms;
int seekTo;
if( m_boundedPlayback )
{
seekTo = m_boundedPlayback->startPosition() + ms;
if( seekTo < m_boundedPlayback->startPosition() )
seekTo = m_boundedPlayback->startPosition();
else if( seekTo > m_boundedPlayback->startPosition() + trackLength() )
seekTo = m_boundedPlayback->startPosition() + trackLength();
}
else
seekTo = ms;
m_media.data()->seek( static_cast<qint64>( seekTo ) );
emit trackPositionChanged( seekTo, true ); /* User seek */
}
else
debug() << "Stream is not seekable.";
}
void
EngineController::seekBy( int ms ) //SLOT
{
qint64 newPos = m_media.data()->currentTime() + ms;
seekTo( newPos <= 0 ? 0 : newPos );
}
int
EngineController::increaseVolume( int ticks ) //SLOT
{
return setVolume( volume() + ticks );
}
int
EngineController::decreaseVolume( int ticks ) //SLOT
{
return setVolume( volume() - ticks );
}
int
EngineController::setVolume( int percent ) //SLOT
{
percent = qBound<qreal>( 0, percent, 100 );
m_volume = percent;
const qreal volume = percent / 100.0;
if ( !m_ignoreVolumeChangeAction && m_audio.data()->volume() != volume )
{
m_ignoreVolumeChangeObserve = true;
m_audio.data()->setVolume( volume );
AmarokConfig::setMasterVolume( percent );
emit volumeChanged( percent );
}
m_ignoreVolumeChangeAction = false;
return percent;
}
int
EngineController::volume() const
{
return m_volume;
}
bool
EngineController::isMuted() const
{
return m_audio.data()->isMuted();
}
void
EngineController::setMuted( bool mute ) //SLOT
{
m_audio.data()->setMuted( mute ); // toggle mute
if( !isMuted() )
setVolume( m_volume );
AmarokConfig::setMuteState( mute );
emit muteStateChanged( mute );
}
void
EngineController::toggleMute() //SLOT
{
setMuted( !isMuted() );
}
Meta::TrackPtr
EngineController::currentTrack() const
{
return m_currentTrack;
}
qint64
EngineController::trackLength() const
{
//When starting a last.fm stream, Phonon still shows the old track's length--trust
//Meta::Track over Phonon
if( m_currentTrack && m_currentTrack->length() > 0 )
return m_currentTrack->length();
else
return m_media.data()->totalTime(); //may return -1
}
void
EngineController::setNextTrack( Meta::TrackPtr track )
{
DEBUG_BLOCK
if( !track )
return;
track->prepareToPlay();
QUrl url = track->playableUrl();
if( url.isEmpty() )
return;
QMutexLocker locker( &m_mutex );
if( isPlaying() )
{
m_media.data()->clearQueue();
// keep in sync with playUrl(), slotPlayableUrlFetched()
if( url.isLocalFile() )
m_media.data()->enqueue( url.toLocalFile() );
else if( url.scheme() != "audiocd" ) // we don't support gapless for CD, bug 305708
m_media.data()->enqueue( url );
m_nextTrack = track;
m_nextUrl = url;
}
else
play( track );
}
bool
EngineController::isStream()
{
Phonon::MediaSource::Type type = Phonon::MediaSource::Invalid;
if( m_media )
// type is determined purely from the MediaSource constructor used in
// setCurrentSource(). For streams we use the QUrl one, see playUrl()
type = m_media.data()->currentSource().type();
return type == Phonon::MediaSource::Url || type == Phonon::MediaSource::Stream;
}
bool
EngineController::isSeekable() const
{
if( m_media )
return m_media.data()->isSeekable();
return false;
}
int
EngineController::trackPosition() const
{
return trackPositionMs() / 1000;
}
qint64
EngineController::trackPositionMs() const
{
return m_media.data()->currentTime();
}
bool
EngineController::supportsFadeout() const
{
return m_fader;
}
bool EngineController::supportsGainAdjustments() const
{
return m_preamp;
}
bool EngineController::supportsAudioDataOutput() const
{
const Phonon::AudioDataOutput out;
return out.isValid();
}
//////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
//////////////////////////////////////////////////////////////////////////////////////////
void
EngineController::slotTick( qint64 position )
{
if( m_boundedPlayback )
{
qint64 newPosition = position;
emit trackPositionChanged(
static_cast<long>( position - m_boundedPlayback->startPosition() ),
false
);
// Calculate a better position. Sometimes the position doesn't update
// with a good resolution (for example, 1 sec for TrueAudio files in the
// Xine-1.1.18 backend). This tick function, in those cases, just gets
// called multiple times with the same position. We count how many
// times this has been called prior, and adjust for it.
if( position == m_lastTickPosition )
newPosition += ++m_lastTickCount * m_tickInterval;
else
m_lastTickCount = 0;
m_lastTickPosition = position;
//don't go beyond the stop point
if( newPosition >= m_boundedPlayback->endPosition() )
{
slotAboutToFinish();
}
}
else
{
m_lastTickPosition = position;
emit trackPositionChanged( static_cast<long>( position ), false );
}
}
void
EngineController::slotAboutToFinish()
{
DEBUG_BLOCK
if( m_fadeouter )
{
debug() << "slotAboutToFinish(): a fadeout is in progress, don't queue new track";
return;
}
if( m_multiPlayback )
{
DEBUG_LINE_INFO
m_mutex.lock();
m_playWhenFetched = false;
m_mutex.unlock();
m_multiPlayback->fetchNext();
debug() << "The queue has: " << m_media.data()->queue().size() << " tracks in it";
}
else if( m_multiSource )
{
debug() << "source finished, lets get the next one";
QUrl nextSource = m_multiSource->nextUrl();
if( !nextSource.isEmpty() )
{ //more sources
m_mutex.lock();
m_playWhenFetched = false;
m_mutex.unlock();
debug() << "playing next source: " << nextSource;
slotPlayableUrlFetched( nextSource );
}
else if( m_media.data()->queue().isEmpty() )
{
debug() << "no more sources, skip to next track";
m_multiSource.reset(); // don't cofuse slotFinished
The::playlistActions()->requestNextTrack();
}
}
else if( m_boundedPlayback )
{
debug() << "finished a track that consists of part of another track, go to next track even if this url is technically not done yet";
//stop this track, now, as the source track might go on and on, and
//there might not be any more tracks in the playlist...
stop( true );
The::playlistActions()->requestNextTrack();
}
else if( m_media.data()->queue().isEmpty() )
The::playlistActions()->requestNextTrack();
}
void
EngineController::slotFinished()
{
DEBUG_BLOCK
// paranoia checking, m_currentTrack shouldn't really be null
if( m_currentTrack )
{
debug() << "Track finished completely, updating statistics";
unsubscribeFrom( m_currentTrack ); // don't bother with trackMetadataChanged()
stampStreamTrackLength(); // update track length in stream for accurate scrobbling
emit trackFinishedPlaying( m_currentTrack, 1.0 );
subscribeTo( m_currentTrack );
}
if( !m_multiPlayback && !m_multiSource )
{
// again. at this point the track is finished so it's trackPositionMs is 0
if( !m_nextTrack && m_nextUrl.isEmpty() )
emit stopped( m_currentTrack ? m_currentTrack->length() : 0,
m_currentTrack ? m_currentTrack->length() : 0 );
unsubscribeFrom( m_currentTrack );
if( m_currentAlbum )
unsubscribeFrom( m_currentAlbum );
m_currentTrack = 0;
m_currentAlbum = 0;
if( !m_nextTrack && m_nextUrl.isEmpty() ) // we will the trackChanged signal later
emit trackChanged( Meta::TrackPtr() );
m_media.data()->setCurrentSource( Phonon::MediaSource() );
}
m_mutex.lock(); // in case setNextTrack is being handled right now.
// Non-local urls are not enqueued so we must play them explicitly.
if( m_nextTrack )
{
DEBUG_LINE_INFO
play( m_nextTrack );
}
else if( !m_nextUrl.isEmpty() )
{
DEBUG_LINE_INFO
playUrl( m_nextUrl, 0 );
}
else
{
DEBUG_LINE_INFO
// possibly we are waiting for a fetch
m_playWhenFetched = true;
}
m_mutex.unlock();
}
static const qreal log10over20 = 0.1151292546497022842; // ln(10) / 20
void
EngineController::slotNewTrackPlaying( const Phonon::MediaSource &source )
{
DEBUG_BLOCK
if( source.type() == Phonon::MediaSource::Empty )
{
debug() << "Empty MediaSource (engine stop)";
return;
}
if( m_currentTrack )
{
unsubscribeFrom( m_currentTrack );
if( m_currentAlbum )
unsubscribeFrom( m_currentAlbum );
}
// only update stats if we are called for something new, some phonon back-ends (at
// least phonon-gstreamer-4.6.1) call slotNewTrackPlaying twice with the same source
if( m_currentTrack && ( m_nextTrack || !m_nextUrl.isEmpty() ) )
{
debug() << "Previous track finished completely, updating statistics";
stampStreamTrackLength(); // update track length in stream for accurate scrobbling
emit trackFinishedPlaying( m_currentTrack, 1.0 );
if( m_multiSource )
// advance source of a multi-source track
m_multiSource->setSource( m_multiSource->current() + 1 );
}
m_nextUrl.clear();
if( m_nextTrack )
{
// already unsubscribed
m_currentTrack = m_nextTrack;
m_nextTrack.clear();
m_multiSource.reset( m_currentTrack->create<Capabilities::MultiSourceCapability>() );
if( m_multiSource )
{
debug() << "Got a MultiSource Track with" << m_multiSource->sources().count() << "sources";
- connect( m_multiSource.data(), SIGNAL(urlChanged(QUrl)),
- SLOT(slotPlayableUrlFetched(QUrl)) );
+ connect( m_multiSource.data(), &Capabilities::MultiSourceCapability::urlChanged,
+ this, &EngineController::slotPlayableUrlFetched );
}
}
if( m_currentTrack
&& AmarokConfig::replayGainMode() != AmarokConfig::EnumReplayGainMode::Off )
{
Meta::ReplayGainTag mode;
// gain is usually negative (but may be positive)
mode = ( AmarokConfig::replayGainMode() == AmarokConfig::EnumReplayGainMode::Track)
? Meta::ReplayGain_Track_Gain
: Meta::ReplayGain_Album_Gain;
qreal gain = m_currentTrack->replayGain( mode );
// peak is usually positive and smaller than gain (but may be negative)
mode = ( AmarokConfig::replayGainMode() == AmarokConfig::EnumReplayGainMode::Track)
? Meta::ReplayGain_Track_Peak
: Meta::ReplayGain_Album_Peak;
qreal peak = m_currentTrack->replayGain( mode );
if( gain + peak > 0.0 )
{
debug() << "Gain of" << gain << "would clip at absolute peak of" << gain + peak;
gain -= gain + peak;
}
if( m_preamp )
{
debug() << "Using gain of" << gain << "with relative peak of" << peak;
// we calculate the volume change ourselves, because m_preamp.data()->setVolumeDecibel is
// a little confused about minus signs
m_preamp.data()->setVolume( qExp( gain * log10over20 ) );
}
else
warning() << "Would use gain of" << gain << ", but current Phonon backend"
<< "doesn't seem to support pre-amplifier (VolumeFaderEffect)";
}
else if( m_preamp )
{
m_preamp.data()->setVolume( 1.0 );
}
bool useTrackWithinStreamDetection = false;
if( m_currentTrack )
{
subscribeTo( m_currentTrack );
Meta::AlbumPtr m_currentAlbum = m_currentTrack->album();
if( m_currentAlbum )
subscribeTo( m_currentAlbum );
/** We only use detect-tracks-in-stream for tracks that have stream type
* (exactly, we purposely exclude stream/lastfm) *and* that don't have length
* already filled in. Bug 311852 */
if( m_currentTrack->type() == "stream" && m_currentTrack->length() == 0 )
useTrackWithinStreamDetection = true;
}
m_lastStreamStampPosition = useTrackWithinStreamDetection ? 0 : -1;
emit trackChanged( m_currentTrack );
emit trackPlaying( m_currentTrack );
}
void
EngineController::slotStateChanged( Phonon::State newState, Phonon::State oldState ) //SLOT
{
debug() << "slotStateChanged from" << oldState << "to" << newState;
static const int maxErrors = 5;
static int errorCount = 0;
// Sanity checks:
if( newState == oldState )
return;
if( newState == Phonon::ErrorState ) // If media is borked, skip to next track
{
emit trackError( m_currentTrack );
warning() << "Phonon failed to play this URL. Error: " << m_media.data()->errorString();
warning() << "Forcing phonon engine reinitialization.";
/* In case of error Phonon MediaObject automatically switches to KioMediaSource,
which cause problems: runs StopAfterCurrentTrack mode, force PlayPause button to
reply the track (can't be paused). So we should reinitiate Phonon after each Error.
*/
initializePhonon();
errorCount++;
if ( errorCount >= maxErrors )
{
// reset error count
errorCount = 0;
Amarok::Components::logger()->longMessage(
i18n( "Too many errors encountered in playlist. Playback stopped." ),
Amarok::Logger::Warning
);
error() << "Stopping playlist.";
}
else
// and start the next song, even if the current failed to start playing
The::playlistActions()->requestUserNextTrack();
}
else if( newState == Phonon::PlayingState )
{
errorCount = 0;
emit playbackStateChanged();
}
else if( newState == Phonon::StoppedState ||
newState == Phonon::PausedState )
{
emit playbackStateChanged();
}
}
void
EngineController::slotPlayableUrlFetched( const QUrl &url )
{
DEBUG_BLOCK
debug() << "Fetched url: " << url;
if( url.isEmpty() )
{
DEBUG_LINE_INFO
The::playlistActions()->requestNextTrack();
return;
}
if( !m_playWhenFetched )
{
DEBUG_LINE_INFO
m_mutex.lock();
m_media.data()->clearQueue();
// keep synced with setNextTrack(), playUrl()
if( url.isLocalFile() )
m_media.data()->enqueue( url.toLocalFile() );
else
m_media.data()->enqueue( url );
m_nextTrack.clear();
m_nextUrl = url;
debug() << "The next url we're playing is: " << m_nextUrl;
// reset this flag each time
m_playWhenFetched = true;
m_mutex.unlock();
}
else
{
DEBUG_LINE_INFO
m_mutex.lock();
playUrl( url, 0 );
m_mutex.unlock();
}
}
void
EngineController::slotTrackLengthChanged( qint64 milliseconds )
{
debug() << "slotTrackLengthChanged(" << milliseconds << ")";
emit trackLengthChanged( ( !m_multiPlayback || !m_boundedPlayback )
? trackLength() : milliseconds );
}
void
EngineController::slotMetaDataChanged()
{
QVariantMap meta;
meta.insert( Meta::Field::URL, m_media.data()->currentSource().url() );
static const QList<FieldPair> fieldPairs = QList<FieldPair>()
<< FieldPair( Phonon::ArtistMetaData, Meta::Field::ARTIST )
<< FieldPair( Phonon::AlbumMetaData, Meta::Field::ALBUM )
<< FieldPair( Phonon::TitleMetaData, Meta::Field::TITLE )
<< FieldPair( Phonon::GenreMetaData, Meta::Field::GENRE )
<< FieldPair( Phonon::TracknumberMetaData, Meta::Field::TRACKNUMBER )
<< FieldPair( Phonon::DescriptionMetaData, Meta::Field::COMMENT );
foreach( const FieldPair &pair, fieldPairs )
{
QStringList values = m_media.data()->metaData( pair.first );
if( !values.isEmpty() )
meta.insert( pair.second, values.first() );
}
// note: don't rely on m_currentTrack here. At least some Phonon backends first emit
// totalTimeChanged(), then metaDataChanged() and only then currentSourceChanged()
// which currently sets correct m_currentTrack.
if( isInRecentMetaDataHistory( meta ) )
{
// slotMetaDataChanged() triggered by phonon, but we've already seen
// exactly the same metadata recently. Ignoring for now.
return;
}
// following is an implementation of song end (and length) within a stream detection.
// This normally fires minutes after the track has started playing so m_currentTrack
// should be accurate
if( m_currentTrack && m_lastStreamStampPosition >= 0 )
{
stampStreamTrackLength();
emit trackFinishedPlaying( m_currentTrack, 1.0 );
// update track length to 0 because length emitted by stampStreamTrackLength()
// is for the previous song
meta.insert( Meta::Field::LENGTH, 0 );
}
debug() << "slotMetaDataChanged(): new meta-data:" << meta;
emit currentMetadataChanged( meta );
}
void
EngineController::slotSeekableChanged( bool seekable )
{
emit seekableChanged( seekable );
}
void
EngineController::slotTitleChanged( int titleNumber )
{
DEBUG_BLOCK
if ( titleNumber != m_currentAudioCdTrack )
{
The::playlistActions()->requestNextTrack();
slotFinished();
}
}
void EngineController::slotVolumeChanged( qreal newVolume )
{
int percent = qBound<qreal>( 0, qRound(newVolume * 100), 100 );
if ( !m_ignoreVolumeChangeObserve && m_volume != percent )
{
m_ignoreVolumeChangeAction = true;
m_volume = percent;
AmarokConfig::setMasterVolume( percent );
emit volumeChanged( percent );
}
else
m_volume = percent;
m_ignoreVolumeChangeObserve = false;
}
void EngineController::slotMutedChanged( bool mute )
{
AmarokConfig::setMuteState( mute );
emit muteStateChanged( mute );
}
void
EngineController::slotTrackFinishedPlaying( Meta::TrackPtr track, double playedFraction )
{
Q_ASSERT( track );
debug() << "slotTrackFinishedPlaying("
<< ( track->artist() ? track->artist()->name() : QString( "[no artist]" ) )
<< "-" << ( track->album() ? track->album()->name() : QString( "[no album]" ) )
<< "-" << track->name()
<< "," << playedFraction << ")";
track->finishedPlaying( playedFraction );
}
void
EngineController::metadataChanged( Meta::TrackPtr track )
{
Meta::AlbumPtr album = m_currentTrack->album();
if( m_currentAlbum != album ) {
if( m_currentAlbum )
unsubscribeFrom( m_currentAlbum );
m_currentAlbum = album;
if( m_currentAlbum )
subscribeTo( m_currentAlbum );
}
emit trackMetadataChanged( track );
}
void
EngineController::metadataChanged( Meta::AlbumPtr album )
{
emit albumMetadataChanged( album );
}
QString EngineController::prettyNowPlaying( bool progress ) const
{
Meta::TrackPtr track = currentTrack();
if( track )
{
QString title = Qt::escape( track->name() );
QString prettyTitle = Qt::escape( track->prettyName() );
QString artist = track->artist() ? Qt::escape( track->artist()->name() ) : QString();
QString album = track->album() ? Qt::escape( track->album()->name() ) : QString();
// ugly because of translation requirements
if( !title.isEmpty() && !artist.isEmpty() && !album.isEmpty() )
title = i18nc( "track by artist on album", "<b>%1</b> by <b>%2</b> on <b>%3</b>", title, artist, album );
else if( !title.isEmpty() && !artist.isEmpty() )
title = i18nc( "track by artist", "<b>%1</b> by <b>%2</b>", title, artist );
else if( !album.isEmpty() )
// we try for pretty title as it may come out better
title = i18nc( "track on album", "<b>%1</b> on <b>%2</b>", prettyTitle, album );
else
title = "<b>" + prettyTitle + "</b>";
if( title.isEmpty() )
title = i18n( "Unknown track" );
QScopedPointer<Capabilities::SourceInfoCapability> sic( track->create<Capabilities::SourceInfoCapability>() );
if( sic )
{
QString source = sic->sourceName();
if( !source.isEmpty() )
title += ' ' + i18nc( "track from source", "from <b>%1</b>", source );
}
if( track->length() > 0 )
{
QString length = Qt::escape( Meta::msToPrettyTime( track->length() ) );
title += " (";
if( progress )
title+= Qt::escape( Meta::msToPrettyTime( m_lastTickPosition ) ) + '/';
title += length + ')';
}
return title;
}
else
return i18n( "No track playing" );
}
bool
EngineController::isInRecentMetaDataHistory( const QVariantMap &meta )
{
// search for Metadata in history
for( int i = 0; i < m_metaDataHistory.size(); i++)
{
if( m_metaDataHistory.at( i ) == meta ) // we already had that one -> spam!
{
m_metaDataHistory.move( i, 0 ); // move spam to the beginning of the list
return true;
}
}
if( m_metaDataHistory.size() == 12 )
m_metaDataHistory.removeLast();
m_metaDataHistory.insert( 0, meta );
return false;
}
void
EngineController::stampStreamTrackLength()
{
if( m_lastStreamStampPosition < 0 )
return;
qint64 currentPosition = trackPositionMs();
debug() << "stampStreamTrackLength(): m_lastStreamStampPosition:" << m_lastStreamStampPosition
<< "currentPosition:" << currentPosition;
if( currentPosition == m_lastStreamStampPosition )
return;
qint64 length = qMax( currentPosition - m_lastStreamStampPosition, qint64( 0 ) );
updateStreamLength( length );
m_lastStreamStampPosition = currentPosition;
}
void
EngineController::updateStreamLength( qint64 length )
{
if( !m_currentTrack )
{
warning() << __PRETTY_FUNCTION__ << "called with cull m_currentTrack";
return;
}
// Last.fm scrobbling needs to know track length before it can scrobble:
QVariantMap lengthMetaData;
// we cannot use m_media->currentSource()->url() here because it is already empty, bug 309976
lengthMetaData.insert( Meta::Field::URL, QUrl( m_currentTrack->playableUrl() ) );
lengthMetaData.insert( Meta::Field::LENGTH, length );
debug() << "updateStreamLength(): emitting currentMetadataChanged(" << lengthMetaData << ")";
emit currentMetadataChanged( lengthMetaData );
}
diff --git a/src/EngineController.h b/src/EngineController.h
index 1848d6e848..3d7259701c 100644
--- a/src/EngineController.h
+++ b/src/EngineController.h
@@ -1,577 +1,585 @@
/****************************************************************************************
* Copyright (c) 2004 Frederik Holljen <fh@ez.no> *
* Copyright (c) 2004,2005 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2004-2013 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_ENGINECONTROLLER_H
#define AMAROK_ENGINECONTROLLER_H
#include "amarok_export.h"
#include "core/capabilities/BoundedPlaybackCapability.h"
#include "core/meta/Observer.h"
#include "playback/EqualizerController.h"
#include "core/meta/Meta.h"
#include <QUrl>
#include <QMutex>
#include <QObject>
#include <QSemaphore>
#include <QStringList>
#include <QWeakPointer>
#include <Phonon/Path>
#include <Phonon/MediaController>
#include <Phonon/MediaObject>
#include <Phonon/Effect>
#include <Phonon/EffectParameter>
#include <phonon/audiodataoutput.h>
class Fadeouter;
namespace Capabilities { class MultiPlayableCapability; class MultiSourceCapability; }
namespace Phonon { class AudioOutput; class MediaSource; class VolumeFaderEffect; }
class QTimer;
/**
* A thin wrapper around Phonon that implements Amarok-specific functionality like
* replay gain, fade-out on stop and various track capabilities that affect
* playback.
*/
class AMAROK_EXPORT EngineController : public QObject, public Meta::Observer
{
Q_OBJECT
public:
static const uint DATAOUTPUT_DATA_SIZE = 512;
/**
* Construct EngineController. Must be called from the main thread.
*/
EngineController();
~EngineController();
/**
* Returns the global EngineController instance
*/
static EngineController* instance();
/**
* Loads and plays the track that was playing when endSession() was last
* called (ie: when Amarok was quit)
*/
void restoreSession();
/**
* Saves the currently playing track and the playing/paused/stopped state
*/
void endSession();
/**
* Returns a list of backend supported mime types. This method is thread-safe.
*/
QStringList supportedMimeTypes();
/** @return track position (elapsed time) in seconds */
int trackPosition() const;
/** @return track position (elapsed time) in milliseconds */
qint64 trackPositionMs() const;
/**
* Returns the current track that is loaded into the engine.
* @return a Meta::TrackPtr which is either the track, or empty if phonon
* has a state of Phonon::ErrorState or Phonon::StoppedState
*/
Meta::TrackPtr currentTrack() const;
/**
* @return the length of the current track in milliseconds
*/
qint64 trackLength() const;
/**
* Used to enqueue a track before it starts to play, for gapless playback.
*
* This will clear any tracks currently in the queue. If no track is playing,
* @p track will be played immediately.
*/
void setNextTrack( Meta::TrackPtr track );
/**
* Gets the volume
* @return the volume as a percentage
*/
int volume() const;
/**
* @return @c true if sound output is disabled, @false otherwise
*/
bool isMuted() const;
/**
* @return @c true if Amarok is paused, @c false if it is stopped or playing
*/
bool isPaused() const;
/**
* @return @c true if Amarok is playing, @c false if it is stopped or pause
* Note: A fading out track is considered already stopped.
*/
bool isPlaying() const;
/**
* @return @c true if Amarok is stopped, @c false if it is playing or pause
* Note: A fading out track is considered already stopped.
*/
bool isStopped() const;
/**
* Streams sometimes have to be treated specially.
* For example, it is typically not possible to rewind a stream (at least,
* not without returning to the start of it).
* However for rewinding we have isSeekable().
* Also for streams usually the meta data received by currentTrack() is only
* for the whole stream while the meta data received by currentMetaDataChanged
* will be more current (or contain advertisment)
*
* @return @c true if the current track is a stream, @c false otherwise
*/
bool isStream();
/**
* @return @c true if the current track is seekable, @c false otherwise
*/
bool isSeekable() const;
/**
* Returns the associated EqualizerController object.
*/
EqualizerController *equalizerController() const;
/**
* @return QString with a pretty name for the current track
* @param whether to include the playing progress (default false)
*/
QString prettyNowPlaying( bool progress = false ) const;
public Q_SLOTS:
/**
* Plays the current track, if there is one
* This happens asynchronously.
*/
void play();
/**
* Plays the specified track
* This happens asynchronously.
*/
void play( Meta::TrackPtr track, uint offset = 0, bool startPaused = false );
/**
* Replays the current track
*
* This is a convenience method, calls seek( 0 ) actually
*/
void replay();
/**
* Pauses the current track
* This happens asynchronously.
*/
void pause();
/**
* Stops playing
* This happens asynchronously.
*
* @param forceInstant skip any fade-out effects
* @param playingWillContinue don't emit stopped() or trackChanged( 0 ) signals
*/
void stop( bool forceInstant = false, bool playingWillContinue = false );
+ /**
+ * Stops playing
+ * This happens asynchronously.
+ * Doesn't skip any fade-out effects
+ * Emits stopped() and trackChanged( 0 ) signals
+ */
+ void regularStop();
+
/**
* Pauses if Amarok is currently playing, plays if Amarok is stopped or paused
* This happens asynchronously.
*/
void playPause(); //pauses if playing, plays if paused or stopped
/**
* Seeks to a position in the track
*
* If the media is not seekable, or the state is something other than
* PlayingState, BufferingState or PausedState, has no effect.
*
* Deals correctly with tracks that have the BoundedPlayback capability.
*
* @param ms the position in milliseconds (counting from the start of the track)
*/
void seekTo( int ms );
/**
* Seeks forward or backward in the track
*
* If the media is not seekable, or the state is something other than
* PlayingState, BufferingState or PausedState, has no effect.
*
* Deals correctly with tracks that have the BoundedPlayback capability.
*
* A negative value seeks backwards, a positive value seeks forwards.
*
* If the value of @p ms would move the position to before the start of the track,
* the position is moved to the start of the track.
*
* @param ms the offset from the current position in milliseconds
*/
void seekBy( int ms );
/**
* Increases the volume
*
* @param ticks the amount to increase the volume by, given as a percentage of the
* maximum possible volume (ie: the same units as for setVolume()).
*/
int increaseVolume( int ticks = 100/25 );
/**
* Decreases the volume
*
* @param ticks the amount to decrease the volume by, given as a percentage of the
* maximum possible volume (ie: the same units as for setVolume()).
*/
int decreaseVolume( int ticks = 100/25 );
/**
* Sets the volume
*
* @param percent the new volume as a percentage of the maximum possible volume.
*/
// this amplifier does not go up to 11
int setVolume( int percent );
/**
* Mutes or unmutes playback
*
* @param mute if @c true, audio output will be disabled; if @c false, audio output
* will be enabled.
*/
void setMuted( bool mute );
/**
* Toggles mute
*
* Works like setMuted( !isMuted() );
*/
void toggleMute();
/**
* Return true if current Phonon back-end supports fade-out.
*/
bool supportsFadeout() const;
/**
* Return true if current Phonon back-end supports our implementation of
* Replay Gain adjustment.
*/
bool supportsGainAdjustments() const;
/**
* Return true if the current Phonon backend supports visualizations.
*/
bool supportsAudioDataOutput() const;
Q_SIGNALS:
/**
* Emitted when the playback stops while playing a track.
* This signal is not emitted when the track pauses or the playback stopps because
* Amarok was closed and "resume at start" is configured.
* It is also not emitted if the playback continues with another track. In such
* a case you would just get another trackPlaying signal.
* Both parameters are in milli seconds.
*/
void stopped( qint64 /*ms*/ finalPosition, qint64 /*ms*/ trackLength );
/**
* Called when the playback is paused.
* When the playback is resumed a trackPlaying signal will be emitted.
* When the playback is stopped then a stopped signal will be emitted.
*/
void paused();
/** While trying to play the track an error occurred.
* This usually means that the engine will try to play the next track in
* the playlist until it gives up.
* So you will get a trackPlaying or stopped signal next.
*/
void trackError( Meta::TrackPtr track );
/**
* Called when a new track starts playing or an old track starts playing now.
*
* It also might be called several time in sequence
* with the same track in cases when e.g. you have
* a multi source track.
*
* Unlike trackChanged(), this is not called when playback stops.
*/
void trackPlaying( Meta::TrackPtr track );
/**
* Called when the current track changes
*
* Note that this is possibly only called once in case of a stream or on
* the other hand multiple times with the same track in cases when e.g. you have
* a multi source track.
*
* Unlike trackPlaying(), this is called when playback stops with Meta::TrackPtr( 0 ) for @p track.
*
* @param track The new track; may be null
*/
void trackChanged( Meta::TrackPtr track );
/**
* Emitted when the metadata of the current track changes.
*
* You might want to connect also to trackChanged() or trackPlaying() to get more
* changes because this signal is only emitted when the track metadata changes
* while it's playing, not when new track starts playing. This method now works
* correctly also for streams and is preferred to currentMetaDataChanged() because
* it providers somehow more filtered values.
*/
void trackMetadataChanged( Meta::TrackPtr track );
/** Emitted when the metadata of the current album changes.
*/
void albumMetadataChanged( Meta::AlbumPtr album );
/**
* Emitted then the information for the current changed.
* This signal contains data from Phonon about the meta data of the track or stream.
* This signal is expecially emitted when a stream changes it's metadata.
* This can happen e.g. in a ogg stream where the currentTrack data will probably
* not be updated.
*
* MetaStream::Track::Private in Stream_p.h will connect to this signal to update it's internal data
* and then itself trigger a trackMetadataChanged.
* @param metadata Contains the url, artist, album title, title, genre, tracknumber and length
*/
void currentMetadataChanged( QVariantMap metadata );
/**
* Called when the seekable value was changed
*/
void seekableChanged( bool seekable );
/**
* Called when the volume was changed
*/
void volumeChanged( int percent );
/**
* Called when audio output was enabled or disabled
*
* NB: if setMute() was called on the engine controller, but it didn't change the
* mute state, this will not be called
*/
void muteStateChanged( bool mute );
/** Called when the track position changes.
If the track just progresses you will get a notification every couple of milliseconds.
@parem position The current position in milliseconds
@param userSeek True if the position change was caused by the user
*/
void trackPositionChanged( qint64 position, bool userSeek );
/**
* Emitted when a track finished playing. You generally get this signal once per
* played track, but in case of a stream this may be emitted more than once when
* stream meta-data changes (which usually indicates that the next track started
* playing) - meta-data in the track are updated in this case. When you receive
* this signal, track score, play count etc. will be already updated.
*
* @param track track that has just finished playing
* @param playedFraction played/total length fraction, between 0 and 1
*/
void trackFinishedPlaying( Meta::TrackPtr track, double playedFraction );
/**
Called when the track length changes, typically because the track has changed but
also when phonon manages to determine the full track length.
*/
void trackLengthChanged( qint64 milliseconds );
/**
* Called when Amarok is closed and we disconnect from Phonon.
* @param resumePlayback True if amarok will continue playback after a restart.
*/
void sessionEnded( bool resumePlayback );
/**
* Called when playback state changes to PlayingState, StoppedState or PausedState.
*/
void playbackStateChanged();
/**
* Is emitted when new audio Data is ready
* @param audioData The audio data that is available
*/
void audioDataReady( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &audioData );
/**
* A trick to call slotFillInSupportedMimeTypes() in a main thread, not to be used
* anywhere else than in supportedMimeTypes().
*/
void fillInSupportedMimeTypes();
private Q_SLOTS:
/**
* Sets up the Phonon system
*/
void initializePhonon();
/** This slot is connected to the phonon finished signal.
It is emitted when the queue is empty and the current media come to an end.
*/
void slotFinished();
void slotAboutToFinish();
void slotNewTrackPlaying( const Phonon::MediaSource &source);
void slotStateChanged( Phonon::State newState, Phonon::State oldState);
void slotPlayableUrlFetched( const QUrl &url );
void slotTick( qint64 );
void slotTrackLengthChanged( qint64 );
void slotMetaDataChanged();
void slotSeekableChanged( bool );
void slotPause();
/**
* For volume/mute changes from the phonon side
*/
void slotVolumeChanged( qreal );
void slotMutedChanged( bool );
/**
* Notify the engine that a new title has been reached when playing a cd. This
* is needed as a cd counts as basically one lone track, and we want to be able
* to play something else once one track has finished
*/
void slotTitleChanged( int titleNumber );
/**
* Fill in m_supportedMimeTypes list and release m_supportedMimeTypesSemaphore. This
* method must be called in the main thread so that there is no chance
* Phonon::BackendCapabilities::availableMimeTypes() is called in a non-gui thread
* for the first time.
*/
void slotFillInSupportedMimeTypes();
/**
* Calls track->finishedPlaying(), connected to trackFinishedPlaying() signal to
* reduce code duplication.
*/
void slotTrackFinishedPlaying( Meta::TrackPtr track, double playedFraction );
protected:
// reimplemented from Meta::Observer
using Observer::metadataChanged;
virtual void metadataChanged( Meta::TrackPtr track );
virtual void metadataChanged( Meta::AlbumPtr album );
private:
/**
* Plays the media at a specified URL
*
* @param url the URL of the media
* @param offset the position in the media to start at in milliseconds
* @param startPaused if true, go to paused state. if false, go to playing state (default)
*/
void playUrl( const QUrl &url, uint offset, bool startPaused = false );
/**
* Try to detect MetaData spam in Streams etc.
*
* Some streams are doing advertisment in the metadata. We try to filter that
* out. Additionally, some Phonon back-ends emit more than one
* metadataChanged() signals per on track, so filter it all altogether.
*/
bool isInRecentMetaDataHistory( const QVariantMap &meta );
/**
* If m_lastStreamStampPosition is non-negative, update it to current position
* and update track length in current stream.
*/
void stampStreamTrackLength();
/**
* emit metadataChanged() with info so that MetaStream::Track that is
* currently listening updates its length.
*
* @param length new track length in milliseconds
*/
void updateStreamLength( qint64 length );
Q_DISABLE_COPY( EngineController )
EqualizerController *m_equalizerController;
QWeakPointer<Phonon::MediaObject> m_media;
QWeakPointer<Phonon::VolumeFaderEffect> m_preamp;
QWeakPointer<Phonon::AudioOutput> m_audio;
QWeakPointer<Phonon::AudioDataOutput> m_audioDataOutput;
QWeakPointer<Phonon::MediaController> m_controller;
Phonon::Path m_path;
Phonon::Path m_dataPath;
QWeakPointer<Fadeouter> m_fadeouter;
QWeakPointer<Phonon::VolumeFaderEffect> m_fader;
Meta::TrackPtr m_currentTrack;
Meta::AlbumPtr m_currentAlbum;
Meta::TrackPtr m_nextTrack;
QUrl m_nextUrl;
Capabilities::BoundedPlaybackCapability* m_boundedPlayback;
Capabilities::MultiPlayableCapability* m_multiPlayback;
QScopedPointer<Capabilities::MultiSourceCapability> m_multiSource;
bool m_playWhenFetched;
int m_volume;
int m_currentAudioCdTrack;
QTimer *m_pauseTimer;
QList<QVariantMap> m_metaDataHistory; // against metadata spam
// last position (in ms) when the song changed (within the current stream) or -1 for non-stream
qint64 m_lastStreamStampPosition;
/**
* Some flags to prevent feedback loops in volume updates
*/
bool m_ignoreVolumeChangeAction;
bool m_ignoreVolumeChangeObserve;
// Used to get a more accurate estimate of the position for slotTick
int m_tickInterval;
qint64 m_lastTickPosition;
qint64 m_lastTickCount;
QMutex m_mutex;
// FIXME: this variable should be updated when
// Phonon::BackendCapabilities::notifier()'s capabilitiesChanged signal is emitted
QStringList m_supportedMimeTypes;
QSemaphore m_supportedMimeTypesSemaphore;
};
namespace The {
AMAROK_EXPORT EngineController* engineController();
}
#endif
diff --git a/src/KNotificationBackend.cpp b/src/KNotificationBackend.cpp
index d10898e1ce..c646442097 100644
--- a/src/KNotificationBackend.cpp
+++ b/src/KNotificationBackend.cpp
@@ -1,138 +1,138 @@
/****************************************************************************************
* Copyright (c) 2009-2011 Kevin Funk <krf@electrostorm.net> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "KNotificationBackend.h"
#include "EngineController.h"
#include "SvgHandler.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include <KIconLoader>
#include <KLocalizedString>
#include <KNotification>
#include <KWindowSystem>
using namespace Amarok;
KNotificationBackend *
KNotificationBackend::s_instance = 0;
KNotificationBackend *
KNotificationBackend::instance()
{
if( !s_instance )
s_instance = new KNotificationBackend();
return s_instance;
}
void
KNotificationBackend::destroy()
{
delete s_instance;
s_instance = 0;
}
KNotificationBackend::KNotificationBackend()
: m_enabled( false )
{
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)), SLOT(showCurrentTrack()) );
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), SLOT(showCurrentTrack()) );
- connect( engine, SIGNAL(albumMetadataChanged(Meta::AlbumPtr)), SLOT(showCurrentTrack()) );
+ connect( engine, &EngineController::trackPlaying, this, &KNotificationBackend::showCurrentTrack );
+ connect( engine, &EngineController::trackMetadataChanged, this, &KNotificationBackend::showCurrentTrack );
+ connect( engine, &EngineController::albumMetadataChanged, this, &KNotificationBackend::showCurrentTrack );
if( engine->isPlaying() )
showCurrentTrack();
}
KNotificationBackend::~KNotificationBackend()
{
if( m_notify )
m_notify.data()->close();
}
void
KNotificationBackend::setEnabled( bool enable )
{
m_enabled = enable;
}
bool
KNotificationBackend::isEnabled() const
{
return m_enabled;
}
bool
KNotificationBackend::isFullscreenWindowActive() const
{
// Get information of the active window.
KWindowInfo activeWindowInfo = KWindowSystem::windowInfo( KWindowSystem::activeWindow(), NET::WMState );
// Check if it is running in fullscreen mode.
return activeWindowInfo.hasState( NET::FullScreen );
}
void
KNotificationBackend::show( const QString &title, const QString &body, const QPixmap &pixmap )
{
QPixmap icon;
if( pixmap.isNull() )
{
KIconLoader loader;
icon = loader.loadIcon( QString("amarok"), KIconLoader::Desktop );
}
else
icon = pixmap;
KNotification *notify = new KNotification( "message" );
notify->setTitle( title );
notify->setText( body );
notify->setPixmap( icon );
notify->sendEvent();
}
void
KNotificationBackend::showCurrentTrack( bool force )
{
if( !m_enabled && !force )
return;
EngineController *engine = The::engineController();
Meta::TrackPtr track = engine->currentTrack();
if( !track )
{
warning() << __PRETTY_FUNCTION__ << "null track!";
return;
}
const QString title = i18n( "Now playing" );
const QString text = engine->prettyNowPlaying();
Meta::AlbumPtr album = track->album();
const QPixmap pixmap = album ? The::svgHandler()->imageWithBorder( album, 80 ) : QPixmap();
KNotification *notify = m_notify.data();
if( !notify )
notify = new KNotification( "trackChange" );
notify->setTitle( title );
notify->setText( text );
notify->setPixmap( pixmap );
if( m_notify ) // existing notification already shown
notify->update();
notify->sendEvent(); // (re)start timeout in both cases
m_notify = notify;
}
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index c8ca2febab..061db3f036 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,1404 +1,1404 @@
/****************************************************************************************
* Copyright (c) 2002-2013 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2002 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2002 Gabor Lehel <illissius@gmail.com> *
* Copyright (c) 2002 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MainWindow"
#include "MainWindow.h"
#include "ActionClasses.h"
#include "EngineController.h" //for actions in ctor
#include "KNotificationBackend.h"
#include "PaletteHandler.h"
#include "PluginManager.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "aboutdialog/ExtendedAboutDialog.h"
#include "aboutdialog/OcsData.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "amarokurls/BookmarkManager.h"
#include "browsers/collectionbrowser/CollectionWidget.h"
#include "browsers/filebrowser/FileBrowser.h"
#include "browsers/playlistbrowser/PlaylistBrowser.h"
#include "browsers/playlistbrowser/PodcastCategory.h"
#include "browsers/servicebrowser/ServiceBrowser.h"
#include "context/ContextDock.h"
#include "core/meta/Statistics.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/CoverManager.h" // for actions
#include "dialogs/DiagnosticDialog.h"
#include "dialogs/EqualizerDialog.h"
#include "moodbar/MoodbarManager.h"
#include "network/NetworkAccessManagerProxy.h"
#ifdef DEBUG_BUILD_TYPE
#include "network/NetworkAccessViewer.h"
#endif // DEBUG_BUILD_TYPE
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistController.h"
#include "playlist/PlaylistModelStack.h"
#include "playlist/PlaylistDock.h"
#include "playlist/ProgressiveSearchWidget.h"
#include "playlist/layouts/LayoutConfigAction.h"
#include "playlistmanager/PlaylistManager.h"
#include "playlistmanager/file/PlaylistFileProvider.h"
#include "services/scriptable/ScriptableService.h"
#include "statsyncing/Controller.h"
#include "toolbar/MainToolbar.h"
#include "toolbar/SlimToolbar.h"
#include "widgets/Osd.h"
#include <QAction> //m_actionCollection
#include <KActionCollection>
#include <QApplication> //qApp
#include <KFileDialog> //openPlaylist()
#include <KInputDialog> //slotAddStream()
#include <KMessageBox>
#include <KLocale>
#include <QMenu>
#include <KMenuBar>
#include <KBugReport>
#include <KStandardAction>
#include <KStandardDirs>
#include <KWindowSystem>
#include <KFileWidget>
#include <KGlobalAccel>
#include <plasma/plasma.h>
#include <QCheckBox>
#include <QClipboard>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDockWidget>
#include <QList>
#include <QStyle>
#include <QVBoxLayout>
#include <iostream>
#ifdef Q_WS_X11
#include <fixx11h.h>
#include <KConfigGroup>
#endif
#ifdef Q_WS_MAC
#include "mac/GrowlInterface.h"
#ifdef HAVE_NOTIFICATION_CENTER
#include "mac/MacSystemNotify.h"
#endif
#endif
#define AMAROK_CAPTION I18N_NOOP( "Amarok" )
extern OcsData ocsData;
QWeakPointer<MainWindow> MainWindow::s_instance;
namespace The {
MainWindow* mainWindow() { return MainWindow::s_instance.data(); }
}
MainWindow::MainWindow()
: KMainWindow( 0 )
, m_showMenuBar( 0 )
, m_lastBrowser( 0 )
, m_waitingForCd( false )
{
DEBUG_BLOCK
setObjectName( "MainWindow" );
s_instance = this;
#ifdef Q_WS_MAC
(void)new GrowlInterface( qApp->applicationName() );
#ifdef HAVE_NOTIFICATION_CENTER
(void)new OSXNotify( qApp->applicationName() );
#endif
#endif
PERF_LOG( "Instantiate Collection Manager" )
CollectionManager::instance();
PERF_LOG( "Started Collection Manager instance" )
/* The PluginManager needs to be loaded before the playlist model
* (which gets started by "statusBar::connectPlaylist" below so that it can handle any
* tracks in the saved playlist that are associated with services. Eg, if
* the playlist has a Magnatune track in it when Amarok is closed, then the
* Magnatune service needs to be initialized before the playlist is loaded
* here. */
PERF_LOG( "Instantiate Plugin Manager" )
The::pluginManager();
PERF_LOG( "Started Plugin Manager instance" )
createActions();
PERF_LOG( "Created actions" )
The::paletteHandler()->setPalette( palette() );
setPlainCaption( i18n( AMAROK_CAPTION ) );
init(); // We could as well move the code from init() here, but meh.. getting a tad long
//restore active category ( as well as filters and levels and whatnot.. )
const QString path = Amarok::config().readEntry( "Browser Path", QString() );
if( !path.isEmpty() )
m_browserDock.data()->list()->navigate( path );
setAutoSaveSettings();
m_showMenuBar->setChecked(!menuBar()->isHidden()); // workaround for bug #171080
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(slotStopped()) );
- connect( engine, SIGNAL(paused()),
- this, SLOT(slotPaused()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(slotNewTrackPlaying()) );
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- this, SLOT(slotMetadataChanged(Meta::TrackPtr)) );
+ connect( engine, &EngineController::stopped, this, &MainWindow::slotStopped );
+ connect( engine,&EngineController::paused, this, &MainWindow::slotPaused );
+ connect( engine, &EngineController::trackPlaying, this, &MainWindow::slotNewTrackPlaying );
+ connect( engine, &EngineController::trackMetadataChanged, this, &MainWindow::slotMetadataChanged );
KGlobal::locale()->insertCatalog( "libplasma" );
}
MainWindow::~MainWindow()
{
DEBUG_BLOCK
//save currently active category
Amarok::config().writeEntry( "Browser Path", m_browserDock.data()->list()->path() );
#ifdef DEBUG_BUILD_TYPE
delete m_networkViewer.data();
#endif // DEBUG_BUILD_TYPE
delete The::svgHandler();
delete The::paletteHandler();
}
///////// public interface
/**
* This function will initialize the main window.
*/
void
MainWindow::init()
{
layout()->setContentsMargins( 0, 0, 0, 0 );
layout()->setSpacing( 0 );
//create main toolbar
m_mainToolbar = new MainToolbar( 0 );
m_mainToolbar.data()->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea );
m_mainToolbar.data()->setMovable ( true );
addToolBar( Qt::TopToolBarArea, m_mainToolbar.data() );
//create slim toolbar
m_slimToolbar = new SlimToolbar( 0 );
m_slimToolbar.data()->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea );
m_slimToolbar.data()->setMovable ( true );
addToolBar( Qt::TopToolBarArea, m_slimToolbar.data() );
m_slimToolbar.data()->hide();
//BEGIN Creating Widgets
PERF_LOG( "Create sidebar" )
m_browserDock = new BrowserDock( this );
m_browserDock.data()->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
m_browserDock.data()->installEventFilter( this );
PERF_LOG( "Sidebar created" )
PERF_LOG( "Create Playlist" )
m_playlistDock = new Playlist::Dock( this );
m_playlistDock.data()->installEventFilter( this );
//HACK, need to connect after because of order in MainWindow()
connect( Amarok::actionCollection()->action( "playlist_edit_queue" ),
- SIGNAL(triggered(bool)), m_playlistDock.data(), SLOT(slotEditQueue()) );
+ &QAction::triggered, m_playlistDock.data(), &Playlist::Dock::slotEditQueue );
PERF_LOG( "Playlist created" )
PERF_LOG( "Creating ContextWidget" )
/* FIXME: disabled temporarily for KF5 porting. Also take care of the usage of the m_contextDock instance below.
m_contextDock = new ContextDock( this );
m_contextDock.data()->installEventFilter( this );
*/
PERF_LOG( "ContextScene created" )
//END Creating Widgets
createMenus();
PERF_LOG( "Loading default contextScene" )
PERF_LOG( "Loaded default contextScene" )
setDockOptions( QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks
| QMainWindow::AnimatedDocks | QMainWindow::VerticalTabs );
addDockWidget( Qt::LeftDockWidgetArea, m_browserDock.data() );
#pragma message("PORTME KF5: line here")
//addDockWidget( Qt::LeftDockWidgetArea, m_contextDock.data(), Qt::Horizontal );
addDockWidget( Qt::LeftDockWidgetArea, m_playlistDock.data(), Qt::Horizontal );
#pragma message("PORTME KF5: line here")
//setLayoutLocked( AmarokConfig::lockLayout() );
//<Browsers>
{
Debug::Block block( "Creating browsers. Please report long start times!" );
//TODO: parent these browsers?
PERF_LOG( "Creating CollectionWidget" )
m_collectionBrowser = new CollectionWidget( "collections", 0 );
//TODO: rename "Music Collections
m_collectionBrowser->setPrettyName( i18n( "Local Music" ) );
m_collectionBrowser->setIcon( QIcon::fromTheme( "drive-harddisk" ) );
m_collectionBrowser->setShortDescription( i18n( "Local sources of content" ) );
m_browserDock.data()->list()->addCategory( m_collectionBrowser );
PERF_LOG( "Created CollectionWidget" )
PERF_LOG( "Creating ServiceBrowser" )
ServiceBrowser *serviceBrowser = ServiceBrowser::instance();
serviceBrowser->setParent( 0 );
serviceBrowser->setPrettyName( i18n( "Internet" ) );
serviceBrowser->setIcon( QIcon::fromTheme( "applications-internet" ) );
serviceBrowser->setShortDescription( i18n( "Online sources of content" ) );
m_browserDock.data()->list()->addCategory( serviceBrowser );
PERF_LOG( "Created ServiceBrowser" )
PERF_LOG( "Creating PlaylistBrowser" )
m_playlistBrowser = new PlaylistBrowserNS::PlaylistBrowser( "playlists", 0 );
m_playlistBrowser->setPrettyName( i18n("Playlists") );
m_playlistBrowser->setIcon( QIcon::fromTheme( "view-media-playlist-amarok" ) );
m_playlistBrowser->setShortDescription( i18n( "Various types of playlists" ) );
m_browserDock.data()->list()->addCategory( m_playlistBrowser );
PERF_LOG( "CreatedPlaylsitBrowser" )
PERF_LOG( "Creating FileBrowser" )
FileBrowser *fileBrowser = new FileBrowser( "files", 0 );
fileBrowser->setPrettyName( i18n("Files") );
fileBrowser->setIcon( QIcon::fromTheme( "folder-amarok" ) );
fileBrowser->setShortDescription( i18n( "Browse local hard drive for content" ) );
m_browserDock.data()->list()->addCategory( fileBrowser );
PERF_LOG( "Created FileBrowser" )
serviceBrowser->setScriptableServiceManager( The::scriptableServiceManager() );
PERF_LOG( "ScriptableServiceManager done" )
PERF_LOG( "Creating Podcast Category" )
m_browserDock.data()->list()->addCategory( The::podcastCategory() );
PERF_LOG( "Created Podcast Category" )
// If Amarok is started for the first time, set initial dock widget sizes
if( !Amarok::config( "MainWindow" ).hasKey( "State" ) )
- QTimer::singleShot( 0, this, SLOT(setDefaultDockSizes()) );
+ QTimer::singleShot( 0, this, &MainWindow::setDefaultDockSizes );
PERF_LOG( "finished MainWindow::init" )
}
The::amarokUrlHandler(); //Instantiate
The::coverFetcher(); //Instantiate
// we must filter ourself to get mouseevents on the "splitter" - what is us, but filtered by the layouter
installEventFilter( this );
}
QMenu*
MainWindow::createPopupMenu()
{
QMenu* menu = new QMenu( this );
// Show/hide menu bar
if (!menuBar()->isVisible())
menu->addAction( m_showMenuBar );
menu->addSeparator();
addViewMenuItems(menu);
return menu;
}
void
MainWindow::addViewMenuItems(QMenu* menu)
{
menu->setTitle( i18nc("@item:inmenu", "&View" ) );
// Layout locking:
QAction* lockAction = new QAction( i18n( "Lock Layout" ), this );
lockAction->setCheckable( true );
lockAction->setChecked( AmarokConfig::lockLayout() );
- connect( lockAction, SIGNAL(toggled(bool)), SLOT(setLayoutLocked(bool)) );
+ connect( lockAction, &QAction::toggled, this, &MainWindow::setLayoutLocked );
menu->addAction( lockAction );
menu->addSeparator();
// Dock widgets:
QList<QDockWidget *> dockwidgets = qFindChildren<QDockWidget *>( this );
foreach( QDockWidget* dockWidget, dockwidgets )
{
if( dockWidget->parentWidget() == this )
menu->addAction( dockWidget->toggleViewAction() );
}
menu->addSeparator();
// Toolbars:
QList<QToolBar *> toolbars = qFindChildren<QToolBar *>( this );
QActionGroup* toolBarGroup = new QActionGroup( this );
toolBarGroup->setExclusive( true );
foreach( QToolBar* toolBar, toolbars )
{
if( toolBar->parentWidget() == this )
{
QAction* action = toolBar->toggleViewAction();
- connect( action, SIGNAL(toggled(bool)), toolBar, SLOT(setVisible(bool)) );
+ connect( action, &QAction::toggled, toolBar, &QToolBar::setVisible );
toolBarGroup->addAction( action );
menu->addAction( action );
}
}
menu->addSeparator();
QAction *resetAction = new QAction( i18n( "Reset Layout" ), this );
- connect( resetAction, SIGNAL( triggered() ), this, SLOT( resetLayout() ) );
+ connect( resetAction, &QAction::triggered, this, &MainWindow::resetLayout );
menu->addAction( resetAction );
}
void
MainWindow::showBrowser( const QString &name )
{
Q_UNUSED( name );
// showBrowser( index ); // FIXME
}
void
MainWindow::showDock( AmarokDockId dockId )
{
QString name;
switch( dockId )
{
case AmarokDockNavigation:
name = m_browserDock.data()->windowTitle();
break;
case AmarokDockContext:
name = m_contextDock.data()->windowTitle();
break;
case AmarokDockPlaylist:
name = m_playlistDock.data()->windowTitle();
break;
}
QList < QTabBar * > tabList = findChildren < QTabBar * > ();
foreach( QTabBar *bar, tabList )
{
for( int i = 0; i < bar->count(); i++ )
{
if( bar->tabText( i ) == name )
{
bar->setCurrentIndex( i );
break;
}
}
}
}
void
MainWindow::closeEvent( QCloseEvent *e )
{
#ifdef Q_WS_MAC
Q_UNUSED( e );
hide();
#else
//KDE policy states we should hide to tray and not quit() when the
//close window button is pushed for the main widget
if( AmarokConfig::showTrayIcon() && e->spontaneous() && !qApp->isSavingSession() )
{
KMessageBox::information( this,
i18n( "<qt>Closing the main window will keep Amarok running in the System Tray. "
"Use <B>Quit</B> from the menu, or the Amarok tray icon to exit the application.</qt>" ),
i18n( "Docking in System Tray" ), "hideOnCloseInfo" );
hide();
e->ignore();
return;
}
e->accept();
App::instance()->quit();
#endif
}
void
MainWindow::exportPlaylist() //SLOT
{
DEBUG_BLOCK
QScopedPointer<KFileDialog> fileDialog( new KFileDialog( QUrl("kfiledialog:///amarok-playlist-export"), QString(), this ) );
QCheckBox *saveRelativeCheck = new QCheckBox( i18n("Use relative path for &saving") );
saveRelativeCheck->setChecked( AmarokConfig::relativePlaylist() );
QStringList supportedMimeTypes;
supportedMimeTypes << "video/x-ms-asf"; //ASX
supportedMimeTypes << "audio/x-mpegurl"; //M3U
supportedMimeTypes << "audio/x-scpls"; //PLS
supportedMimeTypes << "application/xspf+xml"; //XSPF
fileDialog->setMimeFilter( supportedMimeTypes, supportedMimeTypes.first() );
fileDialog->fileWidget()->setCustomWidget( saveRelativeCheck );
fileDialog->setOperationMode( KFileDialog::Saving );
fileDialog->setMode( KFile::File );
fileDialog->setWindowTitle( i18n("Save As") );
fileDialog->setObjectName( "PlaylistExport" );
fileDialog->exec();
QString playlistPath = fileDialog->selectedFile();
if( !playlistPath.isEmpty() )
The::playlist()->exportPlaylist( playlistPath, saveRelativeCheck->isChecked() );
}
void
MainWindow::slotShowActiveTrack() const
{
m_playlistDock.data()->showActiveTrack();
}
void
MainWindow::slotEditTrackInfo() const
{
m_playlistDock.data()->editTrackInfo();
}
void
MainWindow::slotShowCoverManager() //SLOT
{
CoverManager::showOnce( QString(), this );
}
void
MainWindow::slotShowDiagnosticsDialog()
{
DiagnosticDialog *dialog = new DiagnosticDialog( KAboutData::applicationData(), this );
dialog->show();
}
void MainWindow::slotShowBookmarkManager()
{
BookmarkManager::showOnce( this );
}
void MainWindow::slotShowEqualizer()
{
EqualizerDialog::showOnce( this );
}
void
MainWindow::slotPlayMedia() //SLOT
{
// Request location and immediately start playback
slotAddLocation( true );
}
void
MainWindow::slotAddLocation( bool directPlay ) //SLOT
{
static QUrl lastDirectory;
// open a file selector to add media to the playlist
QList<QUrl> files;
KFileDialog dlg( QUrl(QDesktopServices::storageLocation(QDesktopServices::MusicLocation) ), QString("*.*|"), this );
if( !lastDirectory.isEmpty() )
dlg.setUrl( lastDirectory );
dlg.setWindowTitle( directPlay ? i18n("Play Media (Files or URLs)") : i18n("Add Media (Files or URLs)") );
dlg.setMode( KFile::Files | KFile::Directory );
dlg.setObjectName( "PlayMedia" );
dlg.exec();
files = dlg.selectedUrls();
lastDirectory = dlg.baseUrl();
if( files.isEmpty() )
return;
Playlist::AddOptions options = directPlay ? Playlist::OnPlayMediaAction
: Playlist::OnAppendToPlaylistAction;
The::playlistController()->insertOptioned( files, options );
}
void
MainWindow::slotAddStream() //SLOT
{
bool ok;
QString url = KInputDialog::getText( i18n( "Add Stream" ), i18n( "Enter Stream URL:" ),
QString(), &ok, this );
if( !ok )
return;
The::playlistController()->insertOptioned( QUrl( url ),
Playlist::OnAppendToPlaylistAction | Playlist::RemotePlaylistsAreStreams );
}
void
MainWindow::slotFocusPlaylistSearch()
{
showDock( AmarokDockPlaylist ); // ensure that the dock is visible if tabbed
m_playlistDock.data()->searchWidget()->focusInputLine();
}
void
MainWindow::slotFocusCollectionSearch()
{
// ensure collection browser is activated within navigation dock:
browserDock()->list()->navigate( QString("collections") );
showDock( AmarokDockNavigation ); // ensure that the dock is visible if tabbed
m_collectionBrowser->focusInputLine();
}
#ifdef DEBUG_BUILD_TYPE
void
MainWindow::showNetworkRequestViewer() //SLOT
{
if( !m_networkViewer )
{
m_networkViewer = new NetworkAccessViewer( this );
The::networkAccessManager()->setNetworkAccessViewer( m_networkViewer.data() );
}
The::networkAccessManager()->networkAccessViewer()->show();
}
#endif // DEBUG_BUILD_TYPE
/**
* "Toggle Main Window" global shortcut connects to this slot
*/
void
MainWindow::showHide() //SLOT
{
const KWindowInfo info = KWindowSystem::windowInfo( winId(), 0, 0 );
const int currentDesktop = KWindowSystem::currentDesktop();
if( !isVisible() )
{
setVisible( true );
}
else
{
if( !isMinimized() )
{
if( !isActiveWindow() ) // not minimised and without focus
{
KWindowSystem::setOnDesktop( winId(), currentDesktop );
KWindowSystem::activateWindow( winId() );
}
else // Amarok has focus
{
setVisible( false );
}
}
else // Amarok is minimised
{
setWindowState( windowState() & ~Qt::WindowMinimized );
KWindowSystem::setOnDesktop( winId(), currentDesktop );
KWindowSystem::activateWindow( winId() );
}
}
}
void
MainWindow::showNotificationPopup() // slot
{
if( Amarok::KNotificationBackend::instance()->isEnabled()
&& !Amarok::OSD::instance()->isEnabled() )
Amarok::KNotificationBackend::instance()->showCurrentTrack();
else
Amarok::OSD::instance()->forceToggleOSD();
}
void
MainWindow::slotFullScreen() // slot
{
setWindowState( windowState() ^ Qt::WindowFullScreen );
}
void
MainWindow::slotLoveTrack()
{
emit loveTrack( The::engineController()->currentTrack() );
}
void
MainWindow::slotBanTrack()
{
emit banTrack( The::engineController()->currentTrack() );
}
void
MainWindow::slotShufflePlaylist()
{
m_playlistDock.data()->sortWidget()->trimToLevel();
The::playlistActions()->shuffle();
}
void
MainWindow::slotSeekForwardShort()
{
EngineController* ec = The::engineController();
ec->seekBy( AmarokConfig::seekShort() * 1000 );
}
void
MainWindow::slotSeekForwardMedium()
{
EngineController* ec = The::engineController();
ec->seekBy( AmarokConfig::seekMedium() * 1000 );
}
void
MainWindow::slotSeekForwardLong()
{
EngineController* ec = The::engineController();
ec->seekBy( AmarokConfig::seekLong() * 1000 );
}
void
MainWindow::slotSeekBackwardShort()
{
EngineController* ec = The::engineController();
ec->seekBy( AmarokConfig::seekShort() * -1000 );
}
void
MainWindow::slotSeekBackwardMedium()
{
EngineController* ec = The::engineController();
ec->seekBy( AmarokConfig::seekMedium() * -1000 );
}
void
MainWindow::slotSeekBackwardLong()
{
EngineController* ec = The::engineController();
ec->seekBy( AmarokConfig::seekLong() * -1000 );
}
void MainWindow::slotPutCurrentTrackToClipboard()
{
Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
if ( currentTrack )
{
QString text;
Meta::ArtistPtr artist = currentTrack->artist();
if( artist )
text = artist->prettyName() + " - ";
text += currentTrack->prettyName();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText( text );
}
}
void
MainWindow::activate()
{
#ifdef Q_WS_X11
const KWindowInfo info = KWindowSystem::windowInfo( winId(), 0, 0 );
if( KWindowSystem::activeWindow() != winId() )
setVisible( true );
else if( !info.isMinimized() )
setVisible( true );
if( !isHidden() )
KWindowSystem::activateWindow( winId() );
#else
setVisible( true );
#endif
}
void
MainWindow::createActions()
{
KActionCollection* const ac = Amarok::actionCollection();
const EngineController* const ec = The::engineController();
const Playlist::Actions* const pa = The::playlistActions();
const Playlist::Controller* const pc = The::playlistController();
- KStandardAction::keyBindings( qApp, SLOT(slotConfigShortcuts()), ac );
- m_showMenuBar = KStandardAction::showMenubar(this, SLOT(slotShowMenuBar()), ac);
- KStandardAction::preferences( qApp, SLOT(slotConfigAmarok()), ac );
+ if( auto app = qobject_cast<App*>(qApp) )
+ {
+ KStandardAction::keyBindings( app, &App::slotConfigShortcuts, ac );
+ KStandardAction::preferences( app, &App::slotConfigAmarokWithEmptyPage, ac );
+ }
+ m_showMenuBar = KStandardAction::showMenubar(this, &MainWindow::slotShowMenuBar, ac);
ac->action( KStandardAction::name( KStandardAction::KeyBindings ) )->setIcon( QIcon::fromTheme( "configure-shortcuts-amarok" ) );
ac->action( KStandardAction::name( KStandardAction::Preferences ) )->setIcon( QIcon::fromTheme( "configure-amarok" ) );
ac->action( KStandardAction::name( KStandardAction::Preferences ) )->setMenuRole(QAction::PreferencesRole); // Define OS X Prefs menu here, removes need for ifdef later
- KStandardAction::quit( App::instance(), SLOT(quit()), ac );
+ KStandardAction::quit( App::instance(), &App::quit, ac );
QAction *action = new QAction( QIcon::fromTheme( "document-open" ), i18n("&Add Media..."), this );
ac->addAction( "playlist_add", action );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(slotAddLocation()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotAddLocation );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_A ) );
action = new QAction( QIcon::fromTheme( "edit-clear-list" ), i18nc( "clear playlist", "&Clear Playlist" ), this );
- connect( action, SIGNAL(triggered(bool)), pc, SLOT(clear()) );
+ connect( action, &QAction::triggered, pc, &Playlist::Controller::clear );
ac->addAction( "playlist_clear", action );
action = new QAction( QIcon::fromTheme( "format-list-ordered" ),
i18nc( "edit play queue of playlist", "Edit &Queue" ), this );
//Qt::META+Qt::Key_Q is taken by Plasma as a global
action->setShortcut( QKeySequence( Qt::META + Qt::Key_U ) );
ac->addAction( "playlist_edit_queue", action );;
action = new QAction( i18nc( "Remove duplicate and dead (unplayable) tracks from the playlist", "Re&move Duplicates" ), this );
- connect( action, SIGNAL(triggered(bool)), pc, SLOT(removeDeadAndDuplicates()) );
+ connect( action, &QAction::triggered, pc, &Playlist::Controller::removeDeadAndDuplicates );
ac->addAction( "playlist_remove_dead_and_duplicates", action );
action = new Playlist::LayoutConfigAction( this );
ac->addAction( "playlist_layout", action );
action = new QAction( QIcon::fromTheme( "document-open-remote" ), i18n("&Add Stream..."), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(slotAddStream()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotAddStream );
ac->addAction( "stream_add", action );
action = new QAction( QIcon::fromTheme( "document-export-amarok" ), i18n("&Export Playlist As..."), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(exportPlaylist()) );
+ connect( action, &QAction::triggered, this, &MainWindow::exportPlaylist );
ac->addAction( "playlist_export", action );
action = new QAction( QIcon::fromTheme( "bookmark-new" ), i18n( "Bookmark Media Sources View" ), this );
ac->addAction( "bookmark_browser", action );
- connect( action, SIGNAL(triggered()), The::amarokUrlHandler(), SLOT(bookmarkCurrentBrowserView()) );
+ connect( action, &QAction::triggered, The::amarokUrlHandler(), &AmarokUrlHandler::bookmarkCurrentBrowserView );
action = new QAction( QIcon::fromTheme( "bookmarks-organize" ), i18n( "Bookmark Manager" ), this );
ac->addAction( "bookmark_manager", action );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotShowBookmarkManager()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotShowBookmarkManager );
action = new QAction( QIcon::fromTheme( "view-media-equalizer" ), i18n( "Equalizer" ), this );
ac->addAction( "equalizer_dialog", action );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotShowEqualizer()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotShowEqualizer );
action = new QAction( QIcon::fromTheme( "bookmark-new" ), i18n( "Bookmark Playlist Setup" ), this );
ac->addAction( "bookmark_playlistview", action );
- connect( action, SIGNAL(triggered()), The::amarokUrlHandler(), SLOT(bookmarkCurrentPlaylistView()) );
+ connect( action, &QAction::triggered, The::amarokUrlHandler(), &AmarokUrlHandler::bookmarkCurrentPlaylistView );
action = new QAction( QIcon::fromTheme( "bookmark-new" ), i18n( "Bookmark Context Applets" ), this );
ac->addAction( "bookmark_contextview", action );
- connect( action, SIGNAL(triggered()), The::amarokUrlHandler(), SLOT(bookmarkCurrentContextView()) );
+ connect( action, &QAction::triggered, The::amarokUrlHandler(), &AmarokUrlHandler::bookmarkCurrentContextView );
action = new QAction( QIcon::fromTheme( "media-album-cover-manager-amarok" ), i18n( "Cover Manager" ), this );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotShowCoverManager()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotShowCoverManager );
ac->addAction( "cover_manager", action );
action = new QAction( QIcon::fromTheme("document-open"), i18n("Play Media..."), this );
ac->addAction( "playlist_playmedia", action );
action->setShortcut( Qt::CTRL + Qt::Key_O );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotPlayMedia()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotPlayMedia );
action = new QAction( QIcon::fromTheme("media-track-edit-amarok"), i18n("Edit Details of Currently Selected Track"), this );
ac->addAction( "trackdetails_edit", action );
action->setShortcut( Qt::CTRL + Qt::Key_E );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotEditTrackInfo()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotEditTrackInfo );
action = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1",
KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekShort() * 1000 ) ), this );
ac->addAction( "seek_forward_short", action );
action->setShortcut( Qt::CTRL + Qt::Key_Right );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekForwardShort()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotSeekForwardShort );
action = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1",
KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekMedium() * 1000 ) ), this );
ac->addAction( "seek_forward_medium", action );
action->setShortcut( Qt::Key_Right );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_Plus ) );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekForwardMedium()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotSeekForwardMedium );
action = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1",
KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekLong() * 1000 ) ), this );
ac->addAction( "seek_forward_long", action );
action->setShortcut( Qt::SHIFT + Qt::Key_Right );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekForwardLong()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotSeekForwardLong );
action = new QAction( QIcon::fromTheme( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1",
KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekShort() * 1000 ) ), this );
ac->addAction( "seek_backward_short", action );
action->setShortcut( Qt::CTRL + Qt::Key_Left );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekBackwardShort()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotSeekBackwardShort );
action = new QAction( QIcon::fromTheme( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1",
KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekMedium() * 1000 ) ), this );
ac->addAction( "seek_backward_medium", action );
action->setShortcut( Qt::Key_Left );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_Minus ) );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekBackwardMedium()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotSeekBackwardMedium );
action = new QAction( QIcon::fromTheme( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1",
KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekLong() * 1000 ) ), this );
ac->addAction( "seek_backward_long", action );
action->setShortcut( Qt::SHIFT + Qt::Key_Left );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekBackwardLong()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotSeekBackwardLong );
PERF_LOG( "MainWindow::createActions 6" )
action = new QAction( QIcon::fromTheme("view-refresh"), i18n( "Update Collection" ), this );
- connect ( action, SIGNAL(triggered(bool)), CollectionManager::instance(), SLOT(checkCollectionChanges()) );
+ connect ( action, &QAction::triggered, CollectionManager::instance(),&CollectionManager::checkCollectionChanges );
ac->addAction( "update_collection", action );
action = new QAction( QIcon::fromTheme( "amarok_playcount" ), i18n( "Synchronize Statistics..." ), this );
ac->addAction( "synchronize_statistics", action );
- connect( action, SIGNAL(triggered(bool)), Amarok::Components::statSyncingController(), SLOT(synchronize()) );
-
+ connect( action, &QAction::triggered, Amarok::Components::statSyncingController(),
+ &StatSyncing::Controller::synchronize );
+ Amarok::Components::statSyncingController();
action = new QAction( this );
ac->addAction( "prev", action );
action->setIcon( QIcon::fromTheme("media-skip-backward-amarok") );
action->setText( i18n( "Previous Track" ) );
KGlobalAccel::setGlobalShortcut(action, QKeySequence() );
- connect( action, SIGNAL(triggered(bool)), pa, SLOT(back()) );
+ connect( action, &QAction::triggered, pa, &Playlist::Actions::back );
action = new QAction( this );
ac->addAction( "replay", action );
action->setIcon( QIcon::fromTheme("media-playback-start") );
action->setText( i18n( "Restart current track" ) );
KGlobalAccel::setGlobalShortcut(action, QKeySequence() );
- connect( action, SIGNAL(triggered(bool)), ec, SLOT(replay()) );
+ connect( action, &QAction::triggered, ec, &EngineController::replay );
action = new QAction( this );
ac->addAction( "shuffle_playlist", action );
action->setIcon( QIcon::fromTheme("media-playlist-shuffle") );
action->setText( i18n( "Shuffle Playlist" ) );
action->setShortcut( Qt::CTRL + Qt::Key_H );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(slotShufflePlaylist()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotShufflePlaylist );
action = new QAction( this );
ac->addAction( "repopulate", action );
action->setText( i18n( "Repopulate Playlist" ) );
action->setIcon( QIcon::fromTheme("view-refresh-amarok") );
- connect( action, SIGNAL(triggered(bool)), pa, SLOT(repopulateDynamicPlaylist()) );
+ connect( action, &QAction::triggered, pa, &Playlist::Actions::repopulateDynamicPlaylist );
action = new QAction( this );
ac->addAction( "disable_dynamic", action );
action->setText( i18n( "Disable Dynamic Playlist" ) );
action->setIcon( QIcon::fromTheme("edit-delete-amarok") );
//this is connected inside the dynamic playlist category
action = new QAction( QIcon::fromTheme("media-skip-forward-amarok"), i18n( "Next Track" ), this );
ac->addAction( "next", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence() );
- connect( action, SIGNAL(triggered(bool)), pa, SLOT(next()) );
+ connect( action, &QAction::triggered, pa, &Playlist::Actions::next );
action = new QAction( i18n( "Increase Volume" ), this );
ac->addAction( "increaseVolume", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_Plus ) );
action->setShortcut( Qt::Key_Plus );
- connect( action, SIGNAL(triggered()), ec, SLOT(increaseVolume()) );
+ connect( action, &QAction::triggered, ec, &EngineController::increaseVolume );
action = new QAction( i18n( "Decrease Volume" ), this );
ac->addAction( "decreaseVolume", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_Minus ) );
action->setShortcut( Qt::Key_Minus );
- connect( action, SIGNAL(triggered()), ec, SLOT(decreaseVolume()) );
+ connect( action, &QAction::triggered, ec, &EngineController::decreaseVolume );
action = new QAction( i18n( "Toggle Main Window" ), this );
ac->addAction( "toggleMainWindow", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence() );
- connect( action, SIGNAL(triggered()), SLOT(showHide()) );
+ connect( action, &QAction::triggered, this, &MainWindow::showHide );
action = new QAction( i18n( "Toggle Full Screen" ), this );
ac->addAction( "toggleFullScreen", action );
action->setShortcut( QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_F ) );
- connect( action, SIGNAL(triggered()), SLOT(slotFullScreen()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotFullScreen );
action = new QAction( i18n( "Search playlist" ), this );
ac->addAction( "searchPlaylist", action );
action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_J ) );
- connect( action, SIGNAL(triggered()), SLOT(slotFocusPlaylistSearch()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotFocusPlaylistSearch );
action = new QAction( i18n( "Search collection" ), this );
ac->addAction( "searchCollection", action );
action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_F ) );
- connect( action, SIGNAL(triggered()), SLOT(slotFocusCollectionSearch()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotFocusCollectionSearch );
action = new QAction( QIcon::fromTheme( "music-amarok" ), i18n("Show active track"), this );
ac->addAction( "show_active_track", action );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotShowActiveTrack()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotShowActiveTrack );
action = new QAction( i18n( "Show Notification Popup" ), this );
ac->addAction( "showNotificationPopup", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_O ) );
- connect( action, SIGNAL(triggered()), SLOT(showNotificationPopup()) );
+ connect( action, &QAction::triggered, this, &MainWindow::showNotificationPopup );
action = new QAction( i18n( "Mute Volume" ), this );
ac->addAction( "mute", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_M ) );
- connect( action, SIGNAL(triggered()), ec, SLOT(toggleMute()) );
+ connect( action, &QAction::triggered, ec, &EngineController::toggleMute );
action = new QAction( i18n( "Last.fm: Love Current Track" ), this );
ac->addAction( "loveTrack", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_L ) );
- connect( action, SIGNAL(triggered()), SLOT(slotLoveTrack()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotLoveTrack );
action = new QAction( i18n( "Last.fm: Ban Current Track" ), this );
ac->addAction( "banTrack", action );
//KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_B ) );
- connect( action, SIGNAL(triggered()), SLOT(slotBanTrack()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotBanTrack );
action = new QAction( i18n( "Last.fm: Skip Current Track" ), this );
ac->addAction( "skipTrack", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_S ) );
- connect( action, SIGNAL(triggered()), SIGNAL(skipTrack()) );
+ connect( action, &QAction::triggered, this, &MainWindow::skipTrack );
action = new QAction( QIcon::fromTheme( "media-track-queue-amarok" ), i18n( "Queue Track" ), this );
ac->addAction( "queueTrack", action );
action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_D ) );
- connect( action, SIGNAL(triggered()), SIGNAL(switchQueueStateShortcut()) );
+ connect( action, &QAction::triggered, this, &MainWindow::switchQueueStateShortcut );
action = new QAction( i18n( "Put Artist - Title of the current track to the clipboard" ), this );
ac->addAction("artistTitleClipboard", action);
action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_C ) );
- connect( action, SIGNAL(triggered()), SLOT(slotPutCurrentTrackToClipboard()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotPutCurrentTrackToClipboard );
action = new QAction( i18n( "Rate Current Track: 1" ), this );
ac->addAction( "rate1", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_1 ) );
- connect( action, SIGNAL(triggered()), SLOT(setRating1()) );
+ connect( action, &QAction::triggered, this, &MainWindow::setRating1 );
action = new QAction( i18n( "Rate Current Track: 2" ), this );
ac->addAction( "rate2", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_2 ) );
- connect( action, SIGNAL(triggered()), SLOT(setRating2()) );
+ connect( action, &QAction::triggered, this, &MainWindow::setRating2 );
action = new QAction( i18n( "Rate Current Track: 3" ), this );
ac->addAction( "rate3", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_3 ) );
- connect( action, SIGNAL(triggered()), SLOT(setRating3()) );
+ connect( action, &QAction::triggered, this, &MainWindow::setRating3 );
action = new QAction( i18n( "Rate Current Track: 4" ), this );
ac->addAction( "rate4", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_4 ) );
- connect( action, SIGNAL(triggered()), SLOT(setRating4()) );
+ connect( action, &QAction::triggered, this, &MainWindow::setRating4 );
action = new QAction( i18n( "Rate Current Track: 5" ), this );
ac->addAction( "rate5", action );
KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_5 ) );
- connect( action, SIGNAL(triggered()), SLOT(setRating5()) );
+ connect( action, &QAction::triggered, this, &MainWindow::setRating5 );
#ifdef DEBUG_BUILD_TYPE
action = new QAction( i18n( "Network Request Viewer" ), this );
ac->addAction( "network_request_viewer", action );
action->setIcon( QIcon::fromTheme( "utilities-system-monitor" ) );
- connect( action, SIGNAL(triggered()), SLOT(showNetworkRequestViewer()) );
+ connect( action, &QAction::triggered, this, &MainWindow::showNetworkRequestViewer );
#endif // DEBUG_BUILD_TYPE
- action = KStandardAction::redo( pc, SLOT(redo()), this);
+ action = KStandardAction::redo( pc, &Playlist::Controller::redo, this);
ac->addAction( "playlist_redo", action );
action->setEnabled( false );
action->setIcon( QIcon::fromTheme( "edit-redo" ) );
- connect( pc, SIGNAL(canRedoChanged(bool)), action, SLOT(setEnabled(bool)) );
+ connect( pc, &Playlist::Controller::canRedoChanged, action, &QAction::setEnabled );
- action = KStandardAction::undo( pc, SLOT(undo()), this);
+ action = KStandardAction::undo( pc, &Playlist::Controller::undo, this);
ac->addAction( "playlist_undo", action );
action->setEnabled( false );
action->setIcon( QIcon::fromTheme( "edit-undo" ) );
- connect( pc, SIGNAL(canUndoChanged(bool)), action, SLOT(setEnabled(bool)) );
+ connect( pc, &Playlist::Controller::canUndoChanged, action, &QAction::setEnabled );
action = new QAction( QIcon::fromTheme( "amarok" ), i18n( "&About Amarok" ), this );
ac->addAction( "extendedAbout", action );
- connect( action, SIGNAL(triggered()), SLOT(showAbout()) );
+ connect( action, &QAction::triggered, this, &MainWindow::showAbout );
action = new QAction ( QIcon::fromTheme( "info-amarok" ), i18n( "&Diagnostics" ), this );
ac->addAction( "diagnosticDialog", action );
- connect( action, SIGNAL(triggered()), SLOT(slotShowDiagnosticsDialog()) );
+ connect( action, &QAction::triggered, this, &MainWindow::slotShowDiagnosticsDialog );
action = new QAction( QIcon::fromTheme( "tools-report-bug" ), i18n("&Report Bug..."), this );
ac->addAction( "reportBug", action );
- connect( action, SIGNAL(triggered()), SLOT(showReportBug()) );
+ connect( action, &QAction::triggered, this, &MainWindow::showReportBug );
PERF_LOG( "MainWindow::createActions 8" )
new Amarok::MenuAction( ac, this );
new Amarok::StopAction( ac, this );
new Amarok::StopPlayingAfterCurrentTrackAction( ac, this );
new Amarok::PlayPauseAction( ac, this );
new Amarok::ReplayGainModeAction( ac, this );
ac->addAssociatedWidget( this );
foreach( QAction* action, ac->actions() )
action->setShortcutContext( Qt::WindowShortcut );
}
void
MainWindow::setRating( int n )
{
n *= 2;
Meta::TrackPtr track = The::engineController()->currentTrack();
if( track )
{
Meta::StatisticsPtr statistics = track->statistics();
// if we're setting an identical rating then we really must
// want to set the half-star below rating
if( statistics->rating() == n )
n -= 1;
statistics->setRating( n );
Amarok::OSD::instance()->OSDWidget::ratingChanged( statistics->rating() );
}
}
void
MainWindow::createMenus()
{
m_menubar = menuBar();
//BEGIN Actions menu
QMenu *actionsMenu = new QMenu( m_menubar.data() );
#ifdef Q_WS_MAC
// Add these functions to the dock icon menu in OS X
//extern void qt_mac_set_dock_menu(QMenu *);
//qt_mac_set_dock_menu(actionsMenu);
// Change to avoid duplicate menu titles in OS X
actionsMenu->setTitle( i18n("&Music") );
#else
actionsMenu->setTitle( i18n("&Amarok") );
#endif
actionsMenu->addAction( Amarok::actionCollection()->action("playlist_playmedia") );
actionsMenu->addSeparator();
actionsMenu->addAction( Amarok::actionCollection()->action("prev") );
actionsMenu->addAction( Amarok::actionCollection()->action("play_pause") );
actionsMenu->addAction( Amarok::actionCollection()->action("stop") );
actionsMenu->addAction( Amarok::actionCollection()->action("stop_after_current") );
actionsMenu->addAction( Amarok::actionCollection()->action("next") );
#ifndef Q_WS_MAC // Avoid duplicate "Quit" in OS X dock menu
actionsMenu->addSeparator();
actionsMenu->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::Quit ) ) );
#endif
//END Actions menu
//BEGIN View menu
QMenu* viewMenu = new QMenu(this);
addViewMenuItems(viewMenu);
//END View menu
//BEGIN Playlist menu
QMenu *playlistMenu = new QMenu( m_menubar.data() );
playlistMenu->setTitle( i18n("&Playlist") );
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_add") );
playlistMenu->addAction( Amarok::actionCollection()->action("stream_add") );
//playlistMenu->addAction( Amarok::actionCollection()->action("playlist_save") ); //FIXME: See FIXME in PlaylistDock.cpp
playlistMenu->addAction( Amarok::actionCollection()->action( "playlist_export" ) );
playlistMenu->addSeparator();
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_undo") );
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_redo") );
playlistMenu->addSeparator();
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_clear") );
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_remove_dead_and_duplicates") );
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_layout") );
playlistMenu->addAction( Amarok::actionCollection()->action("playlist_edit_queue") );
//END Playlist menu
//BEGIN Tools menu
m_toolsMenu = new QMenu( m_menubar.data() );
m_toolsMenu.data()->setTitle( i18n("&Tools") );
m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("bookmark_manager") );
m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("cover_manager") );
m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("equalizer_dialog") );
#ifdef DEBUG_BUILD_TYPE
m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("network_request_viewer") );
#endif // DEBUG_BUILD_TYPE
m_toolsMenu.data()->addSeparator();
m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("update_collection") );
m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("synchronize_statistics") );
//END Tools menu
//BEGIN Settings menu
m_settingsMenu = new QMenu( m_menubar.data() );
m_settingsMenu.data()->setTitle( i18n("&Settings") );
m_settingsMenu.data()->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::ShowMenubar ) ) );
//TODO use KStandardAction or KXmlGuiWindow
// the phonon-coreaudio backend has major issues with either the VolumeFaderEffect itself
// or with it in the pipeline. track playback stops every ~3-4 tracks, and on tracks >5min it
// stops at about 5:40. while we get this resolved upstream, don't make playing amarok such on osx.
// so we disable replaygain on osx
#ifndef Q_WS_MAC
m_settingsMenu.data()->addAction( Amarok::actionCollection()->action("replay_gain_mode") );
m_settingsMenu.data()->addSeparator();
#endif
m_settingsMenu.data()->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::KeyBindings ) ) );
m_settingsMenu.data()->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::Preferences ) ) );
//END Settings menu
m_menubar.data()->addMenu( actionsMenu );
m_menubar.data()->addMenu( viewMenu );
m_menubar.data()->addMenu( playlistMenu );
m_menubar.data()->addMenu( m_toolsMenu.data() );
m_menubar.data()->addMenu( m_settingsMenu.data() );
QMenu *helpMenu = Amarok::Menu::helpMenu();
helpMenu->insertAction( helpMenu->actions().last(),
Amarok::actionCollection()->action( "extendedAbout" ) );
helpMenu->insertAction( helpMenu->actions().last(),
Amarok::actionCollection()->action( "diagnosticDialog" ) );
m_menubar.data()->addSeparator();
m_menubar.data()->addMenu( helpMenu );
}
void
MainWindow::slotShowMenuBar()
{
if (!m_showMenuBar->isChecked())
{
//User have chosen to hide a menu. Lets warn him
if (KMessageBox::warningContinueCancel(this,
i18n("You have chosen to hide the menu bar.\n\nPlease remember that you can always use the shortcut \"%1\" to bring it back.", m_showMenuBar->shortcut().toString() ),
i18n("Hide Menu"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "showMenubar") != KMessageBox::Continue)
{
//Cancel menu hiding. Revert menu item to checked state.
m_showMenuBar->setChecked(true);
return;
}
}
menuBar()->setVisible(m_showMenuBar->isChecked());
}
void
MainWindow::showAbout()
{
ExtendedAboutDialog dialog( KAboutData::applicationData(), &ocsData );
dialog.exec();
}
void
MainWindow::showReportBug()
{
KBugReport * rbDialog = new KBugReport( KAboutData::applicationData() ,this );
rbDialog->setObjectName( "KBugReport" );
rbDialog->exec();
}
void
MainWindow::changeEvent( QEvent *event )
{
if( event->type() == QEvent::PaletteChange )
The::paletteHandler()->setPalette( palette() );
}
void
MainWindow::slotStopped()
{
setPlainCaption( i18n( AMAROK_CAPTION ) );
}
void
MainWindow::slotPaused()
{
setPlainCaption( i18n( "Paused :: %1", i18n( AMAROK_CAPTION ) ) );
}
void
MainWindow::slotNewTrackPlaying()
{
slotMetadataChanged( The::engineController()->currentTrack() );
}
void
MainWindow::slotMetadataChanged( Meta::TrackPtr track )
{
if( track )
setPlainCaption( i18n( "%1 - %2 :: %3", track->artist() ? track->artist()->prettyName() : i18n( "Unknown" ), track->prettyName(), i18n( AMAROK_CAPTION ) ) );
}
CollectionWidget *
MainWindow::collectionBrowser()
{
return m_collectionBrowser;
}
QString
MainWindow::activeBrowserName()
{
if( m_browserDock.data()->list()->activeCategory() )
return m_browserDock.data()->list()->activeCategory()->name();
else
return QString();
}
void
MainWindow::setLayoutLocked( bool locked )
{
DEBUG_BLOCK
if( locked )
{
m_browserDock.data()->setMovable( false );
m_contextDock.data()->setMovable( false );
m_playlistDock.data()->setMovable( false );
m_slimToolbar.data()->setFloatable( false );
m_slimToolbar.data()->setMovable( false );
m_mainToolbar.data()->setFloatable( false );
m_mainToolbar.data()->setMovable( false );
}
else
{
m_browserDock.data()->setMovable( true );
m_contextDock.data()->setMovable( true );
m_playlistDock.data()->setMovable( true );
m_slimToolbar.data()->setFloatable( true );
m_slimToolbar.data()->setMovable( true );
m_mainToolbar.data()->setFloatable( true );
m_mainToolbar.data()->setMovable( true );
}
AmarokConfig::setLockLayout( locked );
AmarokConfig::self()->writeConfig();
}
void
MainWindow::resetLayout()
{
// Store current state, so that we can undo the operation
const QByteArray state = saveState();
// Remove all dock widgets, then add them again. This resets their state completely.
removeDockWidget( m_browserDock.data() );
removeDockWidget( m_contextDock.data() );
removeDockWidget( m_playlistDock.data() );
addDockWidget( Qt::LeftDockWidgetArea, m_browserDock.data() );
addDockWidget( Qt::LeftDockWidgetArea, m_contextDock.data(), Qt::Horizontal );
addDockWidget( Qt::LeftDockWidgetArea, m_playlistDock.data(), Qt::Horizontal );
m_browserDock.data()->setFloating( false );
m_contextDock.data()->setFloating( false );
m_playlistDock.data()->setFloating( false );
m_browserDock.data()->show();
m_contextDock.data()->show();
m_playlistDock.data()->show();
// Now set Amarok's default dockwidget sizes
setDefaultDockSizes();
if( KMessageBox::warningContinueCancel( this, i18n( "Apply this layout change?" ), i18n( "Reset Layout" ) ) == KMessageBox::Cancel )
restoreState( state );
}
void
MainWindow::setDefaultDockSizes() // SLOT
{
int totalWidgetWidth = contentsRect().width();
//get the width of the splitter handles, we need to subtract these...
const int splitterHandleWidth = style()->pixelMetric( QStyle::PM_DockWidgetSeparatorExtent, 0, 0 );
totalWidgetWidth -= ( splitterHandleWidth * 2 );
const int widgetWidth = totalWidgetWidth / 3;
const int leftover = totalWidgetWidth - 3 * widgetWidth;
#pragma message("PORTME KF5")/*
//We need to set fixed widths initially, just until the main window has been properly laid out. As soon as this has
//happened, we will unlock these sizes again so that the elements can be resized by the user.
const int mins[3] = { m_browserDock.data()->minimumWidth(), m_contextDock.data()->minimumWidth(), m_playlistDock.data()->minimumWidth() };
const int maxs[3] = { m_browserDock.data()->maximumWidth(), m_contextDock.data()->maximumWidth(), m_playlistDock.data()->maximumWidth() };
m_browserDock.data()->setFixedWidth( widgetWidth * 0.65 );
m_contextDock.data()->setFixedWidth( widgetWidth * 1.7 + leftover );
m_playlistDock.data()->setFixedWidth( widgetWidth * 0.65 );
// Important: We need to activate the layout we have just set
layout()->activate();
m_browserDock.data()->setMinimumWidth( mins[0] ); m_browserDock.data()->setMaximumWidth( maxs[0] );
m_contextDock.data()->setMinimumWidth( mins[1] ); m_contextDock.data()->setMaximumWidth( maxs[1] );
m_playlistDock.data()->setMinimumWidth( mins[2] ); m_playlistDock.data()->setMaximumWidth( maxs[2] );
*/
}
bool
MainWindow::playAudioCd()
{
DEBUG_BLOCK
//drop whatever we are doing and play auidocd
QList<Collections::Collection*> collections = CollectionManager::instance()->viewableCollections();
// Search a non-empty MemoryCollection with the id: AudioCd
foreach( Collections::Collection *collection, collections )
{
if( collection->collectionId() == "AudioCd" )
{
debug() << "got audiocd collection";
Collections::MemoryCollection * cdColl = dynamic_cast<Collections::MemoryCollection *>( collection );
if( !cdColl || cdColl->trackMap().count() == 0 )
{
debug() << "cd collection not ready yet (track count = 0 )";
m_waitingForCd = true;
return false;
}
The::playlistController()->insertOptioned( cdColl->trackMap().values(), Playlist::OnPlayMediaAction );
m_waitingForCd = false;
return true;
}
}
debug() << "waiting for cd...";
m_waitingForCd = true;
return false;
}
bool
MainWindow::isWaitingForCd() const
{
DEBUG_BLOCK
debug() << "waiting?: " << m_waitingForCd;
return m_waitingForCd;
}
bool
MainWindow::isOnCurrentDesktop() const
{
#ifdef Q_WS_X11
return KWindowSystem::windowInfo( winId(), NET::WMDesktop ).desktop() == KWindowSystem::currentDesktop();
#else
return true;
#endif
}
diff --git a/src/MediaDeviceCache.cpp b/src/MediaDeviceCache.cpp
index c8b1b597f4..a4540ae07a 100644
--- a/src/MediaDeviceCache.cpp
+++ b/src/MediaDeviceCache.cpp
@@ -1,376 +1,376 @@
/****************************************************************************************
* Copyright (c) 2007 Jeff Mitchell <kde-dev@emailgoeshere.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MediaDeviceCache"
#include "MediaDeviceCache.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <KConfigGroup>
#include <solid/device.h>
#include <solid/deviceinterface.h>
#include <solid/devicenotifier.h>
#include <solid/genericinterface.h>
#include <solid/opticaldisc.h>
#include <solid/portablemediaplayer.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/block.h>
#include <solid/storagevolume.h>
#include <kdeversion.h>
#include <kmountpoint.h>
#include <QDir>
#include <QFile>
#include <QList>
MediaDeviceCache* MediaDeviceCache::s_instance = 0;
MediaDeviceCache::MediaDeviceCache() : QObject()
, m_type()
, m_name()
, m_volumes()
{
DEBUG_BLOCK
s_instance = this;
- connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
- this, SLOT(slotAddSolidDevice(QString)) );
- connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)),
- this, SLOT(slotRemoveSolidDevice(QString)) );
+ connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded,
+ this, &MediaDeviceCache::slotAddSolidDevice );
+ connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved,
+ this, &MediaDeviceCache::slotRemoveSolidDevice );
}
MediaDeviceCache::~MediaDeviceCache()
{
s_instance = 0;
}
void
MediaDeviceCache::refreshCache()
{
DEBUG_BLOCK
m_type.clear();
m_name.clear();
QList<Solid::Device> deviceList = Solid::Device::listFromType( Solid::DeviceInterface::PortableMediaPlayer );
foreach( const Solid::Device &device, deviceList )
{
if( device.as<Solid::StorageDrive>() )
{
debug() << "Found Solid PMP that is also a StorageDrive, skipping";
continue;
}
debug() << "Found Solid::DeviceInterface::PortableMediaPlayer with udi = " << device.udi();
debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
m_type[device.udi()] = MediaDeviceCache::SolidPMPType;
m_name[device.udi()] = device.vendor() + " - " + device.product();
}
deviceList = Solid::Device::listFromType( Solid::DeviceInterface::StorageAccess );
foreach( const Solid::Device &device, deviceList )
{
debug() << "Found Solid::DeviceInterface::StorageAccess with udi = " << device.udi();
debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
const Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
if( ssa )
{
if( !m_volumes.contains( device.udi() ) )
{
- connect( ssa, SIGNAL(accessibilityChanged(bool,QString)),
- this, SLOT(slotAccessibilityChanged(bool,QString)) );
+ connect( ssa, &Solid::StorageAccess::accessibilityChanged,
+ this, &MediaDeviceCache::slotAccessibilityChanged );
m_volumes.append( device.udi() );
}
if( ssa->isAccessible() )
{
m_type[device.udi()] = MediaDeviceCache::SolidVolumeType;
m_name[device.udi()] = ssa->filePath();
m_accessibility[ device.udi() ] = true;
}
else
{
m_accessibility[ device.udi() ] = false;
debug() << "Solid device is not accessible, will wait until it is to consider it added.";
}
}
}
deviceList = Solid::Device::listFromType( Solid::DeviceInterface::StorageDrive );
foreach( const Solid::Device &device, deviceList )
{
debug() << "Found Solid::DeviceInterface::StorageDrive with udi = " << device.udi();
debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
if( device.as<Solid::StorageDrive>() )
{
m_type[device.udi()] = MediaDeviceCache::SolidGenericType;
m_name[device.udi()] = device.vendor() + " - " + device.product();
}
}
deviceList = Solid::Device::listFromType( Solid::DeviceInterface::OpticalDisc );
foreach( const Solid::Device &device, deviceList )
{
debug() << "Found Solid::DeviceInterface::OpticalDisc with udi = " << device.udi();
debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
const Solid::OpticalDisc * opt = device.as<Solid::OpticalDisc>();
if ( opt && opt->availableContent() & Solid::OpticalDisc::Audio )
{
debug() << "device is an Audio CD";
m_type[device.udi()] = MediaDeviceCache::SolidAudioCdType;
m_name[device.udi()] = device.vendor() + " - " + device.product();
}
}
KConfigGroup config = Amarok::config( "PortableDevices" );
const QStringList manualDeviceKeys = config.entryMap().keys();
foreach( const QString &udi, manualDeviceKeys )
{
if( udi.startsWith( "manual" ) )
{
debug() << "Found manual device with udi = " << udi;
m_type[udi] = MediaDeviceCache::ManualType;
m_name[udi] = udi.split( '|' )[1];
}
}
}
void
MediaDeviceCache::slotAddSolidDevice( const QString &udi )
{
DEBUG_BLOCK
Solid::Device device( udi );
debug() << "Found new Solid device with udi = " << device.udi();
debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
Solid::OpticalDisc * opt = device.as<Solid::OpticalDisc>();
if ( opt && opt->availableContent() & Solid::OpticalDisc::Audio )
{
debug() << "device is an Audio CD";
m_type[udi] = MediaDeviceCache::SolidAudioCdType;
m_name[udi] = device.vendor() + " - " + device.product();
}
else if( ssa )
{
debug() << "volume is generic storage";
if( !m_volumes.contains( device.udi() ) )
{
- connect( ssa, SIGNAL(accessibilityChanged(bool,QString)),
- this, SLOT(slotAccessibilityChanged(bool,QString)) );
+ connect( ssa, &Solid::StorageAccess::accessibilityChanged,
+ this, &MediaDeviceCache::slotAccessibilityChanged );
m_volumes.append( device.udi() );
}
if( ssa->isAccessible() )
{
m_type[udi] = MediaDeviceCache::SolidVolumeType;
m_name[udi] = ssa->filePath();
}
else
{
debug() << "storage volume is not accessible right now, not adding.";
return;
}
}
else if( device.is<Solid::StorageDrive>() )
{
debug() << "device is a Storage drive, still need a volume";
m_type[udi] = MediaDeviceCache::SolidGenericType;
m_name[udi] = device.vendor() + " - " + device.product();
}
else if( device.is<Solid::PortableMediaPlayer>() )
{
debug() << "device is a PMP";
m_type[udi] = MediaDeviceCache::SolidPMPType;
m_name[udi] = device.vendor() + " - " + device.product();
}
else if( const Solid::GenericInterface *generic = device.as<Solid::GenericInterface>() )
{
const QMap<QString, QVariant> properties = generic->allProperties();
/* At least iPod touch 3G and iPhone 3G do not advertise AFC (Apple File
* Connection) capabilities. Therefore we have to white-list them so that they are
* still recognised ad iPods
*
* @see IpodConnectionAssistant::identify() for a quirk that is currently also
* needed for proper identification of iPhone-like devices.
*/
if ( !device.product().contains("iPod") && !device.product().contains("iPhone"))
{
if( !properties.contains("info.capabilities") )
{
debug() << "udi " << udi << " does not describe a portable media player or storage volume";
return;
}
const QStringList capabilities = properties["info.capabilities"].toStringList();
if( !capabilities.contains("afc") )
{
debug() << "udi " << udi << " does not describe a portable media player or storage volume";
return;
}
}
debug() << "udi" << udi << "is AFC cabable (Apple mobile device)";
m_type[udi] = MediaDeviceCache::SolidGenericType;
m_name[udi] = device.vendor() + " - " + device.product();
}
else
{
debug() << "udi " << udi << " does not describe a portable media player or storage volume";
return;
}
emit deviceAdded( udi );
}
void
MediaDeviceCache::slotRemoveSolidDevice( const QString &udi )
{
DEBUG_BLOCK
debug() << "udi is: " << udi;
Solid::Device device( udi );
if( m_volumes.contains( udi ) )
{
- disconnect( device.as<Solid::StorageAccess>(), SIGNAL(accessibilityChanged(bool,QString)),
- this, SLOT(slotAccessibilityChanged(bool,QString)) );
+ disconnect( device.as<Solid::StorageAccess>(), &Solid::StorageAccess::accessibilityChanged,
+ this, &MediaDeviceCache::slotAccessibilityChanged );
m_volumes.removeAll( udi );
emit deviceRemoved( udi );
}
if( m_type.contains( udi ) )
{
m_type.remove( udi );
m_name.remove( udi );
emit deviceRemoved( udi );
return;
}
debug() << "Odd, got a deviceRemoved at udi " << udi << " but it did not seem to exist in the first place...";
emit deviceRemoved( udi );
}
void
MediaDeviceCache::slotAccessibilityChanged( bool accessible, const QString &udi )
{
debug() << "accessibility of device " << udi << " has changed to accessible = " << (accessible ? "true":"false");
if( accessible )
{
Solid::Device device( udi );
m_type[udi] = MediaDeviceCache::SolidVolumeType;
Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
if( ssa )
m_name[udi] = ssa->filePath();
emit deviceAdded( udi );
return;
}
else
{
if( m_type.contains( udi ) )
{
m_type.remove( udi );
m_name.remove( udi );
emit deviceRemoved( udi );
return;
}
debug() << "Got accessibility changed to false but was not there in the first place...";
}
emit accessibilityChanged( accessible, udi );
}
MediaDeviceCache::DeviceType
MediaDeviceCache::deviceType( const QString &udi ) const
{
if( m_type.contains( udi ) )
{
return m_type[udi];
}
return MediaDeviceCache::InvalidType;
}
const QString
MediaDeviceCache::deviceName( const QString &udi ) const
{
if( m_name.contains( udi ) )
{
return m_name[udi];
}
return "ERR_NO_NAME"; //Should never happen!
}
const QString
MediaDeviceCache::device( const QString &udi ) const
{
DEBUG_BLOCK
Solid::Device device( udi );
Solid::Device parent( device.parent() );
if( !parent.isValid() )
{
debug() << udi << "has no parent, returning null string.";
return QString();
}
Solid::Block* sb = parent.as<Solid::Block>();
if( !sb )
{
debug() << parent.udi() << "failed to convert to Block, returning null string.";
return QString();
}
return sb->device();
}
bool
MediaDeviceCache::isGenericEnabled( const QString &udi ) const
{
DEBUG_BLOCK
if( m_type[udi] != MediaDeviceCache::SolidVolumeType )
{
debug() << "Not SolidVolumeType, returning false";
return false;
}
Solid::Device device( udi );
Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
if( !ssa || !ssa->isAccessible() )
{
debug() << "Not able to convert to StorageAccess or not accessible, returning false";
return false;
}
if( device.parent().as<Solid::PortableMediaPlayer>() )
{
debug() << "Could convert parent to PortableMediaPlayer, returning true";
return true;
}
if( QFile::exists( ssa->filePath() + QDir::separator() + ".is_audio_player" ) )
{
return true;
}
return false;
}
const QString
MediaDeviceCache::volumeMountPoint( const QString &udi ) const
{
DEBUG_BLOCK
Solid::Device device( udi );
Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
if( !ssa || !ssa->isAccessible() )
{
debug() << "Not able to convert to StorageAccess or not accessible, returning empty";
return QString();
}
return ssa->filePath();
}
diff --git a/src/MediaDeviceMonitor.cpp b/src/MediaDeviceMonitor.cpp
index a912ab7d8a..e6c5cebc9c 100644
--- a/src/MediaDeviceMonitor.cpp
+++ b/src/MediaDeviceMonitor.cpp
@@ -1,202 +1,202 @@
/****************************************************************************************
* Copyright (c) 2008 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MediaDeviceMonitor"
#include "MediaDeviceMonitor.h"
#include "MediaDeviceCache.h"
#include <solid/devicenotifier.h>
#include <solid/device.h>
#include <solid/opticaldisc.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/portablemediaplayer.h>
#include <solid/opticaldrive.h>
#include <QTimer>
MediaDeviceMonitor* MediaDeviceMonitor::s_instance = 0;
MediaDeviceMonitor::MediaDeviceMonitor() : QObject()
, m_udiAssistants()
, m_assistants()
, m_waitingassistants()
, m_nextassistant( 0 )
// NOTE: commented out, needs porting to new device framework
//, m_currentCdId( QString() )
{
DEBUG_BLOCK
s_instance = this;
init();
}
MediaDeviceMonitor::~MediaDeviceMonitor()
{
s_instance = 0;
}
void
MediaDeviceMonitor::init()
{
DEBUG_BLOCK
// connect to device cache so new devices are tested too
- connect( MediaDeviceCache::instance(), SIGNAL(deviceAdded(QString)),
- SLOT(deviceAdded(QString)) );
- connect( MediaDeviceCache::instance(), SIGNAL(deviceRemoved(QString)),
- SLOT(slotDeviceRemoved(QString)) );
- connect( MediaDeviceCache::instance(), SIGNAL(accessibilityChanged(bool,QString)),
- SLOT(slotAccessibilityChanged(bool,QString)) );
+ connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceAdded,
+ this, &MediaDeviceMonitor::deviceAdded );
+ connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceRemoved,
+ this, &MediaDeviceMonitor::slotDeviceRemoved );
+ connect( MediaDeviceCache::instance(), &MediaDeviceCache::accessibilityChanged,
+ this, &MediaDeviceMonitor::slotAccessibilityChanged );
}
QStringList
MediaDeviceMonitor::getDevices()
{
DEBUG_BLOCK
/* get list of devices */
MediaDeviceCache::instance()->refreshCache();
return MediaDeviceCache::instance()->getAll();
}
void MediaDeviceMonitor::checkDevice(const QString& udi)
{
DEBUG_BLOCK
// First let the higher priority devices check
foreach( ConnectionAssistant* assistant, m_assistants )
{
checkOneDevice( assistant, udi );
}
// Then let the assistants that can wait check
foreach( ConnectionAssistant* assistant, m_waitingassistants )
{
checkOneDevice( assistant, udi );
}
}
void MediaDeviceMonitor::checkOneDevice( ConnectionAssistant* assistant, const QString& udi )
{
// Ignore already identified devices
if( m_udiAssistants.keys().contains( udi ) )
{
debug() << "Device already identified with udi: " << udi;
return;
}
if( assistant->identify( udi ) )
{
debug() << "Device identified with udi: " << udi;
// keep track of which assistant deals with which device
m_udiAssistants.insert( udi, assistant );
// inform factory of new device identified
assistant->tellIdentified( udi );
return;
}
}
void MediaDeviceMonitor::checkDevicesFor( ConnectionAssistant* assistant )
{
DEBUG_BLOCK
QStringList udiList = getDevices();
foreach( const QString &udi, udiList )
{
checkOneDevice( assistant, udi );
}
}
void
MediaDeviceMonitor::registerDeviceType( ConnectionAssistant* assistant )
{
DEBUG_BLOCK
// If the device wants to wait and give other device types
// a chance to recognize devices, put it in a queue for
// later device checking
if ( assistant->wait() )
{
// keep track of this type of device from now on
m_waitingassistants << assistant;
- QTimer::singleShot( 1000, this, SLOT(slotDequeueWaitingAssistant()) );
+ QTimer::singleShot( 1000, this, &MediaDeviceMonitor::slotDequeueWaitingAssistant );
}
else
{
// keep track of this type of device from now on
m_assistants << assistant;
// start initial check for devices of this type
checkDevicesFor( assistant );
}
}
void
MediaDeviceMonitor::deviceAdded( const QString &udi )
{
DEBUG_BLOCK
// check if device is a known device
checkDevice( udi );
}
void
MediaDeviceMonitor::slotDeviceRemoved( const QString &udi )
{
DEBUG_BLOCK
if ( m_udiAssistants.contains( udi ) )
{
m_udiAssistants.value( udi )->tellDisconnected( udi );
m_udiAssistants.remove( udi );
}
// emit deviceRemoved( udi );
}
void
MediaDeviceMonitor::slotAccessibilityChanged( bool accessible, const QString & udi)
{
// TODO: build a hack to force a device to become accessible or not
// This means auto-mounting of Ipod, and ejecting of it too
DEBUG_BLOCK
debug() << "Accessibility changed to: " << ( accessible ? "true":"false" );
if ( !accessible )
deviceRemoved( udi );
else
deviceAdded( udi );
}
void
MediaDeviceMonitor::slotDequeueWaitingAssistant()
{
checkDevicesFor( m_waitingassistants.at( m_nextassistant++ ) );
}
diff --git a/src/OpmlParser.cpp b/src/OpmlParser.cpp
index f5297f9de6..e2adb4bdea 100644
--- a/src/OpmlParser.cpp
+++ b/src/OpmlParser.cpp
@@ -1,449 +1,449 @@
/****************************************************************************************
* Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> *
* 2009 Mathias Panzenböck <grosser.meister.morti@gmx.net> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "OpmlParser.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <QFile>
#include <QXmlStreamReader>
#include <KLocale>
#include <kio/job.h>
const QString OpmlParser::OPML_MIME = "text/x-opml+xml";
const OpmlParser::StaticData OpmlParser::sd;
OpmlParser::OpmlParser( const QUrl &url )
: QObject()
, ThreadWeaver::Job()
, QXmlStreamReader()
, m_url( url )
{
}
OpmlParser::~OpmlParser()
{
}
void
OpmlParser::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
read( m_url );
}
void
OpmlParser::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
OpmlParser::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
bool
OpmlParser::read( const QUrl &url )
{
m_url = url;
if( m_url.isLocalFile() )
{
//read directly from local file
QFile localFile( m_url.toLocalFile() );
if( !localFile.open( QIODevice::ReadOnly ) )
{
debug() << "failed to open local OPML file " << m_url.url();
return false;
}
return read( &localFile );
}
m_transferJob = KIO::get( m_url, KIO::Reload, KIO::HideProgressInfo );
- connect( m_transferJob, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotAddData(KIO::Job*,QByteArray)) );
+ connect( m_transferJob, &KIO::TransferJob::data,
+ this, &OpmlParser::slotAddData );
- connect( m_transferJob, SIGNAL(result(KJob*)),
- SLOT(downloadResult(KJob*)) );
+ connect( m_transferJob, &KIO::TransferJob::result,
+ this, &OpmlParser::downloadResult );
// parse data
return read();
}
bool
OpmlParser::read( QIODevice *device )
{
setDevice( device );
return read();
}
void
OpmlParser::slotAddData( KIO::Job *job, const QByteArray &data )
{
Q_UNUSED( job )
QXmlStreamReader::addData( data );
// parse more data
continueRead();
}
void
OpmlParser::downloadResult( KJob *job )
{
// parse more data
continueRead();
KIO::TransferJob *transferJob = dynamic_cast<KIO::TransferJob *>( job );
if( job->error() || ( transferJob && transferJob->isErrorPage() ) )
{
QString errorMessage =
i18n( "Reading OPML podcast from %1 failed with error:\n", m_url.url() );
errorMessage = errorMessage.append( job->errorString() );
// emit statusBarSorryMessage( errorMessage );
}
m_transferJob = 0;
}
void
OpmlParser::slotAbort()
{
DEBUG_BLOCK
}
void
OpmlParser::Action::begin( OpmlParser *opmlParser ) const
{
if( m_begin )
(( *opmlParser ).*m_begin )();
}
void
OpmlParser::Action::end( OpmlParser *opmlParser ) const
{
if( m_end )
(( *opmlParser ).*m_end )();
}
void
OpmlParser::Action::characters( OpmlParser *opmlParser ) const
{
if( m_characters )
(( *opmlParser ).*m_characters )();
}
// initialization of the feed parser automata:
OpmlParser::StaticData::StaticData()
: startAction( rootMap )
, docAction(
docMap,
0,
&OpmlParser::endDocument )
, skipAction( skipMap )
, noContentAction(
noContentMap,
&OpmlParser::beginNoElement,
0,
&OpmlParser::readNoCharacters )
, opmlAction(
opmlMap,
&OpmlParser::beginOpml )
, headAction(
headMap,
0,
&OpmlParser::endHead )
, titleAction(
textMap,
&OpmlParser::beginText,
&OpmlParser::endTitle,
&OpmlParser::readCharacters )
, bodyAction( bodyMap )
, outlineAction(
outlineMap,
&OpmlParser::beginOutline,
&OpmlParser::endOutline )
{
// known elements:
knownElements[ "opml" ] = Opml;
knownElements[ "html" ] = Html;
knownElements[ "HTML" ] = Html;
knownElements[ "head" ] = Head;
knownElements[ "title" ] = Title;
knownElements[ "dateCreated" ] = DateCreated;
knownElements[ "dateModified" ] = DateModified;
knownElements[ "ownerName" ] = OwnerName;
knownElements[ "ownerEmail" ] = OwnerEmail;
knownElements[ "ownerId" ] = OwnerId;
knownElements[ "docs" ] = Docs;
knownElements[ "expansionState" ] = ExpansionState;
knownElements[ "vertScrollState" ] = VertScrollState;
knownElements[ "windowTop" ] = WindowTop;
knownElements[ "windowLeft" ] = WindowLeft;
knownElements[ "windowBottom" ] = WindowBottom;
knownElements[ "windowRight" ] = WindowRight;
knownElements[ "body" ] = Body;
knownElements[ "outline" ] = Outline;
// before start document/after end document
rootMap.insert( Document, &docAction );
// parse document
docMap.insert( Opml, &opmlAction );
// docMap.insert( Html, &htmlAction );
// parse <opml>
opmlMap.insert( Head, &headAction );
opmlMap.insert( Body, &bodyAction );
// parse <head>
headMap.insert( Title, &titleAction );
headMap.insert( DateCreated, &skipAction );
headMap.insert( DateModified, &skipAction );
headMap.insert( OwnerName, &skipAction );
headMap.insert( OwnerEmail, &skipAction );
headMap.insert( OwnerId, &skipAction );
headMap.insert( Docs, &skipAction );
headMap.insert( ExpansionState, &skipAction );
headMap.insert( VertScrollState, &skipAction );
headMap.insert( WindowTop, &skipAction );
headMap.insert( WindowLeft, &skipAction );
headMap.insert( WindowBottom, &skipAction );
headMap.insert( WindowRight, &skipAction );
// parse <body>
bodyMap.insert( Outline, &outlineAction );
// parse <outline> in case of sub-elements
outlineMap.insert( Outline, &outlineAction );
// skip elements
skipMap.insert( Any, &skipAction );
}
OpmlParser::ElementType
OpmlParser::elementType() const
{
if( isEndDocument() || isStartDocument() )
return Document;
if( isCDATA() || isCharacters() )
return CharacterData;
ElementType elementType = sd.knownElements[ QXmlStreamReader::name().toString()];
return elementType;
}
bool
OpmlParser::read()
{
m_buffer.clear();
m_actionStack.clear();
m_actionStack.push( &( OpmlParser::sd.startAction ) );
setNamespaceProcessing( false );
return continueRead();
}
bool
OpmlParser::continueRead()
{
// this is some kind of pushdown automata
// with this it should be possible to parse feeds in parallel
// without using threads
DEBUG_BLOCK
while( !atEnd() && error() != CustomError )
{
TokenType token = readNext();
if( error() == PrematureEndOfDocumentError && m_transferJob )
return true;
if( hasError() )
{
emit doneParsing();
return false;
}
if( m_actionStack.isEmpty() )
{
debug() << "expected element on stack!";
return false;
}
const Action* action = m_actionStack.top();
const Action* subAction = 0;
switch( token )
{
case Invalid:
{
debug() << "invalid token received at line " << lineNumber();
debug() << "Error:\n" << errorString();
return false;
}
case StartDocument:
case StartElement:
subAction = action->actionMap()[ elementType() ];
if( !subAction )
subAction = action->actionMap()[ Any ];
if( !subAction )
subAction = &( OpmlParser::sd.skipAction );
m_actionStack.push( subAction );
subAction->begin( this );
break;
case EndDocument:
case EndElement:
action->end( this );
if( m_actionStack.pop() != action )
{
debug() << "popped other element than expected!";
}
break;
case Characters:
if( !isWhitespace() || isCDATA() )
{
action->characters( this );
}
// ignoreable whitespaces
case Comment:
case EntityReference:
case ProcessingInstruction:
case DTD:
case NoToken:
// ignore
break;
}
}
return !hasError();
}
void
OpmlParser::stopWithError( const QString &message )
{
raiseError( message );
if( m_transferJob )
{
m_transferJob->kill( KJob::EmitResult );
m_transferJob = 0;
}
emit doneParsing();
}
void
OpmlParser::beginOpml()
{
m_outlineStack.clear();
}
void
OpmlParser::beginText()
{
m_buffer.clear();
}
void
OpmlParser::beginOutline()
{
OpmlOutline *parent = m_outlineStack.empty() ? 0 : m_outlineStack.top();
OpmlOutline *outline = new OpmlOutline( parent );
//adding outline to stack
m_outlineStack.push( outline );
if( parent )
{
parent->setHasChildren( true );
parent->addChild( outline );
}
foreach( const QXmlStreamAttribute &attribute, attributes() )
outline->addAttribute( attribute.name().toString(), attribute.value().toString() );
emit outlineParsed( outline );
}
void
OpmlParser::beginNoElement()
{
debug() << "no element expected here, but got element: " << QXmlStreamReader::name();
}
void
OpmlParser::endDocument()
{
emit doneParsing();
}
void
OpmlParser::endHead()
{
emit headerDone();
}
void
OpmlParser::endTitle()
{
m_headerData.insert( "title", m_buffer.trimmed() );
}
void
OpmlParser::endOutline()
{
OpmlOutline *outline = m_outlineStack.pop();
if( m_outlineStack.isEmpty() )
m_outlines << outline;
}
void
OpmlParser::readCharacters()
{
m_buffer += text();
}
void
OpmlParser::readNoCharacters()
{
DEBUG_BLOCK
debug() << "no characters expected here";
}
diff --git a/src/SvgHandler.cpp b/src/SvgHandler.cpp
index 3c8f21fa04..845e61a2d0 100644
--- a/src/SvgHandler.cpp
+++ b/src/SvgHandler.cpp
@@ -1,473 +1,473 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2008 Jeff Mitchell <kde-dev@emailgoeshere.com> *
* Copyright (c) 2009-2013 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SvgHandler"
#include "SvgHandler.h"
#include "App.h"
#include "EngineController.h"
#include "MainWindow.h"
#include "PaletteHandler.h"
#include "SvgTinter.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "covermanager/CoverCache.h"
#include "moodbar/MoodbarManager.h"
#include <KColorScheme>
#include <KColorUtils>
#include <KStandardDirs>
#include <QHash>
#include <QPainter>
#include <QPalette>
#include <QReadLocker>
#include <QStyleOptionSlider>
#include <QWriteLocker>
namespace The {
static SvgHandler* s_SvgHandler_instance = 0;
SvgHandler* svgHandler()
{
if( !s_SvgHandler_instance )
s_SvgHandler_instance = new SvgHandler();
return s_SvgHandler_instance;
}
}
SvgHandler::SvgHandler( QObject* parent )
: QObject( parent )
, m_cache( new KImageCache( "Amarok-pixmaps", 20 * 1024 ) )
, m_themeFile( "amarok/images/default-theme-clean.svg" ) // //use default theme
, m_customTheme( false )
{
DEBUG_BLOCK
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), this, SLOT(discardCache()) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &SvgHandler::discardCache );
}
SvgHandler::~SvgHandler()
{
DEBUG_BLOCK
delete m_cache;
qDeleteAll( m_renderers );
m_renderers.clear();
The::s_SvgHandler_instance = 0;
}
bool SvgHandler::loadSvg( const QString& name )
{
const QString &svgFilename = !m_customTheme ? KStandardDirs::locate( "data", name ) : name;
QSvgRenderer *renderer = new QSvgRenderer( The::svgTinter()->tint( svgFilename ) );
if ( !renderer->isValid() )
{
debug() << "Bluddy 'ell mateys, aye canna' load ya Ess Vee Gee at " << svgFilename;
delete renderer;
return false;
}
QWriteLocker writeLocker( &m_lock );
if( m_renderers[name] )
delete m_renderers[name];
m_renderers[name] = renderer;
return true;
}
QSvgRenderer* SvgHandler::getRenderer( const QString& name )
{
QReadLocker readLocker( &m_lock );
if( ! m_renderers[name] )
{
readLocker.unlock();
if( !loadSvg( name ) )
{
QWriteLocker writeLocker( &m_lock );
m_renderers[name] = new QSvgRenderer();
}
readLocker.relock();
}
return m_renderers[name];
}
QSvgRenderer * SvgHandler::getRenderer()
{
return getRenderer( m_themeFile );
}
QPixmap SvgHandler::renderSvg( const QString &name,
const QString& keyname,
int width,
int height,
const QString& element,
bool skipCache,
const qreal opacity )
{
QString key;
if( !skipCache )
{
key = QString("%1:%2x%3")
.arg( keyname )
.arg( width )
.arg( height );
}
QPixmap pixmap;
if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
{
pixmap = QPixmap( width, height );
pixmap.fill( Qt::transparent );
QReadLocker readLocker( &m_lock );
if( ! m_renderers[name] )
{
readLocker.unlock();
if( !loadSvg( name ) )
{
return pixmap;
}
readLocker.relock();
}
QPainter pt( &pixmap );
pt.setOpacity( opacity );
if ( element.isEmpty() )
m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
else
m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
if( !skipCache )
m_cache->insertPixmap( key, pixmap );
}
return pixmap;
}
QPixmap SvgHandler::renderSvg(const QString & keyname, int width, int height, const QString & element, bool skipCache, const qreal opacity )
{
return renderSvg( m_themeFile, keyname, width, height, element, skipCache, opacity );
}
QPixmap SvgHandler::renderSvgWithDividers(const QString & keyname, int width, int height, const QString & element)
{
const QString key = QString("%1:%2x%3-div")
.arg( keyname )
.arg( width )
.arg( height );
QPixmap pixmap;
if ( !m_cache->findPixmap( key, &pixmap ) ) {
// debug() << QString("svg %1 not in cache...").arg( key );
pixmap = QPixmap( width, height );
pixmap.fill( Qt::transparent );
QString name = m_themeFile;
QReadLocker readLocker( &m_lock );
if( ! m_renderers[name] )
{
readLocker.unlock();
if( ! loadSvg( name ) )
{
return pixmap;
}
readLocker.relock();
}
QPainter pt( &pixmap );
if ( element.isEmpty() )
m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
else
m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
//add dividers. 5% spacing on each side
int margin = width / 20;
m_renderers[name]->render( &pt, "divider_top", QRectF( margin, 0 , width - 1 * margin, 1 ) );
m_renderers[name]->render( &pt, "divider_bottom", QRectF( margin, height - 1 , width - 2 * margin, 1 ) );
m_cache->insertPixmap( key, pixmap );
}
return pixmap;
}
void SvgHandler::reTint()
{
The::svgTinter()->init();
if ( !loadSvg( m_themeFile ))
warning() << "Unable to load theme file: " << m_themeFile;
emit retinted();
}
QString SvgHandler::themeFile()
{
return m_themeFile;
}
void SvgHandler::setThemeFile( const QString & themeFile )
{
DEBUG_BLOCK
debug() << "got new theme file: " << themeFile;
m_themeFile = themeFile;
m_customTheme = true;
discardCache();
}
void SvgHandler::discardCache()
{
//redraw entire app....
reTint();
m_cache->clear();
App::instance()->mainWindow()->update();
}
QPixmap
SvgHandler::imageWithBorder( Meta::AlbumPtr album, int size, int borderWidth )
{
const int imageSize = size - ( borderWidth * 2 );
const QString &loc = album->imageLocation( imageSize ).url();
const QString &key = !loc.isEmpty() ? loc : album->name();
return addBordersToPixmap( The::coverCache()->getCover( album, imageSize ), borderWidth, key );
}
QPixmap SvgHandler::addBordersToPixmap( const QPixmap &orgPixmap, int borderWidth, const QString &name, bool skipCache )
{
int newWidth = orgPixmap.width() + borderWidth * 2;
int newHeight = orgPixmap.height() + borderWidth *2;
QString key;
if( !skipCache )
{
key = QString("%1:%2x%3b%4")
.arg( name )
.arg( newWidth )
.arg( newHeight )
.arg( borderWidth );
}
QPixmap pixmap;
if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
{
// Cache miss! We need to create the pixmap
// if skipCache is true, we might actually already have fetched the image, including borders from the cache....
// so we really need to create a blank pixmap here as well, to not pollute the cached pixmap
pixmap = QPixmap( newWidth, newHeight );
pixmap.fill( Qt::transparent );
QReadLocker readLocker( &m_lock );
if( !m_renderers[m_themeFile] )
{
readLocker.unlock();
if( !loadSvg( m_themeFile ) )
{
return pixmap;
}
readLocker.relock();
}
QPainter pt( &pixmap );
pt.drawPixmap( borderWidth, borderWidth, orgPixmap.width(), orgPixmap.height(), orgPixmap );
m_renderers[m_themeFile]->render( &pt, "cover_border_topleft", QRectF( 0, 0, borderWidth, borderWidth ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_top", QRectF( borderWidth, 0, orgPixmap.width(), borderWidth ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_topright", QRectF( newWidth - borderWidth , 0, borderWidth, borderWidth ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_right", QRectF( newWidth - borderWidth, borderWidth, borderWidth, orgPixmap.height() ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_bottomright", QRectF( newWidth - borderWidth, newHeight - borderWidth, borderWidth, borderWidth ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_bottom", QRectF( borderWidth, newHeight - borderWidth, orgPixmap.width(), borderWidth ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_bottomleft", QRectF( 0, newHeight - borderWidth, borderWidth, borderWidth ) );
m_renderers[m_themeFile]->render( &pt, "cover_border_left", QRectF( 0, borderWidth, borderWidth, orgPixmap.height() ) );
if( !skipCache )
m_cache->insertPixmap( key, pixmap );
}
return pixmap;
}
#if 0
void SvgHandler::paintCustomSlider( QPainter *p, int x, int y, int width, int height, qreal percentage, bool active )
{
int knobSize = height - 4;
int sliderRange = width - ( knobSize + 4 );
int knobRelPos = x + sliderRange * percentage + 2;
int knobY = y + ( height - knobSize ) / 2 + 1;
int sliderY = y + ( height / 2 ) - 1;
//first draw the played part
p->drawPixmap( x, sliderY,
renderSvg(
"new_slider_top_played",
width, 2,
"new_slider_top_played" ),
0, 0, knobRelPos - x, 2 );
//and then the unplayed part
p->drawPixmap( knobRelPos + 1, sliderY,
renderSvg(
"new_slider_top",
width, 2,
"new_slider_top" ),
knobRelPos + 1 - x, 0, -1, 2 );
//and then the bottom
p->drawPixmap( x, sliderY + 2,
renderSvg(
"new_slider_bottom",
width, 2,
"new_slider_bottom" ) );
//draw end markers
p->drawPixmap( x, y,
renderSvg(
"new_slider_end",
2, height,
"new_slider_end" ) );
p->drawPixmap( x + width - 2, y,
renderSvg(
"new_slider_end",
2, height,
"new_slider_endr" ) );
if ( active )
p->drawPixmap( knobRelPos, knobY,
renderSvg(
"new_slider_knob_active",
knobSize, knobSize,
"new_slider_knob_active" ) );
else
p->drawPixmap( knobRelPos, knobY,
renderSvg(
"new_slider_knob",
knobSize, knobSize,
"new_slider_knob" ) );
}
#endif
QRect SvgHandler::sliderKnobRect( const QRect &slider, qreal percent, bool inverse ) const
{
if ( inverse )
percent = 1.0 - percent;
const int knobSize = slider.height() - 4;
QRect ret( 0, 0, knobSize, knobSize );
ret.moveTo( slider.x() + qRound( ( slider.width() - knobSize ) * percent ), slider.y() + 1 );
return ret;
}
// Experimental, using a mockup from Nuno Pinheiro (new_slider_nuno)
void SvgHandler::paintCustomSlider( QPainter *p, QStyleOptionSlider *slider, qreal percentage, bool paintMoodbar )
{
int sliderHeight = slider->rect.height() - 6;
const bool inverse = ( slider->orientation == Qt::Vertical ) ? slider->upsideDown :
( (slider->direction == Qt::RightToLeft) != slider->upsideDown );
QRect knob = sliderKnobRect( slider->rect, percentage, inverse );
QPoint pt = slider->rect.topLeft() + QPoint( 0, 2 );
//debug() << "rel: " << knobRelPos << ", width: " << width << ", height:" << height << ", %: " << percentage;
//if we should paint moodbar, paint this as the bottom layer
bool moodbarPainted = false;
if ( paintMoodbar )
{
Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
if ( currentTrack )
{
if( The::moodbarManager()->hasMoodbar( currentTrack ) )
{
QPixmap moodbar = The::moodbarManager()->getMoodbar( currentTrack, slider->rect.width() - sliderHeight, sliderHeight, inverse );
p->drawPixmap( pt, renderSvg( "moodbar_end_left", sliderHeight / 2, sliderHeight, "moodbar_end_left" ) );
pt.rx() += sliderHeight / 2;
p->drawPixmap( pt, moodbar );
pt.rx() += slider->rect.width() - sliderHeight;
p->drawPixmap( pt, renderSvg( "moodbar_end_right", sliderHeight / 2, sliderHeight, "moodbar_end_right" ) );
moodbarPainted = true;
}
}
}
if( !moodbarPainted )
{
// Draw the slider background in 3 parts
p->drawPixmap( pt, renderSvg( "progress_slider_left", sliderHeight, sliderHeight, "progress_slider_left" ) );
pt.rx() += sliderHeight;
QRect midRect(pt, QSize(slider->rect.width() - sliderHeight * 2, sliderHeight) );
p->drawTiledPixmap( midRect, renderSvg( "progress_slider_mid", 32, sliderHeight, "progress_slider_mid" ) );
pt = midRect.topRight() + QPoint( 1, 0 );
p->drawPixmap( pt, renderSvg( "progress_slider_right", sliderHeight, sliderHeight, "progress_slider_right" ) );
//draw the played background.
int playedBarHeight = sliderHeight - 6;
int sizeOfLeftPlayed = qBound( 0, inverse ? slider->rect.right() - knob.right() + 2 :
knob.x() - 2, playedBarHeight );
if( sizeOfLeftPlayed > 0 )
{
QPoint tl, br;
if ( inverse )
{
tl = knob.topRight() + QPoint( -5, 5 ); // 5px x padding to avoid a "gap" between it and the top and bottom of the round knob.
br = slider->rect.topRight() + QPoint( -3, 5 + playedBarHeight - 1 );
QPixmap rightEnd = renderSvg( "progress_slider_played_right", playedBarHeight, playedBarHeight, "progress_slider_played_right" );
p->drawPixmap( br.x() - rightEnd.width() + 1, tl.y(), rightEnd, qMax(0, rightEnd.width() - (sizeOfLeftPlayed + 3)), 0, sizeOfLeftPlayed + 3, playedBarHeight );
br.rx() -= playedBarHeight;
}
else
{
tl = slider->rect.topLeft() + QPoint( 3, 5 );
br = QPoint( knob.x() + 5, tl.y() + playedBarHeight - 1 );
QPixmap leftEnd = renderSvg( "progress_slider_played_left", playedBarHeight, playedBarHeight, "progress_slider_played_left" );
p->drawPixmap( tl.x(), tl.y(), leftEnd, 0, 0, sizeOfLeftPlayed + 3, playedBarHeight );
tl.rx() += playedBarHeight;
}
if ( sizeOfLeftPlayed == playedBarHeight )
p->drawTiledPixmap( QRect(tl, br), renderSvg( "progress_slider_played_mid", 32, playedBarHeight, "progress_slider_played_mid" ) );
}
}
if ( slider->state & QStyle::State_Enabled )
{ // Draw the knob (handle)
const char *string = ( slider->activeSubControls & QStyle::SC_SliderHandle ) ?
"slider_knob_200911_active" : "slider_knob_200911";
p->drawPixmap( knob.topLeft(), renderSvg( string, knob.width(), knob.height(), string ) );
}
}
diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp
index 0190632a83..e6077f0b82 100644
--- a/src/TrayIcon.cpp
+++ b/src/TrayIcon.cpp
@@ -1,338 +1,337 @@
/****************************************************************************************
* Copyright (c) 2003 Stanislav Karchebny <berkus@users.sf.net> *
* Copyright (c) 2003 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2004 Enrico Ros <eros.kde@email.it> *
* Copyright (c) 2006 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2009-2011 Kevin Funk <krf@electrostorm.net> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TrayIcon.h"
#include "App.h"
#include "EngineController.h"
#include "GlobalCurrentTrackActions.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/capabilities/BookmarkThisCapability.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "core/support/Amarok.h"
#include "playlist/PlaylistActions.h"
#include <KLocalizedString>
#include <KStandardDirs>
#include <KIconLoader>
#include <QAction>
#include <QFontMetrics>
#include <QMenu>
#include <QPixmap>
#include <QToolTip>
#ifdef Q_WS_MAC
extern void qt_mac_set_dock_menu(QMenu *);
#endif
Amarok::TrayIcon::TrayIcon( QObject *parent )
: KStatusNotifierItem( parent )
, m_track( The::engineController()->currentTrack() )
{
PERF_LOG( "Beginning TrayIcon Constructor" );
KActionCollection* const ac = Amarok::actionCollection();
setStatus( KStatusNotifierItem::Active );
// Remove the "Configure Amarok..." action, as it makes no sense in the tray menu
const QString preferences = KStandardAction::name( KStandardAction::Preferences );
contextMenu()->removeAction( ac->action( preferences ) );
PERF_LOG( "Before adding actions" );
#ifdef Q_WS_MAC
// Add these functions to the dock icon menu in OS X
qt_mac_set_dock_menu( contextMenu() );
contextMenu()->addAction( ac->action( "playlist_playmedia" ) );
contextMenu()->addSeparator();
#endif
contextMenu()->addAction( ac->action( "prev" ) );
contextMenu()->addAction( ac->action( "play_pause" ) );
contextMenu()->addAction( ac->action( "stop" ) );
contextMenu()->addAction( ac->action( "next" ) );
contextMenu()->addSeparator();
contextMenu()->setObjectName( "TrayIconContextMenu" );
PERF_LOG( "Initializing system tray icon" );
setIconByName( "amarok" );
updateOverlayIcon();
updateToolTipIcon();
updateMenu();
const EngineController* engine = The::engineController();
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(trackPlaying(Meta::TrackPtr)) );
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped()) );
- connect( engine, SIGNAL(paused()),
- this, SLOT(paused()) );
+ connect( engine, &EngineController::trackPlaying,
+ this, &TrayIcon::trackPlaying );
+ connect( engine, &EngineController::stopped,
+ this, &TrayIcon::stopped );
+ connect( engine, &EngineController::paused,
+ this, &TrayIcon::paused );
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- this, SLOT(metadataChanged(Meta::TrackPtr)) );
+ connect( engine, &EngineController::trackMetadataChanged,
+ this, &TrayIcon::trackMetadataChanged );
- connect( engine, SIGNAL(albumMetadataChanged(Meta::AlbumPtr)),
- this, SLOT(metadataChanged(Meta::AlbumPtr)) );
+ connect( engine, &EngineController::albumMetadataChanged,
+ this, &TrayIcon::albumMetadataChanged );
- connect( engine, SIGNAL(volumeChanged(int)),
- this, SLOT(updateToolTip()) );
+ connect( engine, &EngineController::volumeChanged,
+ this, &TrayIcon::updateToolTip );
- connect( engine, SIGNAL(muteStateChanged(bool)),
- this, SLOT(updateToolTip()) );
+ connect( engine, &EngineController::muteStateChanged,
+ this, &TrayIcon::updateToolTip );
- connect( engine, SIGNAL(playbackStateChanged()),
- this, SLOT(updateOverlayIcon()) );
+ connect( engine, &EngineController::playbackStateChanged,
+ this, &TrayIcon::updateOverlayIcon );
- connect( this, SIGNAL(scrollRequested(int,Qt::Orientation)),
- SLOT(slotScrollRequested(int,Qt::Orientation)) );
- connect( this, SIGNAL(secondaryActivateRequested(QPoint)),
- The::engineController(), SLOT(playPause()) );
+ connect( this, &TrayIcon::scrollRequested, this, &TrayIcon::slotScrollRequested );
+ connect( this, &TrayIcon::secondaryActivateRequested,
+ The::engineController(), &EngineController::playPause );
}
void
Amarok::TrayIcon::updateToolTipIcon()
{
updateToolTip(); // the normal update
if( m_track )
{
if( m_track->album() && m_track->album()->hasImage() )
{
QPixmap image = The::svgHandler()->imageWithBorder( m_track->album(), KIconLoader::SizeLarge, 5 );
setToolTipIconByPixmap( image );
}
else
{
setToolTipIconByName( "amarok" );
}
}
else
{
setToolTipIconByName( "amarok" );
}
}
void
Amarok::TrayIcon::updateToolTip()
{
if( m_track )
{
setToolTipTitle( The::engineController()->prettyNowPlaying( false ) );
QStringList tooltip;
QString volume;
if ( The::engineController()->isMuted() )
{
volume = i18n( "Muted" );
}
else
{
volume = i18n( "%1%", The::engineController()->volume() );
}
tooltip << i18n( "<i>Volume: %1</i>", volume );
Meta::StatisticsPtr statistics = m_track->statistics();
const float score = statistics->score();
if( score > 0.f )
{
tooltip << i18n( "Score: %1", QString::number( score, 'f', 2 ) );
}
const int rating = statistics->rating();
if( rating > 0 )
{
QString stars;
for( int i = 0; i < rating / 2; ++i )
stars += QString( "<img src=\"%1\" height=\"%2\" width=\"%3\">" )
.arg( KStandardDirs::locate( "data", "amarok/images/star.png" ) )
.arg( QFontMetrics( QToolTip::font() ).height() )
.arg( QFontMetrics( QToolTip::font() ).height() );
if( rating % 2 )
stars += QString( "<img src=\"%1\" height=\"%2\" width=\"%3\">" )
.arg( KStandardDirs::locate( "data", "amarok/images/smallstar.png" ) )
.arg( QFontMetrics( QToolTip::font() ).height() )
.arg( QFontMetrics( QToolTip::font() ).height() );
tooltip << i18n( "Rating: %1", stars );
}
const int count = statistics->playCount();
if( count > 0 )
{
tooltip << i18n( "Play count: %1", count );
}
const QDateTime lastPlayed = statistics->lastPlayed();
tooltip << i18n( "Last played: %1", Amarok::verboseTimeSince( lastPlayed ) );
setToolTipSubTitle( tooltip.join("<br>") );
}
else
{
setToolTipTitle( pApp->applicationDisplayName() );
setToolTipSubTitle( The::engineController()->prettyNowPlaying( false ) );
}
}
void
Amarok::TrayIcon::trackPlaying( Meta::TrackPtr track )
{
m_track = track;
updateMenu();
updateToolTipIcon();
}
void
Amarok::TrayIcon::paused()
{
updateToolTipIcon();
}
void
Amarok::TrayIcon::stopped()
{
m_track = 0;
updateMenu(); // remove custom track actions on stop
updateToolTipIcon();
}
void
-Amarok::TrayIcon::metadataChanged( Meta::TrackPtr track )
+Amarok::TrayIcon::trackMetadataChanged( Meta::TrackPtr track )
{
Q_UNUSED( track )
updateToolTip();
updateMenu();
}
void
-Amarok::TrayIcon::metadataChanged( Meta::AlbumPtr album )
+Amarok::TrayIcon::albumMetadataChanged( Meta::AlbumPtr album )
{
Q_UNUSED( album )
updateToolTipIcon();
updateMenu();
}
void
Amarok::TrayIcon::slotScrollRequested( int delta, Qt::Orientation orientation )
{
Q_UNUSED( orientation )
The::engineController()->increaseVolume( delta / Amarok::VOLUME_SENSITIVITY );
}
QAction*
Amarok::TrayIcon::action( const QString& name, QMap<QString, QAction*> actionByName )
{
QAction* action = 0L;
if ( !name.isEmpty() )
action = actionByName.value(name);
return action;
}
void
Amarok::TrayIcon::updateMenu()
{
foreach( QAction* action, m_extraActions )
{
contextMenu()->removeAction( action );
// -- delete actions without parent (e.g. the ones from the capabilities)
if( action && !action->parent() )
{
delete action;
}
}
QMap<QString, QAction*> actionByName;
foreach (QAction* action, actionCollection())
{
actionByName.insert(action->text(), action);
}
m_extraActions.clear();
contextMenu()->removeAction( m_separator.data() );
delete m_separator.data();
if( m_track )
{
foreach( QAction *action, The::globalCurrentTrackActions()->actions() )
m_extraActions.append( action );
QScopedPointer<Capabilities::ActionsCapability> ac( m_track->create<Capabilities::ActionsCapability>() );
if( ac )
{
QList<QAction*> actions = ac->actions();
foreach( QAction *action, actions )
m_extraActions.append( action );
}
QScopedPointer<Capabilities::BookmarkThisCapability> btc( m_track->create<Capabilities::BookmarkThisCapability>() );
if( btc )
{
m_extraActions.append( btc->bookmarkAction() );
}
}
// second statement checks if the menu has already been populated (first startup), if not: do it
if( m_extraActions.count() > 0 ||
contextMenu()->actions().last() != actionByName.value( "file_quit" ) )
{
// remove the 2 bottom items, so we can push them to the bottom again
contextMenu()->removeAction( action( "file_quit", actionByName ) );
contextMenu()->removeAction( action( "minimizeRestore", actionByName ) );
foreach( QAction* action, m_extraActions )
contextMenu()->addAction( action );
m_separator = contextMenu()->addSeparator();
// readd
contextMenu()->addAction( action( "minimizeRestore", actionByName ) );
contextMenu()->addAction( action( "file_quit", actionByName ) );
}
}
void
Amarok::TrayIcon::updateOverlayIcon()
{
if( The::engineController()->isPlaying() )
setOverlayIconByName( "media-playback-start" );
else if( The::engineController()->isPaused() )
setOverlayIconByName( "media-playback-pause" );
else
setOverlayIconByName( QString() );
}
diff --git a/src/TrayIcon.h b/src/TrayIcon.h
index 2731e36c29..ef04083a38 100644
--- a/src/TrayIcon.h
+++ b/src/TrayIcon.h
@@ -1,62 +1,62 @@
/****************************************************************************************
* Copyright (c) 2003 Stanislav Karchebny <berkus@users.sf.net> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2009-2011 Kevin Funk <krf@electrostorm.net> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_TRAYICON_H
#define AMAROK_TRAYICON_H
#include <KStatusNotifierItem> // baseclass
#include "core/meta/forward_declarations.h"
#include "core/support/SmartPointerList.h"
#include <QAction>
#include <QWeakPointer>
namespace Amarok {
class TrayIcon : public KStatusNotifierItem
{
Q_OBJECT
public:
TrayIcon( QObject *parent );
private Q_SLOTS:
void updateOverlayIcon();
void updateToolTipIcon();
void updateToolTip();
void updateMenu();
void trackPlaying( Meta::TrackPtr track );
void stopped();
void paused();
- void metadataChanged( Meta::TrackPtr track );
- void metadataChanged( Meta::AlbumPtr album );
+ void trackMetadataChanged( Meta::TrackPtr track );
+ void albumMetadataChanged( Meta::AlbumPtr album );
void slotScrollRequested( int delta, Qt::Orientation orientation );
QAction* action( const QString& name, QMap<QString, QAction*> actionByName );
private:
Meta::TrackPtr m_track;
SmartPointerList<QAction> m_extraActions;
QWeakPointer<QAction> m_separator;
};
}
#endif // AMAROK_TRAYICON_H
diff --git a/src/aboutdialog/ExtendedAboutDialog.cpp b/src/aboutdialog/ExtendedAboutDialog.cpp
index 8c40b6a8d3..e39d5a9bd6 100644
--- a/src/aboutdialog/ExtendedAboutDialog.cpp
+++ b/src/aboutdialog/ExtendedAboutDialog.cpp
@@ -1,419 +1,429 @@
/****************************************************************************************
* Copyright (c) 2007 Urs Wolfer <uwolfer at kde.org> *
* Copyright (c) 2008 Friedrich W. H. Kossebau <kossebau@kde.org> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* Parts of this class have been take from the KAboutApplication class, which was *
* Copyright (c) 2000 Waldo Bastian (bastian@kde.org) and Espen Sand (espen@kde.org) *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ExtendedAboutDialog.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "OcsPersonItem.h"
#include "libattica-ocsclient/providerinitjob.h"
#include <QLabel>
#include <QLayout>
#include <QPushButton>
#include <QScrollBar>
#include <QTabWidget>
#include <qapplication.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <ktextbrowser.h>
#include <ktitlewidget.h>
#include <solid/networking.h>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include <kdeversion.h>
void ExtendedAboutDialog::Private::_k_showLicense( const QString &number )
{
QDialog *dialog = new QDialog(q);
QWidget *mainWidget = new QWidget;
dialog->setWindowTitle(i18n("License Agreement"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
- dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
- dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
+ dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
+ dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
const QFont font = KGlobalSettings::fixedFont();
QFontMetrics metrics(font);
const QString licenseText = aboutData->licenses().at(number.toInt()).text();
KTextBrowser *licenseBrowser = new KTextBrowser;
licenseBrowser->setFont(font);
licenseBrowser->setLineWrapMode(QTextEdit::NoWrap);
licenseBrowser->setText(licenseText);
QVBoxLayout *mainLayout = new QVBoxLayout;
dialog->setLayout(mainLayout);
mainLayout->addWidget(licenseBrowser);
mainLayout->addWidget(mainWidget);
mainLayout->addWidget(buttonBox);
// try to set up the dialog such that the full width of the
// document is visible without horizontal scroll-bars being required
const qreal idealWidth = licenseBrowser->document()->idealWidth()
+ (2 * QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin))
+ licenseBrowser->verticalScrollBar()->width() * 2;
//TODO KF5:PM_DefaultChildMargin is obsolete. Look in QStyle docs for correctly replacing it.
// try to allow enough height for a reasonable number of lines to be shown
const int idealHeight = metrics.height() * 30;
dialog->resize(dialog->sizeHint().expandedTo(QSize((int)idealWidth,idealHeight)));
dialog->show();
}
ExtendedAboutDialog::ExtendedAboutDialog(const KAboutData about, const OcsData *ocsData, QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
DEBUG_BLOCK
const KAboutData *aboutData = &about;
d->aboutData = aboutData;
if (!aboutData) {
QLabel *errorLabel = new QLabel(i18n("<qt>No information available.<br />"
"The supplied KAboutData object does not exist.</qt>"), this);
errorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(errorLabel);
return;
}
if( !ocsData )
{
QLabel *errorLabel = new QLabel(i18n("<qt>No information available.<br />"
"The supplied OcsData object does not exist.</qt>"), this);
errorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(errorLabel);
return;
}
m_ocsData = *ocsData;
setWindowTitle(i18n("About %1", aboutData->displayName()));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &ExtendedAboutDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &ExtendedAboutDialog::reject);
buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
setModal(false);
//Set up the title widget...
KTitleWidget *titleWidget = new KTitleWidget(this);
QIcon windowIcon;
if (!aboutData->programIconName().isEmpty()) {
windowIcon = QIcon::fromTheme(aboutData->programIconName());
} else {
windowIcon = qApp->windowIcon();
}
titleWidget->setPixmap(windowIcon.pixmap(64, 64), KTitleWidget::ImageLeft);
if (aboutData->programLogo().canConvert<QPixmap>())
titleWidget->setPixmap(aboutData->programLogo().value<QPixmap>(), KTitleWidget::ImageLeft);
else if (aboutData->programLogo().canConvert<QImage>())
titleWidget->setPixmap(QPixmap::fromImage(aboutData->programLogo().value<QImage>()), KTitleWidget::ImageLeft);
titleWidget->setText(i18n("<html><font size=\"5\">%1</font><br /><b>Version %2</b><br />Using KDE %3</html>",
aboutData->displayName(), aboutData->version(), KDE::versionString()));
//Now let's add the tab bar...
QTabWidget *tabWidget = new QTabWidget;
tabWidget->setUsesScrollButtons(false);
//Set up the first page...
QString aboutPageText = aboutData->shortDescription() + '\n';
if (!aboutData->otherText().isEmpty())
aboutPageText += '\n' + aboutData->otherText() + '\n';
if (!aboutData->copyrightStatement().isEmpty())
aboutPageText += '\n' + aboutData->copyrightStatement() + '\n';
if (!aboutData->homepage().isEmpty())
aboutPageText += '\n' + QString("<a href=\"%1\">%1</a>").arg(aboutData->homepage()) + '\n';
aboutPageText = aboutPageText.trimmed();
QLabel *aboutLabel = new QLabel;
aboutLabel->setWordWrap(true);
aboutLabel->setOpenExternalLinks(true);
aboutLabel->setText(aboutPageText.replace('\n', "<br />"));
aboutLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
QVBoxLayout *aboutLayout = new QVBoxLayout;
aboutLayout->addStretch();
aboutLayout->addWidget(aboutLabel);
const int licenseCount = aboutData->licenses().count();
debug()<< "About to show license stuff";
debug()<< "License count is"<<licenseCount;
for (int i = 0; i < licenseCount; ++i) {
const KAboutLicense &license = aboutData->licenses().at(i);
QLabel *showLicenseLabel = new QLabel;
showLicenseLabel->setText(QString("<a href=\"%1\">%2</a>").arg(QString::number(i),
i18n("License: %1",
license.name(KAboutLicense::FullName))));
showLicenseLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
- connect(showLicenseLabel, SIGNAL(linkActivated(QString)), this, SLOT(_k_showLicense(QString)));
+ connect(showLicenseLabel, &QLabel::linkActivated, this, &ExtendedAboutDialog::showLicense);
aboutLayout->addWidget(showLicenseLabel);
}
debug()<<"License widget added";
aboutLayout->addStretch();
QWidget *aboutWidget = new QWidget(this);
aboutWidget->setLayout(aboutLayout);
tabWidget->addTab(aboutWidget, i18n("&About"));
//Stuff needed by both Authors and Credits pages:
QPixmap openDesktopPixmap = QPixmap( KStandardDirs::locate( "data", "amarok/images/opendesktop-22.png" ) );
QIcon openDesktopIcon = QIcon( openDesktopPixmap );
//And now, the Authors page:
const int authorCount = d->aboutData->authors().count();
if (authorCount)
{
m_authorWidget = new QWidget( this );
QVBoxLayout *authorLayout = new QVBoxLayout( m_authorWidget.data() );
m_showOcsAuthorButton = new AnimatedBarWidget( openDesktopIcon,
i18n( "Get data from openDesktop.org to learn more about the team" ),
"process-working", m_authorWidget.data() );
- connect( m_showOcsAuthorButton.data(), SIGNAL(clicked()), this, SLOT(switchToOcsWidgets()) );
+ connect( m_showOcsAuthorButton.data(), &AnimatedBarWidget::clicked, this, &ExtendedAboutDialog::switchToOcsWidgets );
authorLayout->addWidget( m_showOcsAuthorButton.data() );
if (!aboutData->customAuthorTextEnabled() || !aboutData->customAuthorRichText().isEmpty())
{
QLabel *bugsLabel = new QLabel( m_authorWidget.data() );
bugsLabel->setContentsMargins( 4, 2, 0, 4 );
if (!aboutData->customAuthorTextEnabled())
{
if (aboutData->bugAddress().isEmpty() || aboutData->bugAddress() == "submit@bugs.kde.org")
bugsLabel->setText( i18n("Please use <a href=\"http://bugs.kde.org\">http://bugs.kde.org</a> to report bugs.\n") );
else
{
if(aboutData->authors().count() == 1 && (aboutData->authors().first().emailAddress() == aboutData->bugAddress()))
{
bugsLabel->setText( i18n("Please report bugs to <a href=\"mailto:%1\">%2</a>.\n",
aboutData->authors().first().emailAddress(),
aboutData->authors().first().emailAddress()));
}
else
{
bugsLabel->setText( i18n("Please report bugs to <a href=\"mailto:%1\">%2</a>.\n",
aboutData->bugAddress(), aboutData->bugAddress()));
}
}
}
else
bugsLabel->setText( aboutData->customAuthorRichText() );
authorLayout->addWidget( bugsLabel );
}
m_authorListWidget = new OcsPersonListWidget( d->aboutData->authors(), m_ocsData.authors(), OcsPersonItem::Author, m_authorWidget.data() );
- connect( m_authorListWidget.data(), SIGNAL(switchedToOcs()), m_showOcsAuthorButton.data(), SLOT(stop()) );
- connect( m_authorListWidget.data(), SIGNAL(switchedToOcs()), m_showOcsAuthorButton.data(), SLOT(fold()) );
+ connect( m_authorListWidget.data(), &OcsPersonListWidget::switchedToOcs,
+ m_showOcsAuthorButton.data(), &AnimatedBarWidget::stop );
+ connect( m_authorListWidget.data(), &OcsPersonListWidget::switchedToOcs,
+ m_showOcsAuthorButton.data(), &AnimatedBarWidget::fold );
authorLayout->addWidget( m_authorListWidget.data() );
authorLayout->setMargin( 0 );
authorLayout->setSpacing( 2 );
m_authorWidget.data()->setLayout( authorLayout );
m_authorPageTitle = ( authorCount == 1 ) ? i18n("A&uthor") : i18n("A&uthors");
tabWidget->addTab(m_authorWidget.data(), m_authorPageTitle);
m_isOfflineAuthorWidget = true; //is this still used?
}
//Then the Credits page:
const int creditCount = aboutData->credits().count();
if (creditCount)
{
m_creditWidget = new QWidget( this );
QVBoxLayout *creditLayout = new QVBoxLayout( m_creditWidget.data() );
m_showOcsCreditButton = new AnimatedBarWidget( openDesktopIcon,
i18n( "Get data from openDesktop.org to learn more about contributors" ),
"process-working", m_creditWidget.data() );
- connect( m_showOcsCreditButton.data(), SIGNAL(clicked()), this, SLOT(switchToOcsWidgets()) );
+ connect( m_showOcsCreditButton.data(), &AnimatedBarWidget::clicked, this, &ExtendedAboutDialog::switchToOcsWidgets );
creditLayout->addWidget( m_showOcsCreditButton.data() );
m_creditListWidget = new OcsPersonListWidget( d->aboutData->credits(), m_ocsData.credits(), OcsPersonItem::Contributor, m_creditWidget.data() );
- connect( m_creditListWidget.data(), SIGNAL(switchedToOcs()), m_showOcsCreditButton.data(), SLOT(stop()) );
- connect( m_creditListWidget.data(), SIGNAL(switchedToOcs()), m_showOcsCreditButton.data(), SLOT(fold()) );
+ connect( m_creditListWidget.data(), &OcsPersonListWidget::switchedToOcs,
+ m_showOcsCreditButton.data(), &AnimatedBarWidget::stop );
+ connect( m_creditListWidget.data(), &OcsPersonListWidget::switchedToOcs,
+ m_showOcsCreditButton.data(), &AnimatedBarWidget::fold );
creditLayout->addWidget( m_creditListWidget.data() );
creditLayout->setMargin( 0 );
creditLayout->setSpacing( 2 );
m_creditWidget.data()->setLayout( creditLayout );
tabWidget->addTab( m_creditWidget.data(), i18n("&Contributors"));
m_isOfflineCreditWidget = true; //is this still used?
}
//Finally, the Donors page:
const int donorCount = ocsData->donors()->count();
if (donorCount)
{
m_donorWidget = new QWidget( this );
QVBoxLayout *donorLayout = new QVBoxLayout( m_donorWidget.data() );
m_showOcsDonorButton = new AnimatedBarWidget( openDesktopIcon,
i18n( "Get data from openDesktop.org to learn more about our generous donors" ),
"process-working", m_donorWidget.data() );
- connect( m_showOcsDonorButton.data(), SIGNAL(clicked()), this, SLOT(switchToOcsWidgets()) );
+ connect( m_showOcsDonorButton.data(), &AnimatedBarWidget::clicked, this, &ExtendedAboutDialog::switchToOcsWidgets );
donorLayout->addWidget( m_showOcsDonorButton.data() );
QList< KAboutPerson > donors;
for( QList< QPair< QString, KAboutPerson > >::const_iterator it = m_ocsData.donors()->constBegin();
it != m_ocsData.donors()->constEnd(); ++it )
{
donors << ( *it ).second;
}
m_donorListWidget = new OcsPersonListWidget( donors , m_ocsData.donors(), OcsPersonItem::Contributor, m_donorWidget.data() );
- connect( m_donorListWidget.data(), SIGNAL(switchedToOcs()), m_showOcsDonorButton.data(), SLOT(stop()) );
- connect( m_donorListWidget.data(), SIGNAL(switchedToOcs()), m_showOcsDonorButton.data(), SLOT(fold()) );
+ connect( m_donorListWidget.data(), &OcsPersonListWidget::switchedToOcs, m_showOcsDonorButton.data(), &AnimatedBarWidget::stop );
+ connect( m_donorListWidget.data(), &OcsPersonListWidget::switchedToOcs, m_showOcsDonorButton.data(), &AnimatedBarWidget::fold );
donorLayout->addWidget( m_donorListWidget.data() );
donorLayout->setMargin( 0 );
donorLayout->setSpacing( 2 );
QLabel *roktoberLabel =
new QLabel(i18n("<p>Each year in October the Amarok team organizes a funding "
"drive called <b>Roktober</b>.</p>"
"<p>If you want your name mentioned on this list "
"<a href=\"http://amarok.kde.org/donations\"> donate "
"during Roktober</a> and opt-in.</p>"));
roktoberLabel->setOpenExternalLinks(true);
donorLayout->addWidget(roktoberLabel);
m_donorWidget.data()->setLayout( donorLayout );
tabWidget->addTab( m_donorWidget.data(), i18n("&Donors"));
m_isOfflineDonorWidget = true;
}
//And the translators:
QPalette transparentBackgroundPalette;
transparentBackgroundPalette.setColor( QPalette::Base, Qt::transparent );
transparentBackgroundPalette.setColor( QPalette::Text, transparentBackgroundPalette.color( QPalette::WindowText ) );
const QList<KAboutPerson> translatorList = aboutData->translators();
if(translatorList.count() > 0) {
QString translatorPageText;
QList<KAboutPerson>::ConstIterator it;
for(it = translatorList.begin(); it != translatorList.end(); ++it) {
translatorPageText += QString("<p style=\"margin: 0px;\">%1</p>").arg((*it).name());
if (!(*it).emailAddress().isEmpty())
translatorPageText += QString("<p style=\"margin: 0px; margin-left: 15px;\"><a href=\"mailto:%1\">%1</a></p>").arg((*it).emailAddress());
translatorPageText += "<p style=\"margin: 0px;\">&nbsp;</p>";
}
translatorPageText += KAboutData::aboutTranslationTeam();
KTextBrowser *translatorTextBrowser = new KTextBrowser;
translatorTextBrowser->setFrameStyle(QFrame::NoFrame);
translatorTextBrowser->setPalette(transparentBackgroundPalette);
translatorTextBrowser->setHtml(translatorPageText);
tabWidget->addTab(translatorTextBrowser, i18n("T&ranslation"));
}
//Jam everything together in a layout:
mainLayout->addWidget(titleWidget);
mainLayout->addWidget(tabWidget);
mainLayout->setMargin(0);
mainLayout->addWidget(buttonBox);
resize( QSize( 480, 460 ) );
}
ExtendedAboutDialog::~ExtendedAboutDialog()
{
delete d;
}
void
ExtendedAboutDialog::switchToOcsWidgets()
{
if( !( Solid::Networking::status() == Solid::Networking::Connected ||
Solid::Networking::status() == Solid::Networking::Unknown ) )
{
KMessageBox::error( this, i18n( "Internet connection not available" ), i18n( "Network error" ) );
return;
}
if( m_showOcsAuthorButton )
m_showOcsAuthorButton.data()->animate();
if( m_showOcsCreditButton )
m_showOcsCreditButton.data()->animate();
if( m_showOcsDonorButton )
m_showOcsDonorButton.data()->animate();
AmarokAttica::ProviderInitJob *providerJob = AmarokAttica::Provider::byId( m_ocsData.providerId() );
- connect( providerJob, SIGNAL(result(KJob*)), this, SLOT(onProviderFetched(KJob*)) );
+ connect( providerJob, &AmarokAttica::ProviderInitJob::result, this, &ExtendedAboutDialog::onProviderFetched );
}
void
ExtendedAboutDialog::onProviderFetched( KJob *job )
{
AmarokAttica::ProviderInitJob *providerJob = qobject_cast< AmarokAttica::ProviderInitJob * >( job );
if( providerJob->error() == 0 )
{
debug()<<"Successfully fetched OCS provider"<< providerJob->provider().name();
debug()<<"About to request OCS data";
if( m_authorListWidget )
m_authorListWidget.data()->switchToOcs( providerJob->provider() );
if( m_creditListWidget )
m_creditListWidget.data()->switchToOcs( providerJob->provider() );
if( m_donorListWidget )
m_donorListWidget.data()->switchToOcs( providerJob->provider() );
}
else
warning() << "OCS provider fetch failed";
}
+void
+ExtendedAboutDialog::showLicense(const QString& number)
+{
+ d->_k_showLicense(number);
+}
+
diff --git a/src/aboutdialog/ExtendedAboutDialog.h b/src/aboutdialog/ExtendedAboutDialog.h
index 244b9cb410..06f3fd1b58 100644
--- a/src/aboutdialog/ExtendedAboutDialog.h
+++ b/src/aboutdialog/ExtendedAboutDialog.h
@@ -1,91 +1,90 @@
/****************************************************************************************
* Copyright (c) 2007 Urs Wolfer <uwolfer at kde.org> *
* Copyright (c) 2008 Friedrich W. H. Kossebau <kossebau@kde.org> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* Parts of this class have been take from the KAboutApplication class, which was *
* Copyright (c) 2000 Waldo Bastian (bastian@kde.org) and Espen Sand (espen@kde.org) *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_EXTENDEDABOUTDIALOG_H
#define AMAROK_EXTENDEDABOUTDIALOG_H
#include "core/support/Amarok.h"
#include "amarok_export.h"
#include "App.h"
#include "OcsPersonListWidget.h"
#include "AnimatedBarWidget.h"
#include <QDialog>
#include <QWeakPointer>
class AMAROK_EXPORT ExtendedAboutDialog : public QDialog
{
Q_OBJECT
public:
explicit ExtendedAboutDialog( const KAboutData aboutData, const OcsData *ocsData, QWidget *parent = 0 );
virtual ~ExtendedAboutDialog();
private Q_SLOTS:
void switchToOcsWidgets();
void onProviderFetched( KJob *job );
+ void showLicense( const QString &number );
private:
class Private;
Private* const d;
- Q_PRIVATE_SLOT( d, void _k_showLicense(const QString&) )
-
Q_DISABLE_COPY( ExtendedAboutDialog )
OcsData m_ocsData;
//Authors:
QString m_authorPageTitle;
QWeakPointer<AnimatedBarWidget> m_showOcsAuthorButton;
QWeakPointer<QWidget> m_authorWidget;
QWeakPointer<OcsPersonListWidget> m_authorListWidget;
bool m_isOfflineAuthorWidget;
//Contributors:
QWeakPointer<AnimatedBarWidget> m_showOcsCreditButton;
QWeakPointer<QWidget> m_creditWidget;
QWeakPointer<OcsPersonListWidget> m_creditListWidget;
bool m_isOfflineCreditWidget;
//Donors:
QWeakPointer<AnimatedBarWidget> m_showOcsDonorButton;
QWeakPointer<QWidget> m_donorWidget;
QWeakPointer<OcsPersonListWidget> m_donorListWidget;
bool m_isOfflineDonorWidget;
};
class ExtendedAboutDialog::Private
{
public:
Private(ExtendedAboutDialog *parent)
: q(parent),
aboutData(0)
{}
void _k_showLicense( const QString &number );
ExtendedAboutDialog *q;
const KAboutData *aboutData;
};
#endif //AMAROK_EXTENDEDABOUTDIALOG_H
diff --git a/src/aboutdialog/OcsPersonItem.cpp b/src/aboutdialog/OcsPersonItem.cpp
index 95ce72fbc1..c35512527c 100644
--- a/src/aboutdialog/OcsPersonItem.cpp
+++ b/src/aboutdialog/OcsPersonItem.cpp
@@ -1,352 +1,352 @@
/****************************************************************************************
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "OcsPersonItem.h"
#include "core/support/Debug.h"
#include "libattica-ocsclient/provider.h"
#include "libattica-ocsclient/providerinitjob.h"
#include "libattica-ocsclient/personjob.h"
#include <QAction>
#include <KRun>
#include <KStandardDirs>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPainter>
#include <QStyleOption>
OcsPersonItem::OcsPersonItem( const KAboutPerson &person, const QString ocsUsername, PersonStatus status, QWidget *parent )
: QWidget( parent )
, m_status( status )
, m_state( Offline )
{
m_person = &person;
m_ocsUsername = ocsUsername;
setupUi( this );
init();
m_avatar->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
//TODO: Add favorite artists!
}
void
OcsPersonItem::init()
{
m_textLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
m_textLabel->setOpenExternalLinks( true );
m_textLabel->setContentsMargins( 5, 0, 0, 2 );
m_verticalLayout->setSpacing( 0 );
m_vertLine->hide();
m_initialSpacer->changeSize( 0, 40, QSizePolicy::Fixed, QSizePolicy::Fixed );
layout()->invalidate();
m_aboutText.append( "<b>" + m_person->name() + "</b>" );
if( !m_person->task().isEmpty() )
m_aboutText.append( "<br/>" + m_person->task() );
m_iconsBar = new KToolBar( this, false, false );
m_snBar = new KToolBar( this, false, false );
m_iconsBar->setIconSize( QSize( 22, 22 ) );
m_iconsBar->setContentsMargins( 0, 0, 0, 0 );
m_iconsBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
if( m_status == Author )
{
QHBoxLayout *iconsLayout = new QHBoxLayout( this );
iconsLayout->setMargin( 0 );
iconsLayout->setSpacing( 0 );
m_verticalLayout->insertLayout( m_verticalLayout->count() - 1, iconsLayout );
iconsLayout->addWidget( m_iconsBar );
iconsLayout->addWidget( m_snBar );
iconsLayout->addStretch( 0 );
m_snBar->setIconSize( QSize( 16, 16 ) );
m_snBar->setContentsMargins( 0, 0, 0, 0 );
m_snBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
}
else
{
layout()->addWidget( m_iconsBar );
m_snBar->hide();
}
if( !m_person->emailAddress().isEmpty() )
{
QAction *email = new QAction( QIcon::fromTheme( "internet-mail" ), i18n("Email contributor"), this );
email->setToolTip( m_person->emailAddress() );
email->setData( QString( "mailto:" + m_person->emailAddress() ) );
m_iconsBar->addAction( email );
}
if( !m_person->webAddress().isEmpty() )
{
QAction *homepage = new QAction( QIcon::fromTheme( "applications-internet" ), i18n("Visit contributor's homepage"), this );
homepage->setToolTip( m_person->webAddress() );
homepage->setData( m_person->webAddress() );
m_iconsBar->addAction( homepage );
}
- connect( m_iconsBar, SIGNAL(actionTriggered(QAction*)), this, SLOT(launchUrl(QAction*)) );
- connect( m_snBar, SIGNAL(actionTriggered(QAction*)), this, SLOT(launchUrl(QAction*)) );
+ connect( m_iconsBar, &KToolBar::actionTriggered, this, &OcsPersonItem::launchUrl );
+ connect( m_snBar, &KToolBar::actionTriggered, this, &OcsPersonItem::launchUrl );
m_textLabel->setText( m_aboutText );
}
OcsPersonItem::~OcsPersonItem()
{}
QString
OcsPersonItem::name()
{
return m_person->name();
}
void
OcsPersonItem::launchUrl( QAction *action ) //SLOT
{
QUrl url = QUrl( action->data().toString() );
KRun::runUrl( url, "text/html", 0, false );
}
void
OcsPersonItem::switchToOcs( const AmarokAttica::Provider &provider )
{
if( m_state == Online )
return;
m_avatar->setFixedWidth( 56 );
m_vertLine->show();
m_initialSpacer->changeSize( 5, 40, QSizePolicy::Fixed, QSizePolicy::Fixed );
layout()->invalidate();
if( !m_ocsUsername.isEmpty() )
{
AmarokAttica::PersonJob *personJob;
if( m_ocsUsername == QString( "%%category%%" ) ) //TODO: handle grouping
return;
personJob = provider.requestPerson( m_ocsUsername );
- connect( personJob, SIGNAL(result(KJob*)), this, SLOT(onJobFinished(KJob*)) );
+ connect( personJob, &AmarokAttica::PersonJob::result, this, &OcsPersonItem::onJobFinished );
emit ocsFetchStarted();
m_state = Online;
}
}
void
OcsPersonItem::onJobFinished( KJob *job )
{
AmarokAttica::PersonJob *personJob = qobject_cast< AmarokAttica::PersonJob * >( job );
if( personJob->error() == 0 )
{
fillOcsData( personJob->person() );
}
emit ocsFetchResult( personJob->error() );
}
void
OcsPersonItem::fillOcsData( const AmarokAttica::Person &ocsPerson )
{
if( !( ocsPerson.avatar().isNull() ) )
{
m_avatar->setFixedSize( 56, 56 );
m_avatar->setFrameShape( QFrame::StyledPanel ); //this is a FramedLabel, otherwise oxygen wouldn't paint the frame
m_avatar->setPixmap( ocsPerson.avatar() );
m_avatar->setAlignment( Qt::AlignCenter );
}
if( !ocsPerson.country().isEmpty() )
{
m_aboutText.append( "<br/>" );
if( !ocsPerson.city().isEmpty() )
m_aboutText.append( i18nc( "A person's location: City, Country", "%1, %2", ocsPerson.city(), ocsPerson.country() ) );
else
m_aboutText.append( ocsPerson.country() );
}
if( m_status == Author )
{
if( !ocsPerson.extendedAttribute( "ircchannels" ).isEmpty() )
{
QString channelsString = ocsPerson.extendedAttribute( "ircchannels" );
//We extract the channel names from the string provided by OCS:
QRegExp channelrx = QRegExp( "#+[\\w\\.\\-\\/!()+]+([\\w\\-\\/!()+]?)", Qt::CaseInsensitive );
QStringList channels;
int pos = 0;
while( ( pos = channelrx.indexIn( channelsString, pos ) ) != -1 )
{
channels << channelrx.cap( 0 );
pos += channelrx.matchedLength();
}
m_aboutText.append( "<br/>" + i18n("IRC channels: ") );
QString link;
foreach( const QString &channel, channels )
{
const QString channelName = QString( channel ).remove( '#' );
link = QString( "irc://irc.freenode.org/%1" ).arg( channelName );
m_aboutText.append( QString( "<a href=\"%1\">%2</a>" ).arg( link, channel ) + " " );
}
}
if( !ocsPerson.extendedAttribute( "favouritemusic" ).isEmpty() )
{
QStringList artists = ocsPerson.extendedAttribute( "favouritemusic" ).split( ", " );
//TODO: make them clickable
m_aboutText.append( "<br/>" + i18n( "Favorite music: " ) + artists.join( ", " ) );
}
}
QAction *visitProfile = new QAction( QIcon( QPixmap( KStandardDirs::locate( "data",
"amarok/images/opendesktop-22.png" ) ) ), i18n( "Visit %1's openDesktop.org profile", ocsPerson.firstName() ), this );
visitProfile->setToolTip( i18n( "Visit %1's profile on openDesktop.org", ocsPerson.firstName() ) );
visitProfile->setData( ocsPerson.extendedAttribute( "profilepage" ) );
m_iconsBar->addAction( visitProfile );
if( m_status == Author )
{
QList< QPair< QString, QString > > ocsHomepages;
ocsHomepages.append( QPair< QString, QString >( ocsPerson.extendedAttribute( "homepagetype" ), ocsPerson.homepage() ) );
debug() << "USER HOMEPAGE DATA STARTS HERE";
debug() << ocsHomepages.last().first << " :: " << ocsHomepages.last().second;
for( int i = 2; i <= 10; i++ ) //OCS supports 10 total homepages as of 2/oct/2009
{
QString type = ocsPerson.extendedAttribute( QString( "homepagetype%1" ).arg( i ) );
ocsHomepages.append( QPair< QString, QString >( ( type == "&nbsp;" ) ? "" : type,
ocsPerson.extendedAttribute( QString( "homepage%1" ).arg( i ) ) ) );
debug() << ocsHomepages.last().first << " :: " << ocsHomepages.last().second;
}
bool fillHomepageFromOcs = m_person->webAddress().isEmpty(); //We check if the person already has a homepage in KAboutPerson.
for( QList< QPair< QString, QString > >::const_iterator entry = ocsHomepages.constBegin();
entry != ocsHomepages.constEnd(); ++entry )
{
QString type = (*entry).first;
QString url = (*entry).second;
QIcon icon;
QString text;
if( type == "Blog" )
{
icon = QIcon::fromTheme( "kblogger" );
text = i18n( "Visit contributor's blog" );
}
else if( type == "delicious" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-delicious.png" ) ) );
text = i18n( "Visit contributor's del.icio.us profile" );
}
else if( type == "Digg" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-digg.png" ) ) );
text = i18n( "Visit contributor's Digg profile" );
}
else if( type == "Facebook" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-facebook.png" ) ) );
text = i18n( "Visit contributor's Facebook profile" );
}
else if( type == "Homepage" || type == "other" || ( type.isEmpty() && !url.isEmpty() ) )
{
if( fillHomepageFromOcs )
{
QAction *homepage = new QAction( QIcon::fromTheme( "applications-internet" ), i18n("Visit contributor's homepage"), this );
homepage->setToolTip( url );
homepage->setData( url );
m_iconsBar->addAction( homepage );
fillHomepageFromOcs = false;
continue;
}
if( type == "other" && url.contains( "last.fm/" ) ) //HACK: assign a last.fm icon if the URL contains last.fm
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-lastfm.png" ) ) );
text = i18n( "Visit contributor's Last.fm profile" );
}
else
continue;
}
else if( type == "LinkedIn" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-linkedin.png" ) ) );
text = i18n( "Visit contributor's LinkedIn profile" );
}
else if( type == "MySpace" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-myspace.png" ) ) );
text = i18n( "Visit contributor's MySpace homepage" );
}
else if( type == "Reddit" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-reddit.png" ) ) );
text = i18n( "Visit contributor's Reddit profile" );
}
else if( type == "YouTube" )
{
icon = QIcon( "dragonplayer" ); //FIXME: icon
text = i18n( "Visit contributor's YouTube profile" );
}
else if( type == "Twitter" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-twitter.png" ) ) );
text = i18n( "Visit contributor's Twitter feed" );
}
else if( type == "Wikipedia" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-wikipedia.png" ) ) );
text = i18n( "Visit contributor's Wikipedia profile" );
}
else if( type == "Xing" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-xing.png" ) ) );
text = i18n( "Visit contributor's Xing profile" );
}
else if( type == "identi.ca" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-identica.png" ) ) );
text = i18n( "Visit contributor's identi.ca feed" );
}
else if( type == "libre.fm" )
{
icon = QIcon( "juk" ); //FIXME: icon
text = i18n( "Visit contributor's libre.fm profile" );
}
else if( type == "StackOverflow" )
{
icon = QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-stackoverflow.png" ) ) );
text = i18n( "Visit contributor's StackOverflow profile" );
}
else
break;
QAction *action = new QAction( icon, text, this );
action->setToolTip( url );
action->setData( url );
m_snBar->addAction( action );
}
debug() << "END USER HOMEPAGE DATA";
}
m_textLabel->setText( m_aboutText );
}
diff --git a/src/aboutdialog/OcsPersonListWidget.cpp b/src/aboutdialog/OcsPersonListWidget.cpp
index 052a48036a..b9e21d2762 100644
--- a/src/aboutdialog/OcsPersonListWidget.cpp
+++ b/src/aboutdialog/OcsPersonListWidget.cpp
@@ -1,88 +1,88 @@
/****************************************************************************************
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "OcsPersonListWidget.h"
#include "core/support/Debug.h"
#include <QScrollArea>
OcsPersonListWidget::OcsPersonListWidget( const QList< KAboutPerson > &persons,
const OcsData::OcsPersonList *ocsPersons,
OcsPersonItem::PersonStatus status,
QWidget *parent )
: QWidget( parent )
, m_status( status )
, m_fetchCount( 0 )
{
//Set up the layouts...
QHBoxLayout *scrollLayout = new QHBoxLayout( this );
scrollLayout->setMargin( 1 );
setLayout( scrollLayout );
QScrollArea *personsScrollArea = new QScrollArea( this );
scrollLayout->addWidget( personsScrollArea );
personsScrollArea->setFrameStyle( QFrame::NoFrame );
m_personsArea = new QWidget( personsScrollArea );
m_areaLayout = new QVBoxLayout( m_personsArea );
m_areaLayout->setMargin( 0 );
m_personsArea->setLayout( m_areaLayout );
m_personsArea->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
personsScrollArea->setWidgetResizable( true );
personsScrollArea->setWidget( m_personsArea );
m_personsArea->show();
//Populate the scroll area...
for( int i = 0; i < persons.count(); ++i ) //TODO: really ugly and inefficient, fix this
{
OcsPersonItem *item = new OcsPersonItem( persons.at( i ), ocsPersons->at( i ).first, status, m_personsArea );
m_areaLayout->addWidget( item );
- connect( item, SIGNAL(ocsFetchStarted()), this, SLOT(onOcsFetchStarted()) );
- connect( item, SIGNAL(ocsFetchResult(int)), this, SLOT(onOcsDataFetched(int)) );
+ connect( item, &OcsPersonItem::ocsFetchStarted, this, &OcsPersonListWidget::onOcsFetchStarted );
+ connect( item, &OcsPersonItem::ocsFetchResult, this, &OcsPersonListWidget::onOcsDataFetched );
}
}
void
OcsPersonListWidget::switchToOcs( const AmarokAttica::Provider &provider )
{
for( int i = 0; i < m_areaLayout->count(); ++i )
{
OcsPersonItem *item = qobject_cast< OcsPersonItem * >( m_areaLayout->itemAt( i )->widget() );
item->switchToOcs( provider );
}
}
void
OcsPersonListWidget::onOcsFetchStarted() //SLOT
{
m_fetchCount++;
}
void
OcsPersonListWidget::onOcsDataFetched( int err ) //SLOT
{
m_fetchCount--;
debug()<<m_status<<"Fetch count is"<< m_fetchCount;
if( err != 0 )
warning() << "OCS data download failed with error"<<err;
if( m_fetchCount == 0 )
{
debug()<<m_status<<"FETCH COMPLETE";
emit switchedToOcs();
}
}
diff --git a/src/aboutdialog/libattica-ocsclient/CMakeLists.txt b/src/aboutdialog/libattica-ocsclient/CMakeLists.txt
index e219ea5429..b19d287308 100644
--- a/src/aboutdialog/libattica-ocsclient/CMakeLists.txt
+++ b/src/aboutdialog/libattica-ocsclient/CMakeLists.txt
@@ -1,42 +1,42 @@
set(ocsclient_SRCS
providerinitjob.cpp
eventjob.cpp
eventlistjob.cpp
eventparser.cpp
event.cpp
activity.cpp
activityparser.cpp
activitylistjob.cpp
person.cpp
personparser.cpp
personjob.cpp
personlistjob.cpp
provider.cpp
postjob.cpp
folder.cpp
folderlistjob.cpp
folderparser.cpp
message.cpp
messagelistjob.cpp
messageparser.cpp
category.cpp
categorylistjob.cpp
categoryparser.cpp
content.cpp
contentjob.cpp
contentlistjob.cpp
contentparser.cpp
knowledgebase.cpp
knowledgebasejob.cpp
knowledgebaselistjob.cpp
knowledgebaseparser.cpp
)
add_library(amarokocsclient SHARED ${ocsclient_SRCS})
set_target_properties(amarokocsclient PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION})
-target_link_libraries(amarokocsclient KF5::KIOCore ${KDE4_KABC_LIBS} KF5::KDELibs4Support)
+target_link_libraries(amarokocsclient KF5::KIOCore KF5::KDELibs4Support)
install(TARGETS amarokocsclient ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp b/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp
index 0d8ad87fbe..c126d92227 100644
--- a/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp
@@ -1,87 +1,89 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "activitylistjob.h"
#include "activityparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
ActivityListJob::ActivityListJob()
: m_job( )
{
}
void ActivityListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void ActivityListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &ActivityListJob::doWork );
}
Activity::List ActivityListJob::ActivityList() const
{
return m_activityList;
}
void ActivityListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &ActivityListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &ActivityListJob::slotJobData );
+
+ m_job = job;
}
void ActivityListJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
// qDebug() << m_data;
m_activityList = ActivityParser().parseList(
QString::fromUtf8( m_data.data() ) );
emitResult();
}
}
void ActivityListJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp b/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp
index 284f206a2b..dab909b58c 100644
--- a/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp
@@ -1,87 +1,89 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "categorylistjob.h"
#include "categoryparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
CategoryListJob::CategoryListJob()
: m_job( )
{
}
void CategoryListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void CategoryListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &CategoryListJob::doWork );
}
Category::List CategoryListJob::categoryList() const
{
return m_categoryList;
}
void CategoryListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &CategoryListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &CategoryListJob::slotJobData );
+
+ m_job = job;
}
void CategoryListJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
qDebug() << m_data;
m_categoryList = CategoryParser().parseList(
QString::fromUtf8( m_data.data() ) );
emitResult();
}
}
void CategoryListJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/contentjob.cpp b/src/aboutdialog/libattica-ocsclient/contentjob.cpp
index 8ba046bbed..c178dffe54 100644
--- a/src/aboutdialog/libattica-ocsclient/contentjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/contentjob.cpp
@@ -1,84 +1,86 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "contentjob.h"
#include "contentparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
ContentJob::ContentJob()
: m_job( )
{
}
void ContentJob::setUrl( const QUrl &url )
{
m_url = url;
}
void ContentJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &ContentJob::doWork );
}
Content ContentJob::content() const
{
return m_content;
}
void ContentJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &ContentJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &ContentJob::slotJobData );
+
+ m_job = job;
}
void ContentJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
} else {
qDebug() << m_data;
m_content = ContentParser().parse( QString::fromUtf8( m_data.data() ) );
}
emitResult();
}
void ContentJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp b/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp
index 0e91908b5f..241440d8f1 100644
--- a/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp
@@ -1,87 +1,89 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "contentlistjob.h"
#include "contentparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
ContentListJob::ContentListJob()
: m_job( )
{
}
void ContentListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void ContentListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &ContentListJob::doWork );
}
Content::List ContentListJob::contentList() const
{
return m_contentList;
}
void ContentListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &ContentListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &ContentListJob::slotJobData );
+
+ m_job = job;
}
void ContentListJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
qDebug() << m_data;
m_contentList = ContentParser().parseList(
QString::fromUtf8( m_data.data() ) );
emitResult();
}
}
void ContentListJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/eventjob.cpp b/src/aboutdialog/libattica-ocsclient/eventjob.cpp
index 8e49ee1928..e75e338b73 100644
--- a/src/aboutdialog/libattica-ocsclient/eventjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/eventjob.cpp
@@ -1,91 +1,94 @@
/*
This file is part of KDE.
Copyright (c) 2009 Eckhart Wörner <ewoerner@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "eventjob.h"
#include <QTimer>
#include <KIO/Job>
#include "eventparser.h"
using namespace AmarokAttica;
EventJob::EventJob()
: m_job(0)
{
}
void EventJob::setUrl(const QUrl &url)
{
m_url = url;
}
void EventJob::start()
{
- QTimer::singleShot(0, this, SLOT(doWork()));
+ QTimer::singleShot(0, this, &EventJob::doWork);
}
Event EventJob::event() const
{
return m_event;
}
void EventJob::doWork()
{
- m_job = KIO::get(m_url, KIO::NoReload, KIO::HideProgressInfo);
- connect(m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)));
- connect(m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)));
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &EventJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &EventJob::slotJobData );
+
+ m_job = job;
}
void EventJob::slotJobResult(KJob* job)
{
m_job = 0;
if (job->error()) {
setError(job->error());
setErrorText(job->errorText());
emitResult();
} else {
m_event = EventParser().parse(QString::fromUtf8(m_data.data()));
emitResult();
}
}
void EventJob::slotJobData(KIO::Job* job, const QByteArray& data)
{
Q_UNUSED(job);
m_data.append(data);
}
diff --git a/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp b/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp
index 8873f82c1d..704babd990 100644
--- a/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp
@@ -1,91 +1,94 @@
/*
This file is part of KDE.
Copyright (c) 2009 Eckhart Wörner <ewoerner@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "eventlistjob.h"
#include <QTimer>
#include <KIO/Job>
#include "eventparser.h"
using namespace AmarokAttica;
EventListJob::EventListJob()
: m_job(0)
{
}
void EventListJob::setUrl(const QUrl &url)
{
m_url = url;
}
void EventListJob::start()
{
- QTimer::singleShot(0, this, SLOT(doWork()));
+ QTimer::singleShot(0, this, &EventListJob::doWork);
}
Event::List EventListJob::eventList() const
{
return m_eventList;
}
void EventListJob::doWork()
{
- m_job = KIO::get(m_url, KIO::NoReload, KIO::HideProgressInfo);
- connect(m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)));
- connect(m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)));
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &EventListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &EventListJob::slotJobData );
+
+ m_job = job;
}
void EventListJob::slotJobResult(KJob* job)
{
m_job = 0;
if (job->error()) {
setError(job->error());
setErrorText(job->errorText());
emitResult();
} else {
m_eventList = EventParser().parseList(QString::fromUtf8(m_data.data()));
emitResult();
}
}
void EventListJob::slotJobData(KIO::Job* job, const QByteArray& data)
{
Q_UNUSED(job);
m_data.append(data);
}
diff --git a/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp b/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp
index 8cad2b7e58..3b124c8e34 100644
--- a/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp
@@ -1,85 +1,87 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "folderlistjob.h"
#include "folderparser.h"
#include <kio/job.h>
#include <klocale.h>
#include <QTimer>
#include <QDebug>
using namespace AmarokAttica;
FolderListJob::FolderListJob()
: m_job( )
{
}
void FolderListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void FolderListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &FolderListJob::doWork );
}
Folder::List FolderListJob::folderList() const
{
return m_folderList;
}
void FolderListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &FolderListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &FolderListJob::slotJobData );
+
+ m_job = job;
}
void FolderListJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
qDebug() << m_data;
m_folderList = FolderParser().parseList(
QString::fromUtf8( m_data.data() ) );
emitResult();
}
}
void FolderListJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp b/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp
index 25cc10fb5a..cddbad3df8 100644
--- a/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp
@@ -1,91 +1,93 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
Copyright (c) 2009 Marco Martin <notmart@gmail.com>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "knowledgebasejob.h"
#include "knowledgebaseparser.h"
#include <kio/job.h>
#include <klocale.h>
#include <QDebug>
#include <QTimer>
using namespace AmarokAttica;
KnowledgeBaseJob::KnowledgeBaseJob()
: m_job( )
{
}
void KnowledgeBaseJob::setUrl( const QUrl &url )
{
m_url = url;
}
void KnowledgeBaseJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &KnowledgeBaseJob::doWork );
}
KnowledgeBase KnowledgeBaseJob::knowledgeBase() const
{
return m_knowledgeBase;
}
KnowledgeBase::Metadata KnowledgeBaseJob::metadata() const
{
return m_metadata;
}
void KnowledgeBaseJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &KnowledgeBaseJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &KnowledgeBaseJob::slotJobData );
+
+ m_job = job;
}
void KnowledgeBaseJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
} else {
qDebug() << m_data;
KnowledgeBaseParser parser;
m_knowledgeBase = parser.parse( QString::fromUtf8( m_data.data() ) );
m_metadata = parser.lastMetadata();
}
emitResult();
}
void KnowledgeBaseJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp b/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp
index d8e9dc372f..c8755e6175 100644
--- a/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp
@@ -1,93 +1,95 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
Copyright (c) 2009 Marco Martin <notmart@gmail.com>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "knowledgebaselistjob.h"
#include "knowledgebaseparser.h"
#include <kio/job.h>
#include <klocale.h>
#include <QDebug>
#include <QTimer>
using namespace AmarokAttica;
KnowledgeBaseListJob::KnowledgeBaseListJob()
: m_job( )
{
}
void KnowledgeBaseListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void KnowledgeBaseListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &KnowledgeBaseListJob::doWork );
}
KnowledgeBase::List KnowledgeBaseListJob::knowledgeBaseList() const
{
return m_knowledgeBaseList;
}
KnowledgeBase::Metadata KnowledgeBaseListJob::metadata() const
{
return m_metadata;
}
void KnowledgeBaseListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &KnowledgeBaseListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &KnowledgeBaseListJob::slotJobData );
+
+ m_job = job;
}
void KnowledgeBaseListJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
qDebug() << m_data;
KnowledgeBaseParser parser;
m_knowledgeBaseList = parser.parseList(
QString::fromUtf8( m_data.data() ) );
m_metadata = parser.lastMetadata();
emitResult();
}
}
void KnowledgeBaseListJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp b/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp
index db7d451f13..12bdb15c4e 100644
--- a/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp
@@ -1,87 +1,89 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "messagelistjob.h"
#include "messageparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
MessageListJob::MessageListJob()
: m_job( )
{
}
void MessageListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void MessageListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &MessageListJob::doWork );
}
Message::List MessageListJob::messageList() const
{
return m_messageList;
}
void MessageListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &MessageListJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &MessageListJob::slotJobData );
+
+ m_job = job;
}
void MessageListJob::slotJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
qDebug() << m_data;
m_messageList = MessageParser().parseList(
QString::fromUtf8( m_data.data() ) );
emitResult();
}
}
void MessageListJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_data.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/personjob.cpp b/src/aboutdialog/libattica-ocsclient/personjob.cpp
index decb5bc9c4..0fb3f98c1f 100644
--- a/src/aboutdialog/libattica-ocsclient/personjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/personjob.cpp
@@ -1,118 +1,121 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "personjob.h"
#include "personparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
PersonJob::PersonJob()
: m_job( )
{
}
void PersonJob::setUrl( const QUrl &url )
{
m_url = url;
}
void PersonJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &PersonJob::doWork );
}
Person PersonJob::person() const
{
return m_person;
}
void PersonJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotUserJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotUserJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &PersonJob::slotUserJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &PersonJob::slotUserJobData );
+
+ m_job = job;
}
void PersonJob::slotUserJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
// qDebug() << m_userData;
m_person = PersonParser().parse( m_userData );
if (!m_person.avatarUrl().isEmpty()) {
qDebug() << "Getting avatar from" << m_person.avatarUrl();
- m_job = KIO::get( m_person.avatarUrl(), KIO::NoReload,
- KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotAvatarJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotAvatarJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &PersonJob::slotAvatarJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &PersonJob::slotAvatarJobData );
+
+ m_job = job;
} else {
emitResult();
}
}
}
void PersonJob::slotUserJobData( KIO::Job *, const QByteArray &data )
{
m_userData.append( QString::fromUtf8( data.data(), data.size() + 1 ) );
}
void PersonJob::slotAvatarJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
qWarning() << "Error retrieving Avatar:" << job->errorText();
} else {
QPixmap pic;
if ( pic.loadFromData( m_avatarData ) ) {
m_person.setAvatar( pic );
}
}
emitResult();
}
void PersonJob::slotAvatarJobData( KIO::Job *, const QByteArray &data )
{
m_avatarData.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/personlistjob.cpp b/src/aboutdialog/libattica-ocsclient/personlistjob.cpp
index c6e01c6bf0..cfb938bb1e 100644
--- a/src/aboutdialog/libattica-ocsclient/personlistjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/personlistjob.cpp
@@ -1,120 +1,122 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "personlistjob.h"
#include "personparser.h"
#include <QDebug>
#include <QTimer>
#include <kio/job.h>
#include <klocale.h>
using namespace AmarokAttica;
PersonListJob::PersonListJob()
: m_job( )
{
}
void PersonListJob::setUrl( const QUrl &url )
{
m_url = url;
}
void PersonListJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &PersonListJob::doWork );
}
Person::List PersonListJob::personList() const
{
return m_personList;
}
void PersonListJob::doWork()
{
qDebug() << m_url;
- m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotUserJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotUserJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &PersonListJob::slotUserJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &PersonListJob::slotUserJobData );
+
+ m_job = job;
}
void PersonListJob::slotUserJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
} else {
// qDebug() << m_userData;
m_personList = PersonParser().parseList( m_userData );
#if 0
m_job = KIO::get( m_person.avatarUrl(), KIO::NoReload,
KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotAvatarJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotAvatarJobData(KIO::Job*,QByteArray)) );
+ connect( m_job, &KIO::Job::result,
+ this, &PersonListJob::slotAvatarJobResult );
+ connect( m_job, &KIO::Job::data,
+ this, &PersonListJob::slotAvatarJobData );
#else
emitResult();
#endif
}
}
void PersonListJob::slotUserJobData( KIO::Job *, const QByteArray &data )
{
m_userData.append( QString::fromUtf8( data.data(), data.size() + 1 ) );
}
void PersonListJob::slotAvatarJobResult( KJob *job )
{
m_job = 0;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
} else {
QPixmap pic;
if ( !pic.loadFromData( m_avatarData ) ) {
setError( UserDefinedError );
setErrorText( i18n("Unable to parse avatar image data.") );
} else {
// m_person.setAvatar( pic );
}
}
emitResult();
}
void PersonListJob::slotAvatarJobData( KIO::Job *, const QByteArray &data )
{
m_avatarData.append( data );
}
diff --git a/src/aboutdialog/libattica-ocsclient/postjob.cpp b/src/aboutdialog/libattica-ocsclient/postjob.cpp
index 3d493bdbc1..b321d6a00f 100644
--- a/src/aboutdialog/libattica-ocsclient/postjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/postjob.cpp
@@ -1,128 +1,130 @@
/*
This file is part of KDE.
Copyright (c) 2008 Cornelius Schumacher <schumacher@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "postjob.h"
#include <kio/job.h>
#include <klocale.h>
#include <QXmlStreamReader>
#include <QDebug>
#include <QTimer>
using namespace AmarokAttica;
PostJob::PostJob()
: m_job( )
{
}
void PostJob::setUrl( const QUrl &url )
{
m_url = url;
}
void PostJob::setData( const QString &name, const QString &value )
{
m_data.insert( name, value );
}
void PostJob::start()
{
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &PostJob::doWork );
}
QString PostJob::status() const
{
return m_status;
}
QString PostJob::statusMessage() const
{
return m_statusMessage;
}
void PostJob::doWork()
{
QString postData;
const QStringList dataKeys = m_data.keys();
foreach( const QString &name, dataKeys ) {
m_url.addQueryItem( name, m_data.value( name ) );
}
qDebug() << m_url;
- m_job = KIO::http_post( m_url, postData.toUtf8(), KIO::HideProgressInfo );
- connect( m_job, SIGNAL(result(KJob*)),
- SLOT(slotJobResult(KJob*)) );
- connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotJobData(KIO::Job*,QByteArray)) );
+ auto job = KIO::http_post( m_url, postData.toUtf8(), KIO::HideProgressInfo );
+ connect( job, &KIO::TransferJob::result,
+ this, &PostJob::slotJobResult );
+ connect( job, &KIO::TransferJob::data,
+ this, &PostJob::slotJobData );
+
+ m_job = job;
}
void PostJob::slotJobResult( KJob *job )
{
m_job = 0;
qDebug() << "RESPONSE" << m_responseData;
if ( job->error() ) {
setError( job->error() );
setErrorText( job->errorText() );
} else {
qDebug() << "No error ";
QXmlStreamReader xml( m_responseData );
while ( !xml.atEnd() ) {
xml.readNext();
if ( xml.isStartElement() && xml.name() == "meta" ) {
while ( !xml.atEnd() ) {
xml.readNext();
if ( xml.isStartElement() ) {
if ( xml.name() == "status" ) {
m_status = xml.readElementText();
} else if ( xml.name() == "message" ) {
m_statusMessage = xml.readElementText();
}
}
if ( xml.isEndElement() && xml.name() == "meta" ) break;
}
}
}
qDebug() << "STATUS:" << m_status;
if ( m_status != "ok" ) {
setError( KJob::UserDefinedError );
setErrorText( m_status + ": " + m_statusMessage );
}
}
emitResult();
}
void PostJob::slotJobData( KIO::Job *, const QByteArray &data )
{
m_responseData.append( QString::fromUtf8( data.data(), data.size() + 1 ) );
}
diff --git a/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp b/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp
index 03872af861..29627df40f 100644
--- a/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp
+++ b/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp
@@ -1,58 +1,58 @@
/*
This file is part of KDE.
Copyright (c) 2009 Eckhart Wörner <ewoerner@kde.org>
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
#include "providerinitjob.h"
#include <QTimer>
#include <QUrl>
using namespace AmarokAttica;
ProviderInitJob::ProviderInitJob(const QString& id, QObject* parent)
: KJob(parent), m_id(id)
{
}
void ProviderInitJob::start()
{
- QTimer::singleShot(0, this, SLOT(doWork()));
+ QTimer::singleShot(0, this, &ProviderInitJob::doWork);
}
void ProviderInitJob::doWork()
{
if (m_id == "opendesktop") {
m_provider = Provider(m_id, QUrl("https://api.opendesktop.org/v1/"), "OpenDesktop.org");
}
emitResult();
}
Provider ProviderInitJob::provider() const
{
return m_provider;
}
diff --git a/src/amarokurls/AmarokUrlAction.cpp b/src/amarokurls/AmarokUrlAction.cpp
index 0cc391126c..a2c612fc45 100644
--- a/src/amarokurls/AmarokUrlAction.cpp
+++ b/src/amarokurls/AmarokUrlAction.cpp
@@ -1,45 +1,45 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokUrlAction.h"
#include "AmarokUrlHandler.h"
AmarokUrlAction::AmarokUrlAction( const QIcon & icon, AmarokUrlPtr url, QObject * parent )
: QAction( icon, url->name(), parent )
, m_url( url )
{
if ( !url->description().isEmpty() )
setToolTip( url->description() );
- connect( this, SIGNAL(triggered(bool)), this, SLOT(run()) );
+ connect( this, &AmarokUrlAction::triggered, this, &AmarokUrlAction::run );
}
AmarokUrlAction::AmarokUrlAction( AmarokUrlPtr url, QObject * parent )
: QAction(url->name(), parent )
, m_url( url )
{
if ( !url->description().isEmpty() )
setToolTip( url->description() );
setIcon( The::amarokUrlHandler()->iconForCommand( url->command() ) );
- connect( this, SIGNAL(triggered(bool)), this, SLOT(run()) );
+ connect( this, &AmarokUrlAction::triggered, this, &AmarokUrlAction::run );
}
void AmarokUrlAction::run()
{
m_url->run();
}
diff --git a/src/amarokurls/BookmarkCurrentButton.cpp b/src/amarokurls/BookmarkCurrentButton.cpp
index 3c4e531dfb..3c7d4884a1 100644
--- a/src/amarokurls/BookmarkCurrentButton.cpp
+++ b/src/amarokurls/BookmarkCurrentButton.cpp
@@ -1,70 +1,70 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BookmarkCurrentButton.h"
#include "AmarokUrlHandler.h"
#include "BookmarkModel.h"
#include <QIcon>
#include <KLocale>
#include <QMenu>
#include <QString>
BookmarkCurrentButton::BookmarkCurrentButton( QWidget *parent )
: QToolButton( parent )
{
setIcon( QIcon::fromTheme( "bookmark-new" ) );
setText( i18n( "New Bookmark" ) );
setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
- connect( this, SIGNAL(clicked(bool)), this, SLOT(showMenu()) );
+ connect( this, &BookmarkCurrentButton::clicked, this, &BookmarkCurrentButton::showMenu );
}
BookmarkCurrentButton::~BookmarkCurrentButton()
{
}
void BookmarkCurrentButton::showMenu()
{
QPoint pos( 0, height() );
generateMenu( mapToGlobal( pos ) );
}
void BookmarkCurrentButton::generateMenu( const QPoint &pos )
{
QList<AmarokUrlGenerator *> generators = The::amarokUrlHandler()->generators();
QMenu menu;
QMap<QAction *, AmarokUrlGenerator *> generatorMap;
foreach( AmarokUrlGenerator * generator, generators )
{
generatorMap.insert( menu.addAction( generator->icon() ,generator->description() ), generator );
}
QAction * action = menu.exec( pos );
if( action && generatorMap.contains( action ) )
{
AmarokUrl url = generatorMap.value( action )->createUrl();
url.saveToDb();
BookmarkModel::instance()->reloadFromDb();
}
}
diff --git a/src/amarokurls/BookmarkManagerWidget.cpp b/src/amarokurls/BookmarkManagerWidget.cpp
index 62548b7807..e1f9361f7e 100644
--- a/src/amarokurls/BookmarkManagerWidget.cpp
+++ b/src/amarokurls/BookmarkManagerWidget.cpp
@@ -1,95 +1,95 @@
/****************************************************************************************
* Copyright (c) 2008, 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BookmarkManagerWidget.h"
#include "amarokurls/AmarokUrl.h"
#include "amarokurls/BookmarkModel.h"
#include "amarokurls/BookmarkCurrentButton.h"
#include "amarokurls/NavigationUrlGenerator.h"
#include "amarokurls/PlayUrlGenerator.h"
#include "widgets/ProgressWidget.h"
#include <QAction>
#include <QIcon>
#include <KLocale>
#include <KVBox>
#include <QLabel>
BookmarkManagerWidget::BookmarkManagerWidget( QWidget * parent )
: KVBox( parent )
{
setContentsMargins( 0,0,0,0 );
KHBox * topLayout = new KHBox( this );
m_toolBar = new QToolBar( topLayout );
m_toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
QAction * addGroupAction = new QAction( QIcon::fromTheme("media-track-add-amarok" ), i18n( "Add Group" ), this );
m_toolBar->addAction( addGroupAction );
- connect( addGroupAction, SIGNAL(triggered(bool)), BookmarkModel::instance(), SLOT(createNewGroup()) );
+ connect( addGroupAction, &QAction::triggered, BookmarkModel::instance(), &BookmarkModel::createNewGroup );
/*QAction * addBookmarkAction = new QAction( QIcon::fromTheme("bookmark-new" ), i18n( "New Bookmark" ), this );
m_toolBar->addAction( addBookmarkAction );
- connect( addBookmarkAction, SIGNAL(triggered(bool)), BookmarkModel::instance(), SLOT(createNewBookmark()) );*/
+ connect( addBookmarkaction, &QAction::triggered, BookmarkModel::instance(), &BookmarkModel::createNewBookmark );*/
m_toolBar->addWidget( new BookmarkCurrentButton( 0 ) );
m_searchEdit = new Amarok::LineEdit( topLayout );
m_searchEdit->setClickMessage( i18n( "Filter bookmarks" ) );
m_searchEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
m_searchEdit->setClearButtonShown( true );
m_searchEdit->setFrame( true );
m_searchEdit->setToolTip( i18n( "Start typing to progressively filter the bookmarks" ) );
m_searchEdit->setFocusPolicy( Qt::ClickFocus ); // Without this, the widget goes into text input mode directly on startup
m_bookmarkView = new BookmarkTreeView( this );
m_proxyModel = new QSortFilterProxyModel( this );
m_proxyModel->setSourceModel( BookmarkModel::instance() );
m_proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
m_proxyModel->setSortCaseSensitivity( Qt::CaseInsensitive );
m_proxyModel->setDynamicSortFilter( true );
m_proxyModel->setFilterKeyColumn ( -1 ); //filter on all columns
m_bookmarkView->setModel( m_proxyModel );
m_bookmarkView->setProxy( m_proxyModel );
m_bookmarkView->setSortingEnabled( true );
m_bookmarkView->resizeColumnToContents( 0 );
- connect( BookmarkModel::instance(), SIGNAL(editIndex(QModelIndex)), m_bookmarkView, SLOT(slotEdit(QModelIndex)) );
- connect( m_searchEdit, SIGNAL(textChanged(QString)), m_proxyModel, SLOT(setFilterFixedString(QString)) );
+ connect( BookmarkModel::instance(), &BookmarkModel::editIndex, m_bookmarkView, &BookmarkTreeView::slotEdit );
+ connect( m_searchEdit, &Amarok::LineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString );
m_currentBookmarkId = -1;
}
BookmarkManagerWidget::~BookmarkManagerWidget()
{
}
BookmarkTreeView * BookmarkManagerWidget::treeView()
{
return m_bookmarkView;
}
diff --git a/src/amarokurls/BookmarkMetaActions.cpp b/src/amarokurls/BookmarkMetaActions.cpp
index 5ef5fc143b..1bdb87c991 100644
--- a/src/amarokurls/BookmarkMetaActions.cpp
+++ b/src/amarokurls/BookmarkMetaActions.cpp
@@ -1,85 +1,85 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BookmarkMetaActions.h"
#include "EngineController.h"
#include "SvgHandler.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "amarokurls/BookmarkModel.h"
#include "core/meta/Meta.h"
#include "core-impl/capabilities/timecode/TimecodeWriteCapability.h"
#include "widgets/ProgressWidget.h"
#include <QIcon>
#include <KLocale>
BookmarkAlbumAction::BookmarkAlbumAction( QObject *parent, Meta::AlbumPtr album )
: QAction( i18n( "Bookmark this Album" ), parent )
, m_album( album )
{
- connect( this, SIGNAL(triggered(bool)), SLOT(slotTriggered()) );
+ connect( this, &BookmarkAlbumAction::triggered, this, &BookmarkAlbumAction::slotTriggered );
setIcon( QIcon::fromTheme("bookmark-new") );
setProperty( "popupdropper_svg_id", "lastfm" );
}
void
BookmarkAlbumAction::slotTriggered()
{
The::amarokUrlHandler()->bookmarkAlbum( m_album );
}
BookmarkArtistAction::BookmarkArtistAction( QObject *parent, Meta::ArtistPtr artist )
: QAction( i18n( "Bookmark this Artist" ), parent )
, m_artist( artist )
{
- connect( this, SIGNAL(triggered(bool)), SLOT(slotTriggered()) );
+ connect( this, &BookmarkArtistAction::triggered, this, &BookmarkArtistAction::slotTriggered );
setIcon( QIcon::fromTheme("bookmark-new") );
setProperty( "popupdropper_svg_id", "lastfm" );
}
void
BookmarkArtistAction::slotTriggered()
{
The::amarokUrlHandler()->bookmarkArtist( m_artist );
}
BookmarkCurrentTrackPositionAction::BookmarkCurrentTrackPositionAction( QObject * parent )
: QAction( i18n( "Add Position Marker" ), parent )
{
- connect( this, SIGNAL(triggered(bool)), SLOT(slotTriggered()) );
+ connect( this, &BookmarkCurrentTrackPositionAction::triggered, this, &BookmarkCurrentTrackPositionAction::slotTriggered );
setIcon( QIcon::fromTheme("flag-amarok") );
}
void
BookmarkCurrentTrackPositionAction::slotTriggered()
{
DEBUG_BLOCK
Meta::TrackPtr track = The::engineController()->currentTrack();
const qint64 miliseconds = The::engineController()->trackPositionMs();
if ( track && track->has<Capabilities::TimecodeWriteCapability>() )
{
debug() << " has WriteTimecode ";
QScopedPointer<Capabilities::TimecodeWriteCapability> tcw( track->create<Capabilities::TimecodeWriteCapability>() );
tcw->writeTimecode( miliseconds );
}
}
diff --git a/src/amarokurls/BookmarkTreeView.cpp b/src/amarokurls/BookmarkTreeView.cpp
index 608f2c2f9b..00e9fb5fdf 100644
--- a/src/amarokurls/BookmarkTreeView.cpp
+++ b/src/amarokurls/BookmarkTreeView.cpp
@@ -1,441 +1,441 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BookmarkTreeView.h"
#include "BookmarkModel.h"
#include "dialogs/TagDialog.h"
#include "PaletteHandler.h"
#include "AmarokUrl.h"
#include "AmarokUrlHandler.h"
#include "BookmarkGroup.h"
#include "playlist/PlaylistController.h"
#include "SvgHandler.h"
#include "core-impl/meta/timecode/TimecodeMeta.h"
#include <QAction>
#include <QMenu>
#include <KLocalizedString>
#include <QFrame>
#include <QHeaderView>
#include <QHelpEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QModelIndex>
#include <QPoint>
#include <QToolTip>
#include <typeinfo>
BookmarkTreeView::BookmarkTreeView( QWidget *parent )
: QTreeView( parent )
, m_loadAction( 0 )
, m_deleteAction( 0 )
, m_createTimecodeTrackAction( 0 )
, m_addGroupAction( 0 )
{
setEditTriggers( QAbstractItemView::SelectedClicked );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setDragEnabled( true );
setAcceptDrops( true );
setAlternatingRowColors( true );
setDropIndicatorShown( true );
- connect( header(), SIGNAL(sectionCountChanged(int,int)),
- this, SLOT(slotSectionCountChanged(int,int)) );
+ connect( header(), &QHeaderView::sectionCountChanged,
+ this, &BookmarkTreeView::slotSectionCountChanged );
}
BookmarkTreeView::~BookmarkTreeView()
{
}
void BookmarkTreeView::mouseDoubleClickEvent( QMouseEvent * event )
{
QModelIndex index = m_proxyModel->mapToSource( indexAt( event->pos() ) );
if( index.isValid() )
{
BookmarkViewItemPtr item = BookmarkModel::instance()->data( index, 0xf00d ).value<BookmarkViewItemPtr>();
if ( typeid( *item ) == typeid( AmarokUrl ) ) {
AmarokUrl * bookmark = static_cast< AmarokUrl* >( item.data() );
bookmark->run();
}
}
}
void
BookmarkTreeView::keyPressEvent( QKeyEvent *event )
{
switch( event->key() )
{
case Qt::Key_Delete:
slotDelete();
return;
case Qt::Key_F2:
slotRename();
return;
}
QTreeView::keyPressEvent( event );
}
QList<QAction *>
BookmarkTreeView::createCommonActions( QModelIndexList indices )
{
DEBUG_BLOCK
//there are 4 columns, so for each selected row we get 4 indices...
int selectedRowCount = indices.count() / 4;
QList< QAction * > actions;
if ( m_loadAction == 0 )
{
m_loadAction = new QAction( QIcon::fromTheme( "folder-open" ), i18nc( "Load the view represented by this bookmark", "&Load" ), this );
- connect( m_loadAction, SIGNAL(triggered()), this, SLOT(slotLoad()) );
+ connect( m_loadAction, &QAction::triggered, this, &BookmarkTreeView::slotLoad );
}
if ( m_deleteAction == 0 )
{
m_deleteAction = new QAction( QIcon::fromTheme( "media-track-remove-amarok" ), i18n( "&Delete" ), this );
- connect( m_deleteAction, SIGNAL(triggered()), this, SLOT(slotDelete()) );
+ connect( m_deleteAction, &QAction::triggered, this, &BookmarkTreeView::slotDelete );
}
if ( m_createTimecodeTrackAction == 0 )
{
debug() << "creating m_createTimecodeTrackAction";
m_createTimecodeTrackAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ), i18n( "&Create timecode track" ), this );
- connect( m_createTimecodeTrackAction, SIGNAL(triggered()), this, SLOT(slotCreateTimecodeTrack()) );
+ connect( m_createTimecodeTrackAction, &QAction::triggered, this, &BookmarkTreeView::slotCreateTimecodeTrack );
}
if ( selectedRowCount == 1 )
actions << m_loadAction;
if ( selectedRowCount > 0 )
actions << m_deleteAction;
if ( selectedRowCount == 2 ) {
debug() << "adding m_createTimecodeTrackAction";
actions << m_createTimecodeTrackAction;
}
return actions;
}
void BookmarkTreeView::slotLoad()
{
DEBUG_BLOCK
foreach( BookmarkViewItemPtr item, selectedItems() )
{
if( typeid( * item ) == typeid( AmarokUrl ) )
{
AmarokUrlPtr bookmark = AmarokUrlPtr::staticCast( item );
bookmark->run();
}
}
}
void BookmarkTreeView::slotDelete()
{
DEBUG_BLOCK
//TODO FIXME Confirmation of delete
foreach( BookmarkViewItemPtr item, selectedItems() )
{
debug() << "deleting " << item->name();
item->removeFromDb();
item->parent()->deleteChild( item );
}
BookmarkModel::instance()->reloadFromDb();
The::amarokUrlHandler()->updateTimecodes();
}
void BookmarkTreeView::slotRename()
{
DEBUG_BLOCK
if ( selectionModel()->hasSelection() )
edit( selectionModel()->selectedIndexes().first() );
}
void BookmarkTreeView::contextMenuEvent( QContextMenuEvent * event )
{
DEBUG_BLOCK
QModelIndexList indices = selectionModel()->selectedIndexes();
QMenu* menu = new QMenu( this );
QList<QAction *> actions = createCommonActions( indices );
foreach( QAction * action, actions )
menu->addAction( action );
if( indices.count() == 0 )
menu->addAction( m_addGroupAction );
menu->exec( event->globalPos() );
}
void BookmarkTreeView::resizeEvent( QResizeEvent *event )
{
QHeaderView *headerView = header();
const int oldWidth = event->oldSize().width();
const int newWidth = event->size().width();
if( oldWidth == newWidth || oldWidth < 0 || newWidth < 0 )
return;
- disconnect( headerView, SIGNAL(sectionResized(int,int,int)),
- this, SLOT(slotSectionResized(int,int,int)) );
+ disconnect( headerView, &QHeaderView::sectionResized,
+ this, &BookmarkTreeView::slotSectionResized );
QMap<BookmarkModel::Column, qreal>::const_iterator i = m_columnsSize.constBegin();
while( i != m_columnsSize.constEnd() )
{
const BookmarkModel::Column col = i.key();
if( col != BookmarkModel::Command && col != BookmarkModel::Description )
headerView->resizeSection( col, static_cast<int>( i.value() * newWidth ) );
++i;
}
- connect( headerView, SIGNAL(sectionResized(int,int,int)),
- this, SLOT(slotSectionResized(int,int,int)) );
+ connect( headerView, &QHeaderView::sectionResized,
+ this, &BookmarkTreeView::slotSectionResized );
QWidget::resizeEvent( event );
}
bool BookmarkTreeView::viewportEvent( QEvent *event )
{
if( event->type() == QEvent::ToolTip )
{
QHelpEvent *he = static_cast<QHelpEvent*>( event );
QModelIndex idx = indexAt( he->pos() );
if( idx.isValid() )
{
QRect vr = visualRect( idx );
QSize shr = itemDelegate( idx )->sizeHint( viewOptions(), idx );
if( shr.width() > vr.width() )
QToolTip::showText( he->globalPos(), idx.data( Qt::DisplayRole ).toString() );
}
else
{
QToolTip::hideText();
event->ignore();
}
return true;
}
return QTreeView::viewportEvent( event );
}
QSet<BookmarkViewItemPtr>
BookmarkTreeView::selectedItems() const
{
DEBUG_BLOCK
QSet<BookmarkViewItemPtr> selected;
foreach( const QModelIndex &index, selectionModel()->selectedIndexes() )
{
QModelIndex sourceIndex = m_proxyModel->mapToSource( index );
if( sourceIndex.isValid() && sourceIndex.internalPointer() && sourceIndex.column() == 0 )
{
debug() << "inserting item " << sourceIndex.data( Qt::DisplayRole ).toString();
selected.insert( BookmarkModel::instance()->data( sourceIndex, 0xf00d ).value<BookmarkViewItemPtr>() );
}
}
return selected;
}
void BookmarkTreeView::setNewGroupAction( QAction * action )
{
m_addGroupAction = action;
}
void BookmarkTreeView::selectionChanged( const QItemSelection & selected, const QItemSelection & deselected )
{
DEBUG_BLOCK
Q_UNUSED( deselected )
QModelIndexList indexes = selected.indexes();
debug() << indexes.size() << " items selected";
foreach( const QModelIndex &index, indexes )
{
const QModelIndex sourceIndex = m_proxyModel->mapToSource( index );
if( sourceIndex.column() == 0 )
{
BookmarkViewItemPtr item = BookmarkModel::instance()->data( sourceIndex, 0xf00d ).value<BookmarkViewItemPtr>();
if ( typeid( * item ) == typeid( AmarokUrl ) ) {
debug() << "a url was selected...";
AmarokUrl bookmark = *static_cast< AmarokUrl* >( item.data() );
emit( bookmarkSelected( bookmark ) );
}
}
}
}
QMenu* BookmarkTreeView::contextMenu( const QPoint& point )
{
DEBUG_BLOCK
QMenu* menu = new QMenu( 0 );
debug() << "getting menu for point:" << point;
QModelIndex index = m_proxyModel->mapToSource( indexAt( point ) );
if( index.isValid() )
{
debug() << "got valid index";
QModelIndexList indices = selectionModel()->selectedIndexes();
QList<QAction *> actions = createCommonActions( indices );
foreach( QAction * action, actions )
menu->addAction( action );
if( indices.count() == 0 )
menu->addAction( m_addGroupAction );
}
return menu;
}
void BookmarkTreeView::slotCreateTimecodeTrack() const
{
//TODO: Factor into separate class
QList<BookmarkViewItemPtr> list = selectedItems().toList();
if ( list.count() != 2 )
return;
const AmarokUrl * url1 = dynamic_cast<const AmarokUrl *>( list.at( 0 ).data() );
if ( url1 == 0 )
return;
if ( url1->command() != "play" )
return;
const AmarokUrl * url2 = dynamic_cast<const AmarokUrl *>( list.at( 1 ).data() );
if ( url2 == 0 )
return;
if ( url2->command() != "play" )
return;
if ( url1->path() != url2->path() )
return;
//ok, so we actually have to timecodes from the same base url, not get the
//minimum and maximum time:
qreal pos1 = 0;
qreal pos2 = 0;
if ( url1->args().keys().contains( "pos" ) )
{
pos1 = url1->args().value( "pos" ).toDouble();
}
if ( url2->args().keys().contains( "pos" ) )
{
pos2 = url2->args().value( "pos" ).toDouble();
}
if ( pos1 == pos2 )
return;
qint64 start = qMin( pos1, pos2 ) * 1000;
qint64 end = qMax( pos1, pos2 ) * 1000;
//Now we really should pop up a menu to get the user to enter some info about this
//new track, but for now, just fake it as this is just for testing anyway
QString url = QUrl::fromEncoded ( QByteArray::fromBase64 ( url1->path().toUtf8() ) ).toString();
Meta::TimecodeTrackPtr track = Meta::TimecodeTrackPtr( new Meta::TimecodeTrack( i18n( "New Timecode Track" ), url, start, end ) );
Meta::TimecodeAlbumPtr album = Meta::TimecodeAlbumPtr( new Meta::TimecodeAlbum( i18n( "Unknown" ) ) );
Meta::TimecodeArtistPtr artist = Meta::TimecodeArtistPtr( new Meta::TimecodeArtist( i18n( "Unknown" ) ) );
Meta::TimecodeGenrePtr genre = Meta::TimecodeGenrePtr( new Meta::TimecodeGenre( i18n( "Unknown" ) ) );
album->addTrack( track );
artist->addTrack( track );
genre->addTrack( track );
track->setAlbum( album );
track->setArtist( artist );
track->setGenre( genre );
album->setAlbumArtist( artist );
//make the user give us some info about this item...
Meta::TrackList tl;
tl.append( Meta::TrackPtr::staticCast( track ) );
TagDialog *dialog = new TagDialog( tl, 0 );
dialog->show();
//now add it to the playlist
The::playlistController()->insertOptioned( Meta::TrackPtr::staticCast( track ) );
}
void BookmarkTreeView::setProxy( QSortFilterProxyModel *proxy )
{
m_proxyModel = proxy;
}
void BookmarkTreeView::slotEdit( const QModelIndex &index )
{
//translate to proxy terms
edit( m_proxyModel->mapFromSource( index ) );
}
void BookmarkTreeView::slotSectionResized( int logicalIndex, int oldSize, int newSize )
{
Q_UNUSED( oldSize )
BookmarkModel::Column col = BookmarkModel::Column( logicalIndex );
m_columnsSize[ col ] = static_cast<qreal>( newSize ) / header()->length();
}
void BookmarkTreeView::slotSectionCountChanged( int oldCount, int newCount )
{
Q_UNUSED( oldCount )
const QHeaderView *headerView = header();
for( int i = 0; i < newCount; ++i )
{
const int index = headerView->logicalIndex( i );
const int width = columnWidth( index );
const qreal ratio = static_cast<qreal>( width ) / headerView->length();
const BookmarkModel::Column col = BookmarkModel::Column( index );
if( col == BookmarkModel::Command )
header()->setResizeMode( index, QHeaderView::ResizeToContents );
m_columnsSize[ col ] = ratio;
}
}
diff --git a/src/amarokurls/BookmarkTreeView.h b/src/amarokurls/BookmarkTreeView.h
index 7a9f9e82df..37205e90d2 100644
--- a/src/amarokurls/BookmarkTreeView.h
+++ b/src/amarokurls/BookmarkTreeView.h
@@ -1,91 +1,91 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef BOOKMARKTREEVIEW_H
#define BOOKMARKTREEVIEW_H
#include "amarok_export.h"
#include "AmarokUrl.h"
#include "BookmarkModel.h"
#include "BookmarkViewItem.h"
#include "widgets/PrettyTreeView.h"
#include <QSortFilterProxyModel>
class QMenu;
class PopupDropper;
class QAction;
class QAction;
class AMAROK_EXPORT BookmarkTreeView : public QTreeView
{
Q_OBJECT
public:
BookmarkTreeView( QWidget *parent = 0 );
~BookmarkTreeView();
void setNewGroupAction( QAction * action );
QMenu* contextMenu( const QPoint& point );
void setProxy( QSortFilterProxyModel *proxy );
+ void slotEdit( const QModelIndex &index );
protected:
void keyPressEvent( QKeyEvent *event );
void mouseDoubleClickEvent( QMouseEvent *event );
void contextMenuEvent( QContextMenuEvent *event );
void resizeEvent( QResizeEvent *event );
bool viewportEvent( QEvent *event );
protected Q_SLOTS:
void slotLoad();
void slotDelete();
void slotRename();
- void slotEdit( const QModelIndex &index );
//for testing...
void slotCreateTimecodeTrack() const;
void slotSectionResized( int logicalIndex, int oldSize, int newSize );
void slotSectionCountChanged( int oldCount, int newCount );
void selectionChanged ( const QItemSelection & selected, const QItemSelection & deselected );
Q_SIGNALS:
void bookmarkSelected( AmarokUrl bookmark );
void showMenu( QMenu*, const QPointF& );
private:
QSet<BookmarkViewItemPtr> selectedItems() const;
QList<QAction *> createCommonActions( QModelIndexList indices );
QAction *m_loadAction;
QAction *m_deleteAction;
//for testing...
QAction *m_createTimecodeTrackAction;
QAction *m_addGroupAction;
QMap<BookmarkModel::Column, qreal> m_columnsSize;
QSortFilterProxyModel * m_proxyModel;
};
#endif
diff --git a/src/browsers/BrowserBreadcrumbItem.cpp b/src/browsers/BrowserBreadcrumbItem.cpp
index e27b2d50c3..39583fd4e7 100644
--- a/src/browsers/BrowserBreadcrumbItem.cpp
+++ b/src/browsers/BrowserBreadcrumbItem.cpp
@@ -1,174 +1,174 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BrowserBreadcrumbItem.h"
#include "browsers/BrowserCategoryList.h"
#include "browsers/filebrowser/FileBrowser.h"
#include "core/support/Debug.h"
#include "widgets/BreadcrumbItemButton.h"
#include <QIcon>
#include <KLocale>
#include <QDir>
#include <QMenu>
BrowserBreadcrumbItem::BrowserBreadcrumbItem( BrowserCategory *category, QWidget *parent )
: KHBox( parent )
, m_menuButton( 0 )
{
//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<QString,BrowserCategory *> siblingMap = parentList->categories();
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, SIGNAL(triggered()), siblingMap.value( siblingName ), SLOT(activate()) );
+ 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( "user-home" ) );
}
- connect( m_mainButton, SIGNAL(sizePolicyChanged()), this, SLOT(updateSizePolicy()) );
+ connect( m_mainButton, &BreadcrumbItemButton::sizePolicyChanged, this, &BrowserBreadcrumbItem::updateSizePolicy );
//if this is a list, make cliking on this item cause us
//to navigate to its home.
BrowserCategoryList *list = qobject_cast<BrowserCategoryList*>( category );
if ( list )
{
- connect( m_mainButton, SIGNAL(clicked(bool)), list, SLOT(home()) );
+ connect( m_mainButton, &QAbstractButton::clicked, list, &BrowserCategoryList::home );
}
else
{
- connect( m_mainButton, SIGNAL(clicked(bool)), category, SLOT(reActivate()) );
+ 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 )
: KHBox( parent )
, m_menuButton( 0 )
, 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( '&', "&&" ); // 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, SIGNAL(triggered()), this, SLOT(activateSibling()) );
+ connect( action, &QAction::triggered, this, &BrowserBreadcrumbItem::activateSibling );
i++;
}
m_menuButton->setMenu( menu );
}
m_mainButton = new BreadcrumbItemButton( name, this );
- connect( m_mainButton, SIGNAL(sizePolicyChanged()), this, SLOT(updateSizePolicy()) );
- connect( m_mainButton, SIGNAL(clicked(bool)), this, SLOT(activate()) );
- connect( this, SIGNAL(activated(QString)), handler, SLOT(addItemActivated(QString)) );
+ 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()
{
emit activated( m_callback );
}
void BrowserBreadcrumbItem::activateSibling()
{
QAction *action = qobject_cast<QAction *>( sender() );
if( action )
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 ab214ec511..05091e99c8 100644
--- a/src/browsers/BrowserBreadcrumbWidget.cpp
+++ b/src/browsers/BrowserBreadcrumbWidget.cpp
@@ -1,239 +1,239 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#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 <KLocale>
#include <QDir>
#include <QMenu>
#include <QResizeEvent>
BrowserBreadcrumbWidget::BrowserBreadcrumbWidget( QWidget * parent )
: KHBox( parent)
, m_rootList( 0 )
, m_childMenuButton( 0 )
{
setFixedHeight( 28 );
setContentsMargins( 3, 0, 3, 0 );
setSpacing( 0 );
m_breadcrumbArea = new KHBox( this );
m_breadcrumbArea->setContentsMargins( 0, 0, 0, 0 );
m_breadcrumbArea->setSpacing( 0 );
setStretchFactor( m_breadcrumbArea, 10 );
new BreadcrumbUrlMenuButton( "navigate", this );
m_spacer = new QWidget( 0 );
}
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, SIGNAL(viewChanged()), this, SLOT(updateBreadcrumbs()) );
+ connect( m_rootList, &BrowserCategoryList::viewChanged, this, &BrowserBreadcrumbWidget::updateBreadcrumbs );
updateBreadcrumbs();
}
void
BrowserBreadcrumbWidget::updateBreadcrumbs()
{
if( !m_rootList )
return;
clearCrumbs();
m_spacer->setParent( 0 );
addLevel( m_rootList );
m_spacer->setParent( m_breadcrumbArea );
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<BrowserCategoryList*>( childCategory );
if( childList )
{
addLevel( childList );
}
else
{
BrowserBreadcrumbItem *leaf = childCategory->breadcrumb();
addBreadCrumbItem( leaf );
m_items.append( leaf );
const QList<BrowserBreadcrumbItem*> 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<BrowserCategoryList*>( list );
if( childList )
{
m_childMenuButton = new BreadcrumbItemMenuButton( m_breadcrumbArea );
QMenu *menu = new QMenu( item );
menu->hide();
QMap<QString,BrowserCategory *> 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, SIGNAL(triggered()), childMap.value( siblingName ), SLOT(activate()) );
+ 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( 0 ); // may be already shown, we want it to be last, so reparent
item->setParent( m_breadcrumbArea );
}
void BrowserBreadcrumbWidget::resizeEvent( QResizeEvent *event )
{
Q_UNUSED( event )
// we need to postpone the call, because hideAsNeeded() itself may trigger resizeEvent
- QTimer::singleShot( 0 , this, SLOT(showAsNeeded()) );
+ 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<BrowserBreadcrumbItem *> 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<BrowserBreadcrumbItem *> it( allItems );
while( it.hasNext() )
{
if( it.next()->parent() != m_breadcrumbArea )
it.remove();
}
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/BrowserCategory.cpp b/src/browsers/BrowserCategory.cpp
index b8057507c8..e699ea0d20 100644
--- a/src/browsers/BrowserCategory.cpp
+++ b/src/browsers/BrowserCategory.cpp
@@ -1,180 +1,180 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BrowserCategory.h"
#include "App.h"
#include "amarokconfig.h"
#include "BrowserBreadcrumbItem.h"
#include "BrowserCategoryList.h"
#include "PaletteHandler.h"
#include "core/support/Debug.h"
#include <QWidget>
BrowserCategory::BrowserCategory( const QString &name, QWidget *parent )
: KVBox( parent )
, m_name( name )
, m_parentList( 0 )
{
setObjectName( name );
setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
setFrameShape( QFrame::NoFrame );
- connect( App::instance(), SIGNAL(settingsChanged()), SLOT(slotSettingsChanged()) );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(slotSettingsChanged()) );
+ connect( App::instance(), &App::settingsChanged, this, &BrowserCategory::slotSettingsChanged );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &BrowserCategory::slotSettingsChanged );
}
BrowserCategory::~BrowserCategory()
{
}
QString
BrowserCategory::name() const
{
return m_name;
}
void
BrowserCategory::setPrettyName( const QString &prettyName )
{
m_prettyName = prettyName;
}
QString
BrowserCategory::prettyName() const
{
return m_prettyName;
}
void
BrowserCategory::setShortDescription( const QString &shortDescription )
{
m_shortDescription = shortDescription;
}
QString
BrowserCategory::shortDescription() const
{
return m_shortDescription;
}
void
BrowserCategory::setLongDescription( const QString &longDescription )
{
m_longDescription = longDescription;
}
QString
BrowserCategory::longDescription() const
{
return m_longDescription;
}
void
BrowserCategory::setIcon( const QIcon & icon )
{
m_icon = icon;
}
QIcon
BrowserCategory::icon() const
{
return m_icon;
}
void
BrowserCategory::setBackgroundImage(const QString& path)
{
if ( path.isEmpty() || !QUrl(path).isLocalFile() ) {
setStyleSheet( QString() );
return;
}
// Hack alert: Use the class name of the most derived object (using polymorphism) for CSS
// This is required to limit the style to this specific class only (avoiding cascading)
// \sa http://doc.qt.nokia.com/latest/stylesheet-syntax.html#widgets-inside-c-namespaces
const QString escapedClassName = QString( metaObject()->className() ).replace( "::", "--" );
setStyleSheet( QString("%1 { background-image: url(\"%2\"); \
background-repeat: no-repeat; \
background-attachment: fixed; \
background-position: center; }").arg( escapedClassName, path )
);
}
void BrowserCategory::slotSettingsChanged()
{
setBackgroundImage( AmarokConfig::showBrowserBackgroundImage() ? m_imagePath : QString() );
}
void BrowserCategory::setParentList( BrowserCategoryList * parent )
{
m_parentList = parent;
}
BrowserCategoryList * BrowserCategory::parentList() const
{
return m_parentList;
}
void BrowserCategory::activate()
{
DEBUG_BLOCK
if ( parentList() )
parentList()->setActiveCategory( this );
}
BrowserBreadcrumbItem *BrowserCategory::breadcrumb()
{
return new BrowserBreadcrumbItem( this );
}
void BrowserCategory::setImagePath( const QString & path )
{
m_imagePath = path;
}
QString BrowserCategory::imagePath() const
{
return m_imagePath;
}
void
BrowserCategory::addAdditionalItem( BrowserBreadcrumbItem * item )
{
m_additionalItems.append( item );
}
void
BrowserCategory::clearAdditionalItems()
{
foreach( BrowserBreadcrumbItem *item, m_additionalItems )
{
m_additionalItems.removeAll( item );
/* deleting immediatelly isn't safe, this method may be called from an inner
* QEventLoop inside QMenu::exec() of another breadcrumb item, which could
* then leas to crash bug 265626 */
item->deleteLater();
}
}
QList<BrowserBreadcrumbItem *>
BrowserCategory::additionalItems()
{
return m_additionalItems;
}
diff --git a/src/browsers/BrowserCategoryList.cpp b/src/browsers/BrowserCategoryList.cpp
index b53e9c5eb8..668a9e954f 100644
--- a/src/browsers/BrowserCategoryList.cpp
+++ b/src/browsers/BrowserCategoryList.cpp
@@ -1,421 +1,421 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "BrowserCategoryList"
#include "BrowserCategoryList.h"
#include "App.h"
#include "context/ContextView.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 <QStackedWidget>
#include <QTreeView>
#include <KComboBox>
#include <KStandardDirs>
#include <KLocalizedString>
#include <QFile>
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, SIGNAL(filterChanged(QString)), SLOT(setFilter(QString)) );
+ 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, SIGNAL(activated(QModelIndex)),
- SLOT(categoryActivated(QModelIndex)) );
+ connect( m_categoryListView, &Amarok::PrettyTreeView::activated,
+ this, &BrowserCategoryList::categoryActivated );
- connect( m_categoryListView, SIGNAL(entered(QModelIndex)),
- SLOT(categoryEntered(QModelIndex)) );
+ 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<BrowserCategory *>() )
category = index.data( CustomCategoryRoles::CategoryRole ).value<BrowserCategory *>();
else
return;
if( category )
{
debug() << "Show service: " << category->name();
setActiveCategory( category );
}
}
void
BrowserCategoryList::home()
{
DEBUG_BLOCK
if( activeCategory() )
{
BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( activeCategory() );
if( childList )
childList->home();
activeCategory()->clearAdditionalItems();
m_widgetStack->setCurrentIndex( 0 );
emit( viewChanged() );
}
}
QMap<QString, BrowserCategory*>
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<BrowserCategoryList*>( category );
if ( childList )
- connect( childList, SIGNAL(viewChanged()), this, SLOT(childViewChanged()) );
+ connect( childList, &BrowserCategoryList::viewChanged, this, &BrowserCategoryList::childViewChanged );
category->polish(); // service categories do an additional construction in polish
if( m_sorting )
{
m_proxyModel->sort( 0 );
}
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();
emit( viewChanged() );
}
BrowserCategory*
BrowserCategoryList::activeCategory() const
{
return qobject_cast<BrowserCategory*>(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 );
emit( viewChanged() );
}
void BrowserCategoryList::back()
{
DEBUG_BLOCK
BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( activeCategory() );
if( childList )
{
if( childList->activeCategory() != 0 )
{
childList->back();
return;
}
}
home();
}
void BrowserCategoryList::childViewChanged()
{
DEBUG_BLOCK
emit( viewChanged() );
}
QString BrowserCategoryList::navigate( const QString & target )
{
DEBUG_BLOCK
debug() << "target: " << target;
QStringList categories = target.split( '/' );
if ( categories.size() == 0 )
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.size() == 0 )
{
//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<BrowserCategoryList*>( activeCategory() );
if ( childList == 0 )
{
debug() << "child is not a list...";
if ( categories.size() > 1 )
{
categories.removeFirst();
QString leftover = categories.join( "/" );
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( "/" );
return childList->navigate( categories.join( "/" ) );
}
QString BrowserCategoryList::path()
{
DEBUG_BLOCK
QString pathString = name();
BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( 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 it to info proxy.
BrowserCategory *category = 0;
if ( index.data( CustomCategoryRoles::CategoryRole ).canConvert<BrowserCategory *>() )
category = index.data( CustomCategoryRoles::CategoryRole ).value<BrowserCategory *>();
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() )
{
QUrl dataUrl( KStandardDirs::locate( "data", "amarok/data/" ) );
QString dataPath = dataUrl.path();
//load html
QString htmlPath = dataPath + "hover_info_template.html";
QFile file( htmlPath );
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text) )
{
debug() << "error opening file. Error: " << file.error();
return;
}
m_infoHtmlTemplate = file.readAll();
file.close();
m_infoHtmlTemplate.replace( "{background_color}",PaletteHandler::highlightColor().lighter( 150 ).name() );
m_infoHtmlTemplate.replace( "{border_color}", PaletteHandler::highlightColor().lighter( 150 ).name() );
m_infoHtmlTemplate.replace( "{text_color}", App::instance()->palette().brush( QPalette::Text ).color().name() );
QColor highlight( App::instance()->palette().highlight().color() );
highlight.setHsvF( highlight.hueF(), 0.3, .95, highlight.alphaF() );
m_infoHtmlTemplate.replace( "{header_background_color}", highlight.name() );
}
QString currentHtml = m_infoHtmlTemplate;
currentHtml.replace( "%%NAME%%", category->prettyName() );
currentHtml.replace( "%%DESCRIPTION%%", category->longDescription() );
currentHtml.replace( "%%IMAGE_PATH%%", "file://" + category->imagePath() );
QVariantMap variantMap;
variantMap["main_info"] = QVariant( currentHtml );
The::infoProxy()->setInfo( variantMap );
}
}
QString BrowserCategoryList::css()
{
QString style =
"<style type='text/css'>"
"body"
"{"
" text-align:center;"
" background-color: {background_color};"
"}"
"#main"
" {"
" text-align: center;"
" }"
""
"#text-border"
" {"
" display: block;"
" margin-left: 0;"
" margin-right: 0;"
" padding: 4px;"
" border: 4px solid {border_color};"
" -webkit-border-radius: 4px;"
" -khtml-border-radius: 4px;"
" -moz-border-radius: 4px;"
" border-radius: 4px;"
" font-size: 94%;"
" text-align: center;"
" word-wrap: normal;"
" background-color: {content_background_color};"
" color: {text_color};"
" }"
"</style>";
return style;
}
BrowserCategory *BrowserCategoryList::activeCategoryRecursive()
{
BrowserCategory *category = activeCategory();
if( !category )
return this;
BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( category );
if( childList )
return childList->activeCategoryRecursive();
return category;
}
void BrowserCategoryList::setFilter( const QString &filter )
{
m_proxyModel->setFilterFixedString( filter );
}
diff --git a/src/browsers/BrowserDock.cpp b/src/browsers/BrowserDock.cpp
index 3979721833..51521ee3d3 100644
--- a/src/browsers/BrowserDock.cpp
+++ b/src/browsers/BrowserDock.cpp
@@ -1,122 +1,122 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BrowserDock.h"
#include "core/interfaces/Logger.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/logger/ProxyLogger.h"
#include "PaletteHandler.h"
#include "widgets/HorizontalDivider.h"
#include <QAction>
#include <QIcon>
#include <KLocale>
#include <QWidget>
BrowserDock::BrowserDock( QWidget *parent )
: AmarokDockWidget( i18n( "&Media Sources" ), parent )
{
setObjectName( "Media Sources dock" );
setAllowedAreas( Qt::AllDockWidgetAreas );
//we have to create this here as it is used when setting up the
//categories (unless of course we move that to polish as well...)
m_mainWidget = new KVBox( this );
setWidget( m_mainWidget );
m_mainWidget->setContentsMargins( 0, 0, 0, 0 );
m_mainWidget->setFrameShape( QFrame::NoFrame );
m_mainWidget->setMinimumWidth( 200 );
m_mainWidget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
m_mainWidget->setFocus( Qt::ActiveWindowFocusReason );
m_breadcrumbWidget = new BrowserBreadcrumbWidget( m_mainWidget );
new HorizontalDivider( m_mainWidget );
m_categoryList = new BrowserCategoryList( "root list", m_mainWidget );
m_breadcrumbWidget->setRootList( m_categoryList.data() );
m_messageArea = new BrowserMessageArea( m_mainWidget );
m_messageArea->setAutoFillBackground( true );
m_messageArea->setFixedHeight( 36 );
Amarok::Logger *logger = Amarok::Components::logger();
ProxyLogger *proxy = dynamic_cast<ProxyLogger *>( logger );
if( proxy )
proxy->setLogger( m_messageArea );
else
error() << "Was not able to register BrowserDock as logger";
ensurePolish();
}
BrowserDock::~BrowserDock()
{}
void BrowserDock::polish()
{
m_categoryList.data()->setIcon( QIcon::fromTheme( "user-home" ) );
m_categoryList.data()->setMinimumSize( 100, 300 );
- connect( m_breadcrumbWidget, SIGNAL(toHome()), this, SLOT(home()) );
+ connect( m_breadcrumbWidget, &BrowserBreadcrumbWidget::toHome, this, &BrowserDock::home );
// Keyboard shortcut for going back one level
QAction *action = new QAction( QIcon::fromTheme( "go-up" ), i18n( "Go Up in Media Sources Pane" ),
m_mainWidget );
Amarok::actionCollection()->addAction( "browser_previous", action );
- connect( action, SIGNAL(triggered(bool)), m_categoryList.data(), SLOT(back()) );
+ connect( action, &QAction::triggered, m_categoryList.data(), &BrowserCategoryList::back );
// action->setShortcut( QKeySequence( Qt::Key_Backspace ) );
action->setShortcut( Qt::Key_Backspace );
paletteChanged( palette() );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(paletteChanged(QPalette)) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &BrowserDock::paletteChanged );
}
BrowserCategoryList *BrowserDock::list() const
{
return m_categoryList.data();
}
void
BrowserDock::navigate( const QString &target )
{
m_categoryList.data()->navigate( target );
}
void
BrowserDock::home()
{
m_categoryList.data()->home();
}
void
BrowserDock::paletteChanged( const QPalette &palette )
{
m_messageArea->setStyleSheet(
QString( "QFrame#BrowserMessageArea { border: 1px ridge %1; " \
"background-color: %2; color: %3; border-radius: 3px; }" \
"QLabel { color: %3; }" )
.arg( palette.color( QPalette::Active, QPalette::Window ).name() )
.arg( The::paletteHandler()->highlightColor().name() )
.arg( palette.color( QPalette::Active, QPalette::HighlightedText ).name() )
);
}
diff --git a/src/browsers/BrowserMessageArea.cpp b/src/browsers/BrowserMessageArea.cpp
index 13e6dd3456..7b312ae0e2 100644
--- a/src/browsers/BrowserMessageArea.cpp
+++ b/src/browsers/BrowserMessageArea.cpp
@@ -1,170 +1,168 @@
/****************************************************************************************
* Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BrowserMessageArea.h"
-#include "statusbar/KJobProgressBar.h"
#include "statusbar/LongMessageWidget.h"
-#include "statusbar/NetworkProgressBar.h"
#define SHORT_MESSAGE_DURATION 5000
#define POPUP_MESSAGE_DURATION 5000
BrowserMessageArea::BrowserMessageArea( QWidget *parent )
: QFrame( parent )
, m_busy( false )
{
setObjectName( "BrowserMessageArea" );
setLayout( new QVBoxLayout( this ) );
m_progressBar = new CompoundProgressBar( this );
- connect( m_progressBar, SIGNAL(allDone()), this, SLOT(hideProgress()) );
+ connect( m_progressBar, &CompoundProgressBar::allDone, this, &BrowserMessageArea::hideProgress );
layout()->addWidget( m_progressBar );
m_progressBar->hide();
m_messageLabel = new QLabel( this );
m_messageLabel->setAlignment( Qt::AlignCenter );
m_messageLabel->setWordWrap( true );
layout()->addWidget( m_messageLabel );
m_messageLabel->hide();
m_shortMessageTimer = new QTimer( this );
m_shortMessageTimer->setSingleShot( true );
- connect( m_shortMessageTimer, SIGNAL(timeout()), SLOT(nextShortMessage()) );
+ connect( m_shortMessageTimer, &QTimer::timeout, this, &BrowserMessageArea::nextShortMessage );
//register to carry MessageType across threads
qRegisterMetaType<Amarok::Logger::MessageType>( "MessageType" );
- connect( this, SIGNAL(signalLongMessage(QString,MessageType)),
- this, SLOT(slotLongMessage(QString,MessageType)),
+ connect( this, &BrowserMessageArea::signalLongMessage,
+ this, &BrowserMessageArea::slotLongMessage,
Qt::QueuedConnection );
}
void
BrowserMessageArea::shortMessage( const QString &text )
{
if( !m_busy )
{
m_busy = true;
m_messageLabel->setText( text );
m_messageLabel->show();
m_shortMessageTimer->start( SHORT_MESSAGE_DURATION );
}
else
{
m_shortMessageQueue.append( text );
}
}
void
BrowserMessageArea::longMessage( const QString &text, MessageType type )
{
// The purpose of this emit is to make the operation thread safe. If this
// method is called from a non-GUI thread, the "emit" relays it over the
// event loop to the GUI thread, so that we can safely create widgets.
emit signalLongMessage( text, type );
}
void
BrowserMessageArea::newProgressOperation( KJob *job, const QString &text, QObject *obj,
- const char *slot, Qt::ConnectionType type )
+ const char *slot, Qt::ConnectionType type )
{
KJobProgressBar *newBar = new KJobProgressBar( 0, job );
newBar->setDescription( text );
- connect( job, SIGNAL(destroyed(QObject*)), m_progressBar,
- SLOT(endProgressOperation(QObject*)) );
+ connect( job, &KJob::destroyed, m_progressBar,
+ &CompoundProgressBar::endProgressOperation );
newBar->setAbortSlot( obj, slot, type );
m_progressBar->addProgressBar( newBar, job );
m_progressBar->show();
m_busy = true;
}
void
BrowserMessageArea::newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj,
const char *slot, Qt::ConnectionType type )
{
NetworkProgressBar *newBar = new NetworkProgressBar( 0, reply );
newBar->setDescription( text );
- newBar->setAbortSlot( reply, SLOT(deleteLater()) );
- connect( reply, SIGNAL(destroyed(QObject*)), m_progressBar,
- SLOT(endProgressOperation(QObject*)) );
+ newBar->setAbortSlot( reply, &QNetworkReply::deleteLater );
+ connect( reply, &QNetworkReply::destroyed, m_progressBar,
+ &CompoundProgressBar::endProgressOperation );
newBar->setAbortSlot( obj, slot, type );
m_progressBar->addProgressBar( newBar, reply );
m_progressBar->show();
m_busy = true;
}
void
BrowserMessageArea::newProgressOperation( QObject *sender, const QString &text, int maximum,
QObject *obj, const char *slot, Qt::ConnectionType type )
{
ProgressBar *newBar = new ProgressBar( 0 );
newBar->setDescription( text );
newBar->setMaximum( maximum );
- connect( sender, SIGNAL(destroyed(QObject*)), m_progressBar,
- SLOT(endProgressOperation(QObject*)), Qt::QueuedConnection );
+ connect( sender, &QObject::destroyed, m_progressBar,
+ &CompoundProgressBar::endProgressOperation, Qt::QueuedConnection );
connect( sender, SIGNAL(endProgressOperation(QObject*)), m_progressBar,
SLOT(endProgressOperation(QObject*)), Qt::QueuedConnection );
connect( sender, SIGNAL(incrementProgress()), m_progressBar,
SLOT(slotIncrementProgress()), Qt::QueuedConnection );
connect( sender, SIGNAL(totalSteps(int)), newBar, SLOT(slotTotalSteps(int)) );
newBar->setAbortSlot( obj, slot, type );
m_progressBar->addProgressBar( newBar, sender );
m_progressBar->show();
m_busy = true;
}
void
BrowserMessageArea::hideProgress() //SLOT
{
m_progressBar->hide();
m_busy = false;
nextShortMessage();
}
void
BrowserMessageArea::nextShortMessage()
{
if( m_shortMessageQueue.count() > 0 )
{
m_busy = true;
m_messageLabel->setText( m_shortMessageQueue.takeFirst() );
m_messageLabel->show();
m_shortMessageTimer->start( SHORT_MESSAGE_DURATION );
}
else
{
m_messageLabel->hide();
m_busy = false;
}
}
void
BrowserMessageArea::hideLongMessage()
{
sender()->deleteLater();
}
void
BrowserMessageArea::slotLongMessage( const QString &text, MessageType type )
{
LongMessageWidget *message = new LongMessageWidget( this, text, type );
- connect( message, SIGNAL(closed()), this, SLOT(hideLongMessage()) );
+ connect( message, &LongMessageWidget::closed, this, &BrowserMessageArea::hideLongMessage );
}
diff --git a/src/browsers/BrowserMessageArea.h b/src/browsers/BrowserMessageArea.h
index f91efe6416..3dc4f10e21 100644
--- a/src/browsers/BrowserMessageArea.h
+++ b/src/browsers/BrowserMessageArea.h
@@ -1,67 +1,71 @@
/****************************************************************************************
* Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef BROWSERMESSAGEAREA_H
#define BROWSERMESSAGEAREA_H
#include "core-impl/logger/ProxyLogger.h"
#include "statusbar/CompoundProgressBar.h"
+#include "statusbar/KJobProgressBar.h"
+#include "statusbar/NetworkProgressBar.h"
+
#include <QFrame>
#include <QTimer>
class BrowserMessageArea : public QFrame, public Amarok::Logger
{
Q_OBJECT
public:
BrowserMessageArea( QWidget *parent );
~BrowserMessageArea()
{
}
/* Amarok::Logger virtual methods */
virtual void shortMessage( const QString &text );
virtual void longMessage( const QString &text, MessageType type );
virtual void newProgressOperation( KJob *job, const QString &text, QObject *obj,
- const char *slot, Qt::ConnectionType type );
+ const char *slot, Qt::ConnectionType type );
virtual void newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj,
- const char *slot, Qt::ConnectionType type );
+ const char *slot, Qt::ConnectionType type );
virtual void newProgressOperation( QObject *sender, const QString &text, int maximum,
QObject *obj, const char *slot, Qt::ConnectionType type );
+
Q_SIGNALS:
void signalLongMessage( const QString & text, MessageType type );
private Q_SLOTS:
void hideProgress();
void nextShortMessage();
void hideLongMessage();
void slotLongMessage( const QString &text, MessageType type = Information );
private:
CompoundProgressBar *m_progressBar;
QLabel *m_messageLabel;
bool m_busy;
QTimer *m_shortMessageTimer;
QList<QString> m_shortMessageQueue;
};
#endif // BROWSERMESSAGEAREA_H
diff --git a/src/browsers/CollectionTreeItem.cpp b/src/browsers/CollectionTreeItem.cpp
index abc21f51de..48b87f2785 100644
--- a/src/browsers/CollectionTreeItem.cpp
+++ b/src/browsers/CollectionTreeItem.cpp
@@ -1,402 +1,402 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2009 Seb Ruiz <ruiz@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CollectionTreeItem.h"
#include "amarokconfig.h"
#include "browsers/CollectionTreeItemModelBase.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "widgets/PrettyTreeRoles.h"
#include <QIcon>
#include <KLocale>
Q_DECLARE_METATYPE( QAction* )
Q_DECLARE_METATYPE( QList<QAction*> )
CollectionTreeItem::CollectionTreeItem( CollectionTreeItemModelBase *model )
: m_data( 0 )
, m_parent( 0 )
, m_model( model )
, m_parentCollection( 0 )
, m_updateRequired( false )
, m_trackCount( -1 )
, m_type( Root )
//, m_name( "Root" )
, m_isCounting( false )
{
}
CollectionTreeItem::CollectionTreeItem( Meta::DataPtr data, CollectionTreeItem *parent, CollectionTreeItemModelBase *model )
: m_data( data )
, m_parent( parent )
, m_model( model )
, m_parentCollection( 0 )
, m_updateRequired( true )
, m_trackCount( -1 )
, m_type( Data )
//, m_name( data ? data->name() : "NullData" )
, m_isCounting( false )
{
if ( m_parent )
m_parent->appendChild( this );
}
CollectionTreeItem::CollectionTreeItem( Collections::Collection *parentCollection, CollectionTreeItem *parent, CollectionTreeItemModelBase *model )
: m_data( 0 )
, m_parent( parent )
, m_model( model )
, m_parentCollection( parentCollection )
, m_updateRequired( true )
, m_trackCount( -1 )
, m_type( Collection )
//, m_name( parentCollection ? parentCollection->collectionId() : "NullColl" )
, m_isCounting( false )
{
if ( m_parent )
m_parent->appendChild( this );
- connect( parentCollection, SIGNAL(updated()), SLOT(collectionUpdated()) );
+ connect( parentCollection, &Collections::Collection::updated, this, &CollectionTreeItem::collectionUpdated );
}
CollectionTreeItem::CollectionTreeItem( Type type, const Meta::DataList &data, CollectionTreeItem *parent, CollectionTreeItemModelBase *model )
: m_data( 0 )
, m_parent( parent )
, m_model( model )
, m_parentCollection( 0 )
, m_updateRequired( false ) //the node already has all children
, m_trackCount( -1 )
, m_type( type )
, m_isCounting( false )
{
if( m_parent )
m_parent->m_childItems.insert( 0, this );
foreach( Meta::DataPtr datap, data )
new CollectionTreeItem( datap, this, m_model );
}
CollectionTreeItem::~CollectionTreeItem()
{
qDeleteAll( m_childItems );
}
void
CollectionTreeItem::appendChild(CollectionTreeItem *child)
{
m_childItems.append(child);
}
void
CollectionTreeItem::removeChild( int index )
{
CollectionTreeItem *child = m_childItems[index];
m_childItems.removeAt( index );
child->prepareForRemoval();
child->deleteLater();
}
void
CollectionTreeItem::prepareForRemoval()
{
m_parent = 0;
m_model->itemAboutToBeDeleted( this );
foreach( CollectionTreeItem *item, m_childItems )
{
item->prepareForRemoval();
}
}
CollectionTreeItem*
CollectionTreeItem::child( int row )
{
return m_childItems.value( row );
}
QVariant
CollectionTreeItem::data( int role ) const
{
if( isNoLabelItem() )
{
switch( role )
{
case Qt::DisplayRole:
return i18nc( "No labels are assigned to the given item are any of its subitems", "No Labels" );
case Qt::DecorationRole:
return QIcon::fromTheme( "label-amarok" );
}
return QVariant();
}
else if( m_parentCollection )
{
static const QString counting = i18n( "Counting..." );
switch( role )
{
case Qt::DisplayRole:
case PrettyTreeRoles::FilterRole:
case PrettyTreeRoles::SortRole:
return m_parentCollection->prettyName();
case Qt::DecorationRole:
return m_parentCollection->icon();
case PrettyTreeRoles::ByLineRole:
if( m_isCounting )
return counting;
if( m_trackCount < 0 )
{
m_isCounting = true;
Collections::QueryMaker *qm = m_parentCollection->queryMaker();
- connect( qm, SIGNAL(newResultReady(QStringList)),
- SLOT(tracksCounted(QStringList)) );
+ connect( qm, &Collections::QueryMaker::newResultReady,
+ this, &CollectionTreeItem::tracksCounted );
qm->setAutoDelete( true )
->setQueryType( Collections::QueryMaker::Custom )
->addReturnFunction( Collections::QueryMaker::Count, Meta::valUrl )
->run();
return counting;
}
return i18np( "1 track", "%1 tracks", m_trackCount );
case PrettyTreeRoles::HasCapacityRole:
return m_parentCollection->hasCapacity();
case PrettyTreeRoles::UsedCapacityRole:
return m_parentCollection->usedCapacity();
case PrettyTreeRoles::TotalCapacityRole:
return m_parentCollection->totalCapacity();
case PrettyTreeRoles::DecoratorRoleCount:
return decoratorActions().size();
case PrettyTreeRoles::DecoratorRole:
QVariant v;
v.setValue( decoratorActions() );
return v;
}
}
return QVariant();
}
QList<QAction*>
CollectionTreeItem::decoratorActions() const
{
QList<QAction*> decoratorActions;
QScopedPointer<Capabilities::ActionsCapability> dc( m_parentCollection->create<Capabilities::ActionsCapability>() );
if( dc )
decoratorActions = dc->actions();
return decoratorActions;
}
void
CollectionTreeItem::tracksCounted( QStringList res )
{
if( !res.isEmpty() )
m_trackCount = res.first().toInt();
else
m_trackCount = 0;
m_isCounting = false;
emit dataUpdated();
}
void
CollectionTreeItem::collectionUpdated()
{
m_trackCount = -1;
}
int
CollectionTreeItem::row() const
{
if( m_parent )
{
const QList<CollectionTreeItem*> &children = m_parent->m_childItems;
if( !children.isEmpty() && children.contains( const_cast<CollectionTreeItem*>(this) ) )
return children.indexOf( const_cast<CollectionTreeItem*>(this) );
else
return -1;
}
return 0;
}
int
CollectionTreeItem::level() const
{
if( m_parent )
return m_parent->level() + 1;
return -1;
}
bool
CollectionTreeItem::isDataItem() const
{
return m_type == Data;
}
bool
CollectionTreeItem::isVariousArtistItem() const
{
return m_type == CollectionTreeItem::VariousArtist;
}
bool
CollectionTreeItem::isNoLabelItem() const
{
return m_type == CollectionTreeItem::NoLabel;
}
bool
CollectionTreeItem::isAlbumItem() const
{
return m_type == Data && !Meta::AlbumPtr::dynamicCast( m_data ).isNull();
}
bool
CollectionTreeItem::isTrackItem() const
{
return m_type == Data && !Meta::TrackPtr::dynamicCast( m_data ).isNull();
}
Collections::QueryMaker*
CollectionTreeItem::queryMaker() const
{
Collections::Collection* coll = parentCollection();
if( coll )
return coll->queryMaker();
return 0;
}
void
CollectionTreeItem::addMatch( Collections::QueryMaker *qm, CategoryId::CatMenuId levelCategory ) const
{
if( !qm )
return;
if( isVariousArtistItem() )
qm->setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations );
if( isNoLabelItem() )
qm->setLabelQueryMode( Collections::QueryMaker::OnlyWithoutLabels );
else if( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( m_data ) )
qm->addMatch( track );
else if( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( m_data ) )
{
Collections::QueryMaker::ArtistMatchBehaviour behaviour =
( levelCategory == CategoryId::AlbumArtist ) ? Collections::QueryMaker::AlbumArtists :
Collections::QueryMaker::TrackArtists;
qm->addMatch( artist, behaviour );
}
else if( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( m_data ) )
qm->addMatch( album );
else if( Meta::ComposerPtr composer = Meta::ComposerPtr::dynamicCast( m_data ) )
qm->addMatch( composer );
else if( Meta::GenrePtr genre = Meta::GenrePtr::dynamicCast( m_data ) )
qm->addMatch( genre );
else if( Meta::YearPtr year = Meta::YearPtr::dynamicCast( m_data ) )
qm->addMatch( year );
else if( Meta::LabelPtr label = Meta::LabelPtr::dynamicCast( m_data ) )
qm->addMatch( label );
}
QList<QUrl>
CollectionTreeItem::urls() const
{
/*QueryBuilder qb = queryBuilder();
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
QStringList values = qb.run();
QList<QUrl> list;
foreach( QString s, values ) {
list += QUrl( s );
}
return list;*/
QList<QUrl> list;
return list;
}
bool
CollectionTreeItem::operator<( const CollectionTreeItem& other ) const
{
if( isVariousArtistItem() )
return true;
return m_data->sortableName() < other.m_data->sortableName();
}
const Meta::DataPtr
CollectionTreeItem::data() const
{
return m_data;
}
Meta::TrackList
CollectionTreeItem::descendentTracks()
{
QList<Meta::TrackPtr> descendentTracks;
Meta::TrackPtr track;
if( isDataItem() )
track = Meta::TrackPtr::dynamicCast( m_data );
if( !track.isNull() )
descendentTracks << track;
else
{
foreach( CollectionTreeItem *child, m_childItems )
descendentTracks << child->descendentTracks();
}
return descendentTracks;
}
bool
CollectionTreeItem::allDescendentTracksLoaded() const
{
if( isTrackItem() )
return true;
if( requiresUpdate() )
return false;
foreach( CollectionTreeItem *item, m_childItems )
{
if( !item->allDescendentTracksLoaded() )
return false;
}
return true;
}
void
CollectionTreeItem::setRequiresUpdate( bool updateRequired )
{
m_updateRequired = updateRequired;
}
bool
CollectionTreeItem::requiresUpdate() const
{
return m_updateRequired;
}
CollectionTreeItem::Type
CollectionTreeItem::type() const
{
return m_type;
}
QList<CollectionTreeItem*>
CollectionTreeItem::children() const
{
return m_childItems;
}
diff --git a/src/browsers/CollectionTreeItemModel.cpp b/src/browsers/CollectionTreeItemModel.cpp
index 54373a223e..216ada2ddf 100644
--- a/src/browsers/CollectionTreeItemModel.cpp
+++ b/src/browsers/CollectionTreeItemModel.cpp
@@ -1,246 +1,246 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CollectionTreeItemModel"
#include "CollectionTreeItemModel.h"
#include <amarokconfig.h>
#include "AmarokMimeData.h"
#include "CollectionTreeItem.h"
#include "core/support/Debug.h"
#include "core/support/Amarok.h"
#include "core/collections/Collection.h"
#include "core/collections/CollectionLocation.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/collections/support/FileCollectionLocation.h"
#include <KLocale>
#include <QTimer>
#include <QMap>
CollectionTreeItemModel::CollectionTreeItemModel( const QList<CategoryId::CatMenuId> &levelType )
: CollectionTreeItemModelBase()
{
m_rootItem = new CollectionTreeItem( this );
CollectionManager *collMgr = CollectionManager::instance();
- connect( collMgr, SIGNAL(collectionAdded(Collections::Collection*)), this, SLOT(collectionAdded(Collections::Collection*)), Qt::QueuedConnection );
- connect( collMgr, SIGNAL(collectionRemoved(QString)), this, SLOT(collectionRemoved(QString)) );
+ connect( collMgr, &CollectionManager::collectionAdded, this, &CollectionTreeItemModel::collectionAdded, Qt::QueuedConnection );
+ connect( collMgr, &CollectionManager::collectionRemoved, this, &CollectionTreeItemModel::collectionRemoved );
QList<Collections::Collection *> collections = CollectionManager::instance()->viewableCollections();
foreach( Collections::Collection *coll, collections )
{
- connect( coll, SIGNAL(updated()), this, SLOT(slotFilter()) ) ;
+ connect( coll, &Collections::Collection::updated, this, &CollectionTreeItemModel::slotFilterWithoutAutoExpand );
m_collections.insert( coll->collectionId(), CollectionRoot( coll, new CollectionTreeItem( coll, m_rootItem, this ) ) );
}
setLevels( levelType );
}
Qt::ItemFlags
CollectionTreeItemModel::flags( const QModelIndex &idx ) const
{
if( !idx.isValid() )
return 0;
Qt::ItemFlags flags = CollectionTreeItemModelBase::flags( idx );
if( idx.parent().isValid() )
return flags; // has parent -> not a collection -> no drops
// we depend on someone (probably CollectionTreeView) to call
// CollectionTreeItemModelBase::setDragSourceCollections() every time a drag is
// initiated or enters collection browser widget
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( idx.internalPointer() );
Q_ASSERT(item->type() == CollectionTreeItem::Collection);
if( m_dragSourceCollections.contains( item->parentCollection() ) )
return flags; // attempt to drag tracks from the same collection, don't allow this (bug 291068)
if( !item->parentCollection()->isWritable() )
return flags; // not writeable, disallow drops
// all paranoid checks passed, tracks can be dropped to this item
return flags | Qt::ItemIsDropEnabled;
}
QVariant
CollectionTreeItemModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
CollectionTreeItem *item = static_cast<CollectionTreeItem*>(index.internalPointer());
// subtract one here because there is a collection level for this model
return dataForItem( item, role, item->level() - 1 );
}
bool
CollectionTreeItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent )
{
Q_UNUSED(row)
Q_UNUSED(column)
//no drops on empty areas
if( !parent.isValid() )
return false;
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
Q_ASSERT(item->type() == CollectionTreeItem::Collection);
Collections::Collection *targetCollection = item->parentCollection();
Q_ASSERT(targetCollection);
//TODO: accept external drops.
const AmarokMimeData *mimeData = qobject_cast<const AmarokMimeData *>( data );
Q_ASSERT(mimeData);
//TODO: optimize for copy from same provider.
Meta::TrackList tracks = mimeData->tracks();
QMap<Collections::Collection *, Meta::TrackPtr> collectionTrackMap;
foreach( Meta::TrackPtr track, tracks )
{
Collections::Collection *sourceCollection = track->collection();
collectionTrackMap.insertMulti( sourceCollection, track );
}
foreach( Collections::Collection *sourceCollection, collectionTrackMap.uniqueKeys() )
{
if( sourceCollection == targetCollection )
continue; // should be already caught by ...Model::flags(), but hey
Collections::CollectionLocation *sourceLocation;
if( sourceCollection )
{
sourceLocation = sourceCollection->location();
Q_ASSERT(sourceLocation);
}
else
{
sourceLocation = new Collections::FileCollectionLocation();
}
// we need to create target collection location per each source colleciton location
// -- prepareSomething() takes ownership of the pointer.
Collections::CollectionLocation *targetLocation = targetCollection->location();
Q_ASSERT(targetLocation);
if( action == Qt::CopyAction )
{
sourceLocation->prepareCopy( collectionTrackMap.values( sourceCollection ),
targetLocation );
}
else if( action == Qt::MoveAction )
{
sourceLocation->prepareMove( collectionTrackMap.values( sourceCollection ),
targetLocation );
}
}
return true;
}
bool
CollectionTreeItemModel::canFetchMore( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
return false; //children of the root item are the collections, and they are always known
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
return item->level() <= m_levelType.count() && item->requiresUpdate();
}
void
CollectionTreeItemModel::fetchMore( const QModelIndex &parent )
{
if ( !parent.isValid() )
return;
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
ensureChildrenLoaded( item );
}
Qt::DropActions
CollectionTreeItemModel::supportedDropActions() const
{
// this also causes supportedDragActions() to contain move action
return CollectionTreeItemModelBase::supportedDropActions() | Qt::MoveAction;
}
void
CollectionTreeItemModel::collectionAdded( Collections::Collection *newCollection )
{
if( !newCollection )
return;
- connect( newCollection, SIGNAL(updated()), this, SLOT(slotFilter()) ) ;
+ connect( newCollection, &Collections::Collection::updated, this, &CollectionTreeItemModel::slotFilterWithoutAutoExpand ) ;
QString collectionId = newCollection->collectionId();
if( m_collections.contains( collectionId ) )
return;
//inserts new collection at the end.
beginInsertRows( QModelIndex(), m_rootItem->childCount(), m_rootItem->childCount() );
m_collections.insert( collectionId, CollectionRoot( newCollection, new CollectionTreeItem( newCollection, m_rootItem, this ) ) );
endInsertRows();
if( m_collections.count() == 1 )
- QTimer::singleShot( 0, this, SLOT(requestCollectionsExpansion()) );
+ QTimer::singleShot( 0, this, &CollectionTreeItemModel::requestCollectionsExpansion );
}
void
CollectionTreeItemModel::collectionRemoved( const QString &collectionId )
{
int count = m_rootItem->childCount();
for( int i = 0; i < count; i++ )
{
CollectionTreeItem *item = m_rootItem->child( i );
if( item && !item->isDataItem() && item->parentCollection()->collectionId() == collectionId )
{
beginRemoveRows( QModelIndex(), i, i );
m_rootItem->removeChild( i );
m_collections.remove( collectionId );
m_expandedCollections.remove( item->parentCollection() );
endRemoveRows();
}
}
}
void
CollectionTreeItemModel::filterChildren()
{
int count = m_rootItem->childCount();
for ( int i = 0; i < count; i++ )
{
markSubTreeAsDirty( m_rootItem->child( i ) );
ensureChildrenLoaded( m_rootItem->child( i ) );
}
}
void
CollectionTreeItemModel::requestCollectionsExpansion()
{
for( int i = 0, count = m_rootItem->childCount(); i < count; i++ )
{
emit expandIndex( itemIndex( m_rootItem->child( i ) ) );
}
}
diff --git a/src/browsers/CollectionTreeItemModelBase.cpp b/src/browsers/CollectionTreeItemModelBase.cpp
index d19134d134..467e73040f 100644
--- a/src/browsers/CollectionTreeItemModelBase.cpp
+++ b/src/browsers/CollectionTreeItemModelBase.cpp
@@ -1,1217 +1,1217 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#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 <KGlobalSettings>
#include <QIcon>
#include <KIconLoader>
#include <KLocale>
#include <KStandardDirs>
#include <QApplication>
#include <QStyle>
#include <QPixmap>
#include <QTimeLine>
#include <QTimer>
using namespace Meta;
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<CategoryId::CatMenuId> variousArtistCategories =
QSet<CategoryId::CatMenuId>() << CategoryId::AlbumArtist;
CollectionTreeItemModelBase::CollectionTreeItemModelBase( )
: QAbstractItemModel()
, m_rootItem( 0 )
, m_animFrame( 0 )
, m_loading1( QPixmap( KStandardDirs::locate("data", "amarok/images/loading1.png" ) ) )
, m_loading2( QPixmap( KStandardDirs::locate("data", "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, SIGNAL(frameChanged(int)), this, SLOT(loadingAnimationTick()) );
+ connect( m_timeLine, &QTimeLine::frameChanged, this, &CollectionTreeItemModelBase::loadingAnimationTick );
}
CollectionTreeItemModelBase::~CollectionTreeItemModelBase()
{
KConfigGroup config = Amarok::config( "Collection Browser" );
QList<int> 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<CollectionTreeItem*>( 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() );
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() );
}
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() );
}
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() );
}
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() );
}
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() );
}
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( QString("%1 - ").arg(artist->prettyName()) );
if( AmarokConfig::showTrackNumbers() )
{
int trackNum = track->trackNumber();
if( trackNum > 0 )
name.prepend( QString("%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( "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() && !album->name().isEmpty() )
{
Meta::TrackList tracks = album->tracks();
if( !tracks.isEmpty() )
{
Meta::YearPtr year = tracks.first()->year();
if( year && (year->year() != 0) )
name.prepend( QString("%1 - ").arg( year->name() ) );
}
}
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();
}
}
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( "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<CollectionTreeItem*>(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<CollectionTreeItem*>(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<CollectionTreeItem*>(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<QModelIndex> indexSet = QSet<QModelIndex>::fromList( indices );
QMutableSetIterator<QModelIndex> 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<CollectionTreeItem*> items;
foreach( const QModelIndex &index, indexSet )
{
if( index.isValid() )
items << static_cast<CollectionTreeItem*>( index.internalPointer() );
}
return mimeData( items );
}
QMimeData*
CollectionTreeItemModelBase::mimeData( const QList<CollectionTreeItem*> &items ) const
{
if ( items.isEmpty() )
return 0;
Meta::TrackList tracks;
QList<Collections::QueryMaker*> 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<CollectionTreeItem*>(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<CollectionTreeItem *>( 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, SLOT(startAnimationTick()) );
+ QTimer::singleShot( 150, this, &CollectionTreeItemModelBase::startAnimationTick );
}
}
void
CollectionTreeItemModelBase::setLevels( const QList<CategoryId::CatMenuId> &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();
#pragma message("KF5Port: 1 line here")
//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::addQueryMaker( CollectionTreeItem* item,
Collections::QueryMaker *qm ) const
{
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(newResultReady(Meta::TrackList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::ArtistList)), SLOT(newResultReady(Meta::ArtistList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::AlbumList)), SLOT(newResultReady(Meta::AlbumList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::GenreList)), SLOT(newResultReady(Meta::GenreList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::ComposerList)), SLOT(newResultReady(Meta::ComposerList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::YearList)), SLOT(newResultReady(Meta::YearList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::LabelList)), SLOT(newResultReady(Meta::LabelList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::DataList)), SLOT(newResultReady(Meta::DataList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(queryDone()), SLOT(queryDone()), Qt::QueuedConnection );
+ 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<Collections::QueryMaker*>( 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 )
{
emit dataChanged( itemIndex( item ), itemIndex( item ) );
}
//stop timer if there are no more animations active
if( m_runningQueries.isEmpty() )
{
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<class PointerType, class ListType>
static Meta::DataList
convertToDataList( const ListType& list )
{
Meta::DataList data;
foreach( PointerType p, list )
data << Meta::DataPtr::staticCast( p );
return data;
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::TrackList res )
+CollectionTreeItemModelBase::newTracksReady( Meta::TrackList res )
{
- newResultReady( convertToDataList<Meta::TrackPtr, Meta::TrackList>( res ) );
+ newDataReady( convertToDataList<Meta::TrackPtr, Meta::TrackList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::ArtistList res )
+CollectionTreeItemModelBase::newArtistsReady( Meta::ArtistList res )
{
- newResultReady( convertToDataList<Meta::ArtistPtr, Meta::ArtistList>( res ) );
+ newDataReady( convertToDataList<Meta::ArtistPtr, Meta::ArtistList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::AlbumList res )
+CollectionTreeItemModelBase::newAlbumsReady( Meta::AlbumList res )
{
- newResultReady( convertToDataList<Meta::AlbumPtr, Meta::AlbumList>( res ) );
+ newDataReady( convertToDataList<Meta::AlbumPtr, Meta::AlbumList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::GenreList res )
+CollectionTreeItemModelBase::newGenresReady( Meta::GenreList res )
{
- newResultReady( convertToDataList<Meta::GenrePtr, Meta::GenreList>( res ) );
+ newDataReady( convertToDataList<Meta::GenrePtr, Meta::GenreList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::ComposerList res )
+CollectionTreeItemModelBase::newComposersReady( Meta::ComposerList res )
{
- newResultReady( convertToDataList<Meta::ComposerPtr, Meta::ComposerList>( res ) );
+ newDataReady( convertToDataList<Meta::ComposerPtr, Meta::ComposerList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::YearList res )
+CollectionTreeItemModelBase::newYearsReady( Meta::YearList res )
{
- newResultReady( convertToDataList<Meta::YearPtr, Meta::YearList>( res ) );
+ newDataReady( convertToDataList<Meta::YearPtr, Meta::YearList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::LabelList res )
+CollectionTreeItemModelBase::newLabelsReady( Meta::LabelList res )
{
- newResultReady( convertToDataList<Meta::LabelPtr, Meta::LabelList>( res ) );
+ newDataReady( convertToDataList<Meta::LabelPtr, Meta::LabelList>( res ) );
}
void
-CollectionTreeItemModelBase::newResultReady( Meta::DataList data )
+CollectionTreeItemModelBase::newDataReady( 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<Collections::QueryMaker*>( 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 = 0;
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() ) )
{
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() ) )
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 emit dataChanged for the rest
// have to check how that influences performance...
const QSet<Meta::DataPtr> dataSet = dataList.toSet();
QSet<Meta::DataPtr> 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<Meta::DataPtr> dataToBeAdded = dataSet - childrenSet;
const QSet<Meta::DataPtr> dataToBeRemoved = childrenSet - dataSet;
// first remove all rows that have to be removed
// walking through the cildren 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 remainging 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 );
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" );
case CategoryId::Artist :
return QIcon::fromTheme( "view-media-artist-amarok" );
case CategoryId::AlbumArtist :
return QIcon::fromTheme( "view-media-artist-amarok" );
case CategoryId::Composer :
return QIcon::fromTheme( "filename-composer-amarok" );
case CategoryId::Genre :
return QIcon::fromTheme( "favorite-genres-amarok" );
case CategoryId::Year :
return QIcon::fromTheme( "clock" );
case CategoryId::Label :
return QIcon::fromTheme( "label-amarok" );
case CategoryId::None:
default:
return QIcon::fromTheme( "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;
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 )
emit expandIndex( itemIndex( expandedItem ) );
}
}
void
CollectionTreeItemModelBase::slotCollapsed( const QModelIndex &index )
{
if ( index.isValid() ) //probably unnecessary, but let's be safe
{
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( 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<CollectionTreeItem*>( 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::Collection*> &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/CollectionTreeItemModelBase.h b/src/browsers/CollectionTreeItemModelBase.h
index 6a7c0c988f..4252734493 100644
--- a/src/browsers/CollectionTreeItemModelBase.h
+++ b/src/browsers/CollectionTreeItemModelBase.h
@@ -1,201 +1,202 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef COLLECTIONTREEITEMMODELBASE_H
#define COLLECTIONTREEITEMMODELBASE_H
#include "amarok_export.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/forward_declarations.h"
#include "CollectionTreeItem.h"
#include <QAbstractItemModel>
#include <QDateTime>
#include <QHash>
#include <QPair>
#include <QPixmap>
#include <QSet>
namespace Collections
{
class Collection;
}
class CollectionTreeItem;
class QTimeLine;
typedef QPair<Collections::Collection*, CollectionTreeItem* > CollectionRoot;
/**
@author Nikolaj Hald Nielsen <nhn@kde.org>
*/
class AMAROK_EXPORT CollectionTreeItemModelBase : public QAbstractItemModel
{
Q_OBJECT
public:
CollectionTreeItemModelBase();
virtual ~CollectionTreeItemModelBase();
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
virtual QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex &index) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const;
// Writable..
virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole );
virtual QStringList mimeTypes() const;
virtual QMimeData* mimeData( const QModelIndexList &indices ) const;
virtual QMimeData* mimeData( const QList<CollectionTreeItem *> &items ) const;
virtual void listForLevel( int level, Collections::QueryMaker *qm, CollectionTreeItem* parent );
virtual void setLevels( const QList<CategoryId::CatMenuId> &levelType );
virtual QList<CategoryId::CatMenuId> levels() const { return m_levelType; }
virtual CategoryId::CatMenuId levelCategory( const int level ) const;
QString currentFilter() const;
void setCurrentFilter( const QString &filter );
void itemAboutToBeDeleted( CollectionTreeItem *item );
/**
* This should be called every time a drag enters collection browser
*/
void setDragSourceCollections( const QSet<Collections::Collection*> &collections );
/**
* Return true if there are any queries still running. If this returns true,
* you can expect allQueriesFinished(bool) signal in some time.
*/
bool hasRunningQueries() const;
static QIcon iconForCategory( CategoryId::CatMenuId category );
static QString nameForCategory( CategoryId::CatMenuId category, bool showYears = false );
void ensureChildrenLoaded( CollectionTreeItem *item );
/**
* Get a pointer to colleciton tree item given its index. It is not safe to
* cache this pointer unless QWeakPointer is used.
*/
CollectionTreeItem *treeItem( const QModelIndex &index ) const;
/**
* Get (create) index for a collection tree item. The caller must ensure this
* item is in this model. Invalid model index is returned on null or root item.
*/
QModelIndex itemIndex( CollectionTreeItem *item ) const;
Q_SIGNALS:
void expandIndex( const QModelIndex &index );
void allQueriesFinished( bool autoExpand );
public Q_SLOTS:
virtual void queryDone();
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
- void newResultReady( Meta::LabelList );
- virtual void newResultReady( Meta::DataList data );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
+ void newComposersReady( Meta::ComposerList );
+ void newYearsReady( Meta::YearList );
+ void newLabelsReady( Meta::LabelList );
+ virtual void newDataReady( Meta::DataList data );
/**
* Apply the current filter.
*
* @param autoExpand whether to trigger automatic expansion of the tree after
* filtering is done. This should be set to true only if filter is run after
* user has actually just typed something and defaults to false.
*/
void slotFilter( bool autoExpand = false );
+ void slotFilterWithoutAutoExpand() { slotFilter( false ); }
void slotCollapsed( const QModelIndex &index );
void slotExpanded( const QModelIndex &index );
private:
void handleSpecialQueryResult( CollectionTreeItem::Type type, Collections::QueryMaker *qm, const Meta::DataList &dataList );
void handleNormalQueryResult( Collections::QueryMaker *qm, const Meta::DataList &dataList );
Collections::QueryMaker::QueryType mapCategoryToQueryType( int levelType ) const;
protected:
/** Adds the query maker to the running queries and connects the slots */
void addQueryMaker( CollectionTreeItem* item,
Collections::QueryMaker *qm ) const;
virtual void populateChildren(const Meta::DataList &dataList, CollectionTreeItem *parent, const QModelIndex &parentIndex );
virtual void updateHeaderText();
virtual QIcon iconForLevel( int level ) const;
virtual QString nameForLevel( int level ) const;
virtual int levelModifier() const = 0;
virtual QVariant dataForItem( CollectionTreeItem *item, int role, int level = -1 ) const;
virtual void filterChildren() = 0;
void markSubTreeAsDirty( CollectionTreeItem *item );
/** Initiates a special search for albums without artists */
void handleCompilations( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const;
/** Initiates a special search for tracks without label */
void handleTracksWithoutLabels( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const;
QString m_headerText;
CollectionTreeItem *m_rootItem;
QList<CategoryId::CatMenuId> m_levelType;
QTimeLine *m_timeLine;
int m_animFrame;
QPixmap m_loading1, m_loading2, m_currentAnimPixmap; //icons for loading animation
QString m_currentFilter;
QSet<Meta::DataPtr> m_expandedItems;
QSet<Collections::Collection*> m_expandedCollections;
QSet<Collections::Collection*> m_expandedSpecialNodes;
/**
* Contents of this set are undefined if there is no active drag 'n drop operation.
* Additionally, you may _never_ dereference pointers in this set, just compare
* them with other pointers
*/
QSet<Collections::Collection*> m_dragSourceCollections;
QHash<QString, CollectionRoot > m_collections; //I'll concide this one... :-)
mutable QHash<Collections::QueryMaker* , CollectionTreeItem* > m_childQueries;
mutable QHash<Collections::QueryMaker* , CollectionTreeItem* > m_compilationQueries;
mutable QHash<Collections::QueryMaker* , CollectionTreeItem* > m_noLabelsQueries;
mutable QMultiHash<CollectionTreeItem*, Collections::QueryMaker*> m_runningQueries;
bool m_autoExpand; // whether to expand tree after queries are done
protected Q_SLOTS:
void startAnimationTick();
void loadingAnimationTick();
};
#endif
diff --git a/src/browsers/CollectionTreeView.cpp b/src/browsers/CollectionTreeView.cpp
index da1c54aca5..93e22afef5 100644
--- a/src/browsers/CollectionTreeView.cpp
+++ b/src/browsers/CollectionTreeView.cpp
@@ -1,1453 +1,1455 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CollectionTreeView"
#include "CollectionTreeView.h"
#include "AmarokMimeData.h"
#include "GlobalCollectionActions.h"
#include "PopupDropperFactory.h"
#include "SvgHandler.h"
#include "browsers/CollectionSortFilterProxyModel.h"
#include "browsers/CollectionTreeItemModel.h"
#include "context/ContextView.h"
#include "context/popupdropper/libpud/PopupDropper.h"
#include "context/popupdropper/libpud/PopupDropperItem.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/capabilities/BookmarkThisCapability.h"
#include "core/collections/CollectionLocation.h"
#include "core/collections/MetaQueryMaker.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 "core-impl/collections/support/TextualQueryFilter.h"
#include "core-impl/collections/support/TrashCollectionLocation.h"
#include "dialogs/TagDialog.h"
#include "playlist/PlaylistModelStack.h"
#include "scripting/scriptengine/AmarokCollectionViewScript.h"
#include <QAction>
#include <KGlobalSettings>
#include <QIcon>
#include <KComboBox>
#include <QMenu>
#include <KMessageBox> // NOTE: for delete dialog, will move to CollectionCapability later
#include <QContextMenuEvent>
#include <QHash>
#include <QMouseEvent>
#include <QSortFilterProxyModel>
#include <QScrollBar>
using namespace Collections;
/**
* RAII class to perform restoring of the scroll position once all queries are
* finished. DelayedScroller auto-deletes itself once its job is over (ot if it finds
* it is useless).
*/
class DelayedScroller : public QObject
{
Q_OBJECT
public:
DelayedScroller( CollectionTreeView *treeView,
CollectionTreeItemModelBase *treeModel,
const QModelIndex &treeModelScrollToIndex, int topOffset )
: QObject( treeView )
, m_treeView( treeView )
, m_treeModel( treeModel )
, m_topOffset( topOffset )
{
- connect( treeModel, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
- connect( treeModel, SIGNAL(allQueriesFinished(bool)), SLOT(slotScroll()) );
+ connect( treeModel, &CollectionTreeItemModelBase::destroyed,
+ this, &DelayedScroller::deleteLater );
+ connect( treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
+ this, &DelayedScroller::slotScroll );
m_scrollToItem = m_treeModel->treeItem( treeModelScrollToIndex );
if( m_scrollToItem )
- connect( m_scrollToItem, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
+ connect( m_scrollToItem, &CollectionTreeItem::destroyed, this, &DelayedScroller::deleteLater );
else
deleteLater(); // nothing to do
}
private slots:
void slotScroll()
{
deleteLater();
QModelIndex idx = m_treeModel->itemIndex( m_scrollToItem );
QSortFilterProxyModel *filterModel = m_treeView->filterModel();
idx = filterModel ? filterModel->mapFromSource( idx ) : QModelIndex();
QScrollBar *scrollBar = m_treeView->verticalScrollBar();
if( !idx.isValid() || !scrollBar )
return;
int newTopOffset = m_treeView->visualRect( idx ).top();
scrollBar->setValue( scrollBar->value() + (newTopOffset - m_topOffset) );
}
private:
CollectionTreeView *m_treeView;
CollectionTreeItemModelBase *m_treeModel;
CollectionTreeItem *m_scrollToItem;
int m_topOffset;
};
/**
* RAII class to auto-expand collection tree entries after filtering.
* AutoExpander auto-deletes itself once its job is over (or if it finds
* it is useless).
*/
class AutoExpander : public QObject
{
Q_OBJECT
public:
AutoExpander( CollectionTreeView *treeView,
CollectionTreeItemModelBase *treeModel,
QAbstractItemModel *filterModel)
: QObject( treeView )
, m_treeView( treeView )
, m_filterModel( filterModel )
{
- connect( filterModel, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
- connect( treeModel, SIGNAL(allQueriesFinished(bool)), SLOT(slotExpandMore()) );
+ connect( filterModel, &QObject::destroyed, this, &QObject::deleteLater );
+ connect( treeModel, &CollectionTreeItemModelBase::allQueriesFinished, this, &AutoExpander::slotExpandMore );
// start with the root index
m_indicesToCheck.enqueue( QModelIndex() );
slotExpandMore();
}
private slots:
void slotExpandMore()
{
const int maxChildrenToExpand = 3;
QQueue<QModelIndex> pendingIndices;
while( !m_indicesToCheck.isEmpty() )
{
QModelIndex current = m_indicesToCheck.dequeue();
if( m_filterModel->canFetchMore( current ) )
{
m_filterModel->fetchMore( current );
pendingIndices.enqueue( current );
continue;
}
if( m_filterModel->rowCount( current ) <= maxChildrenToExpand )
{
m_treeView->expand( current );
for( int i = 0; i < m_filterModel->rowCount( current ); i++ )
m_indicesToCheck.enqueue( m_filterModel->index( i, 0, current ) );
}
}
if( pendingIndices.isEmpty() )
// nothing left to do
deleteLater();
else
// process pending indices when queries finish
m_indicesToCheck.swap( pendingIndices );
}
private:
Q_DISABLE_COPY(AutoExpander)
CollectionTreeView *m_treeView;
QAbstractItemModel *m_filterModel;
QQueue<QModelIndex> m_indicesToCheck;
};
CollectionTreeView::CollectionTreeView( QWidget *parent)
: Amarok::PrettyTreeView( parent )
, m_filterModel( 0 )
, m_treeModel( 0 )
, m_pd( 0 )
, m_appendAction( 0 )
, m_loadAction( 0 )
, m_editAction( 0 )
, m_organizeAction( 0 )
, m_ongoingDrag( false )
{
setSortingEnabled( true );
setFocusPolicy( Qt::StrongFocus );
sortByColumn( 0, Qt::AscendingOrder );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
setEditTriggers( EditKeyPressed );
setDragDropMode( QAbstractItemView::DragDrop );
- connect( this, SIGNAL(collapsed(QModelIndex)),
- SLOT(slotCollapsed(QModelIndex)) );
- connect( this, SIGNAL(expanded(QModelIndex)),
- SLOT(slotExpanded(QModelIndex)) );
+ connect( this, &CollectionTreeView::collapsed,
+ this, &CollectionTreeView::slotCollapsed );
+ connect( this, &CollectionTreeView::expanded,
+ this, &CollectionTreeView::slotExpanded );
}
void
CollectionTreeView::setModel( QAbstractItemModel *model )
{
if( m_treeModel )
disconnect( m_treeModel, 0, this, 0);
m_treeModel = qobject_cast<CollectionTreeItemModelBase *>( model );
if( !m_treeModel )
return;
- connect( m_treeModel, SIGNAL(allQueriesFinished(bool)), SLOT(slotCheckAutoExpand(bool)) );
- connect( m_treeModel, SIGNAL(expandIndex(QModelIndex)),
- SLOT(slotExpandIndex(QModelIndex)) );
+ connect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
+ this, &CollectionTreeView::slotCheckAutoExpand );
+ connect( m_treeModel, &CollectionTreeItemModelBase::expandIndex,
+ this, &CollectionTreeView::slotExpandIndex );
if( m_filterModel )
m_filterModel->deleteLater();
m_filterModel = new CollectionSortFilterProxyModel( this );
m_filterModel->setSourceModel( model );
QTreeView::setModel( m_filterModel );
- QTimer::singleShot( 0, this, SLOT(slotCheckAutoExpand()) );
+ QTimer::singleShot( 0, this, &CollectionTreeView::slotCheckAutoExpandReally );
}
CollectionTreeView::~CollectionTreeView()
{
// we don't own m_treeModel pointer
// m_filterModel will get deleted by QObject parentship
}
void
CollectionTreeView::setLevels( const QList<CategoryId::CatMenuId> &levels )
{
if( m_treeModel )
m_treeModel->setLevels( levels );
}
QList<CategoryId::CatMenuId>
CollectionTreeView::levels() const
{
if( m_treeModel )
return m_treeModel->levels();
return QList<CategoryId::CatMenuId>();
}
void
CollectionTreeView::setLevel( int level, CategoryId::CatMenuId type )
{
if( !m_treeModel )
return;
QList<CategoryId::CatMenuId> levels = m_treeModel->levels();
if( type == CategoryId::None )
{
while( levels.count() >= level )
levels.removeLast();
}
else
{
levels.removeAll( type );
levels[level] = type;
}
setLevels( levels );
}
QSortFilterProxyModel *
CollectionTreeView::filterModel() const
{
return m_filterModel;
}
void
CollectionTreeView::contextMenuEvent( QContextMenuEvent *event )
{
if( !m_treeModel )
return;
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() )
{
Amarok::PrettyTreeView::contextMenuEvent( event );
return;
}
QModelIndexList indices = selectedIndexes();
// if previously selected indices do not contain the index of the item
// currently under the mouse when context menu is invoked.
if( !indices.contains( index ) )
{
indices.clear();
indices << index;
setCurrentIndex( index );
}
//TODO: get rid of this, it's a hack.
// Put remove actions in model so we don't need access to the internal pointer in view
if( m_filterModel )
{
QModelIndexList tmp;
foreach( const QModelIndex &idx, indices )
{
tmp.append( m_filterModel->mapToSource( idx ) );
}
indices = tmp;
}
// Abort if nothing is selected
if( indices.isEmpty() )
return;
m_currentItems.clear();
foreach( const QModelIndex &index, indices )
{
if( index.isValid() && index.internalPointer() )
m_currentItems.insert(
static_cast<CollectionTreeItem *>( index.internalPointer() )
);
}
QMenu menu( this );
// Destroy the menu when the model is reset (collection update), so that we don't
// operate on invalid data. see BUG 190056
- connect( m_treeModel, SIGNAL(modelReset()), &menu, SLOT(deleteLater()) );
+ connect( m_treeModel, &CollectionTreeItemModelBase::modelReset, &menu, &QMenu::deleteLater );
// create basic actions
QActionList actions = createBasicActions( indices );
foreach( QAction *action, actions ) {
menu.addAction( action );
}
menu.addSeparator();
actions.clear();
QActionList albumActions = createCustomActions( indices );
QMenu menuAlbum( i18n( "Album" ) );
foreach( QAction *action, albumActions )
{
if( !action->parent() )
action->setParent( &menuAlbum );
}
if( albumActions.count() > 1 )
{
menuAlbum.addActions( albumActions );
menuAlbum.setIcon( QIcon::fromTheme( "filename-album-amarok" ) );
menu.addMenu( &menuAlbum );
menu.addSeparator();
}
else if( albumActions.count() == 1 )
{
menu.addActions( albumActions );
}
QActionList collectionActions = createCollectionActions( indices );
QMenu menuCollection( i18n( "Collection" ) );
foreach( QAction *action, collectionActions )
{
if( !action->parent() )
action->setParent( &menuCollection );
}
if( collectionActions.count() > 1 )
{
menuCollection.setIcon( QIcon::fromTheme( "drive-harddisk" ) );
menuCollection.addActions( collectionActions );
menu.addMenu( &menuCollection );
menu.addSeparator();
}
else if( collectionActions.count() == 1 )
{
menu.addActions( collectionActions );
}
m_currentCopyDestination = getCopyActions( indices );
m_currentMoveDestination = getMoveActions( indices );
if( !m_currentCopyDestination.empty() )
{
QMenu *copyMenu = new QMenu( i18n( "Copy to Collection" ), &menu );
copyMenu->setIcon( QIcon::fromTheme( "edit-copy" ) );
copyMenu->addActions( m_currentCopyDestination.keys() );
menu.addMenu( copyMenu );
}
//Move = copy + delete from source
if( !m_currentMoveDestination.empty() )
{
QMenu *moveMenu = new QMenu( i18n( "Move to Collection" ), &menu );
moveMenu->setIcon( QIcon::fromTheme( "go-jump" ) );
moveMenu->addActions( m_currentMoveDestination.keys() );
menu.addMenu( moveMenu );
}
// create trash and delete actions
if( onlyOneCollection( indices ) )
{
Collection *collection = getCollection( indices.first() );
if( collection && collection->isWritable() )
{
//TODO: don't recreate action
QAction *trashAction = new QAction( QIcon::fromTheme( "user-trash" ),
i18n( "Move Tracks to Trash" ),
&menu );
trashAction->setProperty( "popupdropper_svg_id", "delete" );
// key shortcut is only for display purposes here, actual one is
// determined by View in Model/View classes
trashAction->setShortcut( Qt::Key_Delete );
- connect( trashAction, SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)),
- SLOT(slotTrashTracks(Qt::MouseButtons,Qt::KeyboardModifiers)) );
+ connect( trashAction, &QAction::triggered,
+ this, &CollectionTreeView::slotTrashTracks );
menu.addAction( trashAction );
QAction *deleteAction = new QAction( QIcon::fromTheme( "remove-amarok" ),
i18n( "Delete Tracks" ),
&menu );
deleteAction->setProperty( "popupdropper_svg_id", "delete" );
// key shortcut is only for display purposes here, actual one is
// determined by View in Model/View classes
deleteAction->setShortcut( Qt::SHIFT + Qt::Key_Delete );
- connect( deleteAction, SIGNAL(triggered(bool)), SLOT(slotDeleteTracks()) );
+ connect( deleteAction, &QAction::triggered, this, &CollectionTreeView::slotDeleteTracks );
menu.addAction( deleteAction );
}
}
// add extended actions
menu.addSeparator();
actions += createExtendedActions( indices );
foreach( QAction *action, actions ) {
menu.addAction( action );
}
AmarokScript::AmarokCollectionViewScript::createScriptedActions( menu, indices );
menu.exec( event->globalPos() );
}
void
CollectionTreeView::mouseDoubleClickEvent( QMouseEvent *event )
{
if( event->button() == Qt::MidButton )
{
event->accept();
return;
}
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() )
{
event->accept();
return;
}
// code copied in PlaylistBrowserView::mouseDoubleClickEvent(), keep in sync
// mind bug 279513
bool isExpandable = model()->hasChildren( index );
bool wouldExpand = !visualRect( index ).contains( event->pos() ) || // clicked outside item, perhaps on expander icon
( isExpandable && !KGlobalSettings::singleClick() ); // we're in doubleClick
if( event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier &&
!wouldExpand )
{
CollectionTreeItem *item = getItemFromIndex( index );
playChildTracks( item, Playlist::OnDoubleClickOnSelectedItems );
event->accept();
return;
}
PrettyTreeView::mouseDoubleClickEvent( event );
}
void
CollectionTreeView::mouseReleaseEvent( QMouseEvent *event )
{
if( m_pd )
{
- connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(deleteLater()) );
+ connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::deleteLater );
m_pd->hide();
m_pd = 0;
}
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() )
{
PrettyTreeView::mouseReleaseEvent( event );
return;
}
if( event->button() == Qt::MidButton )
{
CollectionTreeItem *item = getItemFromIndex( index );
playChildTracks( item, Playlist::OnMiddleClickOnSelectedItems );
event->accept();
return;
}
PrettyTreeView::mouseReleaseEvent( event );
}
CollectionTreeItem *
CollectionTreeView::getItemFromIndex( QModelIndex &index )
{
QModelIndex filteredIndex;
if( m_filterModel )
filteredIndex = m_filterModel->mapToSource( index );
else
filteredIndex = index;
if( !filteredIndex.isValid() )
{
return 0;
}
return static_cast<CollectionTreeItem *>( filteredIndex.internalPointer() );
}
void
CollectionTreeView::keyPressEvent( QKeyEvent *event )
{
QModelIndexList indices = selectedIndexes();
if( indices.isEmpty() )
{
Amarok::PrettyTreeView::keyPressEvent( event );
return;
}
if( m_filterModel )
{
QModelIndexList tmp;
foreach( const QModelIndex &idx, indices )
tmp.append( m_filterModel->mapToSource( idx ) );
indices = tmp;
}
m_currentItems.clear();
foreach( const QModelIndex &index, indices )
{
if( index.isValid() && index.internalPointer() )
{
m_currentItems.insert(
static_cast<CollectionTreeItem *>( index.internalPointer() ) );
}
}
QModelIndex current = currentIndex();
switch( event->key() )
{
case Qt::Key_Enter:
case Qt::Key_Return:
playChildTracks( m_currentItems, Playlist::OnReturnPressedOnSelectedItems );
return;
case Qt::Key_Delete:
if( !onlyOneCollection( indices ) )
break;
removeTracks( m_currentItems, !( event->modifiers() & Qt::ShiftModifier ) );
return;
case Qt::Key_Up:
if( current.parent() == QModelIndex() && current.row() == 0 )
{
emit leavingTree();
return;
}
break;
case Qt::Key_Down:
break;
// L and R should magically work when we get a patched version of qt
case Qt::Key_Right:
case Qt::Key_Direction_R:
expand( current );
return;
case Qt::Key_Left:
case Qt::Key_Direction_L:
collapse( current );
return;
default:
break;
}
Amarok::PrettyTreeView::keyPressEvent( event );
}
void
CollectionTreeView::dragEnterEvent( QDragEnterEvent *event )
{
// We want to indicate to the user that dropping to the same collection is not possible.
// CollectionTreeItemModel therefore needs to know what collection the drag originated
// so that is can play with Qt::ItemIsDropEnabled in flags()
const AmarokMimeData *mimeData =
qobject_cast<const AmarokMimeData *>( event->mimeData() );
if( mimeData ) // drag from within Amarok
{
QSet<Collection *> srcCollections;
foreach( Meta::TrackPtr track, mimeData->tracks() )
{
srcCollections.insert( track->collection() );
}
m_treeModel->setDragSourceCollections( srcCollections );
}
QAbstractItemView::dragEnterEvent( event );
}
void
CollectionTreeView::dragMoveEvent( QDragMoveEvent *event )
{
// this mangling is not needed for Copy/Move distinction to work, it is only needed
// for mouse cursor changing to work
if( (event->keyboardModifiers() & Qt::ShiftModifier)
&& (event->possibleActions() & Qt::MoveAction) )
{
event->setDropAction( Qt::MoveAction );
}
else if( event->possibleActions() & Qt::CopyAction )
{
event->setDropAction( Qt::CopyAction );
}
QTreeView::dragMoveEvent( event );
}
void
CollectionTreeView::startDrag(Qt::DropActions supportedActions)
{
DEBUG_BLOCK
// Make sure that the left mouse button is actually pressed. Otherwise we're prone to
// mis-detecting clicks as dragging
if( !( QApplication::mouseButtons() & Qt::LeftButton ) )
return;
QModelIndexList indices = selectedIndexes();
if( indices.isEmpty() )
return;
// When a parent item is dragged, startDrag() is called a bunch of times. Here we
// prevent that:
if( m_ongoingDrag )
return;
m_ongoingDrag = true;
/* FIXME: disabled temporarily for KF5 porting
if( !m_pd )
m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
*/
if( m_pd && m_pd->isHidden() )
{
if( m_filterModel )
{
QModelIndexList tmp;
foreach( const QModelIndex &idx, indices )
{
tmp.append( m_filterModel->mapToSource( idx ) );
}
indices = tmp;
}
QActionList actions = createBasicActions( indices );
QFont font;
font.setPointSize( 16 );
font.setBold( true );
foreach( QAction * action, actions )
m_pd->addItem( The::popupDropperFactory()->createItem( action ) );
m_currentCopyDestination = getCopyActions( indices );
m_currentMoveDestination = getMoveActions( indices );
m_currentItems.clear();
foreach( const QModelIndex &index, indices )
{
if( index.isValid() && index.internalPointer() )
{
m_currentItems.insert(
static_cast<CollectionTreeItem *>( index.internalPointer() ) );
}
}
PopupDropperItem *subItem;
actions = createExtendedActions( indices );
PopupDropper *morePud = 0;
if( actions.count() > 1 )
{
morePud = The::popupDropperFactory()->createPopupDropper( 0, true );
foreach( QAction *action, actions )
morePud->addItem( The::popupDropperFactory()->createItem( action ) );
}
else
m_pd->addItem( The::popupDropperFactory()->createItem( actions[0] ) );
//TODO: Keep bugging i18n team about problems with 3 dots
if ( actions.count() > 1 )
{
subItem = m_pd->addSubmenu( &morePud, i18n( "More..." ) );
The::popupDropperFactory()->adjustItem( subItem );
}
m_pd->show();
}
QTreeView::startDrag( supportedActions );
debug() << "After the drag!";
if( m_pd )
{
debug() << "clearing PUD";
- connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(clear()) );
+ connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear );
m_pd->hide();
}
m_ongoingDrag = false;
}
void
CollectionTreeView::selectionChanged( const QItemSelection &selected,
const QItemSelection &deselected )
{
QModelIndexList indexes = selected.indexes();
QModelIndexList changedIndexes = indexes;
changedIndexes << deselected.indexes();
foreach( const QModelIndex &index, changedIndexes )
update( index );
if( indexes.count() < 1 )
return;
QModelIndex index;
if( m_filterModel )
index = m_filterModel->mapToSource( indexes[0] );
else
index = indexes[0];
CollectionTreeItem *item =
static_cast<CollectionTreeItem *>( index.internalPointer() );
emit( itemSelected ( item ) );
}
void
CollectionTreeView::slotCollapsed( const QModelIndex &index )
{
if( !m_treeModel )
return;
if( m_filterModel )
m_treeModel->slotCollapsed( m_filterModel->mapToSource( index ) );
else
m_treeModel->slotCollapsed( index );
}
void
CollectionTreeView::slotExpanded( const QModelIndex &index )
{
if( !m_treeModel )
return;
if( m_filterModel )
m_treeModel->slotExpanded( m_filterModel->mapToSource( index ));
else
m_treeModel->slotExpanded( index );
}
void
CollectionTreeView::slotExpandIndex( const QModelIndex &index )
{
if( !m_treeModel )
return;
if( m_filterModel )
expand( m_filterModel->mapFromSource( index ) );
}
void
CollectionTreeView::slotCheckAutoExpand( bool reallyExpand )
{
if( !m_filterModel || !reallyExpand )
return;
// auto-deletes itself:
new AutoExpander( this, m_treeModel, m_filterModel );
}
void
CollectionTreeView::playChildTracks( CollectionTreeItem *item, Playlist::AddOptions insertMode )
{
QSet<CollectionTreeItem*> items;
items.insert( item );
playChildTracks( items, insertMode );
}
void
CollectionTreeView::playChildTracks( const QSet<CollectionTreeItem *> &items,
Playlist::AddOptions insertMode )
{
if( !m_treeModel )
return;
//Ensure that if a parent and child are both selected we ignore the child
QSet<CollectionTreeItem *> parents( cleanItemSet( items ) );
//Store the type of playlist insert to be done and cause a slot to be invoked when the tracklist has been generated.
AmarokMimeData *mime = dynamic_cast<AmarokMimeData*>(
m_treeModel->mimeData( QList<CollectionTreeItem *>::fromSet( parents ) ) );
m_playChildTracksMode.insert( mime, insertMode );
- connect( mime, SIGNAL(trackListSignal(Meta::TrackList)), this,
- SLOT(playChildTracksSlot(Meta::TrackList)) );
+ connect( mime, &AmarokMimeData::trackListSignal,
+ this, &CollectionTreeView::playChildTracksSlot );
mime->getTrackListSignal();
}
void
CollectionTreeView::playChildTracksSlot( Meta::TrackList list ) //slot
{
AmarokMimeData *mime = dynamic_cast<AmarokMimeData *>( sender() );
Playlist::AddOptions insertMode = m_playChildTracksMode.take( mime );
qStableSort( list.begin(), list.end(), Meta::Track::lessThan );
The::playlistController()->insertOptioned( list, insertMode );
mime->deleteLater();
}
void
CollectionTreeView::organizeTracks( const QSet<CollectionTreeItem *> &items ) const
{
DEBUG_BLOCK
if( !items.count() )
return;
//Create query based upon items, ensuring that if a parent and child are both
//selected we ignore the child
Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
if( !qm )
return;
CollectionTreeItem *item = items.toList().first();
while( item->isDataItem() )
item = item->parent();
Collection *coll = item->parentCollection();
CollectionLocation *location = coll->location();
if( !location->isOrganizable() )
{
debug() << "Collection not organizable";
//how did we get here??
delete location;
delete qm;
return;
}
location->prepareMove( qm, coll->location() );
}
void
CollectionTreeView::copySelectedToLocalCollection()
{
DEBUG_BLOCK
// Get the local collection
Collections::Collection *collection = 0;
const QList<Collections::Collection*> collections = CollectionManager::instance()->collections().keys();
foreach( collection, collections )
{
if ( collection->collectionId() == "localCollection" )
break;
}
if( !collection )
return;
// Get selected items
QModelIndexList indexes = selectedIndexes();
if( m_filterModel )
{
QModelIndexList tmp;
foreach( const QModelIndex &idx, indexes )
tmp.append( m_filterModel->mapToSource( idx ) );
indexes = tmp;
}
m_currentItems.clear();
foreach( const QModelIndex &index, indexes )
{
if( index.isValid() && index.internalPointer() )
m_currentItems.insert( static_cast<CollectionTreeItem *>( index.internalPointer() ) );
}
copyTracks( m_currentItems, collection, false );
}
void
CollectionTreeView::copyTracks( const QSet<CollectionTreeItem *> &items,
Collection *destination, bool removeSources ) const
{
DEBUG_BLOCK
if( !destination )
{
warning() << "collection is not writable (0-pointer)! Aborting";
return;
}
if( !destination->isWritable() )
{
warning() << "collection " << destination->prettyName() << " is not writable! Aborting";
return;
}
//copied from organizeTracks. create a method for this somewhere
if( !items.count() )
{
warning() << "No items to copy! Aborting";
return;
}
//Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
if( !qm )
{
warning() << "could not get qm!";
return;
}
CollectionTreeItem *item = items.toList().first();
while( item->isDataItem() )
{
item = item->parent();
}
Collection *coll = item->parentCollection();
CollectionLocation *source = coll->location();
CollectionLocation *dest = destination->location();
if( removeSources )
{
if( !source->isWritable() ) //error
{
warning() << "We can not write to ze source!!! OMGooses!";
delete dest;
delete source;
delete qm;
return;
}
debug() << "starting source->prepareMove";
source->prepareMove( qm, dest );
}
else
{
debug() << "starting source->prepareCopy";
source->prepareCopy( qm, dest );
}
}
void
CollectionTreeView::removeTracks( const QSet<CollectionTreeItem *> &items,
bool useTrash ) const
{
DEBUG_BLOCK
//copied from organizeTracks. create a method for this somewhere
if( !items.count() )
return;
//Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
if( !qm )
return;
CollectionTreeItem *item = items.toList().first();
while( item->isDataItem() )
item = item->parent();
Collection *coll = item->parentCollection();
CollectionLocation *source = coll->location();
if( !source->isWritable() ) //error
{
warning() << "We can not write to ze source!!! OMGooses!";
delete source;
delete qm;
return;
}
if( useTrash )
{
TrashCollectionLocation *trash = new TrashCollectionLocation();
source->prepareMove( qm, trash );
}
else
source->prepareRemove( qm );
}
void
CollectionTreeView::editTracks( const QSet<CollectionTreeItem *> &items ) const
{
//Create query based upon items, ensuring that if a parent and child are both
//selected we ignore the child
Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
if( !qm )
return;
(void)new TagDialog( qm ); //the dialog will show itself automatically as soon as it is ready
}
void
CollectionTreeView::slotSetFilter( const QString &filter )
{
QString currentFilter = m_treeModel ? m_treeModel->currentFilter() : QString();
if( !m_filterModel || !m_treeModel || filter == currentFilter )
return;
// special case: transitioning from non-empty to empty buffer
// -> trigger later restoring of the scroll position
if( filter.isEmpty() ) // currentFilter must not be empty then (see earlier check)
{
// take first item, descending to leaf ones if expanded. There may be better
// ways to determine what item should stay "fixed".
QModelIndex scrollToIndex = m_filterModel->index( 0, 0 );
while( isExpanded( scrollToIndex ) && m_filterModel->rowCount( scrollToIndex ) > 0 )
scrollToIndex = scrollToIndex.child( 0, 0 );
int topOffset = visualRect( scrollToIndex ).top();
QModelIndex bottomIndex = m_filterModel->mapToSource( scrollToIndex );
// if we have somewhere to scroll to after filter is cleared...
if( bottomIndex.isValid() )
// auto-destroys itself
new DelayedScroller( this, m_treeModel, bottomIndex, topOffset );
}
m_treeModel->setCurrentFilter( filter );
}
void
CollectionTreeView::slotAddFilteredTracksToPlaylist()
{
if( !m_treeModel )
return;
// disconnect any possible earlier connection we've done
- disconnect( m_treeModel, SIGNAL(allQueriesFinished(bool)),
- this, SLOT(slotAddFilteredTracksToPlaylist()) );
+ disconnect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
+ this, &CollectionTreeView::slotAddFilteredTracksToPlaylist );
if( m_treeModel->hasRunningQueries() )
// wait for the queries to finish
- connect( m_treeModel, SIGNAL(allQueriesFinished(bool)),
- this, SLOT(slotAddFilteredTracksToPlaylist()) );
+ connect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
+ this, &CollectionTreeView::slotAddFilteredTracksToPlaylist );
else
{
// yay, we can add the tracks now
QSet<CollectionTreeItem *> items;
for( int row = 0; row < m_treeModel->rowCount(); row++ )
{
QModelIndex idx = m_treeModel->index( row, 0 );
CollectionTreeItem *item = idx.isValid()
? static_cast<CollectionTreeItem *>( idx.internalPointer() ) : 0;
if( item )
items.insert( item );
}
if( !items.isEmpty() )
playChildTracks( items, Playlist::OnAppendToPlaylistAction );
emit addingFilteredTracksDone();
}
}
QActionList
CollectionTreeView::createBasicActions( const QModelIndexList &indices )
{
QActionList actions;
if( !indices.isEmpty() )
{
if( m_appendAction == 0 )
{
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, SIGNAL(triggered()), this, SLOT(slotAppendChildTracks()) );
+ connect( m_appendAction, &QAction::triggered, this, &CollectionTreeView::slotAppendChildTracks );
}
actions.append( m_appendAction );
if( m_loadAction == 0 )
{
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, SIGNAL(triggered()),
- this, SLOT(slotReplacePlaylistWithChildTracks()) );
+ connect( m_loadAction, &QAction::triggered,
+ this, &CollectionTreeView::slotReplacePlaylistWithChildTracks );
}
actions.append( m_loadAction );
}
return actions;
}
QActionList
CollectionTreeView::createExtendedActions( const QModelIndexList &indices )
{
QActionList actions;
if( !indices.isEmpty() )
{
{ //keep the scope of item minimal
CollectionTreeItem *item =
static_cast<CollectionTreeItem *>( indices.first().internalPointer() );
while( item->isDataItem() )
item = item->parent();
Collection *collection = item->parentCollection();
CollectionLocation* location = collection->location();
if( location->isOrganizable() )
{
bool onlyOneCollection = true;
foreach( const QModelIndex &index, indices )
{
Q_UNUSED( index )
CollectionTreeItem *item = static_cast<CollectionTreeItem *>(
indices.first().internalPointer() );
while( item->isDataItem() )
item = item->parent();
onlyOneCollection = item->parentCollection() == collection;
if( !onlyOneCollection )
break;
}
if( onlyOneCollection )
{
if( m_organizeAction == 0 )
{
m_organizeAction = new QAction( QIcon::fromTheme("folder-open" ),
i18nc( "Organize Files", "Organize Files" ), this );
m_organizeAction->setProperty( "popupdropper_svg_id", "organize" );
- connect( m_organizeAction, SIGNAL(triggered()),
- this, SLOT(slotOrganize()) );
+ connect( m_organizeAction, &QAction::triggered,
+ this, &CollectionTreeView::slotOrganize );
}
actions.append( m_organizeAction );
}
}
delete location;
}
//hmmm... figure out what kind of item we are dealing with....
if( indices.size() == 1 )
{
debug() << "checking for global actions";
CollectionTreeItem *item = static_cast<CollectionTreeItem *>(
indices.first().internalPointer() );
QActionList gActions = The::globalCollectionActions()->actionsFor( item->data() );
foreach( QAction *action, gActions )
{
if( action ) // Can become 0-pointer, see http://bugs.kde.org/show_bug.cgi?id=183250
{
actions.append( action );
debug() << "Got global action: " << action->text();
}
}
}
if( m_editAction == 0 )
{
m_editAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ),
i18n( "&Edit Track Details" ), this );
setProperty( "popupdropper_svg_id", "edit" );
- connect( m_editAction, SIGNAL(triggered()), this, SLOT(slotEditTracks()) );
+ connect( m_editAction, &QAction::triggered, this, &CollectionTreeView::slotEditTracks );
}
actions.append( m_editAction );
}
else
debug() << "invalid index or null internalPointer";
return actions;
}
QActionList
CollectionTreeView::createCustomActions( const QModelIndexList &indices )
{
QActionList actions;
if( indices.count() == 1 )
{
if( indices.first().isValid() && indices.first().internalPointer() )
{
Meta::DataPtr data = static_cast<CollectionTreeItem *>(
indices.first().internalPointer() )->data();
if( data )
{
QScopedPointer<Capabilities::ActionsCapability> ac(
data->create<Capabilities::ActionsCapability>() );
if( ac )
{
QActionList cActions = ac->actions();
foreach( QAction *action, cActions )
{
Q_ASSERT( action );
actions.append( action );
debug() << "Got custom action: " << action->text();
}
}
//check if this item can be bookmarked...
QScopedPointer<Capabilities::BookmarkThisCapability> btc(
data->create<Capabilities::BookmarkThisCapability>() );
if( btc && btc->isBookmarkable() && btc->bookmarkAction() )
actions.append( btc->bookmarkAction() );
}
}
}
return actions;
}
QActionList
CollectionTreeView::createCollectionActions( const QModelIndexList &indices )
{
QActionList actions;
// Extract collection whose constituent was selected
CollectionTreeItem *item =
static_cast<CollectionTreeItem *>( indices.first().internalPointer() );
// Don't return any collection actions for non collection items
if( item->isDataItem() )
return actions;
Collection *collection = item->parentCollection();
// Generate CollectionCapability, test for existence
QScopedPointer<Capabilities::ActionsCapability> cc(
collection->create<Capabilities::ActionsCapability>() );
if( cc )
actions = cc->actions();
return actions;
}
QHash<QAction *, Collection *>
CollectionTreeView::getCopyActions( const QModelIndexList &indices )
{
QHash<QAction *, Collection *> currentCopyDestination;
if( onlyOneCollection( indices ) )
{
Collection *collection = getCollection( indices.first() );
QList<Collection *> writableCollections;
QHash<Collection *, CollectionManager::CollectionStatus> hash =
CollectionManager::instance()->collections();
QHash<Collection *, CollectionManager::CollectionStatus>::const_iterator it =
hash.constBegin();
while( it != hash.constEnd() )
{
Collection *coll = it.key();
if( coll && coll->isWritable() && coll != collection )
writableCollections.append( coll );
++it;
}
if( !writableCollections.isEmpty() )
{
foreach( Collection *coll, writableCollections )
{
QAction *action = new QAction( coll->icon(), coll->prettyName(), 0 );
action->setProperty( "popupdropper_svg_id", "collection" );
- connect( action, SIGNAL(triggered()), this, SLOT(slotCopyTracks()) );
+ connect( action, &QAction::triggered, this, &CollectionTreeView::slotCopyTracks );
currentCopyDestination.insert( action, coll );
}
}
}
return currentCopyDestination;
}
QHash<QAction *, Collection *>
CollectionTreeView::getMoveActions( const QModelIndexList &indices )
{
QHash<QAction *, Collection *> currentMoveDestination;
if( onlyOneCollection( indices ) )
{
Collection *collection = getCollection( indices.first() );
QList<Collection *> writableCollections;
QHash<Collection *, CollectionManager::CollectionStatus> hash =
CollectionManager::instance()->collections();
QHash<Collection *, CollectionManager::CollectionStatus>::const_iterator it =
hash.constBegin();
while( it != hash.constEnd() )
{
Collection *coll = it.key();
if( coll && coll->isWritable() && coll != collection )
writableCollections.append( coll );
++it;
}
if( !writableCollections.isEmpty() )
{
if( collection->isWritable() )
{
foreach( Collection *coll, writableCollections )
{
QAction *action = new QAction( coll->icon(), coll->prettyName(), 0 );
action->setProperty( "popupdropper_svg_id", "collection" );
- connect( action, SIGNAL(triggered()), this, SLOT(slotMoveTracks()) );
+ connect( action, &QAction::triggered, this, &CollectionTreeView::slotMoveTracks );
currentMoveDestination.insert( action, coll );
}
}
}
}
return currentMoveDestination;
}
bool CollectionTreeView::onlyOneCollection( const QModelIndexList &indices )
{
if( !indices.isEmpty() )
{
Collection *collection = getCollection( indices.first() );
foreach( const QModelIndex &index, indices )
{
Collection *currentCollection = getCollection( index );
if( collection != currentCollection )
return false;
}
}
return true;
}
Collection *
CollectionTreeView::getCollection( const QModelIndex &index )
{
Collection *collection = 0;
if( index.isValid() )
{
CollectionTreeItem *item =
static_cast<CollectionTreeItem *>( index.internalPointer() );
while( item->isDataItem() )
item = item->parent();
collection = item->parentCollection();
}
return collection;
}
void
CollectionTreeView::slotReplacePlaylistWithChildTracks()
{
playChildTracks( m_currentItems, Playlist::OnReplacePlaylistAction );
}
void
CollectionTreeView::slotAppendChildTracks()
{
playChildTracks( m_currentItems, Playlist::OnAppendToPlaylistAction );
}
void
CollectionTreeView::slotQueueChildTracks()
{
playChildTracks( m_currentItems, Playlist::OnQueueToPlaylistAction );
}
void
CollectionTreeView::slotEditTracks()
{
editTracks( m_currentItems );
}
void
CollectionTreeView::slotCopyTracks()
{
if( !sender() )
return;
if( QAction *action = dynamic_cast<QAction *>( sender() ) )
copyTracks( m_currentItems, m_currentCopyDestination[ action ], false );
}
void
CollectionTreeView::slotMoveTracks()
{
if( !sender() )
return;
if ( QAction *action = dynamic_cast<QAction *>( sender() ) )
copyTracks( m_currentItems, m_currentMoveDestination[ action ], true );
}
void
-CollectionTreeView::slotTrashTracks( Qt::MouseButtons, Qt::KeyboardModifiers modifiers )
+CollectionTreeView::slotTrashTracks()
{
- bool useTrash = !modifiers.testFlag( Qt::ShiftModifier );
- removeTracks( m_currentItems, useTrash );
+ removeTracks( m_currentItems, true );
}
void
CollectionTreeView::slotDeleteTracks()
{
removeTracks( m_currentItems, false /* do not use trash */ );
}
void
CollectionTreeView::slotOrganize()
{
if( sender() )
{
if( QAction *action = dynamic_cast<QAction *>( sender() ) )
{
Q_UNUSED( action )
organizeTracks( m_currentItems );
}
}
}
QSet<CollectionTreeItem *>
CollectionTreeView::cleanItemSet( const QSet<CollectionTreeItem *> &items )
{
QSet<CollectionTreeItem *> parents;
foreach( CollectionTreeItem *item, items )
{
CollectionTreeItem *tmpItem = item;
while( tmpItem )
{
if( items.contains( tmpItem->parent() ) )
tmpItem = tmpItem->parent();
else
{
parents.insert( tmpItem );
break;
}
}
}
return parents;
}
Collections::QueryMaker *
CollectionTreeView::createMetaQueryFromItems( const QSet<CollectionTreeItem *> &items,
bool cleanItems ) const
{
if( !m_treeModel )
return 0;
QSet<CollectionTreeItem*> parents = cleanItems ? cleanItemSet( items ) : items;
QList<Collections::QueryMaker *> queryMakers;
foreach( CollectionTreeItem *item, parents )
{
Collections::QueryMaker *qm = item->queryMaker();
for( CollectionTreeItem *tmp = item; tmp; tmp = tmp->parent() )
tmp->addMatch( qm, m_treeModel->levelCategory( tmp->level() - 1 ) );
Collections::addTextualFilter( qm, m_treeModel->currentFilter() );
queryMakers.append( qm );
}
return new Collections::MetaQueryMaker( queryMakers );
}
#include "CollectionTreeView.moc" // Q_OBJECTs defined in CollectionTreeView.cpp
#include "moc_CollectionTreeView.cpp" // Q_OBJECTs defined in CollectionTreeView.h
diff --git a/src/browsers/CollectionTreeView.h b/src/browsers/CollectionTreeView.h
index daaf2ac122..bb405878b4 100644
--- a/src/browsers/CollectionTreeView.h
+++ b/src/browsers/CollectionTreeView.h
@@ -1,165 +1,166 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef COLLECTIONTREEVIEW_H
#define COLLECTIONTREEVIEW_H
#include "amarok_export.h"
#include "BrowserDefines.h"
#include "widgets/PrettyTreeView.h"
#include "core/meta/forward_declarations.h"
#include "playlist/PlaylistController.h"
#include <QModelIndex>
#include <QMutex>
#include <QSet>
#include <QApplication>
class AmarokMimeData;
class CollectionSortFilterProxyModel;
class CollectionTreeItemModelBase;
class CollectionTreeItem;
class PopupDropper;
namespace Collections {
class Collection;
class QueryMaker;
}
class QAction;
class QSortFilterProxyModel;
typedef QList<QAction *> QActionList;
class CollectionTreeView: public Amarok::PrettyTreeView
{
Q_OBJECT
public:
explicit CollectionTreeView( QWidget *parent = 0 );
~CollectionTreeView();
QSortFilterProxyModel* filterModel() const;
AMAROK_EXPORT void setLevels( const QList<CategoryId::CatMenuId> &levels );
QList<CategoryId::CatMenuId> levels() const;
void setLevel( int level, CategoryId::CatMenuId type );
void setModel( QAbstractItemModel *model );
//Helper function to remove children if their parent is already present
static QSet<CollectionTreeItem*> cleanItemSet( const QSet<CollectionTreeItem*> &items );
static bool onlyOneCollection( const QModelIndexList &indices );
static Collections::Collection *getCollection( const QModelIndex &index );
Collections::QueryMaker* createMetaQueryFromItems( const QSet<CollectionTreeItem*> &items, bool cleanItems=true ) const;
/**
* Copies all selected tracks to the local collection. The user can also
* choose to do on-the-fly transcoding.
*/
AMAROK_EXPORT void copySelectedToLocalCollection();
public Q_SLOTS:
void slotSetFilter( const QString &filter );
/**
* This should append all currently visible tracks to the playlist. Takes
* care to ensure that the tracks are added only after any pending searches
* are finished.
*/
void slotAddFilteredTracksToPlaylist();
void playChildTracksSlot( Meta::TrackList list );
Q_SIGNALS:
/**
* This signal is emitted when slotAddFilteredTracksToPlaylist() has done its
* work.
*/
void addingFilteredTracksDone();
protected:
void contextMenuEvent( QContextMenuEvent *event );
void mouseDoubleClickEvent( QMouseEvent *event );
void mouseReleaseEvent( QMouseEvent *event );
void keyPressEvent( QKeyEvent *event );
void dragEnterEvent( QDragEnterEvent *event );
void dragMoveEvent( QDragMoveEvent *event );
void startDrag( Qt::DropActions supportedActions );
protected Q_SLOTS:
virtual void selectionChanged ( const QItemSelection & selected, const QItemSelection & deselected );
void slotCollapsed( const QModelIndex &index );
void slotExpanded( const QModelIndex &index );
void slotExpandIndex( const QModelIndex &index );
void slotCheckAutoExpand( bool reallyExpand = true );
+ void slotCheckAutoExpandReally() { slotCheckAutoExpand( true ); }
void slotReplacePlaylistWithChildTracks();
void slotAppendChildTracks();
void slotQueueChildTracks();
void slotEditTracks();
void slotCopyTracks();
void slotMoveTracks();
- void slotTrashTracks( Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers );
+ void slotTrashTracks();
void slotDeleteTracks();
void slotOrganize();
private:
// Utility function to play all items
// that have this as a parent..
void playChildTracks( CollectionTreeItem *item, Playlist::AddOptions insertMode );
void playChildTracks( const QSet<CollectionTreeItem*> &items, Playlist::AddOptions insertMode );
void editTracks( const QSet<CollectionTreeItem*> &items ) const;
void organizeTracks( const QSet<CollectionTreeItem*> &items ) const;
void copyTracks( const QSet<CollectionTreeItem*> &items, Collections::Collection *destination,
bool removeSources) const;
void removeTracks( const QSet<CollectionTreeItem*> &items, bool useTrash ) const;
// creates different actions from the different objects.
// note: you should not delete the created actions.
QActionList createBasicActions( const QModelIndexList &indcies );
QActionList createExtendedActions( const QModelIndexList &indcies );
QActionList createCollectionActions( const QModelIndexList &indices );
QActionList createCustomActions( const QModelIndexList &indices );
QHash<QAction*, Collections::Collection*> getCopyActions( const QModelIndexList &indcies );
QHash<QAction*, Collections::Collection*> getMoveActions( const QModelIndexList &indcies );
CollectionTreeItem* getItemFromIndex( QModelIndex &index );
CollectionSortFilterProxyModel *m_filterModel;
CollectionTreeItemModelBase *m_treeModel;
PopupDropper* m_pd;
QAction* m_appendAction;
QAction* m_loadAction;
QAction* m_editAction;
QAction* m_organizeAction;
QHash<QAction*, Collections::Collection*> m_currentCopyDestination;
QHash<QAction*, Collections::Collection*> m_currentMoveDestination;
QMap<AmarokMimeData*, Playlist::AddOptions> m_playChildTracksMode;
QSet<CollectionTreeItem*> m_currentItems;
bool m_ongoingDrag;
Q_SIGNALS:
void itemSelected( CollectionTreeItem * item );
void leavingTree();
};
#endif
diff --git a/src/browsers/SingleCollectionTreeItemModel.cpp b/src/browsers/SingleCollectionTreeItemModel.cpp
index a525582864..da040637e1 100644
--- a/src/browsers/SingleCollectionTreeItemModel.cpp
+++ b/src/browsers/SingleCollectionTreeItemModel.cpp
@@ -1,90 +1,90 @@
/****************************************************************************************
* Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SingleCollectionTreeItemModel"
#include "SingleCollectionTreeItemModel.h"
#include "amarokconfig.h"
#include "browsers/CollectionTreeItem.h"
#include "core/collections/Collection.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <KLocale>
SingleCollectionTreeItemModel::SingleCollectionTreeItemModel( Collections::Collection *collection,
const QList<CategoryId::CatMenuId> &levelType )
: m_collection( collection )
{
m_rootItem = new CollectionTreeItem( m_collection, 0, this );
- connect( collection, SIGNAL(updated()), this, SLOT(slotFilter()) ) ;
+ connect( collection, &Collections::Collection::updated, this, &SingleCollectionTreeItemModel::slotFilterWithoutAutoExpand ) ;
m_collections.insert( m_collection->collectionId(), CollectionRoot( m_collection, m_rootItem ) );
//we only have one collection that, by its very nature, is always expanded
m_expandedCollections.insert( m_collection );
setLevels( levelType );
}
QVariant
SingleCollectionTreeItemModel::data(const QModelIndex &index, int role) const
{
if( !index.isValid() )
return QVariant();
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( index.internalPointer() );
return dataForItem( item, role );
}
Qt::ItemFlags
SingleCollectionTreeItemModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags f = CollectionTreeItemModelBase::flags( index );
return ( f &= ~Qt::ItemIsEditable );
}
bool
SingleCollectionTreeItemModel::canFetchMore( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
return m_rootItem->requiresUpdate();
CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
return item->level() < m_levelType.count() && item->requiresUpdate();
}
void
SingleCollectionTreeItemModel::fetchMore( const QModelIndex &parent )
{
CollectionTreeItem *item;
if ( parent.isValid() )
item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
else
item = m_rootItem;
ensureChildrenLoaded( item );
}
void
SingleCollectionTreeItemModel::filterChildren()
{
markSubTreeAsDirty( m_rootItem );
ensureChildrenLoaded( m_rootItem );
}
diff --git a/src/browsers/collectionbrowser/CollectionWidget.cpp b/src/browsers/collectionbrowser/CollectionWidget.cpp
index dfc3bdcb54..9a15636318 100644
--- a/src/browsers/collectionbrowser/CollectionWidget.cpp
+++ b/src/browsers/collectionbrowser/CollectionWidget.cpp
@@ -1,479 +1,478 @@
/****************************************************************************************
* Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008-2009 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CollectionWidget"
#include "CollectionWidget.h"
#include "amarokconfig.h"
#include "browsers/CollectionTreeItemModel.h"
#include "browsers/CollectionTreeItemModelBase.h"
#include "browsers/SingleCollectionTreeItemModel.h"
#include "browsers/collectionbrowser/CollectionBrowserTreeView.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core-impl/collections/aggregate/AggregateCollection.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "widgets/SearchWidget.h"
#include "widgets/PrettyTreeDelegate.h"
#include <QAction>
#include <QIcon>
#include <KLocale>
#include <QMenu>
#include <KMenuBar>
#include <KStandardDirs>
#include <KStandardGuiItem>
#include <QActionGroup>
#include <QMetaEnum>
#include <QMetaObject>
#include <QRect>
#include <QSortFilterProxyModel>
#include <QStackedWidget>
#include <QToolBar>
#include <QToolButton>
CollectionWidget *CollectionWidget::s_instance = 0;
#define CATEGORY_LEVEL_COUNT 3
Q_DECLARE_METATYPE( QList<CategoryId::CatMenuId> ) // needed to QAction payload
class CollectionWidget::Private
{
public:
Private()
: treeView( 0 )
, singleTreeView( 0 )
, viewMode( CollectionWidget::NormalCollections ) {}
~Private() {}
CollectionBrowserTreeView *view( CollectionWidget::ViewMode mode );
CollectionBrowserTreeView *treeView;
CollectionBrowserTreeView *singleTreeView;
QStackedWidget *stack;
SearchWidget *searchWidget;
CollectionWidget::ViewMode viewMode;
QMenu *menuLevel[CATEGORY_LEVEL_COUNT];
QActionGroup *levelGroups[CATEGORY_LEVEL_COUNT];
};
CollectionBrowserTreeView *
CollectionWidget::Private::view( CollectionWidget::ViewMode mode )
{
CollectionBrowserTreeView *v(0);
switch( mode )
{
case CollectionWidget::NormalCollections:
if( !treeView )
{
v = new CollectionBrowserTreeView( stack );
v->setAlternatingRowColors( true );
v->setFrameShape( QFrame::NoFrame );
v->setRootIsDecorated( false );
- connect( v, SIGNAL(leavingTree()), searchWidget->comboBox(), SLOT(setFocus()) );
+ connect( v, &CollectionBrowserTreeView::leavingTree,
+ searchWidget->comboBox(), QOverload<>::of(&QWidget::setFocus) );
PrettyTreeDelegate *delegate = new PrettyTreeDelegate( v );
v->setItemDelegate( delegate );
CollectionTreeItemModelBase *multiModel = new CollectionTreeItemModel( QList<CategoryId::CatMenuId>() );
multiModel->setParent( stack );
v->setModel( multiModel );
treeView = v;
}
else
{
v = treeView;
}
break;
case CollectionWidget::UnifiedCollection:
if( !singleTreeView )
{
v = new CollectionBrowserTreeView( stack );
v->setAlternatingRowColors( true );
v->setFrameShape( QFrame::NoFrame );
Collections::AggregateCollection *aggregateColl = new Collections::AggregateCollection();
- connect( CollectionManager::instance(),
- SIGNAL(collectionAdded(Collections::Collection*,CollectionManager::CollectionStatus)),
- aggregateColl,
- SLOT(addCollection(Collections::Collection*,CollectionManager::CollectionStatus)));
- connect( CollectionManager::instance(), SIGNAL(collectionRemoved(QString)),
- aggregateColl, SLOT(removeCollection(QString)));
+ connect( CollectionManager::instance(), &CollectionManager::collectionAdded,
+ aggregateColl, &Collections::AggregateCollection::addCollection );
+ connect( CollectionManager::instance(), &CollectionManager::collectionRemoved,
+ aggregateColl, &Collections::AggregateCollection::removeCollectionById );
foreach( Collections::Collection* coll, CollectionManager::instance()->viewableCollections() )
{
aggregateColl->addCollection( coll, CollectionManager::CollectionViewable );
}
CollectionTreeItemModelBase *singleModel = new SingleCollectionTreeItemModel( aggregateColl, QList<CategoryId::CatMenuId>() );
singleModel->setParent( stack );
v->setModel( singleModel );
singleTreeView = v;
}
else
{
v = singleTreeView;
}
break;
}
return v;
}
CollectionWidget::CollectionWidget( const QString &name , QWidget *parent )
: BrowserCategory( name, parent )
, d( new Private )
{
s_instance = this;
setObjectName( name );
setMargin( 0 );
setSpacing( 0 );
//TODO: we have a really nice opportunity to make these info blurbs both helpful and pretty
setLongDescription( i18n( "This is where you will find your local music, as well as music from mobile audio players and CDs." ) );
setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_collections.png" ) );
// set background
if( AmarokConfig::showBrowserBackgroundImage() )
setBackgroundImage( imagePath() );
// --- the box for the UI elements.
KHBox *hbox = new KHBox( this );
d->stack = new QStackedWidget( this );
// -- read the current view mode from the configuration
const QMetaObject *mo = metaObject();
const QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "ViewMode" ) );
const QString &value = Amarok::config( "Collection Browser" ).readEntry( "View Mode" );
int enumValue = me.keyToValue( value.toLocal8Bit().constData() );
enumValue == -1 ? d->viewMode = NormalCollections : d->viewMode = (ViewMode) enumValue;
// -- the search widget
d->searchWidget = new SearchWidget( hbox );
d->searchWidget->setClickMessage( i18n( "Search collection" ) );
// Filter presets. UserRole is used to store the actual syntax.
KComboBox *combo = d->searchWidget->comboBox();
const QIcon icon = KStandardGuiItem::find().icon();
combo->addItem( icon, i18nc("@item:inlistbox Collection widget filter preset", "Added This Hour"),
QString(Meta::shortI18nForField( Meta::valCreateDate ) + ":<1h") );
combo->addItem( icon, i18nc("@item:inlistbox Collection widget filter preset", "Added Today"),
QString(Meta::shortI18nForField( Meta::valCreateDate ) + ":<1d") );
combo->addItem( icon, i18nc("@item:inlistbox Collection widget filter preset", "Added This Week"),
QString(Meta::shortI18nForField( Meta::valCreateDate ) + ":<1w") );
combo->addItem( icon, i18nc("@item:inlistbox Collection widget filter preset", "Added This Month"),
QString(Meta::shortI18nForField( Meta::valCreateDate ) + ":<1m") );
combo->insertSeparator( combo->count() );
QMenu *filterMenu = new QMenu( 0 );
using namespace CategoryId;
static const QList<QList<CatMenuId> > levelPresets = QList<QList<CatMenuId> >()
<< ( QList<CatMenuId>() << CategoryId::AlbumArtist << CategoryId::Album )
<< ( QList<CatMenuId>() << CategoryId::Album << CategoryId::Artist ) // album artist has no sense here
<< ( QList<CatMenuId>() << CategoryId::Genre << CategoryId::AlbumArtist )
<< ( QList<CatMenuId>() << CategoryId::Genre << CategoryId::AlbumArtist << CategoryId::Album );
foreach( const QList<CatMenuId> &levels, levelPresets )
{
QStringList categoryLabels;
foreach( CatMenuId category, levels )
categoryLabels << CollectionTreeItemModelBase::nameForCategory( category );
QAction *action = filterMenu->addAction( categoryLabels.join( i18nc(
"separator between collection browser level categories, i.e. the ' / ' "
"in 'Artist / Album'", " / " ) ) );
action->setData( QVariant::fromValue( levels ) );
}
// following catches all actions in the filter menu
- connect( filterMenu, SIGNAL(triggered(QAction*)), SLOT(sortByActionPayload(QAction*)) );
+ connect( filterMenu, &QMenu::triggered, this, &CollectionWidget::sortByActionPayload );
filterMenu->addSeparator();
// -- read the view level settings from the configuration
QList<CategoryId::CatMenuId> levels = readLevelsFromConfig();
if ( levels.isEmpty() )
levels << levelPresets.at( 0 ); // use first preset as default
// -- generate the level menus
d->menuLevel[0] = filterMenu->addMenu( i18n( "First Level" ) );
d->menuLevel[1] = filterMenu->addMenu( i18n( "Second Level" ) );
d->menuLevel[2] = filterMenu->addMenu( i18n( "Third Level" ) );
// - fill the level menus
static const QList<CatMenuId> levelChoices = QList<CatMenuId>()
<< CategoryId::AlbumArtist
<< CategoryId::Artist
<< CategoryId::Album
<< CategoryId::Genre
<< CategoryId::Composer
<< CategoryId::Label;
for( int i = 0; i < CATEGORY_LEVEL_COUNT; i++ )
{
QList<CatMenuId> usedLevelChoices = levelChoices;
QActionGroup *actionGroup = new QActionGroup( this );
if( i > 0 ) // skip first submenu
usedLevelChoices.prepend( CategoryId::None );
QMenu *menuLevel = d->menuLevel[i];
foreach( CatMenuId level, usedLevelChoices )
{
QAction *action = menuLevel->addAction( CollectionTreeItemModelBase::nameForCategory( level ) );
action->setData( QVariant::fromValue<CatMenuId>( level ) );
action->setCheckable( true );
action->setChecked( ( levels.count() > i ) ? ( levels[i] == level )
: ( level == CategoryId::None ) );
actionGroup->addAction( action );
}
d->levelGroups[i] = actionGroup;
- connect( menuLevel, SIGNAL(triggered(QAction*)), SLOT(sortLevelSelected(QAction*)) );
+ connect( menuLevel, &QMenu::triggered, this, &CollectionWidget::sortLevelSelected );
}
// -- create the checkboxesh
filterMenu->addSeparator();
QAction *showYears = filterMenu->addAction( i18n( "Show Years" ) );
showYears->setCheckable( true );
showYears->setChecked( AmarokConfig::showYears() );
- connect( showYears, SIGNAL(toggled(bool)), SLOT(slotShowYears(bool)) );
+ connect( showYears, &QAction::toggled, this, &CollectionWidget::slotShowYears );
QAction *showTrackNumbers = filterMenu->addAction( i18nc("@action:inmenu", "Show Track Numbers") );
showTrackNumbers->setCheckable( true );
showTrackNumbers->setChecked( AmarokConfig::showTrackNumbers() );
- connect( showTrackNumbers, SIGNAL(toggled(bool)), SLOT(slotShowTrackNumbers(bool)) );
+ connect( showTrackNumbers, &QAction::toggled, this, &CollectionWidget::slotShowTrackNumbers );
QAction *showCovers = filterMenu->addAction( i18n( "Show Cover Art" ) );
showCovers->setCheckable( true );
showCovers->setChecked( AmarokConfig::showAlbumArt() );
- connect( showCovers, SIGNAL(toggled(bool)), SLOT(slotShowCovers(bool)) );
+ connect( showCovers, &QAction::toggled, this, &CollectionWidget::slotShowCovers );
d->searchWidget->toolBar()->addSeparator();
QAction *toggleAction = new QAction( QIcon::fromTheme( "view-list-tree" ), i18n( "Merged View" ), this );
toggleAction->setCheckable( true );
toggleAction->setChecked( d->viewMode == CollectionWidget::UnifiedCollection );
toggleView( d->viewMode == CollectionWidget::UnifiedCollection );
- connect( toggleAction, SIGNAL(triggered(bool)), SLOT(toggleView(bool)) );
+ connect( toggleAction, &QAction::triggered, this, &CollectionWidget::toggleView );
d->searchWidget->toolBar()->addAction( toggleAction );
QAction *searchMenuAction = new QAction( QIcon::fromTheme( "preferences-other" ), i18n( "Sort Options" ), this );
searchMenuAction->setMenu( filterMenu );
d->searchWidget->toolBar()->addAction( searchMenuAction );
QToolButton *tbutton = qobject_cast<QToolButton*>( d->searchWidget->toolBar()->widgetForAction( searchMenuAction ) );
if( tbutton )
tbutton->setPopupMode( QToolButton::InstantPopup );
setLevels( levels );
}
CollectionWidget::~CollectionWidget()
{
delete d;
}
void
CollectionWidget::focusInputLine()
{
return d->searchWidget->comboBox()->setFocus();
}
void
CollectionWidget::sortLevelSelected( QAction *action )
{
Q_UNUSED( action );
QList<CategoryId::CatMenuId> levels;
for( int i = 0; i < CATEGORY_LEVEL_COUNT; i++ )
{
const QAction *action = d->levelGroups[i]->checkedAction();
if( action )
{
CategoryId::CatMenuId category = action->data().value<CategoryId::CatMenuId>();
if( category != CategoryId::None )
levels << category;
}
}
setLevels( levels );
}
void
CollectionWidget::sortByActionPayload( QAction *action )
{
QList<CategoryId::CatMenuId> levels = action->data().value<QList<CategoryId::CatMenuId> >();
if( !levels.isEmpty() )
setLevels( levels );
}
void
CollectionWidget::slotShowYears( bool checked )
{
AmarokConfig::setShowYears( checked );
setLevels( levels() );
}
void
CollectionWidget::slotShowTrackNumbers( bool checked )
{
AmarokConfig::setShowTrackNumbers( checked );
setLevels( levels() );
}
void
CollectionWidget::slotShowCovers(bool checked)
{
AmarokConfig::setShowAlbumArt( checked );
setLevels( levels() );
}
QString
CollectionWidget::filter() const
{
return d->searchWidget->currentText();
}
void CollectionWidget::setFilter( const QString &filter )
{
d->searchWidget->setSearchString( filter );
}
QList<CategoryId::CatMenuId>
CollectionWidget::levels() const
{
// return const_cast<CollectionWidget*>( this )->view( d->viewMode )->levels();
return d->view( d->viewMode )->levels();
}
void CollectionWidget::setLevels( const QList<CategoryId::CatMenuId> &levels )
{
// -- select the corrrect menu entries
QSet<CategoryId::CatMenuId> encounteredLevels;
for( int i = 0; i < CATEGORY_LEVEL_COUNT; i++ )
{
CategoryId::CatMenuId category;
if( levels.count() > i )
category = levels[i];
else
category = CategoryId::None;
foreach( QAction *action, d->levelGroups[i]->actions() )
{
CategoryId::CatMenuId actionCategory = action->data().value<CategoryId::CatMenuId>();
if( actionCategory == category )
action->setChecked( true ); // unchecks other actions in the same group
action->setEnabled( !encounteredLevels.contains( actionCategory ) );
}
if( category != CategoryId::None )
encounteredLevels << category;
}
// -- set the levels in the view
d->view( d->viewMode )->setLevels( levels );
debug() << "Sort levels:" << levels;
}
void CollectionWidget::toggleView( bool merged )
{
CollectionWidget::ViewMode newMode = merged ? UnifiedCollection : NormalCollections;
CollectionBrowserTreeView *oldView = d->view( d->viewMode );
if( oldView )
{
d->searchWidget->disconnect( oldView );
oldView->disconnect( d->searchWidget );
}
CollectionBrowserTreeView *newView = d->view( newMode );
- connect( d->searchWidget, SIGNAL(filterChanged(QString)),
- newView, SLOT(slotSetFilter(QString)) );
- connect( d->searchWidget, SIGNAL(returnPressed()),
- newView, SLOT(slotAddFilteredTracksToPlaylist()) );
+ connect( d->searchWidget, &SearchWidget::filterChanged,
+ newView, &CollectionBrowserTreeView::slotSetFilter );
+ connect( d->searchWidget, &SearchWidget::returnPressed,
+ newView, &CollectionBrowserTreeView::slotAddFilteredTracksToPlaylist );
// reset search string after successful adding of filtered items to playlist
- connect( newView, SIGNAL(addingFilteredTracksDone()),
- d->searchWidget, SLOT(setSearchString()) );
+ connect( newView, &CollectionBrowserTreeView::addingFilteredTracksDone,
+ d->searchWidget, &SearchWidget::emptySearchString );
if( d->stack->indexOf( newView ) == -1 )
d->stack->addWidget( newView );
d->stack->setCurrentWidget( newView );
const QString &filter = d->searchWidget->currentText();
if( !filter.isEmpty() )
{
typedef CollectionTreeItemModelBase CTIMB;
CTIMB *model = qobject_cast<CTIMB*>( newView->filterModel()->sourceModel() );
model->setCurrentFilter( filter );
}
d->viewMode = newMode;
if( oldView )
setLevels( oldView->levels() );
const QMetaObject *mo = metaObject();
const QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "ViewMode" ) );
Amarok::config( "Collection Browser" ).writeEntry( "View Mode", me.valueToKey( d->viewMode ) );
}
QList<CategoryId::CatMenuId>
CollectionWidget::readLevelsFromConfig() const
{
QList<int> levelNumbers = Amarok::config( "Collection Browser" ).readEntry( "TreeCategory", QList<int>() );
QList<CategoryId::CatMenuId> levels;
// we changed "Track Artist" to "Album Artist" default before Amarok 2.8. Migrate user
// config mentioning Track Artist to Album Artist where it makes sense:
static const int OldArtistValue = 2;
bool albumOrAlbumArtistEncountered = false;
foreach( int levelNumber, levelNumbers )
{
CategoryId::CatMenuId category;
if( levelNumber == OldArtistValue )
{
if( albumOrAlbumArtistEncountered )
category = CategoryId::Artist;
else
category = CategoryId::AlbumArtist;
}
else
category = CategoryId::CatMenuId( levelNumber );
levels << category;
if( category == CategoryId::Album || category == CategoryId::AlbumArtist )
albumOrAlbumArtistEncountered = true;
}
return levels;
}
CollectionBrowserTreeView*
CollectionWidget::currentView()
{
return d->view( d->viewMode );
}
CollectionWidget::ViewMode
CollectionWidget::viewMode() const
{
return d->viewMode;
}
SearchWidget*
CollectionWidget::searchWidget()
{
return d->searchWidget;
}
diff --git a/src/browsers/filebrowser/FileBrowser.cpp b/src/browsers/filebrowser/FileBrowser.cpp
index 2e8ea5a1fb..321f69dd7d 100644
--- a/src/browsers/filebrowser/FileBrowser.cpp
+++ b/src/browsers/filebrowser/FileBrowser.cpp
@@ -1,631 +1,637 @@
/****************************************************************************************
* Copyright (c) 2010 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "FileBrowser"
#include "FileBrowser_p.h"
#include "FileBrowser.h"
#include "amarokconfig.h"
#include "EngineController.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/meta/file/File.h"
#include "browsers/BrowserBreadcrumbItem.h"
#include "browsers/BrowserCategoryList.h"
#include "browsers/filebrowser/DirPlaylistTrackFilterProxyModel.h"
#include "browsers/filebrowser/FileView.h"
#include "playlist/PlaylistController.h"
#include "widgets/SearchWidget.h"
#include <QAction>
#include <KComboBox>
#include <KConfigGroup>
#include <KDirLister>
#include <KIO/NetAccess>
#include <KSaveFile>
#include <KStandardAction>
#include <KStandardDirs>
#include <KToolBar>
#include <KLocalizedString>
#include <QHeaderView>
static const QString placesString( "places://" );
static const QUrl placesUrl( placesString );
FileBrowser::Private::Private( FileBrowser *parent )
: placesModel( 0 )
, q( parent )
{
KHBox *topHBox = new KHBox( q );
KToolBar *navigationToolbar = new KToolBar( topHBox );
navigationToolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
navigationToolbar->setIconDimensions( 16 );
- backAction = KStandardAction::back( q, SLOT(back()), topHBox );
- forwardAction = KStandardAction::forward( q, SLOT(forward()), topHBox );
+ backAction = KStandardAction::back( q, &FileBrowser::back, topHBox );
+ forwardAction = KStandardAction::forward( q, &FileBrowser::forward, topHBox );
backAction->setEnabled( false );
forwardAction->setEnabled( false );
- upAction = KStandardAction::up( q, SLOT(up()), topHBox );
- homeAction = KStandardAction::home( q, SLOT(home()), topHBox );
+ upAction = KStandardAction::up( q, &FileBrowser::up, topHBox );
+ homeAction = KStandardAction::home( q, &FileBrowser::home, topHBox );
refreshAction = new QAction( QIcon::fromTheme("view-refresh"), i18n( "Refresh" ), topHBox );
- QObject::connect( refreshAction, SIGNAL(triggered(bool)), q, SLOT(refresh()) );
+ QObject::connect( refreshAction, &QAction::triggered, q, &FileBrowser::refresh );
navigationToolbar->addAction( backAction );
navigationToolbar->addAction( forwardAction );
navigationToolbar->addAction( upAction );
navigationToolbar->addAction( homeAction );
navigationToolbar->addAction( refreshAction );
searchWidget = new SearchWidget( topHBox, false );
searchWidget->setClickMessage( i18n( "Filter Files" ) );
fileView = new FileView( q );
}
FileBrowser::Private::~Private()
{
writeConfig();
}
void
FileBrowser::Private::readConfig()
{
const QUrl homeUrl = QUrl::fromLocalFile( QDir::homePath() );
const QUrl savedUrl = Amarok::config( "File Browser" ).readEntry( "Current Directory", homeUrl );
bool useHome( true );
// fall back to $HOME if the saved dir has since disappeared or is a remote one
if( savedUrl.isLocalFile() )
{
QDir dir( savedUrl.path() );
if( dir.exists() )
useHome = false;
}
else if( KIO::NetAccess::exists( savedUrl, KIO::NetAccess::DestinationSide, 0 ) )
{
useHome = false;
}
currentPath = useHome ? homeUrl : savedUrl;
}
void
FileBrowser::Private::writeConfig()
{
Amarok::config( "File Browser" ).writeEntry( "Current Directory", kdirModel->dirLister()->url() );
}
BreadcrumbSiblingList
FileBrowser::Private::siblingsForDir( const QUrl &path )
{
BreadcrumbSiblingList siblings;
if( path.scheme() == "places" )
{
for( int i = 0; i < placesModel->rowCount(); i++ )
{
QModelIndex idx = placesModel->index( i, 0 );
QString name = idx.data( Qt::DisplayRole ).toString();
QString url = idx.data( KFilePlacesModel::UrlRole ).toString();
if( url.isEmpty() )
// the place perhaps needs mounting, use places url instead
url = placesString + name;
siblings << BreadcrumbSibling( idx.data( Qt::DecorationRole ).value<QIcon>(),
name, url );
}
}
else if( path.isLocalFile() )
{
QDir dir( path.toLocalFile() );
dir.cdUp();
foreach( const QString &item, dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
{
siblings << BreadcrumbSibling( QIcon::fromTheme( "folder-amarok" ), item,
dir.absoluteFilePath( item ) );
}
}
return siblings;
}
void
FileBrowser::Private::updateNavigateActions()
{
backAction->setEnabled( !backStack.isEmpty() );
forwardAction->setEnabled( !forwardStack.isEmpty() );
upAction->setEnabled( currentPath != placesUrl );
}
void
FileBrowser::Private::restoreDefaultHeaderState()
{
fileView->hideColumn( 3 );
fileView->hideColumn( 4 );
fileView->hideColumn( 5 );
fileView->hideColumn( 6 );
fileView->sortByColumn( 0, Qt::AscendingOrder );
}
void
FileBrowser::Private::restoreHeaderState()
{
QFile file( Amarok::saveLocation() + "file_browser_layout" );
if( !file.open( QIODevice::ReadOnly ) )
{
restoreDefaultHeaderState();
return;
}
if( !fileView->header()->restoreState( file.readAll() ) )
{
warning() << "invalid header state saved, unable to restore. Restoring defaults";
restoreDefaultHeaderState();
return;
}
}
void
FileBrowser::Private::saveHeaderState()
{
//save the state of the header (column size and order). Yay, another QByteArray thingie...
KSaveFile file( Amarok::saveLocation() + "file_browser_layout" );
if( !file.open( QIODevice::WriteOnly ) )
{
warning() << "unable to save header state";
return;
}
if( file.write( fileView->header()->saveState() ) < 0 )
{
warning() << "unable to save header state, writing failed";
return;
}
if( !file.finalize() )
{
warning() << "failed to write header state";
return;
}
}
void
FileBrowser::Private::updateHeaderState()
{
// this slot is triggered right after model change, when currentPath is not yet updated
if( fileView->model() == mimeFilterProxyModel && currentPath == placesUrl )
// we are transitioning from places to files
restoreHeaderState();
}
FileBrowser::FileBrowser( const char *name, QWidget *parent )
: BrowserCategory( name, parent )
, d( new FileBrowser::Private( this ) )
{
setLongDescription( i18n( "The file browser lets you browse files anywhere on your system, "
"regardless of whether these files are part of your local collection. "
"You can then add these files to the playlist as well as perform basic "
"file operations." )
);
setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_files.png" ) );
// set background
if( AmarokConfig::showBrowserBackgroundImage() )
setBackgroundImage( imagePath() );
initView();
}
void
FileBrowser::initView()
{
d->bottomPlacesModel = new FilePlacesModel( this );
- connect( d->bottomPlacesModel, SIGNAL(setupDone(QModelIndex,bool)),
- SLOT(setupDone(QModelIndex,bool)) );
+ connect( d->bottomPlacesModel, &KFilePlacesModel::setupDone,
+ this, &FileBrowser::setupDone );
d->placesModel = new QSortFilterProxyModel( this );
d->placesModel->setSourceModel( d->bottomPlacesModel );
d->placesModel->setSortRole( -1 );
d->placesModel->setDynamicSortFilter( true );
d->placesModel->setFilterRole( KFilePlacesModel::HiddenRole );
// HiddenRole is bool, but QVariant( false ).toString() gives "false"
d->placesModel->setFilterFixedString( "false" );
d->placesModel->setObjectName( "PLACESMODEL");
d->kdirModel = new DirBrowserModel( this );
d->mimeFilterProxyModel = new DirPlaylistTrackFilterProxyModel( this );
d->mimeFilterProxyModel->setSourceModel( d->kdirModel );
d->mimeFilterProxyModel->setSortCaseSensitivity( Qt::CaseInsensitive );
d->mimeFilterProxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
d->mimeFilterProxyModel->setDynamicSortFilter( true );
- connect( d->searchWidget, SIGNAL(filterChanged(QString)),
- d->mimeFilterProxyModel, SLOT(setFilterFixedString(QString)) );
+ connect( d->searchWidget, &SearchWidget::filterChanged,
+ d->mimeFilterProxyModel, &DirPlaylistTrackFilterProxyModel::setFilterFixedString );
d->fileView->setModel( d->mimeFilterProxyModel );
d->fileView->header()->setContextMenuPolicy( Qt::ActionsContextMenu );
d->fileView->header()->setVisible( true );
d->fileView->setDragEnabled( true );
d->fileView->setSortingEnabled( true );
d->fileView->setSelectionMode( QAbstractItemView::ExtendedSelection );
d->readConfig();
d->restoreHeaderState();
setDir( d->currentPath );
for( int i = 0, columns = d->fileView->model()->columnCount(); i < columns ; ++i )
{
QAction *action =
new QAction( d->fileView->model()->headerData( i, Qt::Horizontal ).toString(),
d->fileView->header()
);
d->fileView->header()->addAction( action );
d->columnActions.append( action );
action->setCheckable( true );
if( !d->fileView->isColumnHidden( i ) )
action->setChecked( true );
- connect( action, SIGNAL(toggled(bool)), this, SLOT(toggleColumn(bool)) );
+ connect( action, &QAction::toggled, this, &FileBrowser::toggleColumn );
}
- connect( d->fileView->header(), SIGNAL(geometriesChanged()),
- SLOT(updateHeaderState()) );
- connect( d->fileView, SIGNAL(navigateToDirectory(QModelIndex)),
- SLOT(slotNavigateToDirectory(QModelIndex)) );
- connect( d->fileView, SIGNAL(refreshBrowser()),
- SLOT(refresh()) );
+ connect( d->fileView->header(), &QHeaderView::geometriesChanged,
+ this, &FileBrowser::updateHeaderState );
+ connect( d->fileView, &FileView::navigateToDirectory,
+ this, &FileBrowser::slotNavigateToDirectory );
+ connect( d->fileView, &FileView::refreshBrowser,
+ this, &FileBrowser::refresh );
}
+void
+FileBrowser::updateHeaderState()
+{
+ d->updateHeaderState();
+}
+
+
FileBrowser::~FileBrowser()
{
if( d->fileView->model() == d->mimeFilterProxyModel && d->currentPath != placesUrl )
d->saveHeaderState();
delete d;
}
void
FileBrowser::toggleColumn( bool toggled )
{
int index = d->columnActions.indexOf( qobject_cast< QAction* >( sender() ) );
if( index != -1 )
{
if( toggled )
d->fileView->showColumn( index );
else
d->fileView->hideColumn( index );
}
}
QString
FileBrowser::currentDir() const
{
if( d->currentPath.isLocalFile() )
return d->currentPath.toLocalFile();
else
return d->currentPath.url();
}
void
FileBrowser::slotNavigateToDirectory( const QModelIndex &index )
{
if( d->currentPath == placesUrl )
{
QString url = index.data( KFilePlacesModel::UrlRole ).value<QString>();
if( !url.isEmpty() )
{
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( QUrl( url ) );
}
else
{
//check if this url needs setup/mounting
if( index.data( KFilePlacesModel::SetupNeededRole ).value<bool>() )
{
d->bottomPlacesModel->requestSetup( d->placesModel->mapToSource( index ) );
}
else
warning() << __PRETTY_FUNCTION__ << "empty places url that doesn't need setup?";
}
}
else
{
KFileItem file = index.data( KDirModel::FileItemRole ).value<KFileItem>();
if( file.isDir() )
{
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( file.url() );
}
else
warning() << __PRETTY_FUNCTION__ << "called for non-directory";
}
}
void
FileBrowser::addItemActivated( const QString &callbackString )
{
if( callbackString.isEmpty() )
return;
QUrl newPath;
// we have been called with a places name, it means that we'll probably have to mount
// the place
if( callbackString.startsWith( placesString ) )
{
QString name = callbackString.mid( placesString.length() );
for( int i = 0; i < d->placesModel->rowCount(); i++ )
{
QModelIndex idx = d->placesModel->index( i, 0 );
if( idx.data().toString() == name )
{
if( idx.data( KFilePlacesModel::SetupNeededRole ).toBool() )
{
d->bottomPlacesModel->requestSetup( d->placesModel->mapToSource( idx ) );
return;
}
newPath = QUrl::fromUserInput(idx.data( KFilePlacesModel::UrlRole ).toString());
break;
}
}
if( newPath.isEmpty() )
{
warning() << __PRETTY_FUNCTION__ << "name" << name << "not found under Places";
return;
}
}
else
newPath = QUrl::fromUserInput(callbackString);
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( QUrl( newPath ) );
}
void
FileBrowser::setupAddItems()
{
clearAdditionalItems();
if( d->currentPath == placesUrl )
return; // no more items to add
QString workingUrl = d->currentPath.toDisplayString( QUrl::StripTrailingSlash );
int currentPosition = 0;
QString name;
QString callback;
BreadcrumbSiblingList siblings;
// find QModelIndex of the NON-HIDDEN closestItem
QModelIndex placesIndex;
QUrl tempUrl = d->currentPath;
do
{
placesIndex = d->bottomPlacesModel->closestItem( tempUrl );
if( !placesIndex.isValid() )
break; // no valid index even in the bottom model
placesIndex = d->placesModel->mapFromSource( placesIndex );
if( placesIndex.isValid() )
break; // found shown placesindex, good!
if( KIO::upUrl(tempUrl) == tempUrl )
break; // prevent infinite loop
tempUrl = KIO::upUrl(tempUrl);
} while( true );
// special handling for the first additional item
if( placesIndex.isValid() )
{
name = placesIndex.data( Qt::DisplayRole ).toString();
callback = placesIndex.data( KFilePlacesModel::UrlRole ).toString();
QUrl currPlaceUrl = d->placesModel->data( placesIndex, KFilePlacesModel::UrlRole ).toUrl();
currPlaceUrl.setPath( QDir::toNativeSeparators(currPlaceUrl.path() + '/') );
currentPosition = currPlaceUrl.toString().length();
}
else
{
QRegExp threeSlashes( "^[^/]*/[^/]*/[^/]*/" );
if( workingUrl.indexOf( threeSlashes ) == 0 )
currentPosition = threeSlashes.matchedLength();
else
currentPosition = workingUrl.length();
callback = workingUrl.left( currentPosition );
name = callback;
if( name == "file:///" )
name = '/'; // just niceness
else
name.remove( QRegExp( "/$" ) );
}
/* always provide siblings for places, regardless of what first item is; this also
* work-arounds bug 312639, where creating QUrl with accented chars crashes */
siblings = d->siblingsForDir( placesUrl );
addAdditionalItem( new BrowserBreadcrumbItem( name, callback, siblings, this ) );
// other additional items
while( !workingUrl.midRef( currentPosition ).isEmpty() )
{
int nextPosition = workingUrl.indexOf( '/', currentPosition ) + 1;
if( nextPosition <= 0 )
nextPosition = workingUrl.length();
name = workingUrl.mid( currentPosition, nextPosition - currentPosition );
name.remove( QRegExp( "/$" ) );
callback = workingUrl.left( nextPosition );
siblings = d->siblingsForDir( QUrl::fromLocalFile(callback) );
addAdditionalItem( new BrowserBreadcrumbItem( name, callback, siblings, this ) );
currentPosition = nextPosition;
}
if( parentList() )
parentList()->childViewChanged(); // emits viewChanged() which causes breadCrumb update
}
void
FileBrowser::reActivate()
{
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( placesUrl );
}
void
FileBrowser::setDir( const QUrl &dir )
{
if( dir == placesUrl )
{
if( d->currentPath != placesUrl )
{
d->saveHeaderState();
d->fileView->setModel( d->placesModel );
d->fileView->setSelectionMode( QAbstractItemView::SingleSelection );
d->fileView->header()->setVisible( false );
d->fileView->setDragEnabled( false );
}
}
else
{
// if we are currently showing "places" we need to remember to change the model
// back to the regular file model
if( d->currentPath == placesUrl )
{
d->fileView->setModel( d->mimeFilterProxyModel );
d->fileView->setSelectionMode( QAbstractItemView::ExtendedSelection );
d->fileView->setDragEnabled( true );
d->fileView->header()->setVisible( true );
}
d->kdirModel->dirLister()->openUrl( dir );
}
d->currentPath = dir;
d->updateNavigateActions();
setupAddItems();
// set the first item as current so that keyboard navigation works
new DelayedActivator( d->fileView );
}
void
FileBrowser::back()
{
if( d->backStack.isEmpty() )
return;
d->forwardStack.push( d->currentPath );
setDir( d->backStack.pop() );
}
void
FileBrowser::forward()
{
if( d->forwardStack.isEmpty() )
return;
d->backStack.push( d->currentPath );
// no clearing forward stack here!
setDir( d->forwardStack.pop() );
}
void
FileBrowser::up()
{
if( d->currentPath == placesUrl )
return; // nothing to do, we consider places as the root view
QUrl upUrl = KIO::upUrl(d->currentPath);
if( upUrl == d->currentPath ) // apparently, we cannot go up withn url
upUrl = placesUrl;
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( upUrl );
}
void
FileBrowser::home()
{
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( QUrl( QDir::homePath() ) );
}
void
FileBrowser::refresh()
{
setDir( d->currentPath );
}
void
FileBrowser::setupDone( const QModelIndex &index, bool success )
{
if( success )
{
QString url = index.data( KFilePlacesModel::UrlRole ).value<QString>();
if( !url.isEmpty() )
{
d->backStack.push( d->currentPath );
d->forwardStack.clear(); // navigating resets forward stack
setDir( QUrl::fromLocalFile(url) );
}
}
}
DelayedActivator::DelayedActivator( QAbstractItemView *view )
: QObject( view )
, m_view( view )
{
QAbstractItemModel *model = view->model();
if( !model )
{
deleteLater();
return;
}
// short-cut for already-filled models
if( model->rowCount() > 0 )
{
slotRowsInserted( QModelIndex(), 0 );
return;
}
- connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)),
- SLOT(slotRowsInserted(QModelIndex,int)) );
+ connect( model, &QAbstractItemModel::rowsInserted, this, &DelayedActivator::slotRowsInserted );
- connect( model, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
- connect( model, SIGNAL(layoutChanged()), SLOT(deleteLater()) );
- connect( model, SIGNAL(modelReset()), SLOT(deleteLater()) );
+ connect( model, &QAbstractItemModel::destroyed, this, &DelayedActivator::deleteLater );
+ connect( model, &QAbstractItemModel::layoutChanged, this, &DelayedActivator::deleteLater );
+ connect( model, &QAbstractItemModel::modelReset, this, &DelayedActivator::deleteLater );
}
void
DelayedActivator::slotRowsInserted( const QModelIndex &parent, int start )
{
QAbstractItemModel *model = m_view->model();
if( model )
{
// prevent duplicate calls, deleteLater() may fire REAL later
disconnect( model, 0, this, 0 );
QModelIndex idx = model->index( start, 0, parent );
m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
}
deleteLater();
}
#include "moc_FileBrowser.cpp"
diff --git a/src/browsers/filebrowser/FileBrowser.h b/src/browsers/filebrowser/FileBrowser.h
index 372adb0fea..68d11f7afc 100644
--- a/src/browsers/filebrowser/FileBrowser.h
+++ b/src/browsers/filebrowser/FileBrowser.h
@@ -1,122 +1,122 @@
/****************************************************************************************
* Copyright (c) 2010 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef FILEBROWSERMKII_H
#define FILEBROWSERMKII_H
#include "browsers/BrowserCategory.h"
#include <QUrl>
class QAbstractItemView;
class QModelIndex;
class FileBrowser : public BrowserCategory
{
Q_OBJECT
public:
FileBrowser( const char *name, QWidget *parent );
~FileBrowser();
virtual void setupAddItems();
/**
* Navigate to a specific directory
*/
void setDir( const QUrl &dir );
/**
* Return the path of the currently shown dir.
*/
QString currentDir() const;
+public Q_SLOTS:
+ void addItemActivated( const QString &callback );
+
protected Q_SLOTS:
void slotNavigateToDirectory( const QModelIndex &index );
- void addItemActivated( const QString &callback );
-
virtual void reActivate();
/**
* Shows/hides the columns as selected in the context menu of the header of the
* file view.
* @param toggled the visibility state of a column in the context menu.
*/
void toggleColumn( bool toggled );
/**
* Go backward in history
*/
void back();
/**
* Go forward in history
*/
void forward();
/**
* Navigates up one level in the path shown
*/
void up();
/**
* Navigates to home directory
*/
void home();
/*
* Refreshes current directory
*/
void refresh();
/**
* Handle results of tryiong to setup an item in "places" that needed mouting or other
* special setup.
* @param index the index that we tried to setup
* @param success did the setup succeed?
*/
void setupDone( const QModelIndex &index, bool success );
private Q_SLOTS:
void initView();
+ void updateHeaderState();
private:
class Private;
Private *const d;
-
- Q_PRIVATE_SLOT( d, void updateHeaderState() )
};
/**
* Helper class that calls setCurrentIndex on a model view as soon as the model
* adds a row and then it auto-deletes itself.
*/
class DelayedActivator : public QObject
{
Q_OBJECT
public:
explicit DelayedActivator( QAbstractItemView *view );
private Q_SLOTS:
void slotRowsInserted( const QModelIndex &parent, int start );
private:
QAbstractItemView *m_view;
};
#endif // FILEBROWSERMKII_H
diff --git a/src/browsers/filebrowser/FileBrowser_p.h b/src/browsers/filebrowser/FileBrowser_p.h
index 7be0bfa6c5..ae92868013 100644
--- a/src/browsers/filebrowser/FileBrowser_p.h
+++ b/src/browsers/filebrowser/FileBrowser_p.h
@@ -1,162 +1,162 @@
/****************************************************************************************
* Copyright (c) 2010 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_FILEBROWSER_P_H
#define AMAROK_FILEBROWSER_P_H
#include "FileBrowser.h"
#include "browsers/BrowserBreadcrumbItem.h"
#include <KDirModel>
#include <KFilePlacesModel>
#include <KGlobalSettings>
#include <QFontMetrics>
#include <QStack>
class QSortFilterProxyModel;
class DirBrowserModel;
class SearchWidget;
class FileView;
class QAction;
class KFilePlacesModel;
class DirPlaylistTrackFilterProxyModel;
template<typename T>
class UniqueStack : public QStack<T>
{
public:
inline void push( const T &t )
{
if( QStack<T>::isEmpty() || t != QStack<T>::top() )
QStack<T>::push( t );
}
};
class FileBrowser::Private
{
public:
Private( FileBrowser *parent );
~Private();
void readConfig();
void writeConfig();
void restoreHeaderState();
void saveHeaderState();
void updateNavigateActions();
BreadcrumbSiblingList siblingsForDir( const QUrl &path );
void updateHeaderState();
QList<QAction *> columnActions; //!< Maintains the mapping action<->column
KFilePlacesModel *bottomPlacesModel;
QSortFilterProxyModel *placesModel;
DirBrowserModel *kdirModel;
DirPlaylistTrackFilterProxyModel *mimeFilterProxyModel;
SearchWidget *searchWidget;
QUrl currentPath;
FileView *fileView;
QAction *upAction;
QAction *homeAction;
QAction *refreshAction;
QAction *backAction;
QAction *forwardAction;
UniqueStack<QUrl> backStack;
UniqueStack<QUrl> forwardStack;
private:
void restoreDefaultHeaderState();
FileBrowser *const q;
};
class DirBrowserModel : public KDirModel
{
Q_OBJECT
public:
DirBrowserModel( QObject *parent = 0 ) : KDirModel( parent )
{
updateRowHeight();
- connect( KGlobalSettings::self(), SIGNAL(appearanceChanged()), SLOT(updateRowHeight()) );
+ connect( KGlobalSettings::self(), &KGlobalSettings::appearanceChanged, this, &DirBrowserModel::updateRowHeight );
}
virtual ~DirBrowserModel() {}
virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const
{
if( role == Qt::SizeHintRole )
return QSize( 1, rowHeight );
else
return KDirModel::data( index, role );
}
private Q_SLOTS:
void updateRowHeight()
{
QFont font;
rowHeight = QFontMetrics( font ).height() + 4;
}
private:
int rowHeight;
};
class FilePlacesModel : public KFilePlacesModel
{
Q_OBJECT
public:
FilePlacesModel( QObject *parent = 0 ) : KFilePlacesModel( parent )
{
updateRowHeight();
- connect( KGlobalSettings::self(), SIGNAL(appearanceChanged()), SLOT(updateRowHeight()) );
+ connect( KGlobalSettings::self(), &KGlobalSettings::appearanceChanged, this, &FilePlacesModel::updateRowHeight );
}
virtual ~FilePlacesModel() {}
virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const
{
if( role == Qt::SizeHintRole )
return QSize( 1, rowHeight );
else
return KFilePlacesModel::data( index, role );
}
private Q_SLOTS:
void updateRowHeight()
{
QFont font;
rowHeight = QFontMetrics( font ).height() + 4;
}
private:
int rowHeight;
};
#endif /* AMAROK_FILEBROWSER_P_H */
diff --git a/src/browsers/filebrowser/FileView.cpp b/src/browsers/filebrowser/FileView.cpp
index 606ebfcc50..6f16e48a96 100644
--- a/src/browsers/filebrowser/FileView.cpp
+++ b/src/browsers/filebrowser/FileView.cpp
@@ -1,615 +1,614 @@
/****************************************************************************************
* Copyright (c) 2010 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "FileView"
#include "FileView.h"
#include "EngineController.h"
#include "PaletteHandler.h"
#include "PopupDropperFactory.h"
#include "SvgHandler.h"
#include "context/ContextView.h"
#include "context/popupdropper/libpud/PopupDropper.h"
#include "context/popupdropper/libpud/PopupDropperItem.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 <QAction>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <QDialog>
#include <KDirModel>
#include <KFileItem>
#include <KGlobalSettings>
#include <KMessageBox>
#include <QIcon>
#include <KLocale>
#include <QMenu>
#include <QUrl>
#include <QContextMenuEvent>
#include <QFileSystemModel>
#include <QItemDelegate>
#include <QPainter>
#include <KConfigGroup>
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(), SIGNAL(newPalette(QPalette)),
- SLOT(newPalette(QPalette)) );
+ 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<Collections::Collection*> writableCollections;
QHash<Collections::Collection*, CollectionManager::CollectionStatus> hash =
CollectionManager::instance()->collections();
QHash<Collections::Collection*, CollectionManager::CollectionStatus>::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" ) );
foreach( Collections::Collection *coll, writableCollections )
{
CollectionAction *copyAction = new CollectionAction( coll, &menu );
- connect( copyAction, SIGNAL(triggered()), this, SLOT(slotPrepareCopyTracks()) );
+ 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" ) );
foreach( Collections::Collection *coll, writableCollections )
{
CollectionAction *moveAction = new CollectionAction( coll, &menu );
- connect( moveAction, SIGNAL(triggered()), this, SLOT(slotPrepareMoveTracks()) );
+ 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<KFileItem>();
if( state() == QAbstractItemView::NoState &&
event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier &&
KGlobalSettings::singleClick() &&
( file.isDir() || file.isNull() ) )
{
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<KFileItem>();
QUrl url = file.url();
if( !file.isNull() && ( Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) )
addIndexToPlaylist( index, Playlist::OnDoubleClickOnSelectedItems );
else
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<KFileItem>();
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
emit navigateToDirectory( index );
return;
}
case Qt::Key_Delete:
slotMoveToTrash( Qt::NoButton, event->modifiers() );
break;
case Qt::Key_F5:
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<CollectionAction*>( 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, SIGNAL(finished(Meta::TrackList)), SLOT(slotMoveTracks(Meta::TrackList)) );
+ connect( dl, &TrackLoader::finished, this, &FileView::slotMoveTracks );
dl->init( list.urlList() );
}
void
FileView::slotPrepareCopyTracks()
{
if( m_copyDestinationCollection )
return;
CollectionAction *action = dynamic_cast<CollectionAction*>( 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, SIGNAL(finished(Meta::TrackList)), SLOT(slotCopyTracks(Meta::TrackList)) );
+ connect( dl, &TrackLoader::finished, this, &FileView::slotMoveTracks );
dl->init( list.urlList() );
}
void
FileView::slotCopyTracks( const Meta::TrackList& tracks )
{
if( !m_copyDestinationCollection )
return;
QSet<Collections::Collection *> 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.data()->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::Collection *> 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.data()->location();
source->prepareMove( tracks, destination );
}
else
warning() << "Cannot handle moving tracks from multiple collections, doing nothing to be safe";
m_moveDestinationCollection.clear();
}
QList<QAction *>
FileView::actionsForIndices( const QModelIndexList &indices, ActionType type )
{
QList<QAction *> 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, SIGNAL(triggered()), this, SLOT(slotAppendToPlaylist()) );
+ 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, SIGNAL(triggered()), this, SLOT(slotReplacePlaylist()) );
+ 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, SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)),
- this, SLOT(slotMoveToTrash(Qt::MouseButtons,Qt::KeyboardModifiers)) );
+ 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, SIGNAL(triggered(bool)), SLOT(slotDelete()) );
+ 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, SIGNAL(triggered()), this, SLOT(slotEditTracks()) );
+ 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<QUrl> urls;
foreach( const QModelIndex &index, indices )
{
KFileItem file = index.data( KDirModel::FileItemRole ).value<KFileItem>();
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();
/* FIXME: disabled temporarily for KF5 porting
if( !m_pd )
m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
*/
if( m_pd && m_pd->isHidden() )
{
QModelIndexList indices = selectedIndexes();
QList<QAction *> 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, SIGNAL(fadeHideFinished()), m_pd, SLOT(clear()) );
+ 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<KFileItem>();
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<KFileItem>();
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<QUrl> urls;
QStringList filepaths;
foreach( const QModelIndex& index, indices )
{
KFileItem file = index.data( KDirModel::FileItemRole ).value<KFileItem>();
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/filebrowser/FileView.h b/src/browsers/filebrowser/FileView.h
index 780bbcbe34..7b1db184be 100644
--- a/src/browsers/filebrowser/FileView.h
+++ b/src/browsers/filebrowser/FileView.h
@@ -1,121 +1,122 @@
/****************************************************************************************
* Copyright (c) 2010 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_FILEVIEW_H
#define AMAROK_FILEVIEW_H
#include "core/collections/Collection.h"
#include "core/meta/forward_declarations.h"
#include "playlist/PlaylistController.h"
#include "widgets/PrettyTreeView.h"
#include <KFileItem>
#include <QAction>
#include <QList>
#include <QTreeView>
#include <QMutex>
class PopupDropper;
/**
* Stores a collection associated with an action for move/copy to collection
*/
class CollectionAction : public QAction
{
public:
explicit CollectionAction( Collections::Collection *coll, QObject *parent = 0 )
: QAction( parent )
, m_collection( coll )
{
setText( m_collection->prettyName() );
}
Collections::Collection *collection() const
{
return m_collection;
}
private:
Collections::Collection *m_collection;
};
class FileView : public Amarok::PrettyTreeView
{
Q_OBJECT
public:
FileView( QWidget *parent );
Q_SIGNALS:
void navigateToDirectory( const QModelIndex &index );
void refreshBrowser();
protected Q_SLOTS:
void slotAppendToPlaylist();
void slotReplacePlaylist();
void slotEditTracks();
void slotPrepareMoveTracks();
void slotPrepareCopyTracks();
void slotMoveTracks( const Meta::TrackList &tracks );
void slotCopyTracks( const Meta::TrackList &tracks );
void slotMoveToTrash( Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers );
+ void slotMoveToTrashWithoutModifiers() { slotMoveToTrash( Qt::NoButton, Qt::NoModifier ); }
void slotDelete();
protected:
enum ActionType {
PlaylistAction = 1,
OrganizeAction = 2,
EditAction = 4,
AllActions = PlaylistAction | OrganizeAction | EditAction
};
QList<QAction *> actionsForIndices( const QModelIndexList &indices, ActionType type = AllActions );
void addSelectionToPlaylist( Playlist::AddOptions options );
/**
* Convenience.
*/
void addIndexToPlaylist( const QModelIndex &idx, Playlist::AddOptions options );
void addIndicesToPlaylist( QModelIndexList indices, Playlist::AddOptions options );
virtual void contextMenuEvent( QContextMenuEvent *e );
virtual void mouseReleaseEvent( QMouseEvent *event );
virtual void mouseDoubleClickEvent( QMouseEvent *event );
virtual void keyPressEvent( QKeyEvent *event );
virtual void startDrag( Qt::DropActions supportedActions );
KFileItemList selectedItems() const;
private:
Meta::TrackList tracksForEdit() const;
QAction *m_appendAction;
QAction *m_loadAction;
QAction *m_editAction;
QAction *m_moveToTrashAction;
QAction *m_deleteAction;
PopupDropper *m_pd;
QMutex m_dragMutex;
bool m_ongoingDrag;
QWeakPointer<Collections::Collection> m_moveDestinationCollection;
QWeakPointer<Collections::Collection> m_copyDestinationCollection;
};
#endif // end include guard
diff --git a/src/browsers/playlistbrowser/APGCategory.cpp b/src/browsers/playlistbrowser/APGCategory.cpp
index 5c129eb680..5f3022a204 100644
--- a/src/browsers/playlistbrowser/APGCategory.cpp
+++ b/src/browsers/playlistbrowser/APGCategory.cpp
@@ -1,152 +1,152 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "APGCategory.h"
#include "amarokconfig.h"
#include "playlistgenerator/ConstraintSolver.h"
#include "playlistgenerator/PresetModel.h"
#include "widgets/PrettyTreeView.h"
#include <QIcon>
#include <KLocale>
#include <KStandardDirs>
#include <QAction>
#include <QHBoxLayout>
#include <QLabel>
#include <QModelIndex>
#include <QToolBar>
PlaylistBrowserNS::APGCategory::APGCategory( QWidget* )
: BrowserCategory ( "APG", 0 )
{
m_qualityFactor = AmarokConfig::qualityFactorAPG();
setPrettyName( i18n( "Automated Playlist Generator" ) );
setShortDescription( i18n("Create playlists by specifying criteria") );
setIcon( QIcon::fromTheme( "playlist-generator" ) );
// set background
if( AmarokConfig::showBrowserBackgroundImage() )
setBackgroundImage( imagePath() );
setLongDescription( i18n("Create playlists by specifying criteria") );
setContentsMargins( 0, 0, 0, 0 );
APG::PresetModel* presetmodel = APG::PresetModel::instance();
- connect( presetmodel, SIGNAL(lock(bool)), this, SLOT(setDisabled(bool)) );
+ connect( presetmodel, &APG::PresetModel::lock, this, &APGCategory::setDisabled );
/* Create the toolbar -- Qt's Designer doesn't let us put a toolbar
* anywhere except in a MainWindow, so we've got to create it by hand here. */
QToolBar* toolBar_Actions = new QToolBar( this );
toolBar_Actions->setMovable( false );
toolBar_Actions->setFloatable( false );
toolBar_Actions->setIconSize( QSize( 22, 22 ) );
toolBar_Actions->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
QAction* a;
a = toolBar_Actions->addAction( QIcon::fromTheme( "list-add-amarok" ), i18n("Add new preset") );
- connect( a, SIGNAL(triggered(bool)), presetmodel, SLOT(addNew()) );
+ connect( a, &QAction::triggered, presetmodel, &APG::PresetModel::addNew );
a = toolBar_Actions->addAction( QIcon::fromTheme( "document-properties-amarok" ), i18n("Edit selected preset") );
a->setEnabled( false );
- connect( a, SIGNAL(triggered(bool)), presetmodel, SLOT(edit()) );
- connect( this, SIGNAL(validIndexSelected(bool)), a, SLOT(setEnabled(bool)) );
+ connect( a, &QAction::triggered, presetmodel, &APG::PresetModel::edit );
+ connect( this, &APGCategory::validIndexSelected, a, &QAction::setEnabled );
a = toolBar_Actions->addAction( QIcon::fromTheme( "list-remove-amarok" ), i18n("Delete selected preset") );
a->setEnabled( false );
- connect( a, SIGNAL(triggered(bool)), presetmodel, SLOT(removeActive()) );
- connect( this, SIGNAL(validIndexSelected(bool)), a, SLOT(setEnabled(bool)) );
+ connect( a, &QAction::triggered, presetmodel, &APG::PresetModel::removeActive );
+ connect( this, &APGCategory::validIndexSelected, a, &QAction::setEnabled );
a = toolBar_Actions->addAction( QIcon::fromTheme( "document-import-amarok" ), i18n("Import a new preset") );
a->setEnabled( true );
- connect( a, SIGNAL(triggered(bool)), presetmodel, SLOT(import()) );
+ connect( a, &QAction::triggered, presetmodel, &APG::PresetModel::import );
a = toolBar_Actions->addAction( QIcon::fromTheme( "document-export-amarok" ), i18n("Export the selected preset") );
a->setEnabled( false );
- connect( a, SIGNAL(triggered(bool)), presetmodel, SLOT(exportActive()) );
- connect( this, SIGNAL(validIndexSelected(bool)), a, SLOT(setEnabled(bool)) );
+ connect( a, &QAction::triggered, presetmodel, &APG::PresetModel::exportActive );
+ connect( this, &APGCategory::validIndexSelected, a, &QAction::setEnabled );
toolBar_Actions->addSeparator();
a = toolBar_Actions->addAction( QIcon::fromTheme( "go-next-amarok" ), i18n("Run APG with selected preset") );
a->setEnabled( false );
- connect( a, SIGNAL(triggered(bool)), this, SLOT(runGenerator()) );
- connect( this, SIGNAL(validIndexSelected(bool)), a, SLOT(setEnabled(bool)) );
+ connect( a, &QAction::triggered, this, &APGCategory::runGenerator );
+ connect( this, &APGCategory::validIndexSelected, a, &QAction::setEnabled );
/* Create the preset list view */
QLabel* label_Title = new QLabel( i18n("APG Presets"), this );
label_Title->setAlignment( Qt::AlignCenter );
Amarok::PrettyTreeView* listView = new Amarok::PrettyTreeView( this );
listView->setHeaderHidden( true );
listView->setRootIsDecorated( false );
listView->setModel( presetmodel );
listView->setSelectionMode( QAbstractItemView::SingleSelection );
listView->setFrameShape( QFrame::NoFrame );
listView->setAutoFillBackground( false );
- connect( listView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(activeChanged(QModelIndex)) );
- connect( listView, SIGNAL(doubleClicked(QModelIndex)), presetmodel, SLOT(editPreset(QModelIndex)) );
+ connect( listView->selectionModel(), &QItemSelectionModel::currentChanged, this, &APGCategory::activeChanged );
+ connect( listView, &Amarok::PrettyTreeView::doubleClicked, presetmodel, &APG::PresetModel::editPreset );
// Speed/Quality tradeoff slider
QLabel* label_Tradeoff = new QLabel( i18n("Generator Optimization"), this );
label_Tradeoff->setAlignment( Qt::AlignCenter );
QFrame* qual_Frame = new QFrame( this );
QLabel* label_Speed = new QLabel( i18n("Speed"), qual_Frame );
QSlider* qual_Slider = new QSlider( Qt::Horizontal, qual_Frame );
qual_Slider->setRange( 0, APG::ConstraintSolver::QUALITY_RANGE );
qual_Slider->setValue( m_qualityFactor );
- connect( qual_Slider, SIGNAL(sliderMoved(int)), this, SLOT (setQualityFactor(int)) );
+ connect( qual_Slider, &QSlider::sliderMoved, this, &APGCategory::setQualityFactor );
QLabel* label_Quality = new QLabel( i18n("Accuracy"), qual_Frame );
QLayout* qf_Layout = new QHBoxLayout( qual_Frame );
qf_Layout->addWidget( label_Speed );
qf_Layout->addWidget( qual_Slider );
qf_Layout->addWidget( label_Quality );
qual_Frame->setLayout( qf_Layout );
QMetaObject::connectSlotsByName( this );
}
PlaylistBrowserNS::APGCategory::~APGCategory()
{
APG::PresetModel::destroy();
AmarokConfig::setQualityFactorAPG( m_qualityFactor );
AmarokConfig::self()->writeConfig();
}
void
PlaylistBrowserNS::APGCategory::activeChanged( const QModelIndex& index )
{
APG::PresetModel::instance()->setActivePreset( index );
emit validIndexSelected( index.isValid() );
}
void
PlaylistBrowserNS::APGCategory::setQualityFactor( int f )
{
m_qualityFactor = f;
}
void
PlaylistBrowserNS::APGCategory::runGenerator()
{
- APG::PresetModel::instance()->savePresetsToXml();
+ APG::PresetModel::instance()->savePresetsToXmlDefault();
APG::PresetModel::instance()->runGenerator( m_qualityFactor );
}
diff --git a/src/browsers/playlistbrowser/DynamicBiasDialog.cpp b/src/browsers/playlistbrowser/DynamicBiasDialog.cpp
index 43d0d87ad6..b428562f55 100644
--- a/src/browsers/playlistbrowser/DynamicBiasDialog.cpp
+++ b/src/browsers/playlistbrowser/DynamicBiasDialog.cpp
@@ -1,203 +1,203 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DynamicBiasDialog.h"
#include "core/support/Debug.h"
#include "dynamic/Bias.h"
#include "dynamic/BiasFactory.h"
#include <KComboBox>
#include <KLocale>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
PlaylistBrowserNS::BiasDialog::BiasDialog( Dynamic::BiasPtr bias, QWidget* parent )
: QDialog( parent )
, m_mainLayout( 0 )
, m_biasLayout( 0 )
, m_descriptionLabel( 0 )
, m_biasWidget( 0 )
, m_origBias( bias )
, m_bias( bias->clone() ) // m_bias is a clone
{
setWindowTitle( i18nc( "Bias dialog window title", "Edit bias" ) );
m_mainLayout = new QVBoxLayout( this );
// -- the bias selection combo
QLabel* selectionLabel = new QLabel( i18nc("Bias selection label in bias view.", "Match Type:" ) );
m_biasSelection = new KComboBox();
QHBoxLayout *selectionLayout = new QHBoxLayout();
selectionLabel->setBuddy( m_biasSelection );
selectionLayout->addWidget( selectionLabel );
selectionLayout->addWidget( m_biasSelection );
selectionLayout->addStretch( 1 );
m_mainLayout->addLayout( selectionLayout );
// -- bias itself
m_descriptionLabel = new QLabel( "" );
m_descriptionLabel->setWordWrap( true );
m_mainLayout->addWidget( m_descriptionLabel );
m_biasLayout = new QVBoxLayout();
m_mainLayout->addLayout( m_biasLayout );
// -- button box
QDialogButtonBox* buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
m_mainLayout->addWidget( buttonBox );
factoriesChanged();
biasReplaced( Dynamic::BiasPtr(), m_bias );
- connect( Dynamic::BiasFactory::instance(), SIGNAL(changed()),
- this, SLOT(factoriesChanged()) );
- connect( m_biasSelection, SIGNAL(activated(int)),
- this, SLOT(selectionChanged(int)) );
- connect(buttonBox, SIGNAL(accepted()),
- this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()),
- this, SLOT(reject()));
+ connect( Dynamic::BiasFactory::instance(), &Dynamic::BiasFactory::changed,
+ this, &PlaylistBrowserNS::BiasDialog::factoriesChanged );
+ connect( m_biasSelection, QOverload<int>::of(&KComboBox::activated),
+ this, &PlaylistBrowserNS::BiasDialog::selectionChanged );
+ connect(buttonBox, &QDialogButtonBox::accepted,
+ this, &PlaylistBrowserNS::BiasDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected,
+ this, &PlaylistBrowserNS::BiasDialog::reject);
}
void PlaylistBrowserNS::BiasDialog::accept()
{
// use the newly edited bias
m_origBias->replace( m_bias ); // tell the old bias it has just been replaced
QDialog::accept();
}
void PlaylistBrowserNS::BiasDialog::reject()
{
// do nothing.
QDialog::reject();
}
PlaylistBrowserNS::BiasDialog::~BiasDialog()
{ }
void
PlaylistBrowserNS::BiasDialog::factoriesChanged()
{
m_biasSelection->clear();
- disconnect( Dynamic::BiasFactory::instance(), SIGNAL(changed()),
- this, SLOT(factoriesChanged()) );
+ disconnect( Dynamic::BiasFactory::instance(), &Dynamic::BiasFactory::changed,
+ this, &PlaylistBrowserNS::BiasDialog::factoriesChanged );
// -- add all the bias types to the list
bool factoryFound = false;
QList<Dynamic::AbstractBiasFactory*> factories = Dynamic::BiasFactory::factories();
for( int i = 0; i < factories.count(); i++ )
{
Dynamic::AbstractBiasFactory* factory = factories.at( i );
m_biasSelection->addItem( factory->i18nName(), QVariant( factory->name() ) );
// -- set the current index if we have found our own factory
if( m_bias && factory->name() == m_bias->name() )
{
factoryFound = true;
m_biasSelection->setCurrentIndex( i );
m_descriptionLabel->setText( factory->i18nDescription() );
}
}
// -- In cases of replacement bias
if( !factoryFound )
{
m_biasSelection->addItem( m_bias->name() );
m_biasSelection->setCurrentIndex( m_biasSelection->count() - 1 );
m_descriptionLabel->setText( i18n( "This bias is a replacement for another bias\n"
"which is currently not loaded or deactivated.\n"
"The original bias name was %1.", m_bias->name() ) );
}
- connect( Dynamic::BiasFactory::instance(), SIGNAL(changed()),
- this, SLOT(factoriesChanged()) );
+ connect( Dynamic::BiasFactory::instance(), &Dynamic::BiasFactory::changed,
+ this, &PlaylistBrowserNS::BiasDialog::factoriesChanged );
}
void
PlaylistBrowserNS::BiasDialog::selectionChanged( int index )
{
DEBUG_BLOCK;
Q_ASSERT( m_biasSelection );
QString biasName = m_biasSelection->itemData( index ).toString();
Dynamic::BiasPtr oldBias( m_bias );
Dynamic::BiasPtr newBias( Dynamic::BiasFactory::fromName( biasName ) );
if( !newBias )
{
warning() << "Could not create bias with name:"<<biasName;
return;
}
debug() << "replace bias" << oldBias->toString() << "with" << newBias->toString();
m_bias->replace( newBias ); // tell the old bias it has just been replaced
debug() << "replaced";
// -- if the new bias is AndBias, try to add the old biase(s) into it
Dynamic::AndBias *oldABias = qobject_cast<Dynamic::AndBias*>(oldBias.data());
Dynamic::AndBias *newABias = qobject_cast<Dynamic::AndBias*>(newBias.data());
if( newABias ) {
if( oldABias ) {
for( int i = 0; i < oldABias->biases().count(); i++ )
{
newABias->appendBias( oldABias->biases()[i] );
}
}
else
{
newABias->appendBias( oldBias );
}
}
}
void
PlaylistBrowserNS::BiasDialog::biasReplaced( Dynamic::BiasPtr oldBias, Dynamic::BiasPtr newBias )
{
Q_UNUSED( oldBias );
if( m_biasWidget )
{
m_biasLayout->removeWidget( m_biasWidget );
m_biasWidget->deleteLater();
m_biasWidget = 0;
}
m_bias = newBias;
if( !newBias )
return;
- connect( newBias.data(), SIGNAL(replaced(Dynamic::BiasPtr,Dynamic::BiasPtr)),
- this, SLOT(biasReplaced(Dynamic::BiasPtr,Dynamic::BiasPtr)) );
+ connect( newBias.data(), &Dynamic::AbstractBias::replaced,
+ this, &PlaylistBrowserNS::BiasDialog::biasReplaced );
m_biasWidget = newBias->widget( 0 );
if( !m_biasWidget )
m_biasWidget = new QLabel( i18n("This bias has no settings.") );
m_biasLayout->addWidget( m_biasWidget );
factoriesChanged(); // update the bias description and select the new combo entry
}
diff --git a/src/browsers/playlistbrowser/DynamicCategory.cpp b/src/browsers/playlistbrowser/DynamicCategory.cpp
index cd83202037..a721651e80 100644
--- a/src/browsers/playlistbrowser/DynamicCategory.cpp
+++ b/src/browsers/playlistbrowser/DynamicCategory.cpp
@@ -1,253 +1,255 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com> *
* Copyright (c) 2009-2010 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2010-2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DynamicCategory.h"
#include "DynamicView.h"
#include "amarokconfig.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "dynamic/DynamicModel.h"
#include "dynamic/BiasedPlaylist.h"
#include <QInputDialog>
#include <QMessageBox>
#include <QLabel>
#include <QCheckBox>
#include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout>
#include <QSpinBox>
#include <KHBox>
#include <QIcon>
#include <KStandardDirs>
#include <KSeparator>
#include <KToolBar>
PlaylistBrowserNS::DynamicCategory::DynamicCategory( QWidget* parent )
: BrowserCategory( "dynamic category", parent )
{
setPrettyName( i18n( "Dynamic Playlists" ) );
setShortDescription( i18n( "Dynamically updating parameter based playlists" ) );
setIcon( QIcon::fromTheme( "dynamic-amarok" ) );
setLongDescription( i18n( "With a dynamic playlist, Amarok becomes your own personal dj, automatically selecting tracks for you, based on a number of parameters that you select." ) );
setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_dynamic_playlists.png" ) );
// set background
if( AmarokConfig::showBrowserBackgroundImage() )
setBackgroundImage( imagePath() );
bool enabled = AmarokConfig::dynamicMode();
setContentsMargins( 0, 0, 0, 0 );
KHBox* controls2Layout = new KHBox( this );
QLabel *label;
label = new QLabel( i18n( "Previous:" ), controls2Layout );
label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
m_previous = new QSpinBox( controls2Layout );
m_previous->setMinimum( 0 );
m_previous->setToolTip( i18n( "Number of previous tracks to remain in the playlist." ) );
m_previous->setValue( AmarokConfig::previousTracks() );
- QObject::connect( m_previous, SIGNAL(valueChanged(int)), this, SLOT(setPreviousTracks(int)) );
+ connect( m_previous, QOverload<int>::of(&QSpinBox::valueChanged),
+ this, &PlaylistBrowserNS::DynamicCategory::setPreviousTracks );
label = new QLabel( i18n( "Upcoming:" ), controls2Layout );
// label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
m_upcoming = new QSpinBox( controls2Layout );
m_upcoming->setMinimum( 1 );
m_upcoming->setToolTip( i18n( "Number of upcoming tracks to add to the playlist." ) );
m_upcoming->setValue( AmarokConfig::upcomingTracks() );
- QObject::connect( m_upcoming, SIGNAL(valueChanged(int)), this, SLOT(setUpcomingTracks(int)) );
+ connect( m_upcoming, QOverload<int>::of(&QSpinBox::valueChanged),
+ this, &PlaylistBrowserNS::DynamicCategory::setUpcomingTracks );
- QObject::connect( (const QObject*)Amarok::actionCollection()->action( "playlist_clear" ), SIGNAL(triggered(bool)), this, SLOT(playlistCleared()) );
- QObject::connect( (const QObject*)Amarok::actionCollection()->action( "disable_dynamic" ), SIGNAL(triggered(bool)), this, SLOT(playlistCleared()), Qt::DirectConnection );
+ connect( Amarok::actionCollection()->action( "playlist_clear" ), &QAction::triggered, this, &DynamicCategory::playlistCleared );
+ connect( Amarok::actionCollection()->action( "disable_dynamic" ), &QAction::triggered, this, &DynamicCategory::playlistCleared, Qt::DirectConnection );
// -- the tool bar
KHBox* presetLayout = new KHBox( this );
KToolBar* presetToolbar = new KToolBar( presetLayout );
presetToolbar->setIconSize( QSize( 22, 22 ) );
presetToolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
presetToolbar->setMovable( false );
presetToolbar->setFloatable( false );
presetToolbar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
m_onOffButton = new QToolButton( presetToolbar );
m_onOffButton->setText( i18nc( "Turn dynamic mode on", "On") );
m_onOffButton->setCheckable( true );
m_onOffButton->setIcon( QIcon::fromTheme( "dynamic-amarok" ) );
m_onOffButton->setToolTip( i18n( "Turn dynamic mode on." ) );
presetToolbar->addWidget( m_onOffButton );
m_duplicateButton = new QToolButton( presetToolbar );
m_duplicateButton->setText( i18n("Duplicates") );
m_duplicateButton->setCheckable( true );
m_duplicateButton->setChecked( allowDuplicates() );
m_duplicateButton->setIcon( QIcon::fromTheme( "edit-copy" ) );
m_duplicateButton->setToolTip( i18n( "Allow duplicate songs in result" ) );
presetToolbar->addWidget( m_duplicateButton );
m_addButton = new QToolButton( presetToolbar );
m_addButton->setText( i18n("New") );
m_addButton->setIcon( QIcon::fromTheme( "document-new" ) );
m_addButton->setToolTip( i18n( "New playlist" ) );
presetToolbar->addWidget( m_addButton );
m_editButton = new QToolButton( presetToolbar );
m_editButton->setText( i18n("Edit") );
m_editButton->setIcon( QIcon::fromTheme( "document-properties-amarok" ) );
m_editButton->setToolTip( i18n( "Edit the selected playlist or bias" ) );
presetToolbar->addWidget( m_editButton );
m_deleteButton = new QToolButton( presetToolbar );
m_deleteButton->setText( i18n("Delete") );
m_deleteButton->setEnabled( false );
m_deleteButton->setIcon( QIcon::fromTheme( "edit-delete" ) );
m_deleteButton->setToolTip( i18n( "Delete the selected playlist or bias") );
presetToolbar->addWidget( m_deleteButton );
m_repopulateButton = new QPushButton( presetLayout );
m_repopulateButton->setText( i18n("Repopulate") );
m_repopulateButton->setToolTip( i18n("Replace the upcoming tracks with fresh ones.") );
m_repopulateButton->setIcon( QIcon::fromTheme( "view-refresh-amarok" ) );
m_repopulateButton->setEnabled( enabled );
// m_repopulateButton->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ) );
- QObject::connect( m_repopulateButton, SIGNAL(clicked(bool)), The::playlistActions(), SLOT(repopulateDynamicPlaylist()) );
+ QObject::connect( m_repopulateButton, &QAbstractButton::clicked, The::playlistActions(), &Playlist::Actions::repopulateDynamicPlaylist );
// -- the tree view
m_tree = new DynamicView( this );
- connect( m_tree->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
- this, SLOT(selectionChanged()) );
+ connect( m_tree->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &DynamicCategory::selectionChanged );
- connect( m_onOffButton, SIGNAL(toggled(bool)), The::playlistActions(), SLOT(enableDynamicMode(bool)) );
- connect( m_duplicateButton, SIGNAL(toggled(bool)), this, SLOT(setAllowDuplicates(bool)) );
+ connect( m_onOffButton, &QAbstractButton::toggled, The::playlistActions(), &Playlist::Actions::enableDynamicMode );
+ connect( m_duplicateButton, &QAbstractButton::toggled, this, &DynamicCategory::setAllowDuplicates );
- connect( m_addButton, SIGNAL(clicked(bool)), m_tree, SLOT(addPlaylist()) );
- connect( m_editButton, SIGNAL(clicked(bool)), m_tree, SLOT(editSelected()) );
- connect( m_deleteButton, SIGNAL(clicked(bool)), m_tree, SLOT(removeSelected()) );
+ connect( m_addButton, &QAbstractButton::clicked, m_tree, &DynamicView::addPlaylist );
+ connect( m_editButton, &QAbstractButton::clicked, m_tree, &DynamicView::editSelected );
+ connect( m_deleteButton, &QAbstractButton::clicked, m_tree, &DynamicView::removeSelected );
navigatorChanged();
selectionChanged();
- connect( The::playlistActions(), SIGNAL(navigatorChanged()),
- this, SLOT(navigatorChanged()) );
+ connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
+ this, &DynamicCategory::navigatorChanged );
}
PlaylistBrowserNS::DynamicCategory::~DynamicCategory()
{ }
void
PlaylistBrowserNS::DynamicCategory::navigatorChanged()
{
m_onOffButton->setChecked( AmarokConfig::dynamicMode() );
m_repopulateButton->setEnabled( AmarokConfig::dynamicMode() );
}
void
PlaylistBrowserNS::DynamicCategory::selectionChanged()
{
DEBUG_BLOCK;
QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes();
if( indexes.isEmpty() )
{
m_addButton->setEnabled( true );
m_editButton->setEnabled( false );
m_deleteButton->setEnabled( false );
return;
}
QVariant v = m_tree->model()->data( indexes.first(), Dynamic::DynamicModel::PlaylistRole );
if( v.isValid() )
{
m_addButton->setEnabled( true );
m_editButton->setEnabled( true );
m_deleteButton->setEnabled( true );
return;
}
v = m_tree->model()->data( indexes.first(), Dynamic::DynamicModel::BiasRole );
if( v.isValid() )
{
m_addButton->setEnabled( true );
m_editButton->setEnabled( true );
m_deleteButton->setEnabled( false ); // TODO
return;
}
}
bool
PlaylistBrowserNS::DynamicCategory::allowDuplicates() const
{
return AmarokConfig::dynamicDuplicates();
}
void
PlaylistBrowserNS::DynamicCategory::playlistCleared() // SLOT
{
The::playlistActions()->enableDynamicMode( false );
}
void
PlaylistBrowserNS::DynamicCategory::setUpcomingTracks( int n ) // SLOT
{
if( n >= 1 )
AmarokConfig::setUpcomingTracks( n );
}
void
PlaylistBrowserNS::DynamicCategory::setPreviousTracks( int n ) // SLOT
{
if( n >= 0 )
AmarokConfig::setPreviousTracks( n );
}
void
PlaylistBrowserNS::DynamicCategory::setAllowDuplicates( bool value ) // SLOT
{
if( AmarokConfig::dynamicDuplicates() == value )
return;
AmarokConfig::setDynamicDuplicates( value );
AmarokConfig::self()->writeConfig();
m_duplicateButton->setChecked( value );
}
diff --git a/src/browsers/playlistbrowser/DynamicView.cpp b/src/browsers/playlistbrowser/DynamicView.cpp
index 458a3f9247..8f9ca48a48 100644
--- a/src/browsers/playlistbrowser/DynamicView.cpp
+++ b/src/browsers/playlistbrowser/DynamicView.cpp
@@ -1,292 +1,292 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> *
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "DynamicView"
#include "DynamicView.h"
#include "DynamicBiasDelegate.h"
#include "DynamicBiasDialog.h"
#include "core/support/Debug.h"
#include "dynamic/Bias.h"
#include "dynamic/DynamicModel.h"
#include "dynamic/DynamicPlaylist.h"
#include "dynamic/biases/SearchQueryBias.h"
#include "playlist/PlaylistActions.h"
#include "PopupDropperFactory.h"
#include "context/popupdropper/libpud/PopupDropperItem.h"
#include "context/popupdropper/libpud/PopupDropper.h"
#include "PaletteHandler.h"
#include <klocale.h>
#include <QAction>
#include <KGlobalSettings>
#include <QMenu>
#include <QAction>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QModelIndex>
#include <QToolTip>
PlaylistBrowserNS::DynamicView::DynamicView( QWidget *parent )
: Amarok::PrettyTreeView( parent )
{
DEBUG_BLOCK
setHeaderHidden( true );
setSelectionMode( QAbstractItemView::SingleSelection );
setModel( Dynamic::DynamicModel::instance() );
setItemDelegate( new PlaylistBrowserNS::DynamicBiasDelegate(this) );
setSelectionBehavior( QAbstractItemView::SelectItems );
setDragDropMode( QAbstractItemView::DragDrop /*| QAbstractItemView::InternalMove*/ );
setDragEnabled( true );
setAcceptDrops( true );
setDropIndicatorShown( true );
setEditTriggers( QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed );
// -- expanding the playlist should expand the whole tree
- connect( this, SIGNAL(expanded(QModelIndex)),
- this, SLOT(expandRecursive(QModelIndex)) );
- connect( this, SIGNAL(collapsed(QModelIndex)),
- this, SLOT(collapseRecursive(QModelIndex)) );
+ connect( this, &DynamicView::expanded,
+ this, &DynamicView::expandRecursive );
+ connect( this, &DynamicView::collapsed,
+ this, &DynamicView::collapseRecursive );
}
PlaylistBrowserNS::DynamicView::~DynamicView()
{
}
void
PlaylistBrowserNS::DynamicView::addPlaylist()
{
DEBUG_BLOCK;
QModelIndex newIndex = Dynamic::DynamicModel::instance()->newPlaylist();
selectionModel()->select( newIndex, QItemSelectionModel::ClearAndSelect );
}
void
PlaylistBrowserNS::DynamicView::addToSelected()
{
DEBUG_BLOCK;
QModelIndexList indexes = selectionModel()->selectedIndexes();
if( indexes.isEmpty() )
return;
QModelIndex newIndex = Dynamic::DynamicModel::instance()->insertBias( 0, indexes.first(), Dynamic::BiasPtr( new Dynamic::SearchQueryBias() ) );
selectionModel()->select( newIndex, QItemSelectionModel::ClearAndSelect );
}
void
PlaylistBrowserNS::DynamicView::cloneSelected()
{
DEBUG_BLOCK;
QModelIndexList indexes = selectionModel()->selectedIndexes();
if( indexes.isEmpty() )
return;
QModelIndex newIndex = Dynamic::DynamicModel::instance()->cloneAt( indexes.first() );
selectionModel()->select( newIndex, QItemSelectionModel::ClearAndSelect );
}
void
PlaylistBrowserNS::DynamicView::editSelected()
{
DEBUG_BLOCK;
QModelIndexList indexes = selectionModel()->selectedIndexes();
if( indexes.isEmpty() )
return;
QVariant v = model()->data( indexes.first(), Dynamic::DynamicModel::PlaylistRole );
if( v.isValid() )
{
edit( indexes.first() ); // call the normal editor
return;
}
v = model()->data( indexes.first(), Dynamic::DynamicModel::BiasRole );
if( v.isValid() )
{
Dynamic::AbstractBias* bias = qobject_cast<Dynamic::AbstractBias*>(v.value<QObject*>() );
PlaylistBrowserNS::BiasDialog dialog( Dynamic::BiasPtr(bias), this);
dialog.exec();
return;
}
}
void
PlaylistBrowserNS::DynamicView::removeSelected()
{
DEBUG_BLOCK;
QModelIndexList indexes = selectionModel()->selectedIndexes();
if( indexes.isEmpty() )
return;
Dynamic::DynamicModel::instance()->removeAt( indexes.first() );
}
void
PlaylistBrowserNS::DynamicView::expandRecursive(const QModelIndex &index)
{
for( int i = 0; i < index.model()->rowCount( index ); i++ )
expand( index.model()->index( i, 0, index ) );
}
void
PlaylistBrowserNS::DynamicView::collapseRecursive(const QModelIndex &index)
{
for( int i = 0; i < index.model()->rowCount( index ); i++ )
collapse( index.model()->index( i, 0, index ) );
}
void
PlaylistBrowserNS::DynamicView::keyPressEvent( QKeyEvent *event )
{
switch( event->key() )
{
case Qt::Key_Delete:
removeSelected();
return;
case Qt::Key_Enter:
case Qt::Key_Return:
editSelected();
return;
}
Amarok::PrettyTreeView::keyPressEvent( event );
}
void
PlaylistBrowserNS::DynamicView::mouseDoubleClickEvent( QMouseEvent *event )
{
QModelIndex index = indexAt( event->pos() );
if( index.isValid() )
{
// if the click was on a playlist
QVariant v = model()->data( index, Dynamic::DynamicModel::PlaylistRole );
if( v.isValid() )
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
model->setActivePlaylist( model->playlistIndex( qobject_cast<Dynamic::DynamicPlaylist*>(v.value<QObject*>() ) ) );
The::playlistActions()->enableDynamicMode( true );
event->accept();
return;
}
// if the click was on a bias
v = model()->data( index, Dynamic::DynamicModel::BiasRole );
if( v.isValid() )
{
editSelected();
event->accept();
return;
}
}
Amarok::PrettyTreeView::mouseDoubleClickEvent( event );
}
void
PlaylistBrowserNS::DynamicView::contextMenuEvent( QContextMenuEvent *event )
{
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() )
return;
QList<QAction*> actions;
// if the click was on a playlist
QVariant v = model()->data( index, Dynamic::DynamicModel::PlaylistRole );
if( v.isValid() )
{
Dynamic::DynamicPlaylist* playlist = qobject_cast<Dynamic::DynamicPlaylist*>(v.value<QObject*>() );
Q_UNUSED( playlist );
QAction* action;
action = new QAction( QIcon::fromTheme( "document-properties-amarok" ), i18n( "&Rename playlist" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(editSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::editSelected );
actions.append( action );
action = new QAction( QIcon::fromTheme( "document-new" ), i18n( "&Add new Bias" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(addToSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::addToSelected );
actions.append( action );
action = new QAction( QIcon::fromTheme( "edit-copy" ), i18n( "&Clone Playlist" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(cloneSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::cloneSelected );
actions.append( action );
action = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete playlist" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(removeSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::removeSelected );
actions.append( action );
}
// if the click was on a bias
v = model()->data( index, Dynamic::DynamicModel::BiasRole );
if( v.isValid() )
{
Dynamic::AbstractBias* bias = qobject_cast<Dynamic::AbstractBias*>(v.value<QObject*>() );
Q_UNUSED( bias );
Dynamic::AndBias* aBias = qobject_cast<Dynamic::AndBias*>(v.value<QObject*>() );
QAction* action;
action = new QAction( QIcon::fromTheme( "document-properties-amarok" ), i18n( "&Edit bias..." ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(editSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::editSelected );
actions.append( action );
action = new QAction( QIcon::fromTheme( "edit-copy" ), i18n( "&Clone bias" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(cloneSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::cloneSelected );
actions.append( action );
// don't allow deleting a top bias unless it'a an and-bias containing at least one
// other bias
QModelIndex parentIndex = index.parent();
QVariant parentV = model()->data( parentIndex, Dynamic::DynamicModel::PlaylistRole );
if( !parentV.isValid() || (aBias && aBias->biases().count() > 0) )
{
action = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete bias" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(removeSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::removeSelected );
actions.append( action );
}
if( aBias )
{
action = new QAction( QIcon::fromTheme( "document-new" ), i18n( "&Add new bias" ), this );
- connect( action, SIGNAL(triggered(bool)), this, SLOT(addToSelected()) );
+ connect( action, &QAction::triggered, this, &DynamicView::addToSelected );
actions.append( action );
}
}
if( actions.isEmpty() )
return;
QMenu menu;
foreach( QAction *action, actions )
{
if( action )
menu.addAction( action );
}
menu.exec( mapToGlobal( event->pos() ) );
}
diff --git a/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp b/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp
index afccbf1701..1db8c7f194 100644
--- a/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp
+++ b/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp
@@ -1,315 +1,315 @@
/****************************************************************************************
* Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PlaylistBrowserCategory"
#include "PlaylistBrowserCategory.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include "PaletteHandler.h"
#include "PlaylistBrowserModel.h"
#include "playlist/PlaylistModel.h"
#include "playlistmanager/PlaylistManager.h"
#include "PlaylistsInFoldersProxy.h"
#include "PlaylistsByProviderProxy.h"
#include "PlaylistBrowserFilterProxy.h"
#include "SvgHandler.h"
#include "PlaylistBrowserView.h"
#include "widgets/PrettyTreeDelegate.h"
#include <KActionMenu>
#include <KConfigGroup>
#include <QIcon>
#include <KStandardDirs>
#include <KToolBar>
#include <QHeaderView>
#include <QToolBar>
#include <typeinfo>
using namespace PlaylistBrowserNS;
QString PlaylistBrowserCategory::s_mergeViewKey( "Merged View" );
PlaylistBrowserCategory::PlaylistBrowserCategory( int playlistCategory,
const QString &categoryName,
const QString &configGroup,
PlaylistBrowserModel *model,
QWidget *parent ) :
BrowserCategory( categoryName, parent ),
m_configGroup( configGroup ),
m_playlistCategory( playlistCategory )
{
setContentsMargins( 0, 0, 0, 0 );
setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_podcasts.png" ) );
// set background
if( AmarokConfig::showBrowserBackgroundImage() )
setBackgroundImage( imagePath() );
m_toolBar = new KToolBar( this, false, false );
m_toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
//a QWidget with minimumExpanding makes the next button right aligned.
QWidget *spacerWidget = new QWidget( this );
spacerWidget->setSizePolicy( QSizePolicy::MinimumExpanding,
QSizePolicy::MinimumExpanding );
// add a separator so subclasses can add their actions before it
m_separator = m_toolBar->addWidget( spacerWidget );
m_toolBar->addSeparator();
m_addFolderAction = new QAction( QIcon::fromTheme( "folder-new" ), i18n( "Add Folder" ), this );
m_addFolderAction->setPriority( QAction::LowPriority );
m_toolBar->addAction( m_addFolderAction );
- connect( m_addFolderAction, SIGNAL(triggered(bool)), SLOT(createNewFolder()) );
+ connect( m_addFolderAction, &QAction::triggered, this, &PlaylistBrowserCategory::createNewFolder );
m_providerMenu = new KActionMenu( QIcon::fromTheme( "checkbox" ), i18n( "Visible Sources"), this );
m_providerMenu->setDelayed( false );
m_providerMenu->setPriority( QAction::HighPriority );
m_toolBar->addAction( m_providerMenu );
QAction *toggleAction = new QAction( QIcon::fromTheme( "view-list-tree" ), i18n( "Merged View" ),
m_toolBar );
toggleAction->setCheckable( true );
toggleAction->setChecked( Amarok::config( m_configGroup ).readEntry( s_mergeViewKey, false ) );
toggleAction->setPriority( QAction::LowPriority );
m_toolBar->addAction( toggleAction );
- connect( toggleAction, SIGNAL(triggered(bool)), SLOT(toggleView(bool)) );
+ connect( toggleAction, &QAction::triggered, this, &PlaylistBrowserCategory::toggleView );
m_toolBar->addSeparator();
m_byProviderProxy = new PlaylistsByProviderProxy( m_playlistCategory, this );
m_byProviderProxy->setSourceModel( model );
m_byProviderProxy->setGroupedColumn( PlaylistBrowserModel::ProviderColumn );
m_byFolderProxy = new PlaylistsInFoldersProxy( model );
m_filterProxy = new PlaylistBrowserFilterProxy( this );
//no need to setModel on filterProxy since it will be done in toggleView anyway.
m_filterProxy->setDynamicSortFilter( true );
m_filterProxy->setFilterKeyColumn( PlaylistBrowserModel::ProviderColumn );
m_playlistView = new PlaylistBrowserView( m_filterProxy, this );
m_defaultItemDelegate = m_playlistView->itemDelegate();
m_byProviderDelegate = new PrettyTreeDelegate( m_playlistView );
toggleView( toggleAction->isChecked() );
m_playlistView->setFrameShape( QFrame::NoFrame );
m_playlistView->setContentsMargins( 0, 0, 0, 0 );
m_playlistView->setAlternatingRowColors( true );
m_playlistView->header()->hide();
//hide all columns except the first.
for( int i = 1; i < m_playlistView->model()->columnCount(); i++ )
m_playlistView->hideColumn( i );
m_playlistView->setDragEnabled( true );
m_playlistView->setAcceptDrops( true );
m_playlistView->setDropIndicatorShown( true );
foreach( const Playlists::PlaylistProvider *provider,
The::playlistManager()->providersForCategory( m_playlistCategory ) )
{
createProviderButton( provider );
}
- connect( The::playlistManager(), SIGNAL(providerAdded(Playlists::PlaylistProvider*,int)),
- SLOT(slotProviderAdded(Playlists::PlaylistProvider*,int)) );
- connect( The::playlistManager(), SIGNAL(providerRemoved(Playlists::PlaylistProvider*,int)),
- SLOT(slotProviderRemoved(Playlists::PlaylistProvider*,int)) );
+ connect( The::playlistManager(), &PlaylistManager::providerAdded,
+ this, &PlaylistBrowserCategory::slotProviderAdded );
+ connect( The::playlistManager(), &PlaylistManager::providerRemoved,
+ this, &PlaylistBrowserCategory::slotProviderRemoved );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)),
- SLOT(newPalette(QPalette)) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette,
+ this, &PlaylistBrowserCategory::newPalette );
}
PlaylistBrowserCategory::~PlaylistBrowserCategory()
{
}
QString
PlaylistBrowserCategory::filter() const
{
return QUrl::toPercentEncoding( m_filterProxy->filterRegExp().pattern() );
}
void
PlaylistBrowserCategory::setFilter( const QString &filter )
{
debug() << "Setting filter " << filter;
m_filterProxy->setFilterRegExp( QRegExp( QUrl::fromPercentEncoding( filter.toUtf8() ) ) );
//disable all other provider-buttons
foreach( QAction * const providerAction, m_providerActions )
{
const Playlists::PlaylistProvider *provider =
providerAction->data().value<const Playlists::PlaylistProvider *>();
if( provider )
providerAction->setChecked(
m_filterProxy->filterRegExp().exactMatch( provider->prettyName() ) );
}
}
QTreeView *
PlaylistBrowserCategory::playlistView()
{
return m_playlistView;
}
void
PlaylistBrowserCategory::toggleView( bool merged )
{
if( merged )
{
m_filterProxy->setSourceModel( m_byFolderProxy );
m_playlistView->setItemDelegate( m_defaultItemDelegate );
m_playlistView->setRootIsDecorated( true );
setHelpText( m_addFolderAction->text(), m_addFolderAction );
}
else
{
m_filterProxy->setSourceModel( m_byProviderProxy );
m_playlistView->setItemDelegate( m_byProviderDelegate );
m_playlistView->setRootIsDecorated( false );
setHelpText( i18n( "Folders are only shown in <b>merged view</b>." ), m_addFolderAction );
}
//folders don't make sense in per-provider view
m_addFolderAction->setEnabled( merged );
Amarok::config( m_configGroup ).writeEntry( s_mergeViewKey, merged );
}
void
PlaylistBrowserCategory::setHelpText(const QString &text, QAction *qa)
{
qa->setStatusTip(text);
qa->setToolTip(text);
if ((qa->whatsThis()).isEmpty()) {
qa->setWhatsThis(text);
}
}
void
PlaylistBrowserCategory::slotProviderAdded( Playlists::PlaylistProvider *provider, int category )
{
if( category != m_playlistCategory )
return; //ignore
if( !m_providerActions.keys().contains( provider ) )
createProviderButton( provider );
if( provider->playlistCount() != 0 )
toggleView( false ); // set view to non-merged if new provider has some tracks
slotToggleProviderButton();
}
void
PlaylistBrowserCategory::slotProviderRemoved( Playlists::PlaylistProvider *provider, int category )
{
Q_UNUSED( category )
if( m_providerActions.keys().contains( provider ) )
{
QAction *providerToggle = m_providerActions.take( provider );
m_providerMenu->removeAction( providerToggle );
}
}
void
PlaylistBrowserCategory::createProviderButton( const Playlists::PlaylistProvider *provider )
{
QAction *providerToggle = new QAction( provider->icon(), provider->prettyName(), this );
providerToggle->setCheckable( true );
providerToggle->setChecked( true );
providerToggle->setData( QVariant::fromValue( provider ) );
- connect( providerToggle, SIGNAL(toggled(bool)), SLOT(slotToggleProviderButton()) );
+ connect( providerToggle, &QAction::toggled, this, &PlaylistBrowserCategory::slotToggleProviderButton );
m_providerMenu->addAction( providerToggle );
//if there is only one provider the button needs to be disabled.
//When a second is added we can enable the first.
if( m_providerActions.count() == 0 )
providerToggle->setEnabled( false );
else if( m_providerActions.count() == 1 )
m_providerActions.values().first()->setEnabled( true );
m_providerActions.insert( provider, providerToggle );
}
void
PlaylistBrowserCategory::slotToggleProviderButton()
{
QString filter;
QActionList checkedActions;
foreach( const Playlists::PlaylistProvider *p, m_providerActions.keys() )
{
QAction *action = m_providerActions.value( p );
if( action->isChecked() )
{
QString escapedName = QRegExp::escape( p->prettyName() ).replace( ' ', "\\ " );
filter += QString( filter.isEmpty() ? "%1" : "|%1" ).arg( escapedName );
checkedActions << action;
action->setEnabled( true );
}
}
//if all are enabled the filter can be completely disabled.
if( checkedActions.count() == m_providerActions.count() )
filter.clear();
m_filterProxy->setFilterRegExp( filter );
//don't allow the last visible provider to be hidden
if( checkedActions.count() == 1 )
checkedActions.first()->setEnabled( false );
}
void
PlaylistBrowserCategory::createNewFolder()
{
QString name = i18nc( "default name for new folder", "New Folder" );
const QModelIndex &rootIndex = m_byFolderProxy->index(0,0);
QModelIndexList folderIndices = m_byFolderProxy->match( rootIndex, Qt::DisplayRole, name, -1 );
QString groupName = name;
if( !folderIndices.isEmpty() )
{
int folderCount( 0 );
foreach( const QModelIndex &folder, folderIndices )
{
QRegExp regex( name + " \\((\\d+)\\)" );
int matchIndex = regex.indexIn( folder.data( Qt::DisplayRole ).toString() );
if (matchIndex != -1)
{
int newNumber = regex.cap( 1 ).toInt();
if (newNumber > folderCount)
folderCount = newNumber;
}
}
groupName += QString( " (%1)" ).arg( folderCount + 1 );
}
QModelIndex idx = m_filterProxy->mapFromSource( m_byFolderProxy->createNewFolder( groupName ) );
m_playlistView->setCurrentIndex( idx );
m_playlistView->edit( idx );
}
void
PlaylistBrowserCategory::newPalette( const QPalette &palette )
{
Q_UNUSED( palette )
The::paletteHandler()->updateItemView( m_playlistView );
}
diff --git a/src/browsers/playlistbrowser/PlaylistBrowserModel.cpp b/src/browsers/playlistbrowser/PlaylistBrowserModel.cpp
index a5d545433e..ce2a4abf3f 100644
--- a/src/browsers/playlistbrowser/PlaylistBrowserModel.cpp
+++ b/src/browsers/playlistbrowser/PlaylistBrowserModel.cpp
@@ -1,736 +1,736 @@
/****************************************************************************************
* Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PlaylistBrowserModel"
#include "PlaylistBrowserModel.h"
#include "AmarokMimeData.h"
#include "playlistmanager/PlaylistManager.h"
#include "playlist/PlaylistController.h"
#include "playlist/PlaylistModel.h"
#include "core/support/Debug.h"
#include "widgets/PrettyTreeRoles.h"
#include <QIcon>
#include <QAction>
using namespace PlaylistBrowserNS;
// to be used with qSort.
static bool
lessThanPlaylistTitles( const Playlists::PlaylistPtr &lhs, const Playlists::PlaylistPtr &rhs )
{
return lhs->prettyName().toLower() < rhs->prettyName().toLower();
}
PlaylistBrowserModel::PlaylistBrowserModel( int playlistCategory )
: m_playlistCategory( playlistCategory )
{
- connect( The::playlistManager(), SIGNAL(updated(int)), SLOT(slotUpdate(int)) );
+ connect( The::playlistManager(), &PlaylistManager::updated, this, &PlaylistBrowserModel::slotUpdate );
- connect( The::playlistManager(), SIGNAL(playlistAdded(Playlists::PlaylistPtr,int)),
- SLOT(slotPlaylistAdded(Playlists::PlaylistPtr,int)) );
- connect( The::playlistManager(), SIGNAL(playlistRemoved(Playlists::PlaylistPtr,int)),
- SLOT(slotPlaylistRemoved(Playlists::PlaylistPtr,int)) );
- connect( The::playlistManager(), SIGNAL(playlistUpdated(Playlists::PlaylistPtr,int)),
- SLOT(slotPlaylistUpdated(Playlists::PlaylistPtr,int)) );
+ connect( The::playlistManager(), &PlaylistManager::playlistAdded,
+ this, &PlaylistBrowserModel::slotPlaylistAdded );
+ connect( The::playlistManager(), &PlaylistManager::playlistRemoved,
+ this, &PlaylistBrowserModel::slotPlaylistRemoved );
+ connect( The::playlistManager(), &PlaylistManager::playlistUpdated,
+ this, &PlaylistBrowserModel::slotPlaylistUpdated );
- connect( The::playlistManager(), SIGNAL(renamePlaylist(Playlists::PlaylistPtr)),
- SLOT(slotRenamePlaylist(Playlists::PlaylistPtr)) );
+ connect( The::playlistManager(), &PlaylistManager::renamePlaylist,
+ this, &PlaylistBrowserModel::slotRenamePlaylist );
m_playlists = loadPlaylists();
}
QVariant
PlaylistBrowserModel::data( const QModelIndex &index, int role ) const
{
int row = REMOVE_TRACK_MASK(index.internalId());
Playlists::PlaylistPtr playlist = m_playlists.value( row );
QString name;
QIcon icon;
int playlistCount = 0;
QList<QAction *> providerActions;
QList<Playlists::PlaylistProvider *> providers =
The::playlistManager()->getProvidersForPlaylist( playlist );
Playlists::PlaylistProvider *provider = providers.count() == 1 ? providers.first() : 0;
Meta::TrackPtr track;
switch( index.column() )
{
case PlaylistBrowserModel::PlaylistItemColumn: //playlist or track data
{
if( IS_TRACK(index) )
{
track = playlist->tracks()[index.row()];
name = track->prettyName();
icon = QIcon::fromTheme( "amarok_track" );
}
else
{
name = playlist->prettyName();
icon = QIcon::fromTheme( "amarok_playlist" );
}
break;
}
case PlaylistBrowserModel::LabelColumn: //group
{
if( !playlist->groups().isEmpty() )
{
name = playlist->groups().first();
icon = QIcon::fromTheme( "folder" );
}
break;
}
case PlaylistBrowserModel::ProviderColumn: //source
{
if( providers.count() > 1 )
{
QVariantList nameData;
QVariantList iconData;
QVariantList playlistCountData;
QVariantList providerActionsData;
foreach( Playlists::PlaylistProvider *provider, providers )
{
name = provider->prettyName();
nameData << name;
icon = provider->icon();
iconData << QVariant( icon );
playlistCount = provider->playlists().count();
if( playlistCount >= 0 )
playlistCountData << i18ncp(
"number of playlists from one source",
"One Playlist", "%1 playlists",
playlistCount );
else
playlistCountData << i18nc(
"normally number of playlists, but they are still loading",
"Loading..." );
providerActions << provider->providerActions();
providerActionsData << QVariant::fromValue( providerActions );
}
switch( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return nameData;
case Qt::DecorationRole:
return iconData;
case PrettyTreeRoles::ByLineRole:
return playlistCountData;
case PrettyTreeRoles::DecoratorRoleCount:
return providerActions.count();
case PrettyTreeRoles::DecoratorRole:
return providerActionsData;
}
}
else if( provider )
{
name = provider->prettyName();
icon = provider->icon();
playlistCount = provider->playlistCount();
providerActions << provider->providerActions();
}
break;
}
default: break;
}
switch( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return name;
case Qt::DecorationRole:
return QVariant( icon );
case PrettyTreeRoles::ByLineRole:
if( IS_TRACK(index) )
return QVariant();
else
return i18ncp( "number of playlists from one source", "One playlist",
"%1 playlists", playlistCount );
case PlaylistBrowserModel::ProviderRole:
return provider ? QVariant::fromValue( provider ) : QVariant();
case PlaylistBrowserModel::PlaylistRole:
return playlist ? QVariant::fromValue( playlist ) : QVariant();
case PlaylistBrowserModel::TrackRole:
return track ? QVariant::fromValue( track ) : QVariant();
default:
return QVariant();
}
}
bool
PlaylistBrowserModel::setData( const QModelIndex &idx, const QVariant &value, int role )
{
if( !idx.isValid() )
return false;
switch( idx.column() )
{
case ProviderColumn:
{
if( role == Qt::DisplayRole || role == Qt::EditRole )
{
Playlists::PlaylistProvider *provider = getProviderByName( value.toString() );
if( !provider )
return false;
if( IS_TRACK( idx ) )
{
Meta::TrackPtr track = trackFromIndex( idx );
if( !track )
return false;
debug() << QString( "Copy track \"%1\" to \"%2\"." )
.arg( track->prettyName(), provider->prettyName() );
// return !provider->addTrack( track ).isNull();
provider->addTrack( track ); //ignore result since UmsPodcastProvider returns NULL
return true;
}
else
{
Playlists::PlaylistPtr playlist = playlistFromIndex( idx );
if( !playlist || ( playlist->provider() == provider ) )
return false;
foreach( Playlists::PlaylistPtr tempPlaylist , provider->playlists() )
{
if ( tempPlaylist->name() == playlist->name() )
return false;
}
debug() << QString( "Copy playlist \"%1\" to \"%2\"." )
.arg( playlist->prettyName(), provider->prettyName() );
return !provider->addPlaylist( playlist ).isNull();
}
}
//return true even for the data we didn't handle to get QAbstractItemModel::setItemData to work
//TODO: implement setItemData()
return true;
}
case LabelColumn:
{
debug() << "changing group of item " << idx.internalId() << " to " << value.toString();
Playlists::PlaylistPtr item = m_playlists.value( idx.internalId() );
item->setGroups( value.toStringList() );
return true;
}
}
return false;
}
QModelIndex
PlaylistBrowserModel::index( int row, int column, const QModelIndex &parent) const
{
if( !parent.isValid() )
{
if( row >= 0 && row < m_playlists.count() )
return createIndex( row, column, row );
}
else //if it has a parent it is a track
{
//but check if the playlist indeed has that track
Playlists::PlaylistPtr playlist = m_playlists.value( parent.row() );
if( row < playlist->tracks().count() )
return createIndex( row, column, SET_TRACK_MASK(parent.row()) );
}
return QModelIndex();
}
QModelIndex
PlaylistBrowserModel::parent( const QModelIndex &index ) const
{
if( IS_TRACK(index) )
{
int row = REMOVE_TRACK_MASK(index.internalId());
return this->index( row, index.column(), QModelIndex() );
}
return QModelIndex();
}
bool
PlaylistBrowserModel::hasChildren( const QModelIndex &parent ) const
{
if( parent.column() > 0 )
return false;
if( !parent.isValid() )
{
return !m_playlists.isEmpty();
}
else if( !IS_TRACK(parent) )
{
Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
return playlist->trackCount() != 0; //-1 might mean there are tracks, but not yet loaded.
}
return false;
}
int
PlaylistBrowserModel::rowCount( const QModelIndex &parent ) const
{
if( parent.column() > 0 )
return 0;
if( !parent.isValid() )
{
return m_playlists.count();
}
else if( !IS_TRACK(parent) )
{
Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
return playlist->trackCount();
}
return 0;
}
int
PlaylistBrowserModel::columnCount( const QModelIndex &parent ) const
{
if( !parent.isValid() ) //for playlists (children of root)
return 3; //name, group and provider
//for tracks
return 1; //only name
}
bool
PlaylistBrowserModel::canFetchMore( const QModelIndex &parent ) const
{
if( parent.column() > 0 )
return false;
if( !parent.isValid() )
{
return false;
}
else if( !IS_TRACK(parent) )
{
Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
//TODO: imeplement incremental loading of tracks by checking for ==
if( playlist->trackCount() != playlist->tracks().count() )
return true; //tracks still need to be loaded.
}
return false;
}
void
PlaylistBrowserModel::fetchMore ( const QModelIndex &parent )
{
if( parent.column() > 0 )
return;
//TODO: load playlists dynamically from provider
if( !parent.isValid() )
return;
if( !IS_TRACK(parent) )
{
Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
// TODO: following doesn't seem to be needed, PlaylistBrowserModel seems to be able to cope with async track loading fine
playlist->makeLoadingSync();
playlist->triggerTrackLoad();
}
}
Qt::ItemFlags
PlaylistBrowserModel::flags( const QModelIndex &idx ) const
{
//Both providers and groups can be empty. QtGroupingProxy makes empty groups from the data in
//the rootnode (here an invalid QModelIndex).
//TODO: editable only if provider is writable.
if( idx.column() == PlaylistBrowserModel::ProviderColumn )
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
if( idx.column() == PlaylistBrowserModel::LabelColumn )
return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
if( !idx.isValid() )
return Qt::ItemIsDropEnabled;
if( IS_TRACK(idx) )
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
//item is a playlist
return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled |
Qt::ItemIsDropEnabled;
}
QVariant
PlaylistBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if( orientation == Qt::Horizontal && role == Qt::DisplayRole )
{
switch( section )
{
case PlaylistBrowserModel::PlaylistItemColumn: return i18n("Name");
case PlaylistBrowserModel::LabelColumn: return i18n("Group");
case PlaylistBrowserModel::ProviderColumn: return i18n("Source");
default: return QVariant();
}
}
return QVariant();
}
QStringList
PlaylistBrowserModel::mimeTypes() const
{
QStringList ret;
ret << AmarokMimeData::PLAYLIST_MIME;
ret << AmarokMimeData::TRACK_MIME;
return ret;
}
QMimeData*
PlaylistBrowserModel::mimeData( const QModelIndexList &indices ) const
{
AmarokMimeData* mime = new AmarokMimeData();
Playlists::PlaylistList playlists;
Meta::TrackList tracks;
foreach( const QModelIndex &index, indices )
{
if( IS_TRACK(index) )
tracks << trackFromIndex( index );
else
playlists << m_playlists.value( index.internalId() );
}
mime->setPlaylists( playlists );
mime->setTracks( tracks );
return mime;
}
bool
PlaylistBrowserModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent )
{
DEBUG_BLOCK
debug() << "Dropped on" << parent << "row" << row << "column" << column << "action" << action;
if( action == Qt::IgnoreAction )
return true;
//drop on track is not possible
if( IS_TRACK(parent) )
return false;
const AmarokMimeData* amarokMime = dynamic_cast<const AmarokMimeData*>( data );
if( !amarokMime )
return false;
if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) )
{
// TODO: is this ever called????
Playlists::PlaylistList playlists = amarokMime->playlists();
foreach( Playlists::PlaylistPtr playlist, playlists )
{
if( !m_playlists.contains( playlist ) )
debug() << "Unknown playlist dragged in: " << playlist->prettyName();
}
return true;
}
else if( data->hasFormat( AmarokMimeData::TRACK_MIME ) )
{
Meta::TrackList tracks = amarokMime->tracks();
if( !parent.isValid() && row == -1 && column == -1 )
{
debug() << "Dropped tracks on empty area: create new playlist in a default provider";
The::playlistManager()->save( tracks, Amarok::generatePlaylistName( tracks ) );
return true;
}
else if( !parent.isValid() )
{
warning() << "Dropped tracks between root items, this is not supported!";
return false;
}
else
{
debug() << "Dropped tracks on " << parent << " at row: " << row;
Playlists::PlaylistPtr playlist = playlistFromIndex( parent );
if( !playlist )
return false;
foreach( Meta::TrackPtr track, tracks )
playlist->addTrack( track, ( row >= 0 ) ? row++ : -1 ); // increment only if positive
return true;
}
}
return false;
}
void
PlaylistBrowserModel::metadataChanged( Playlists::PlaylistPtr playlist )
{
int indexNumber = m_playlists.indexOf( playlist );
if( indexNumber == -1 )
{
error() << "This playlist is not in the list of this model.";
return;
}
QModelIndex playlistIdx = index( indexNumber, 0 );
emit dataChanged( playlistIdx, playlistIdx );
}
void
PlaylistBrowserModel::trackAdded( Playlists::PlaylistPtr playlist, Meta::TrackPtr track,
int position )
{
Q_UNUSED( track );
int indexNumber = m_playlists.indexOf( playlist );
if( indexNumber == -1 )
{
error() << "This playlist is not in the list of this model.";
return;
}
QModelIndex playlistIdx = index( indexNumber, 0, QModelIndex() );
beginInsertRows( playlistIdx, position, position );
endInsertRows();
}
void
PlaylistBrowserModel::trackRemoved( Playlists::PlaylistPtr playlist, int position )
{
int indexNumber = m_playlists.indexOf( playlist );
if( indexNumber == -1 )
{
error() << "This playlist is not in the list of this model.";
return;
}
QModelIndex playlistIdx = index( indexNumber, 0, QModelIndex() );
beginRemoveRows( playlistIdx, position, position );
endRemoveRows();
}
void
PlaylistBrowserModel::slotRenamePlaylist( Playlists::PlaylistPtr playlist )
{
if( !playlist->provider() || playlist->provider()->category() != m_playlistCategory )
return;
int row = 0;
foreach( Playlists::PlaylistPtr p, m_playlists )
{
if( p == playlist )
{
emit renameIndex( index( row, 0 ) );
break;
}
row++;
}
}
void
PlaylistBrowserModel::slotUpdate( int category )
{
if( category != m_playlistCategory )
return;
beginResetModel();
foreach( Playlists::PlaylistPtr playlist, m_playlists )
unsubscribeFrom( playlist );
m_playlists.clear();
m_playlists = loadPlaylists();
endResetModel();
}
Playlists::PlaylistList
PlaylistBrowserModel::loadPlaylists()
{
Playlists::PlaylistList playlists =
The::playlistManager()->playlistsOfCategory( m_playlistCategory );
QListIterator<Playlists::PlaylistPtr> i( playlists );
debug() << playlists.count() << " playlists for category " << m_playlistCategory;
while( i.hasNext() )
{
Playlists::PlaylistPtr playlist = i.next();
subscribeTo( playlist );
}
qSort( playlists.begin(), playlists.end(), lessThanPlaylistTitles );
return playlists;
}
void
PlaylistBrowserModel::slotPlaylistAdded( Playlists::PlaylistPtr playlist, int category )
{
//ignore playlists of a different category
if( category != m_playlistCategory )
return;
subscribeTo( playlist );
int i;
for( i = 0; i < m_playlists.count(); i++ )
{
if( lessThanPlaylistTitles( playlist, m_playlists[i] ) )
break;
}
beginInsertRows( QModelIndex(), i, i );
m_playlists.insert( i, playlist );
endInsertRows();
}
void
PlaylistBrowserModel::slotPlaylistRemoved( Playlists::PlaylistPtr playlist, int category )
{
if( category != m_playlistCategory )
return;
int position = m_playlists.indexOf( playlist );
if( position == -1 )
{
error() << "signal received for removed playlist not in m_playlists";
return;
}
beginRemoveRows( QModelIndex(), position, position );
m_playlists.removeAt( position );
endRemoveRows();
}
void
PlaylistBrowserModel::slotPlaylistUpdated( Playlists::PlaylistPtr playlist, int category )
{
if( category != m_playlistCategory )
return;
int position = m_playlists.indexOf( playlist );
if( position == -1 )
{
error() << "signal received for updated playlist not in m_playlists";
return;
}
//TODO: this should work by signaling a change in the model data, but QtGroupingProxy doesn't
//work like that ATM
// const QModelIndex &idx = index( position, 0 );
// emit dataChanged( idx, idx );
//HACK: remove and readd so QtGroupingProxy can put it in the correct groups.
beginRemoveRows( QModelIndex(), position, position );
endRemoveRows();
beginInsertRows( QModelIndex(), position, position );
endInsertRows();
}
Meta::TrackList
PlaylistBrowserModel::tracksFromIndexes( const QModelIndexList &list ) const
{
Meta::TrackList tracks;
foreach( const QModelIndex &index, list )
{
if( IS_TRACK(index) )
tracks << trackFromIndex( index );
else if( Playlists::PlaylistPtr playlist = playlistFromIndex( index ) )
{
playlist->makeLoadingSync();
//first trigger a load of the tracks or we'll end up with an empty list
playlist->triggerTrackLoad();
tracks << playlist->tracks();
}
}
return tracks;
}
Meta::TrackPtr
PlaylistBrowserModel::trackFromIndex( const QModelIndex &idx ) const
{
if( !idx.isValid() || !IS_TRACK(idx) )
return Meta::TrackPtr();
int playlistRow = REMOVE_TRACK_MASK(idx.internalId());
if( playlistRow >= m_playlists.count() )
return Meta::TrackPtr();
Playlists::PlaylistPtr playlist = m_playlists.value( playlistRow );
if( playlist.isNull() || playlist->tracks().count() <= idx.row() )
return Meta::TrackPtr();
return playlist->tracks()[idx.row()];
}
Playlists::PlaylistPtr
PlaylistBrowserModel::playlistFromIndex( const QModelIndex &index ) const
{
if( !index.isValid() )
return Playlists::PlaylistPtr();
return m_playlists.value( index.internalId() );
}
Playlists::PlaylistProvider *
PlaylistBrowserModel::providerForIndex( const QModelIndex &idx ) const
{
if( !idx.isValid() )
return 0;
int playlistRow;
if( IS_TRACK( idx ) )
playlistRow = REMOVE_TRACK_MASK( idx.internalId() );
else
playlistRow = idx.row();
if( playlistRow >= m_playlists.count() )
return 0;
return m_playlists.at( playlistRow )->provider();
}
Playlists::PlaylistProvider *
PlaylistBrowserModel::getProviderByName( const QString &name )
{
QList<Playlists::PlaylistProvider *> providers =
The::playlistManager()->providersForCategory( m_playlistCategory );
foreach( Playlists::PlaylistProvider *provider, providers )
{
if( provider->prettyName() == name )
return provider;
}
return 0;
}
diff --git a/src/browsers/playlistbrowser/PlaylistBrowserView.cpp b/src/browsers/playlistbrowser/PlaylistBrowserView.cpp
index 811dfccd35..d96439f4b1 100644
--- a/src/browsers/playlistbrowser/PlaylistBrowserView.cpp
+++ b/src/browsers/playlistbrowser/PlaylistBrowserView.cpp
@@ -1,600 +1,600 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PlaylistBrowserView"
#include "PlaylistBrowserView.h"
#include "PaletteHandler.h"
#include "PopupDropperFactory.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "browsers/playlistbrowser/PlaylistBrowserModel.h"
#include "browsers/playlistbrowser/PlaylistsByProviderProxy.h"
#include "browsers/playlistbrowser/PlaylistsInFoldersProxy.h"
#include "context/ContextView.h"
#include "context/popupdropper/libpud/PopupDropperItem.h"
#include "context/popupdropper/libpud/PopupDropper.h"
#include "core/support/Debug.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "playlist/PlaylistModel.h"
#include "playlistmanager/PlaylistManager.h"
#include "widgets/PrettyTreeRoles.h"
#include <KFileDialog>
#include <KGlobalSettings>
#include <QMenu>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QCheckBox>
#include <QLabel>
#include <KConfigGroup>
using namespace PlaylistBrowserNS;
PlaylistBrowserNS::PlaylistBrowserView::PlaylistBrowserView( QAbstractItemModel *model,
QWidget *parent )
: Amarok::PrettyTreeView( parent )
, m_pd( 0 )
, m_ongoingDrag( false )
{
DEBUG_BLOCK
setModel( model );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectItems );
setDragDropMode( QAbstractItemView::DragDrop );
setAcceptDrops( true );
setEditTriggers( QAbstractItemView::EditKeyPressed );
setMouseTracking( true ); // needed for highlighting provider action icons
m_createEmptyPlaylistAction = new QAction( QIcon::fromTheme( "media-track-add-amarok" ),
i18n( "Create an Empty Playlist" ), this );
- connect( m_createEmptyPlaylistAction, SIGNAL(triggered()), SLOT(slotCreateEmptyPlaylist()) );
+ connect( m_createEmptyPlaylistAction, &QAction::triggered, this, &PlaylistBrowserView::slotCreateEmptyPlaylist );
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, SIGNAL(triggered()), this, SLOT(slotAppend()) );
+ connect( m_appendAction, &QAction::triggered, this, &PlaylistBrowserView::slotAppend );
m_loadAction = new QAction( QIcon::fromTheme( "folder-open" ), i18nc( "Replace the currently "
"loaded tracks with these", "&Replace Playlist" ), this );
m_loadAction->setProperty( "popupdropper_svg_id", "load" );
- connect( m_loadAction, SIGNAL(triggered()), this, SLOT(slotLoad()) );
+ connect( m_loadAction, &QAction::triggered, this, &PlaylistBrowserView::slotLoad );
m_setNewAction = new QAction( QIcon::fromTheme( "rating" ), i18nc( "toggle the \"new\" status "
" of this podcast episode", "&New" ), this );
m_setNewAction->setProperty( "popupdropper_svg_id", "new" );
m_setNewAction->setCheckable( true );
- connect( m_setNewAction, SIGNAL(triggered(bool)), SLOT(slotSetNew(bool)) );
+ connect( m_setNewAction, &QAction::triggered, this, &PlaylistBrowserView::slotSetNew );
m_renamePlaylistAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ),
i18n( "&Rename..." ), this );
m_renamePlaylistAction->setProperty( "popupdropper_svg_id", "edit" );
// key shortcut is only for display purposes here, actual one is determined by View in Model/View classes
m_renamePlaylistAction->setShortcut( Qt::Key_F2 );
- connect( m_renamePlaylistAction, SIGNAL(triggered()), this, SLOT(slotRename()) );
+ connect( m_renamePlaylistAction, &QAction::triggered, this, &PlaylistBrowserView::slotRename );
m_deletePlaylistAction = new QAction( QIcon::fromTheme( "media-track-remove-amarok" ),
i18n( "&Delete..." ), this );
m_deletePlaylistAction->setProperty( "popupdropper_svg_id", "delete" );
// key shortcut is only for display purposes here, actual one is determined by View in Model/View classes
m_deletePlaylistAction->setShortcut( Qt::Key_Delete );
- connect( m_deletePlaylistAction, SIGNAL(triggered()), SLOT(slotDelete()) );
+ connect( m_deletePlaylistAction, &QAction::triggered, this, &PlaylistBrowserView::slotDelete );
m_removeTracksAction = new QAction( QIcon::fromTheme( "media-track-remove-amarok" ),
QString( "<placeholder>" ), this );
m_removeTracksAction->setProperty( "popupdropper_svg_id", "delete" );
// key shortcut is only for display purposes here, actual one is determined by View in Model/View classes
m_removeTracksAction->setShortcut( Qt::Key_Delete );
- connect( m_removeTracksAction, SIGNAL(triggered()), SLOT(slotRemoveTracks()) );
+ connect( m_removeTracksAction, &QAction::triggered, this, &PlaylistBrowserView::slotRemoveTracks );
m_exportAction = new QAction( QIcon::fromTheme( "document-export-amarok" ),
i18n( "&Export As..." ), this );
- connect( m_exportAction, SIGNAL(triggered()), this, SLOT(slotExport()) );
+ connect( m_exportAction, &QAction::triggered, this, &PlaylistBrowserView::slotExport );
m_separatorAction = new QAction( this );
m_separatorAction->setSeparator( true );
}
void
PlaylistBrowserNS::PlaylistBrowserView::setModel( QAbstractItemModel *model )
{
if( this->model() )
disconnect( this->model(), 0, this, 0 );
Amarok::PrettyTreeView::setModel( model );
connect( this->model(), SIGNAL(renameIndex(QModelIndex)), SLOT(edit(QModelIndex)) );
}
void
PlaylistBrowserNS::PlaylistBrowserView::mouseReleaseEvent( QMouseEvent *event )
{
if( m_pd )
{
- connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(deleteLater()) );
+ connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &QObject::deleteLater );
m_pd->hide();
m_pd = 0;
}
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() )
{
PrettyTreeView::mouseReleaseEvent( event );
return;
}
if( event->button() == Qt::MidButton )
{
insertIntoPlaylist( index, Playlist::OnMiddleClickOnSelectedItems );
event->accept();
return;
}
PrettyTreeView::mouseReleaseEvent( event );
}
void PlaylistBrowserNS::PlaylistBrowserView::startDrag( Qt::DropActions supportedActions )
{
// Waah? when a parent item is dragged, startDrag is called a bunch of times
if( m_ongoingDrag )
return;
m_ongoingDrag = true;
/* FIXME: disabled temporarily for KF5 porting
if( !m_pd )
m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
*/
if( m_pd && m_pd->isHidden() )
{
QActionList actions = actionsFor( selectedIndexes() );
foreach( QAction *action, actions )
m_pd->addItem( The::popupDropperFactory()->createItem( action ) );
m_pd->show();
}
QTreeView::startDrag( supportedActions );
// We keep the items that the actions need to be applied to.
// Clear the data from all actions now that the PUD has executed.
resetActionTargets();
if( m_pd )
{
- connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(clear()) );
+ connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear );
m_pd->hide();
}
m_ongoingDrag = false;
}
void
PlaylistBrowserNS::PlaylistBrowserView::keyPressEvent( QKeyEvent *event )
{
QModelIndexList indices = selectedIndexes();
// mind bug 305203
if( indices.isEmpty() || state() != QAbstractItemView::NoState )
{
Amarok::PrettyTreeView::keyPressEvent( event );
return;
}
switch( event->key() )
{
//activated() only works for current index, not all selected
case Qt::Key_Enter:
case Qt::Key_Return:
insertIntoPlaylist( indices, Playlist::OnReturnPressedOnSelectedItems );
return;
case Qt::Key_Delete:
{
QActionList actions = actionsFor( indices ); // sets action targets
if( actions.contains( m_removeTracksAction ) )
m_removeTracksAction->trigger();
else if( actions.contains( m_deletePlaylistAction ) )
m_deletePlaylistAction->trigger();
resetActionTargets();
return;
}
default:
break;
}
Amarok::PrettyTreeView::keyPressEvent( event );
}
void
PlaylistBrowserNS::PlaylistBrowserView::mouseDoubleClickEvent( QMouseEvent *event )
{
if( event->button() == Qt::MidButton )
{
event->accept();
return;
}
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() )
{
event->accept();
return;
}
// code copied in CollectionTreeView::mouseDoubleClickEvent(), keep in sync
// mind bug 279513
bool isExpandable = model()->hasChildren( index );
bool wouldExpand = !visualRect( index ).contains( event->pos() ) || // clicked outside item, perhaps on expander icon
( isExpandable && !KGlobalSettings::singleClick() ); // we're in doubleClick
if( event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier &&
!wouldExpand )
{
insertIntoPlaylist( index, Playlist::OnDoubleClickOnSelectedItems );
event->accept();
return;
}
PrettyTreeView::mouseDoubleClickEvent( event );
}
void PlaylistBrowserNS::PlaylistBrowserView::contextMenuEvent( QContextMenuEvent *event )
{
QModelIndex clickedIdx = indexAt( event->pos() );
QModelIndexList indices;
if( clickedIdx.isValid() && selectedIndexes().contains( clickedIdx ) )
indices << selectedIndexes();
else if( clickedIdx.isValid() )
indices << clickedIdx;
QActionList actions = actionsFor( indices );
if( actions.isEmpty() )
{
resetActionTargets();
return;
}
QMenu menu;
foreach( QAction *action, actions )
menu.addAction( action );
menu.exec( mapToGlobal( event->pos() ) );
// We keep the items that the action need to be applied to.
// Clear the data from all actions now that the context menu has executed.
resetActionTargets();
}
QList<QAction *>
PlaylistBrowserNS::PlaylistBrowserView::actionsFor( const QModelIndexList &indexes )
{
resetActionTargets();
if( indexes.isEmpty() )
return QActionList();
using namespace Playlists;
QSet<PlaylistProvider *> providers, writableProviders;
QActionList actions;
QModelIndexList newPodcastEpisodes, oldPodcastEpisodes;
foreach( const QModelIndex &idx, indexes )
{
// direct provider actions:
actions << idx.data( PrettyTreeRoles::DecoratorRole ).value<QActionList>();
PlaylistProvider *provider = idx.data( PlaylistBrowserModel::ProviderRole ).value<PlaylistProvider *>();
if( provider )
providers << provider;
bool isWritable = provider ? provider->isWritable() : false;
if( isWritable )
writableProviders |= provider;
Meta::TrackPtr track = idx.data( PlaylistBrowserModel::TrackRole ).value<Meta::TrackPtr>();
PlaylistPtr playlist = idx.data( PlaylistBrowserModel::PlaylistRole ).value<PlaylistPtr>();
if( !track && playlist ) // a playlist (must check it is not a track)
{
m_actionPlaylists << playlist;
if( isWritable )
m_writableActionPlaylists << playlist;
}
if( track )
{
m_actionTracks.insert( playlist, idx.row() );
if( isWritable )
m_writableActionTracks.insert( playlist, idx.row() );
}
QVariant episodeIsNew = idx.data( PlaylistBrowserModel::EpisodeIsNewRole );
if( episodeIsNew.type() == QVariant::Bool )
{
if( episodeIsNew.toBool() )
newPodcastEpisodes << idx;
else
oldPodcastEpisodes << idx;
}
}
// all actions taking provider have only sense with one provider
if( writableProviders.count() == 1 )
m_writableActionProvider = writableProviders.toList().first();
// process per-provider actions
foreach( PlaylistProvider *provider, providers )
{
// prepare arguments and get relevant actions
PlaylistList providerPlaylists;
foreach( const PlaylistPtr &playlist, m_actionPlaylists )
{
if( playlist->provider() == provider )
providerPlaylists << playlist;
}
actions << provider->playlistActions( providerPlaylists );
QMultiHash<PlaylistPtr, int> playlistTracks;
QHashIterator<PlaylistPtr, int> it( m_actionTracks );
while( it.hasNext() )
{
it.next();
if( it.key()->provider() == provider )
playlistTracks.insert( it.key(), it.value() );
}
actions << provider->trackActions( playlistTracks );
}
// separate model actions from standard actions we provide (at the top)
QActionList standardActions;
if( m_actionPlaylists.isEmpty() && m_actionTracks.isEmpty() && m_writableActionProvider )
standardActions << m_createEmptyPlaylistAction;
if( !m_actionPlaylists.isEmpty() || !m_actionTracks.isEmpty() )
standardActions << m_appendAction << m_loadAction;
if( !newPodcastEpisodes.isEmpty() || !oldPodcastEpisodes.isEmpty() )
{
m_setNewAction->setChecked( oldPodcastEpisodes.isEmpty() );
m_setNewAction->setData( QVariant::fromValue( newPodcastEpisodes + oldPodcastEpisodes ) );
standardActions << m_setNewAction;
}
if( m_writableActionPlaylists.count() == 1 && m_actionTracks.isEmpty() )
standardActions << m_renamePlaylistAction;
if( !m_writableActionPlaylists.isEmpty() && m_actionTracks.isEmpty() )
standardActions << m_deletePlaylistAction;
if( m_actionPlaylists.isEmpty() && !m_writableActionTracks.isEmpty() )
{
const int actionTrackCount = m_writableActionTracks.count();
const int playlistCount = m_writableActionTracks.uniqueKeys().count();
if( playlistCount > 1 )
m_removeTracksAction->setText( i18nc( "%1: number of tracks. %2: number of playlists",
"Remove %1 From %2", i18ncp ("First part of 'Remove %1 From %2'", "a Track",
"%1 Tracks", actionTrackCount), i18ncp ("Second part of 'Remove %1 From %2'", "1 Playlist",
"%1 Playlists", playlistCount ) ) );
else
m_removeTracksAction->setText( i18ncp( "%2 is saved playlist name",
"Remove a Track From %2", "Remove %1 Tracks From %2", actionTrackCount,
m_writableActionTracks.uniqueKeys().first()->prettyName() ) );
standardActions << m_removeTracksAction;
}
if( m_actionPlaylists.count() == 1 && m_actionTracks.isEmpty() )
standardActions << m_exportAction;
standardActions << m_separatorAction;
return standardActions + actions;
}
void
PlaylistBrowserView::resetActionTargets()
{
m_writableActionProvider = 0;
m_actionPlaylists.clear();
m_writableActionPlaylists.clear();
m_actionTracks.clear();
m_writableActionTracks.clear();
}
void
PlaylistBrowserNS::PlaylistBrowserView::currentChanged( const QModelIndex &current,
const QModelIndex &previous )
{
Q_UNUSED( previous )
emit currentItemChanged( current );
Amarok::PrettyTreeView::currentChanged( current, previous );
}
void
PlaylistBrowserView::slotCreateEmptyPlaylist()
{
// m_actionProvider may be null, which is fine
The::playlistManager()->save( Meta::TrackList(), Amarok::generatePlaylistName(
Meta::TrackList() ), m_writableActionProvider );
}
void
PlaylistBrowserView::slotAppend()
{
insertIntoPlaylist( Playlist::OnAppendToPlaylistAction );
}
void
PlaylistBrowserView::slotLoad()
{
insertIntoPlaylist( Playlist::OnReplacePlaylistAction );
}
void
PlaylistBrowserView::slotSetNew( bool newState )
{
QModelIndexList indices = m_setNewAction->data().value<QModelIndexList>();
foreach( const QModelIndex &idx, indices )
model()->setData( idx, newState, PlaylistBrowserModel::EpisodeIsNewRole );
}
void
PlaylistBrowserView::slotRename()
{
if( m_writableActionPlaylists.count() != 1 )
{
warning() << __PRETTY_FUNCTION__ << "m_writableActionPlaylists.count() is not 1";
return;
}
Playlists::PlaylistPtr playlist = m_writableActionPlaylists.at( 0 );
// TODO: this makes a rather complicated round-trip and ends up in edit(QModelIndex)
// here -- simplify that
The::playlistManager()->rename( playlist );
}
void
PlaylistBrowserView::slotDelete()
{
if( m_writableActionPlaylists.isEmpty() )
return;
using namespace Playlists;
QHash<PlaylistProvider *, PlaylistList> providerPlaylists;
foreach( const PlaylistPtr &playlist, m_writableActionPlaylists )
{
if( playlist->provider() )
providerPlaylists[ playlist->provider() ] << playlist;
}
QStringList providerNames;
foreach( const PlaylistProvider *provider, providerPlaylists.keys() )
providerNames << provider->prettyName();
QDialog dialog;
dialog.setWindowTitle( i18n( "Confirm Playlist Deletion" ) );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout;
dialog.setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
okButton->setText( i18nc( "%1 is playlist provider pretty name",
"Yes, delete from %1.", providerNames.join( ", " ) ) );
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
QLabel *label = new QLabel( i18np( "Are you sure you want to delete this playlist?",
"Are you sure you want to delete these %1 playlists?",
m_writableActionPlaylists.count() ), &dialog );
// TODO: include a text area with all the names of the playlists
mainLayout->addWidget(label);
mainLayout->addWidget(buttonBox);
if( dialog.exec() == QDialog::Accepted )
{
foreach( PlaylistProvider *provider, providerPlaylists.keys() )
provider->deletePlaylists( providerPlaylists.value( provider ) );
}
}
void
PlaylistBrowserView::slotRemoveTracks()
{
foreach( Playlists::PlaylistPtr playlist, m_writableActionTracks.uniqueKeys() )
{
QList<int> trackIndices = m_writableActionTracks.values( playlist );
qSort( trackIndices );
int removed = 0;
foreach( int trackIndex, trackIndices )
{
playlist->removeTrack( trackIndex - removed /* account for already removed */ );
removed++;
}
}
}
void
PlaylistBrowserView::slotExport()
{
if( m_actionPlaylists.count() != 1 )
{
warning() << __PRETTY_FUNCTION__ << "m_actionPlaylists.count() is not 1";
return;
}
Playlists::PlaylistPtr playlist = m_actionPlaylists.at( 0 );
// --- display save location dialog
// compare with MainWindow::exportPlaylist
// TODO: have this code only once
QCheckBox *saveRelativeCheck = new QCheckBox( i18n("Use relative path for &saving") );
saveRelativeCheck->setChecked( AmarokConfig::relativePlaylist() );
KFileDialog fileDialog( QUrl("kfiledialog:///amarok-playlist-export"), QString(), 0, saveRelativeCheck );
QStringList supportedMimeTypes;
supportedMimeTypes << "video/x-ms-asf"; // ASX
supportedMimeTypes << "audio/x-mpegurl"; // M3U
supportedMimeTypes << "audio/x-scpls"; // PLS
supportedMimeTypes << "application/xspf+xml"; // XSPF
fileDialog.setSelection( playlist->name() );
fileDialog.setMimeFilter( supportedMimeTypes, supportedMimeTypes.value( 1 ) );
fileDialog.setOperationMode( KFileDialog::Saving );
fileDialog.setMode( KFile::File );
fileDialog.setWindowTitle( i18n("Save As") );
fileDialog.setObjectName( "PlaylistExport" );
fileDialog.exec();
QString playlistPath = fileDialog.selectedFile();
// --- actually save the playlist
if( !playlistPath.isEmpty() )
Playlists::exportPlaylistFile( playlist->tracks(), QUrl::fromLocalFile(playlistPath), saveRelativeCheck->isChecked() );
}
void
PlaylistBrowserView::insertIntoPlaylist( const QModelIndex &index, Playlist::AddOptions options )
{
insertIntoPlaylist( QModelIndexList() << index, options );
}
void
PlaylistBrowserView::insertIntoPlaylist( const QModelIndexList &list, Playlist::AddOptions options )
{
actionsFor( list ); // sets action targets
insertIntoPlaylist( options );
resetActionTargets();
}
void
PlaylistBrowserView::insertIntoPlaylist( Playlist::AddOptions options )
{
Meta::TrackList tracks;
// add tracks for fully-selected playlists:
foreach( Playlists::PlaylistPtr playlist, m_actionPlaylists )
{
tracks << playlist->tracks();
}
// filter-out tracks from playlists that are selected, add lone tracks:
foreach( Playlists::PlaylistPtr playlist, m_actionTracks.uniqueKeys() )
{
if( m_actionPlaylists.contains( playlist ) )
continue;
Meta::TrackList playlistTracks = playlist->tracks();
QList<int> positions = m_actionTracks.values( playlist );
qSort( positions );
foreach( int position, positions )
{
if( position >= 0 && position < playlistTracks.count() )
tracks << playlistTracks.at( position );
}
}
if( !tracks.isEmpty() )
The::playlistController()->insertOptioned( tracks, options );
}
diff --git a/src/browsers/playlistbrowser/PlaylistsByProviderProxy.cpp b/src/browsers/playlistbrowser/PlaylistsByProviderProxy.cpp
index 8de7ad1531..17b86dbcfc 100644
--- a/src/browsers/playlistbrowser/PlaylistsByProviderProxy.cpp
+++ b/src/browsers/playlistbrowser/PlaylistsByProviderProxy.cpp
@@ -1,282 +1,282 @@
/****************************************************************************************
* Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistsByProviderProxy.h"
#include "AmarokMimeData.h"
#include "PlaylistBrowserModel.h"
#include "core/playlists/PlaylistProvider.h"
#include "core/support/Debug.h"
#include "playlistmanager/PlaylistManager.h"
#include "widgets/PrettyTreeRoles.h"
#include <QIcon>
#include <QStack>
PlaylistsByProviderProxy::PlaylistsByProviderProxy( int playlistCategory, QObject *parent )
: QtGroupingProxy( parent )
, m_playlistCategory( playlistCategory )
{
// we need this to track providers with no playlists
- connect( The::playlistManager(), SIGNAL(providerAdded(Playlists::PlaylistProvider*,int)),
- this, SLOT(slotProviderAdded(Playlists::PlaylistProvider*,int)) );
- connect( The::playlistManager(), SIGNAL(providerRemoved(Playlists::PlaylistProvider*,int)),
- this, SLOT(slotProviderRemoved(Playlists::PlaylistProvider*,int)) );
+ connect( The::playlistManager(), &PlaylistManager::providerAdded,
+ this, &PlaylistsByProviderProxy::slotProviderAdded );
+ connect( The::playlistManager(), &PlaylistManager::providerRemoved,
+ this, &PlaylistsByProviderProxy::slotProviderRemoved );
}
//TODO: remove this contructor
PlaylistsByProviderProxy::PlaylistsByProviderProxy( QAbstractItemModel *model, int column, int playlistCategory )
: QtGroupingProxy( model, QModelIndex(), column )
, m_playlistCategory( playlistCategory )
{
setSourceModel( model );
// we need this to track providers with no playlists
- connect( The::playlistManager(), SIGNAL(providerAdded(Playlists::PlaylistProvider*,int)),
- this, SLOT(slotProviderAdded(Playlists::PlaylistProvider*,int)) );
- connect( The::playlistManager(), SIGNAL(providerRemoved(Playlists::PlaylistProvider*,int)),
- this, SLOT(slotProviderRemoved(Playlists::PlaylistProvider*,int)) );
+ connect( The::playlistManager(), &PlaylistManager::providerAdded,
+ this, &PlaylistsByProviderProxy::slotProviderAdded );
+ connect( The::playlistManager(), &PlaylistManager::providerRemoved,
+ this, &PlaylistsByProviderProxy::slotProviderRemoved );
}
QVariant
PlaylistsByProviderProxy::data( const QModelIndex &idx, int role ) const
{
//TODO: actions for empty providers
//TODO: filter out actions not from the provider, possibly using QAction separators marking
// the source of the actions (makes sense in the UI as well.
//Turn the QVariantList of the source into a comma separated string, but only for the real items
if( !isGroup( idx ) && idx.column() == PlaylistBrowserNS::PlaylistBrowserModel::ProviderColumn
&& role == Qt::DisplayRole )
{
QVariant indexData = QtGroupingProxy::data( idx, role );
if( indexData.type() != QVariant::List )
return indexData;
QString providerString = indexData.toStringList().join( ", " );
return QVariant( providerString );
}
return QtGroupingProxy::data( idx, role );
}
Qt::ItemFlags
PlaylistsByProviderProxy::flags( const QModelIndex &idx ) const
{
//TODO: check if provider supports addPlaylist for DropEnabled
if( isGroup( idx ) )
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
return QtGroupingProxy::flags( idx );
}
bool
PlaylistsByProviderProxy::removeRows( int row, int count, const QModelIndex &parent )
{
DEBUG_BLOCK
bool result;
debug() << "in parent " << parent << "remove " << count << " starting at row " << row;
QModelIndex originalIdx = mapToSource( parent );
result = sourceModel()->removeRows( row, count, originalIdx );
if( result )
{
beginRemoveRows( parent, row, row + count - 1 );
endRemoveRows();
}
return result;
}
//TODO: move the next 3 implementation to QtGroupingProxy
QStringList
PlaylistsByProviderProxy::mimeTypes() const
{
//nothing to add
return sourceModel()->mimeTypes();
}
QMimeData *
PlaylistsByProviderProxy::mimeData( const QModelIndexList &indexes ) const
{
DEBUG_BLOCK
QModelIndexList sourceIndexes;
foreach( const QModelIndex &idx, indexes )
{
if( isGroup( idx ) )
continue; // drags not enabled for playlist providers
QModelIndex originalIdx = mapToSource( idx );
if( originalIdx.isValid() )
sourceIndexes << originalIdx;
}
if( sourceIndexes.isEmpty() )
return 0;
return sourceModel()->mimeData( sourceIndexes );
}
bool
PlaylistsByProviderProxy::dropMimeData( const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent )
{
DEBUG_BLOCK
debug() << "Dropped on" << parent << "row" << row << "column" << column << "action" << action;
if( action == Qt::IgnoreAction )
return true;
if( !isGroup( parent ) ) // drops on empty space fall here, it is okay
{
QModelIndex sourceIndex = mapToSource( parent );
return sourceModel()->dropMimeData( data, action, row, column, sourceIndex );
}
const AmarokMimeData *amarokData = dynamic_cast<const AmarokMimeData *>( data );
if( !amarokData )
{
debug() << __PRETTY_FUNCTION__ << "supports only drag & drop originating in Amarok.";
return false;
}
Playlists::PlaylistProvider *provider =
parent.data( PlaylistBrowserNS::PlaylistBrowserModel::ProviderRole )
.value<Playlists::PlaylistProvider *>();
if( !provider )
{
warning() << "Dropped tracks to a group with no (or multiple) providers!";
return false;
}
if( amarokData->hasFormat( AmarokMimeData::PLAYLIST_MIME ) )
{
debug() << "Dropped playlists to provider" << provider->prettyName();
foreach( Playlists::PlaylistPtr pl, amarokData->playlists() )
{
// few PlaylistProviders implement addPlaylist(), use save() instead:
The::playlistManager()->save( pl->tracks(), pl->name(), provider, false /* editName */ );
}
return true;
}
if( amarokData->hasFormat( AmarokMimeData::TRACK_MIME ) )
{
debug() << "Dropped tracks to provider" << provider->prettyName();
Meta::TrackList tracks = amarokData->tracks();
QString playlistName = Amarok::generatePlaylistName( tracks );
return The::playlistManager()->save( tracks, playlistName, provider );
}
debug() << __PRETTY_FUNCTION__ << "Unsupported drop mime-data:" << data->formats();
return false;
}
Qt::DropActions
PlaylistsByProviderProxy::supportedDropActions() const
{
//always add CopyAction because playlists can copied to a Provider
return sourceModel()->supportedDropActions() | Qt::CopyAction;
}
Qt::DropActions
PlaylistsByProviderProxy::supportedDragActions() const
{
//always add CopyAction because playlists can be put into a different group
return sourceModel()->supportedDragActions() | Qt::CopyAction;
}
void
PlaylistsByProviderProxy::setSourceModel( QAbstractItemModel *model )
{
if( sourceModel() )
sourceModel()->disconnect();
QtGroupingProxy::setSourceModel( model );
connect( sourceModel(), SIGNAL(renameIndex(QModelIndex)),
SLOT(slotRenameIndex(QModelIndex)) );
}
void
PlaylistsByProviderProxy::buildTree()
{
//clear that data anyway since provider can disappear and should no longer be listed.
m_groupMaps.clear();
//add the empty providers at the top of the list
PlaylistProviderList providerList =
The::playlistManager()->providersForCategory( m_playlistCategory );
foreach( Playlists::PlaylistProvider *provider, providerList )
{
slotProviderAdded( provider, provider->category() );
}
QtGroupingProxy::buildTree();
}
void
PlaylistsByProviderProxy::slotRenameIndex( const QModelIndex &sourceIdx )
{
QModelIndex idx = mapFromSource( sourceIdx );
if( idx.isValid() )
emit renameIndex( idx );
}
void
PlaylistsByProviderProxy::slotProviderAdded( Playlists::PlaylistProvider *provider, int category )
{
DEBUG_BLOCK
if( category != m_playlistCategory )
return;
if( provider->playlistCount() > 0
|| ( provider->playlistCount() < 0 /* not counted */
&& !provider->playlists().isEmpty() ) )
return; // non-empty providers are handled by PlaylistBrowserModel
ItemData itemData;
itemData.insert( Qt::DisplayRole, provider->prettyName() );
itemData.insert( Qt::DecorationRole, provider->icon() );
itemData.insert( PrettyTreeRoles::DecoratorRole, QVariant::fromValue( provider->providerActions() ) );
itemData.insert( PrettyTreeRoles::DecoratorRoleCount, provider->providerActions().count() );
itemData.insert( PlaylistBrowserNS::PlaylistBrowserModel::ProviderRole,
QVariant::fromValue<Playlists::PlaylistProvider*>( provider ) );
RowData rowData;
rowData.insert( PlaylistBrowserNS::PlaylistBrowserModel::PlaylistItemColumn, itemData );
//Provider column is used for filtering.
rowData.insert( PlaylistBrowserNS::PlaylistBrowserModel::ProviderColumn, itemData );
addEmptyGroup( rowData );
}
void
PlaylistsByProviderProxy::slotProviderRemoved( Playlists::PlaylistProvider *provider, int category )
{
DEBUG_BLOCK
if( category != m_playlistCategory )
return;
for( int i = 0; i < rowCount(); i++ )
{
QModelIndex idx = index( i, PlaylistBrowserNS::PlaylistBrowserModel::PlaylistItemColumn );
Playlists::PlaylistProvider *rowProvider = data( idx, PlaylistBrowserNS::PlaylistBrowserModel::ProviderRole )
.value<Playlists::PlaylistProvider *>();
if( rowProvider != provider )
continue;
removeGroup( idx );
}
}
diff --git a/src/browsers/playlistbrowser/PlaylistsInFoldersProxy.cpp b/src/browsers/playlistbrowser/PlaylistsInFoldersProxy.cpp
index 726cf9a1fc..c2c55aeb03 100644
--- a/src/browsers/playlistbrowser/PlaylistsInFoldersProxy.cpp
+++ b/src/browsers/playlistbrowser/PlaylistsInFoldersProxy.cpp
@@ -1,380 +1,379 @@
/****************************************************************************************
* Copyright (c) 2009 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistsInFoldersProxy.h"
#include "AmarokMimeData.h"
#include "core/support/Debug.h"
#include "core/playlists/Playlist.h"
#include "SvgHandler.h"
#include "UserPlaylistModel.h"
#include "playlist/PlaylistModelStack.h"
#include "widgets/PrettyTreeRoles.h"
#include <KDialog>
#include <QIcon>
#include <KInputDialog>
#include <QLabel>
PlaylistsInFoldersProxy::PlaylistsInFoldersProxy( QAbstractItemModel *model )
: QtGroupingProxy( model, QModelIndex(), PlaylistBrowserNS::UserModel::LabelColumn )
{
m_renameFolderAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ),
i18n( "&Rename Folder..." ), this );
m_renameFolderAction->setProperty( "popupdropper_svg_id", "edit_group" );
- connect( m_renameFolderAction, SIGNAL(triggered()), this,
- SLOT(slotRenameFolder()) );
+ connect( m_renameFolderAction, &QAction::triggered, this, &PlaylistsInFoldersProxy::slotRenameFolder );
m_deleteFolderAction = new QAction( QIcon::fromTheme( "media-track-remove-amarok" ),
i18n( "&Delete Folder" ), this );
m_deleteFolderAction->setProperty( "popupdropper_svg_id", "delete_group" );
m_deleteFolderAction->setObjectName( "deleteAction" );
- connect( m_deleteFolderAction, SIGNAL(triggered()), this,
- SLOT(slotDeleteFolder()) );
+ connect( m_deleteFolderAction, &QAction::triggered, this, &PlaylistsInFoldersProxy::slotDeleteFolder );
- connect( sourceModel(), SIGNAL(renameIndex(QModelIndex)),
- SLOT(slotRenameIndex(QModelIndex)) );
+ if( auto m = static_cast<PlaylistBrowserNS::PlaylistBrowserModel*>(sourceModel()) )
+ connect( m, &PlaylistBrowserNS::PlaylistBrowserModel::renameIndex,
+ this, &PlaylistsInFoldersProxy::slotRenameIndex );
}
PlaylistsInFoldersProxy::~PlaylistsInFoldersProxy()
{
}
QVariant
PlaylistsInFoldersProxy::data( const QModelIndex &idx, int role ) const
{
//Turn the QVariantList of the source into a comma separated string, but only for the real items
if( !isGroup( idx ) && idx.column() == PlaylistBrowserNS::PlaylistBrowserModel::ProviderColumn
&& role == Qt::DisplayRole )
{
QVariant indexData = QtGroupingProxy::data( idx, role );
if( indexData.type() != QVariant::List )
return indexData;
QString providerString = indexData.toStringList().join( ", " );
return QVariant( providerString );
}
if( idx.column() == 0 && isGroup( idx ) &&
role == PrettyTreeRoles::DecoratorRole )
{
//whether we use the list from m_deleteFolderAction or m_renameFolderAction does not matter
//they are the same anyway
QPersistentModelIndexList actionList =
m_deleteFolderAction->data().value<QPersistentModelIndexList>();
//make a persistant modelindex since the location of the groups can change while executing
//actions.
actionList << QPersistentModelIndex( idx );
QVariant value = QVariant::fromValue( actionList );
m_deleteFolderAction->setData( value );
m_renameFolderAction->setData( value );
QList<QAction *> actions;
actions << m_renameFolderAction << m_deleteFolderAction;
return QVariant::fromValue( actions );
}
if( idx.column() == 0 && isGroup( idx ) &&
role == PrettyTreeRoles::DecoratorRoleCount )
return 2; // 2 actions, see above
return QtGroupingProxy::data( idx, role );
}
bool
PlaylistsInFoldersProxy::removeRows( int row, int count, const QModelIndex &parent )
{
DEBUG_BLOCK
bool result;
debug() << "in parent " << parent << "remove " << count << " starting at row " << row;
if( !parent.isValid() )
{
QModelIndex folderIdx = index( row, 0, QModelIndex() );
if( isGroup( folderIdx ) )
{
deleteFolder( folderIdx );
return true;
}
//is a playlist not in a folder
QModelIndex childIdx = mapToSource( index( row, 0, m_rootIndex ) );
result = sourceModel()->removeRows( childIdx.row(), count, m_rootIndex );
if( result )
{
beginRemoveRows( parent, row, row + count - 1 );
endRemoveRows();
}
return result;
}
if( isGroup( parent ) )
{
result = true;
for( int i = row; i < row + count; i++ )
{
QModelIndex childIdx = mapToSource( index( i, 0, parent ) );
//set success to false if removeRows returns false
result = sourceModel()->removeRow( childIdx.row(), QModelIndex() ) ? result : false;
}
return result;
}
//removing a track from a playlist
beginRemoveRows( parent, row, row + count - 1 );
QModelIndex originalIdx = mapToSource( parent );
result = sourceModel()->removeRows( row, count, originalIdx );
endRemoveRows();
return result;
}
QStringList
PlaylistsInFoldersProxy::mimeTypes() const
{
QStringList mimeTypes = sourceModel()->mimeTypes();
mimeTypes << AmarokMimeData::PLAYLISTBROWSERGROUP_MIME;
return mimeTypes;
}
QMimeData *
PlaylistsInFoldersProxy::mimeData( const QModelIndexList &indexes ) const
{
DEBUG_BLOCK
AmarokMimeData* mime = new AmarokMimeData();
QModelIndexList sourceIndexes;
foreach( const QModelIndex &idx, indexes )
{
debug() << idx;
if( isGroup( idx ) )
{
debug() << "is a group, add mimeData of all children";
}
else
{
debug() << "is original item, add mimeData from source model";
sourceIndexes << mapToSource( idx );
}
}
if( !sourceIndexes.isEmpty() )
return sourceModel()->mimeData( sourceIndexes );
return mime;
}
bool
PlaylistsInFoldersProxy::dropMimeData( const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent )
{
DEBUG_BLOCK
Q_UNUSED( row );
Q_UNUSED( column );
debug() << "dropped on " << QString("row: %1, column: %2, parent:").arg( row ).arg( column );
debug() << parent;
if( action == Qt::IgnoreAction )
{
debug() << "ignored";
return true;
}
if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) ||
data->hasFormat( AmarokMimeData::PLAYLISTBROWSERGROUP_MIME ) )
{
debug() << "has amarok mime data";
const AmarokMimeData *amarokMime = dynamic_cast<const AmarokMimeData *>(data);
if( amarokMime == 0 )
{
error() << "could not cast to amarokMimeData";
return false;
}
if( !parent.isValid() )
{
debug() << "dropped on the root";
Playlists::PlaylistList playlists = amarokMime->playlists();
foreach( Playlists::PlaylistPtr playlist, playlists )
playlist->setGroups( QStringList() );
buildTree();
return true;
}
if( isGroup( parent ) )
{
debug() << "dropped on a group";
if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) )
{
debug() << "playlist dropped on group";
if( parent.row() < 0 || parent.row() >= rowCount( QModelIndex() ) )
{
debug() << "ERROR: something went seriously wrong in " << __FILE__ << __LINE__;
return false;
}
//apply the new groupname to the source index
QString groupName = parent.data( Qt::DisplayRole ).toString();
//TODO: apply the new groupname to the source index
Playlists::PlaylistList playlists = amarokMime->playlists();
foreach( Playlists::PlaylistPtr playlist, playlists )
playlist->setGroups( QStringList( groupName ) );
buildTree();
return true;
}
else if( data->hasFormat( AmarokMimeData::PLAYLISTBROWSERGROUP_MIME ) )
{
debug() << "playlistgroup dropped on group";
//TODO: multilevel group support
debug() << "ignore drop until we have multilevel group support";
return false;
}
}
}
else
{
QModelIndex sourceIndex = mapToSource( parent );
return sourceModel()->dropMimeData( data, action, row, column,
sourceIndex );
}
return false;
}
Qt::DropActions
PlaylistsInFoldersProxy::supportedDropActions() const
{
//always add MoveAction because playlists can be put into a different group
return sourceModel()->supportedDropActions() | Qt::MoveAction;
}
Qt::DropActions
PlaylistsInFoldersProxy::supportedDragActions() const
{
//always add MoveAction because playlists can be put into a different group
return sourceModel()->supportedDragActions() | Qt::MoveAction;
}
void
PlaylistsInFoldersProxy::setSourceModel( QAbstractItemModel *model )
{
if( sourceModel() )
sourceModel()->disconnect();
QtGroupingProxy::setSourceModel( model );
connect( sourceModel(), SIGNAL(renameIndex(QModelIndex)),
SLOT(slotRenameIndex(QModelIndex)) );
}
void
PlaylistsInFoldersProxy::slotRenameIndex( const QModelIndex &sourceIdx )
{
QModelIndex idx = mapFromSource( sourceIdx );
if( idx.isValid() )
emit renameIndex( idx );
}
void
PlaylistsInFoldersProxy::slotDeleteFolder()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
QPersistentModelIndexList indexes = action->data().value<QPersistentModelIndexList>();
foreach( const QModelIndex &groupIdx, indexes )
deleteFolder( groupIdx );
}
void
PlaylistsInFoldersProxy::slotRenameFolder()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
QPersistentModelIndexList indexes = action->data().value<QPersistentModelIndexList>();
if( indexes.isEmpty() )
return;
//get the name for this new group
//inline rename is handled by the view using setData()
QModelIndex folder = indexes.first();
QString folderName = folder.data( Qt::DisplayRole ).toString();
bool ok;
const QString newName = KInputDialog::getText( i18n("New name"),
i18nc("Enter a new name for a folder that already exists",
"Enter new folder name:"),
folderName,
&ok );
if( !ok || newName == folderName )
return;
setData( folder, newName );
}
void
PlaylistsInFoldersProxy::deleteFolder( const QModelIndex &groupIdx )
{
int childCount = rowCount( groupIdx );
if( childCount > 0 )
{
KDialog dialog;
dialog.setCaption( i18n( "Confirm Delete" ) );
dialog.setButtons( KDialog::Ok | KDialog::Cancel );
QLabel label( i18n( "Are you sure you want to delete this folder and its contents?" )
, &dialog
);
//TODO:include a text area with all the names of the playlists
dialog.setButtonText( KDialog::Ok, i18n( "Yes, delete folder." ) );
dialog.setMainWidget( &label );
if( dialog.exec() != QDialog::Accepted )
return;
removeRows( 0, childCount, groupIdx );
}
removeGroup( groupIdx );
//force a rebuild because groupHash might be incorrect
//TODO: make QtGroupingProxy adjust groupHash keys
buildTree();
}
QModelIndex
PlaylistsInFoldersProxy::createNewFolder( const QString &groupName )
{
RowData data;
ItemData roleData;
roleData.insert( Qt::DisplayRole, groupName );
roleData.insert( Qt::DecorationRole, QVariant( QIcon::fromTheme( "folder" ) ) );
roleData.insert( Qt::EditRole, groupName );
data.insert( 0, roleData );
return addEmptyGroup( data );
}
Qt::ItemFlags PlaylistsInFoldersProxy::flags(const QModelIndex &idx) const
{
if( isGroup(idx) && idx.column() == 0)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable |
Qt::ItemIsDropEnabled;
return QtGroupingProxy::flags(idx);
}
diff --git a/src/browsers/playlistbrowser/PodcastCategory.cpp b/src/browsers/playlistbrowser/PodcastCategory.cpp
index cae625387c..cb1e20ee58 100644
--- a/src/browsers/playlistbrowser/PodcastCategory.cpp
+++ b/src/browsers/playlistbrowser/PodcastCategory.cpp
@@ -1,283 +1,283 @@
/****************************************************************************************
* Copyright (c) 2007-2010 Bart Cerneels <bart.cerneels@kde.org> *
* Copyright (c) 2007-2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2007 Henry de Valence <hdevalence@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PodcastCategory"
#include "PodcastCategory.h"
#include "amarokconfig.h"
#include "amarokurls/AmarokUrl.h"
#include "App.h"
#include "browsers/InfoProxy.h"
#include "core/support/Debug.h"
#include "core/meta/support/MetaUtility.h"
#include "PaletteHandler.h"
#include "PodcastModel.h"
#include "PlaylistBrowserView.h"
#include "widgets/PrettyTreeRoles.h"
#include <QModelIndexList>
#include <QTextBrowser>
#include <QAction>
#include <QIcon>
#include <KStandardDirs>
#include <KUrlRequesterDialog>
#include <KGlobal>
#include <KLocale>
#include <KToolBar>
namespace The
{
PlaylistBrowserNS::PodcastCategory* podcastCategory()
{
return PlaylistBrowserNS::PodcastCategory::instance();
}
}
using namespace PlaylistBrowserNS;
QString PodcastCategory::s_configGroup( "Podcast View" );
PodcastCategory* PodcastCategory::s_instance = 0;
PodcastCategory*
PodcastCategory::instance()
{
return s_instance ? s_instance : new PodcastCategory( 0 );
}
void
PodcastCategory::destroy()
{
if( s_instance )
{
delete s_instance;
s_instance = 0;
}
}
PodcastCategory::PodcastCategory( QWidget *parent )
: PlaylistBrowserCategory( Playlists::PodcastChannelPlaylist,
"podcasts",
s_configGroup,
The::podcastModel(),
parent )
{
setPrettyName( i18n( "Podcasts" ) );
setShortDescription( i18n( "List of podcast subscriptions and episodes" ) );
setIcon( QIcon::fromTheme( "podcast-amarok" ) );
setLongDescription( i18n( "Manage your podcast subscriptions and browse individual episodes. "
"Downloading episodes to the disk is also done here, or you can tell "
"Amarok to do this automatically." ) );
setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_podcasts.png" ) );
// set background
if( AmarokConfig::showBrowserBackgroundImage() )
setBackgroundImage( imagePath() );
QAction *addPodcastAction = new QAction( QIcon::fromTheme( "list-add-amarok" ), i18n("&Add Podcast"),
m_toolBar );
addPodcastAction->setPriority( QAction::NormalPriority );
m_toolBar->insertAction( m_separator, addPodcastAction );
- connect( addPodcastAction, SIGNAL(triggered(bool)), The::podcastModel(), SLOT(addPodcast()) );
+ connect( addPodcastAction, &QAction::triggered, The::podcastModel(), &PodcastModel::addPodcast );
QAction *updateAllAction = new QAction( QIcon::fromTheme("view-refresh-amarok"), QString(), m_toolBar );
updateAllAction->setToolTip( i18n("&Update All") );
updateAllAction->setPriority( QAction::LowPriority );
m_toolBar->insertAction( m_separator, updateAllAction );
- connect( updateAllAction, SIGNAL(triggered(bool)),
- The::podcastModel(), SLOT(refreshPodcasts()) );
+ connect( updateAllAction, &QAction::triggered,
+ The::podcastModel(), &PodcastModel::refreshPodcasts );
QAction *importOpmlAction = new QAction( QIcon::fromTheme("document-import")
, i18n( "Import OPML File" )
, m_toolBar
);
importOpmlAction->setToolTip( i18n( "Import OPML File" ) );
importOpmlAction->setPriority( QAction::LowPriority );
m_toolBar->addAction( importOpmlAction );
- connect( importOpmlAction, SIGNAL(triggered()), SLOT(slotImportOpml()) );
+ connect( importOpmlAction, &QAction::triggered, this, &PodcastCategory::slotImportOpml );
PlaylistBrowserView *view = static_cast<PlaylistBrowserView*>( playlistView() );
- connect( view, SIGNAL(currentItemChanged(QModelIndex)), SLOT(showInfo(QModelIndex)) );
+ connect( view, &PlaylistBrowserView::currentItemChanged, this, &PodcastCategory::showInfo );
//transparency
// QPalette p = m_podcastTreeView->palette();
// QColor c = p.color( QPalette::Base );
// c.setAlpha( 0 );
// p.setColor( QPalette::Base, c );
//
// c = p.color( QPalette::AlternateBase );
// c.setAlpha( 77 );
// p.setColor( QPalette::AlternateBase, c );
//
// m_podcastTreeView->setPalette( p );
//
// QSizePolicy sizePolicy1(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
// sizePolicy1.setHorizontalStretch(0);
// sizePolicy1.setVerticalStretch(0);
// sizePolicy1.setHeightForWidth(m_podcastTreeView->sizePolicy().hasHeightForWidth());
// m_podcastTreeView->setSizePolicy(sizePolicy1);
}
PodcastCategory::~PodcastCategory()
{
}
void
PodcastCategory::showInfo( const QModelIndex &index )
{
if( !index.isValid() )
return;
const int row = index.row();
QString description;
QString title( index.data( Qt::DisplayRole ).toString() );
QString subtitle( index.sibling( row, SubtitleColumn ).data( Qt::DisplayRole ).toString() );
QUrl imageUrl( qvariant_cast<QUrl>(
index.sibling( row, ImageColumn ).data( Qt::DisplayRole )
) );
QString author( index.sibling( row, AuthorColumn ).data( Qt::DisplayRole ).toString() );
QStringList keywords( qvariant_cast<QStringList>(
index.sibling( row, KeywordsColumn ).data( Qt::DisplayRole )
) );
bool isEpisode = index.sibling( row, IsEpisodeColumn ).data( Qt::DisplayRole ).toBool();
QString authorAndPubDate;
if( !author.isEmpty() )
{
authorAndPubDate = QString( "<b>%1</b> %2 " )
.arg( i18n( "By" ) )
.arg( Qt::escape( author ) );
}
if( !subtitle.isEmpty() )
{
description += QString( "<h1 class=\"subtitle\">%1</h1>" )
.arg( Qt::escape( subtitle ) );
}
if( !imageUrl.isEmpty() )
{
description += QString( "<p style=\"float:right;\"><img src=\"%1\" onclick=\""
"if (this.style.width=='150px') {"
"this.style.width='auto';"
"this.style.marginLeft='0em';"
"this.style.cursor='-webkit-zoom-out';"
"this.parentNode.style.float='inherit';"
"this.parentNode.style.textAlign='center';"
"} else {"
"this.style.width='150px';"
"this.style.marginLeft='1em';"
"this.style.cursor='-webkit-zoom-in';"
"this.parentNode.style.float='right';"
"this.parentNode.style.textAlign='inherit';"
"}\""
" style=\"width: 150px; margin-left: 1em;"
" margin-right: 0em; cursor: -webkit-zoom-in;\""
"/></p>" )
.arg( Qt::escape( imageUrl.url() ) );
}
if( isEpisode )
{
QDateTime pubDate( index.sibling( row, DateColumn ).data( Qt::DisplayRole ).toDateTime() );
if( pubDate.isValid() )
{
authorAndPubDate += QString( "<b>%1</b> %2" )
.arg( i18nc( "Podcast published on date", "On" ) )
.arg( KGlobal::locale()->formatDateTime( pubDate, KLocale::FancyShortDate ) );
}
}
if( !authorAndPubDate.isEmpty() )
{
description += QString( "<p>%1</p>" )
.arg( authorAndPubDate );
}
if( isEpisode )
{
int fileSize = index.sibling( row, FilesizeColumn ).data( Qt::DisplayRole ).toInt();
if( fileSize != 0 )
{
description += QString( "<p><b>%1</b> %2</p>" )
.arg( i18n( "File Size:" ) )
.arg( Meta::prettyFilesize( fileSize ) );
}
}
else
{
QDate subsDate( index.sibling( row, DateColumn ).data( Qt::DisplayRole ).toDate() );
if( subsDate.isValid() )
{
description += QString( "<p><b>%1</b> %2</p>" )
.arg( i18n( "Subscription Date:" ) )
.arg( KGlobal::locale()->formatDate( subsDate, KLocale::FancyShortDate ) );
}
}
if( !keywords.isEmpty() )
{
description += QString( "<p><b>%1</b> %2</p>" )
.arg( i18n( "Keywords:" ) )
.arg( Qt::escape( keywords.join( ", " ) ) );
}
description += index.data( PrettyTreeRoles::ByLineRole ).toString();
description = QString(
"<html>"
" <head>"
" <title>%1</title>"
" <style type=\"text/css\">"
"body {color: %3;}"
"::selection {background-color: %4;}"
"h1 {text-align:center; font-size: 1.2em;}"
"h1.subtitle {text-align:center; font-size: 1em; font-weight: normal;}"
" </style>"
" </head>"
" <body>"
" <h1>%1</h1>"
" %2"
" </body>"
"</html>")
.arg( Qt::escape( title ) )
.arg( description )
.arg( App::instance()->palette().brush( QPalette::Text ).color().name() )
.arg( PaletteHandler::highlightColor().name() );
QVariantMap map;
map["service_name"] = title;
map["main_info"] = description;
The::infoProxy()->setInfo( map );
}
void
PodcastCategory::slotImportOpml()
{
AmarokUrl( "amarok://service-podcastdirectory/addOpml" ).run();
}
diff --git a/src/browsers/playlistbrowser/QtGroupingProxy.cpp b/src/browsers/playlistbrowser/QtGroupingProxy.cpp
index b04e118aef..c5803e917b 100644
--- a/src/browsers/playlistbrowser/QtGroupingProxy.cpp
+++ b/src/browsers/playlistbrowser/QtGroupingProxy.cpp
@@ -1,897 +1,898 @@
/****************************************************************************************
* Copyright (c) 2007-2011 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "QtGroupingProxy.h"
#include <QDebug>
#include <QIcon>
#include <QInputDialog>
#include <QTimer>
/*!
\class QtGroupingProxy
\brief The QtGroupingProxy class will group source model rows by adding a new top tree-level.
The source model can be flat or tree organized, but only the original top level rows are used
for determining the grouping.
\ingroup model-view
*/
QtGroupingProxy::QtGroupingProxy( QObject *parent )
: QAbstractProxyModel( parent )
{
}
QtGroupingProxy::QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootIndex,
int groupedColumn, QObject *parent )
: QAbstractProxyModel( parent )
, m_rootIndex( rootIndex )
, m_groupedColumn( 0 )
{
setSourceModel( model );
if( groupedColumn != -1 )
setGroupedColumn( groupedColumn );
}
QtGroupingProxy::~QtGroupingProxy()
{
}
void
QtGroupingProxy::setSourceModel( QAbstractItemModel *sourceModel )
{
QAbstractProxyModel::setSourceModel( sourceModel );
// signal proxies
- connect( sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- SLOT(modelDataChanged(QModelIndex,QModelIndex)) );
- connect( sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
- SLOT(modelRowsInserted(QModelIndex,int,int)) );
- connect( sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
- SLOT(modelRowsAboutToBeInserted(QModelIndex,int,int)));
- connect( sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
- SLOT(modelRowsRemoved(QModelIndex,int,int)) );
- connect( sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
- SLOT(modelRowsAboutToBeRemoved(QModelIndex,int,int)) );
- connect( sourceModel, SIGNAL(layoutChanged()), SLOT(buildTree()) );
- connect( sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- SLOT(modelDataChanged(QModelIndex,QModelIndex)) );
+ connect( sourceModel, &QAbstractItemModel::dataChanged,
+ this, &QtGroupingProxy::modelDataChanged );
+ connect( sourceModel, &QAbstractItemModel::rowsInserted,
+ this, &QtGroupingProxy::modelRowsInserted );
+ connect( sourceModel, &QAbstractItemModel::rowsAboutToBeInserted,
+ this, &QtGroupingProxy::modelRowsAboutToBeInserted );
+ connect( sourceModel, &QAbstractItemModel::rowsRemoved,
+ this, &QtGroupingProxy::modelRowsRemoved );
+ connect( sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved,
+ this, &QtGroupingProxy::modelRowsAboutToBeRemoved );
+ connect( sourceModel, &QAbstractItemModel::layoutChanged,
+ this, &QtGroupingProxy::buildTree );
+ connect( sourceModel, &QAbstractItemModel::dataChanged,
+ this, &QtGroupingProxy::modelDataChanged );
//set invalid index from source as root index
m_rootIndex = sourceModel->index( -1, -1 );
}
void
QtGroupingProxy::setRootIndex( const QModelIndex &rootIndex )
{
if( m_rootIndex == rootIndex )
return;
m_rootIndex = rootIndex;
//TODO: invalidate tree so buildTree() can be called later.
}
void
QtGroupingProxy::setGroupedColumn( int groupedColumn )
{
m_groupedColumn = groupedColumn;
//TODO: invalidate tree so buildTree() can be called later.
buildTree();
}
/** Maps to what groups the source row belongs by returning the data of those groups.
*
* @returns a list of data for the rows the argument belongs to. In common cases this list will
* contain only one entry. An empty list means that the source item will be placed in the root of
* this proxyModel. There is no support for hiding source items.
*
* Group data can be pre-loaded in the return value so it's added to the cache maintained by this
* class. This is required if you want to have data that is not present in the source model.
*/
QList<RowData>
QtGroupingProxy::belongsTo( const QModelIndex &idx )
{
//qDebug() << __FILE__ << __FUNCTION__;
QList<RowData> rowDataList;
//get all the data for this index from the model
ItemData itemData = sourceModel()->itemData( idx );
QMapIterator<int, QVariant> i( itemData );
while( i.hasNext() )
{
i.next();
int role = i.key();
QVariant variant = i.value();
// qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant;
if( variant.type() == QVariant::List )
{
//a list of variants get's expanded to multiple rows
QVariantList list = variant.toList();
for( int i = 0; i < list.length(); i++ )
{
//take an existing row data or create a new one
RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt( i )
: RowData();
//we only gather data for the first column
ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData();
indexData.insert( role, list.value( i ) );
rowData.insert( 0, indexData );
//for the grouped column the data should not be gathered from the children
//this will allow filtering on the content of this column with a
//QSortFilterProxyModel
rowData.insert( m_groupedColumn, indexData );
rowDataList.insert( i, rowData );
}
}
else if( !variant.isNull() )
{
//it's just a normal item. Copy all the data and break this loop.
RowData rowData;
rowData.insert( 0, itemData );
rowDataList << rowData;
break;
}
}
return rowDataList;
}
/* m_groupHash layout
* key : index of the group in m_groupMaps
* value : a QList of the original rows in sourceModel() for the children of this group
*
* key = -1 contains a QList of the non-grouped indexes
*
* TODO: sub-groups
*/
void
QtGroupingProxy::buildTree()
{
if( !sourceModel() )
return;
beginResetModel();
m_groupHash.clear();
//don't clear the data maps since most of it will probably be needed again.
m_parentCreateList.clear();
int max = sourceModel()->rowCount( m_rootIndex );
//qDebug() << QString("building tree with %1 leafs.").arg( max );
//WARNING: these have to be added in order because the addToGroups function is optimized for
//modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best.
for( int row = 0; row < max; row++ )
{
QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootIndex );
addSourceRow( idx );
}
// dumpGroups();
endResetModel();
}
QList<int>
QtGroupingProxy::addSourceRow( const QModelIndex &idx )
{
QList<int> updatedGroups;
QList<RowData> groupData = belongsTo( idx );
//an empty list here means it's supposed to go in root.
if( groupData.isEmpty() )
{
updatedGroups << -1;
if( !m_groupHash.keys().contains( -1 ) )
m_groupHash.insert( -1, QList<int>() ); //add an empty placeholder
}
//an item can be in multiple groups
foreach( RowData data, groupData )
{
int updatedGroup = -1;
if( !data.isEmpty() )
{
// qDebug() << QString("index %1 belongs to group %2").arg( row )
// .arg( data[0][Qt::DisplayRole].toString() );
foreach( const RowData &cachedData, m_groupMaps )
{
//when this matches the index belongs to an existing group
if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] )
{
data = cachedData;
break;
}
}
updatedGroup = m_groupMaps.indexOf( data );
//-1 means not found
if( updatedGroup == -1 )
{
QModelIndex newGroupIdx = addEmptyGroup( data );
updatedGroup = newGroupIdx.row();
}
if( !m_groupHash.keys().contains( updatedGroup ) )
m_groupHash.insert( updatedGroup, QList<int>() ); //add an empty placeholder
}
if( !updatedGroups.contains( updatedGroup ) )
updatedGroups << updatedGroup;
}
//update m_groupHash to the new source-model layout (one row added)
QMutableHashIterator<quint32, QList<int> > i( m_groupHash );
while( i.hasNext() )
{
i.next();
QList<int> &groupList = i.value();
int insertedProxyRow = groupList.count();
for( ; insertedProxyRow > 0 ; insertedProxyRow-- )
{
int &rowValue = groupList[insertedProxyRow-1];
if( idx.row() <= rowValue )
{
//increment the rows that come after the new row since they moved one place up.
rowValue++;
}
else
{
break;
}
}
if( updatedGroups.contains( i.key() ) )
{
//the row needs to be added to this group
beginInsertRows( index( i.key() ), insertedProxyRow, insertedProxyRow );
groupList.insert( insertedProxyRow, idx.row() );
endInsertRows();
}
}
return updatedGroups;
}
/** Each ModelIndex has in it's internalId a position in the parentCreateList.
* struct ParentCreate are the instructions to recreate the parent index.
* It contains the proxy row number of the parent and the postion in this list of the grandfather.
* This function creates the ParentCreate structs and saves them in a list.
*/
int
QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const
{
if( !parent.isValid() )
return -1;
struct ParentCreate pc;
for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
{
pc = m_parentCreateList[i];
if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() )
return i;
}
//there is no parentCreate yet for this index, so let's create one.
pc.parentCreateIndex = parent.internalId();
pc.row = parent.row();
m_parentCreateList << pc;
//dumpParentCreateList();
// qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() );
// for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
// {
// qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex <<
// " | " << m_parentCreateList[i].row;
// }
return m_parentCreateList.size() - 1;
}
QModelIndex
QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
{
// qDebug() << "index requested for: (" << row << "," << column << "), " << parent;
if( !hasIndex(row, column, parent) )
return QModelIndex();
if( parent.column() > 0 )
return QModelIndex();
/* We save the instructions to make the parent of the index in a struct.
* The place of the struct in the list is stored in the internalId
*/
int parentCreateIndex = indexOfParentCreate( parent );
return createIndex( row, column, parentCreateIndex );
}
QModelIndex
QtGroupingProxy::parent( const QModelIndex &index ) const
{
//qDebug() << "parent: " << index;
if( !index.isValid() )
return QModelIndex();
int parentCreateIndex = index.internalId();
//qDebug() << "parentCreateIndex: " << parentCreateIndex;
if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() )
return QModelIndex();
struct ParentCreate pc = m_parentCreateList[parentCreateIndex];
//qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")";
//only items at column 0 have children
return createIndex( pc.row, 0, pc.parentCreateIndex );
}
int
QtGroupingProxy::rowCount( const QModelIndex &index ) const
{
//qDebug() << "rowCount: " << index;
if( !index.isValid() )
{
//the number of top level groups + the number of non-grouped playlists
int rows = m_groupMaps.count() + m_groupHash.value( -1 ).count();
//qDebug() << rows << " in root group";
return rows;
}
//TODO:group in group support.
if( isGroup( index ) )
{
qint64 groupIndex = index.row();
int rows = m_groupHash.value( groupIndex ).count();
//qDebug() << rows << " in group " << m_groupMaps[groupIndex];
return rows;
}
QModelIndex originalIndex = mapToSource( index );
int rowCount = sourceModel()->rowCount( originalIndex );
//qDebug() << "original item: rowCount == " << rowCount;
return rowCount;
}
int
QtGroupingProxy::columnCount( const QModelIndex &index ) const
{
if( !index.isValid() )
return sourceModel()->columnCount( m_rootIndex );
if( index.column() != 0 )
return 0;
return sourceModel()->columnCount( mapToSource( index ) );
}
QVariant
QtGroupingProxy::data( const QModelIndex &index, int role ) const
{
if( !index.isValid() )
return sourceModel()->data( m_rootIndex, role ); //rootNode could have useful data
//qDebug() << __FUNCTION__ << index << " role: " << role;
int row = index.row();
int column = index.column();
if( isGroup( index ) )
{
//qDebug() << __FUNCTION__ << "is a group";
//use cached or precalculated data
if( m_groupMaps[row][column].contains( role ) )
{
//qDebug() << "Using cached data";
return m_groupMaps[row][column].value( role );
}
//for column 0 we gather data from the grouped column instead
if( column == 0 )
column = m_groupedColumn;
//map all data from children to columns of group to allow grouping one level up
QVariantList variantsOfChildren;
int childCount = m_groupHash.value( row ).count();
if( childCount == 0 )
return QVariant();
//qDebug() << __FUNCTION__ << "childCount: " << childCount;
//Need a parentIndex with column == 0 because only those have children.
QModelIndex parentIndex = this->index( row, 0, index.parent() );
for( int childRow = 0; childRow < childCount; childRow++ )
{
QModelIndex childIndex = this->index( childRow, column, parentIndex );
QVariant data = mapToSource( childIndex ).data( role );
//qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type());
if( data.isValid() && !variantsOfChildren.contains( data ) )
variantsOfChildren << data;
}
//qDebug() << "gathered this data from children: " << variantsOfChildren;
//saving in cache
ItemData roleMap = m_groupMaps[row].value( column );
foreach( const QVariant &variant, variantsOfChildren )
{
if( roleMap[ role ] != variant )
roleMap.insert( role, variantsOfChildren );
}
//qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role];
//only one unique variant? No need to return a list
if( variantsOfChildren.count() == 1 )
return variantsOfChildren.first();
if( variantsOfChildren.count() == 0 )
return QVariant();
return variantsOfChildren;
}
return mapToSource( index ).data( role );
}
bool
QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role )
{
if( !idx.isValid() )
return false;
//no need to set data to exactly the same value
if( idx.data( role ) == value )
return false;
if( isGroup( idx ) )
{
ItemData columnData = m_groupMaps[idx.row()][idx.column()];
columnData.insert( role, value );
//QItemDelegate will always use Qt::EditRole
if( role == Qt::EditRole )
columnData.insert( Qt::DisplayRole, value );
//and make sure it's stored in the map
m_groupMaps[idx.row()].insert( idx.column(), columnData );
int columnToChange = idx.column() ? idx.column() : m_groupedColumn;
foreach( int originalRow, m_groupHash.value( idx.row() ) )
{
QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange,
m_rootIndex );
if( childIdx.isValid() )
sourceModel()->setData( childIdx, value, role );
}
//TODO: we might need to reload the data from the children at this point
emit dataChanged( idx, idx );
return true;
}
return sourceModel()->setData( mapToSource( idx ), value, role );
}
bool
QtGroupingProxy::isGroup( const QModelIndex &index ) const
{
int parentCreateIndex = index.internalId();
if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() )
return true;
return false;
}
QModelIndex
QtGroupingProxy::mapToSource( const QModelIndex &index ) const
{
//qDebug() << "mapToSource: " << index;
if( !index.isValid() )
return m_rootIndex;
if( isGroup( index ) )
{
//qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString();
return m_rootIndex;
}
QModelIndex proxyParent = index.parent();
//qDebug() << "parent: " << proxyParent;
QModelIndex originalParent = mapToSource( proxyParent );
//qDebug() << "originalParent: " << originalParent;
int originalRow = index.row();
if( originalParent == m_rootIndex )
{
int indexInGroup = index.row();
if( !proxyParent.isValid() )
indexInGroup -= m_groupMaps.count();
//qDebug() << "indexInGroup" << indexInGroup;
QList<int> childRows = m_groupHash.value( proxyParent.row() );
if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 )
return QModelIndex();
originalRow = childRows.at( indexInGroup );
//qDebug() << "originalRow: " << originalRow;
}
return sourceModel()->index( originalRow, index.column(), originalParent );
}
QModelIndexList
QtGroupingProxy::mapToSource( const QModelIndexList& list ) const
{
QModelIndexList originalList;
foreach( const QModelIndex &index, list )
{
QModelIndex originalIndex = mapToSource( index );
if( originalIndex.isValid() )
originalList << originalIndex;
}
return originalList;
}
QModelIndex
QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const
{
if( !idx.isValid() )
return QModelIndex();
QModelIndex proxyParent;
QModelIndex sourceParent = idx.parent();
//qDebug() << "sourceParent: " << sourceParent;
int proxyRow = idx.row();
int sourceRow = idx.row();
if( sourceParent.isValid() && ( sourceParent != m_rootIndex ) )
{
//idx is a child of one of the items in the source model
proxyParent = mapFromSource( sourceParent );
}
else
{
//idx is an item in the top level of the source model (child of the rootnode)
int groupRow = -1;
QHashIterator<quint32, QList<int> > iterator( m_groupHash );
while( iterator.hasNext() )
{
iterator.next();
if( iterator.value().contains( sourceRow ) )
{
groupRow = iterator.key();
break;
}
}
if( groupRow != -1 ) //it's in a group, let's find the correct row.
{
proxyParent = this->index( groupRow, 0, QModelIndex() );
proxyRow = m_groupHash.value( groupRow ).indexOf( sourceRow );
}
else
{
proxyParent = QModelIndex();
// if the proxy item is not in a group it will be below the groups.
int groupLength = m_groupMaps.count();
//qDebug() << "groupNames length: " << groupLength;
int i = m_groupHash.value( -1 ).indexOf( sourceRow );
//qDebug() << "index in hash: " << i;
proxyRow = groupLength + i;
}
}
//qDebug() << "proxyParent: " << proxyParent;
//qDebug() << "proxyRow: " << proxyRow;
return this->index( proxyRow, 0, proxyParent );
}
Qt::ItemFlags
QtGroupingProxy::flags( const QModelIndex &idx ) const
{
if( !idx.isValid() )
{
Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootIndex );
if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) )
return Qt::ItemFlags( Qt::ItemIsDropEnabled );
return 0;
}
//only if the grouped column has the editable flag set allow the
//actions leading to setData on the source (edit & drop)
// qDebug() << idx;
if( isGroup( idx ) )
{
// dumpGroups();
Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
bool groupIsEditable = true;
//it's possible to have empty groups
if( m_groupHash.value( idx.row() ).count() == 0 )
{
//check the flags of this column with the root node
QModelIndex originalRootNode = sourceModel()->index( m_rootIndex.row(), m_groupedColumn,
m_rootIndex.parent() );
groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable );
}
else
{
foreach( int originalRow, m_groupHash.value( idx.row() ) )
{
QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn,
m_rootIndex );
// qDebug() << "originalIdx: " << originalIdx;
groupIsEditable = groupIsEditable
? originalIdx.flags().testFlag( Qt::ItemIsEditable )
: false;
if( !groupIsEditable ) //all children need to have an editable grouped column
break;
}
}
if( groupIsEditable )
return ( defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled );
return defaultFlags;
}
QModelIndex originalIdx = mapToSource( idx );
Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx );
//check the source model to see if the grouped column is editable;
QModelIndex groupedColumnIndex =
sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() );
bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable );
if( groupIsEditable )
return originalItemFlags | Qt::ItemIsDragEnabled;
return originalItemFlags;
}
QModelIndex
QtGroupingProxy::buddy( const QModelIndex &index ) const
{
/* We need to override this method in case of groups. Otherwise, at least editing
* of groups is prevented, following sequence occurs:
*
* #0 QtGroupingProxy::mapToSource (this=0x15ad8a0, index=...) at /home/strohel/projekty/amarok/src/browsers/playlistbrowser/QtGroupingProxy.cpp:492
* #1 0x00007ffff609d7b6 in QAbstractProxyModel::buddy (this=0x15ad8a0, index=...) at itemviews/qabstractproxymodel.cpp:306
* #2 0x00007ffff609ed25 in QSortFilterProxyModel::buddy (this=0x15ae730, index=...) at itemviews/qsortfilterproxymodel.cpp:2015
* #3 0x00007ffff6012a2c in QAbstractItemView::edit (this=0x15aec30, index=..., trigger=QAbstractItemView::AllEditTriggers, event=0x0) at itemviews/qabstractitemview.cpp:2569
* #4 0x00007ffff6f9aa9f in Amarok::PrettyTreeView::edit (this=0x15aec30, index=..., trigger=QAbstractItemView::AllEditTriggers, event=0x0)
* at /home/strohel/projekty/amarok/src/widgets/PrettyTreeView.cpp:58
* #5 0x00007ffff6007f1e in QAbstractItemView::edit (this=0x15aec30, index=...) at itemviews/qabstractitemview.cpp:1138
* #6 0x00007ffff6dc86e4 in PlaylistBrowserNS::PlaylistBrowserCategory::createNewFolder (this=0x159bf90)
* at /home/strohel/projekty/amarok/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp:298
*
* but we return invalid index in mapToSource() for group index.
*/
if( index.isValid() && isGroup( index ) )
return index;
return QAbstractProxyModel::buddy( index );
}
QVariant
QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const
{
return sourceModel()->headerData( section, orientation, role );
}
bool
QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const
{
if( !parent.isValid() )
return false;
if( isGroup( parent ) )
return false;
return sourceModel()->canFetchMore( mapToSource( parent ) );
}
void
QtGroupingProxy::fetchMore ( const QModelIndex & parent )
{
if( !parent.isValid() )
return;
if( isGroup( parent ) )
return;
return sourceModel()->fetchMore( mapToSource( parent ) );
}
QModelIndex
QtGroupingProxy::addEmptyGroup( const RowData &data )
{
int newRow = m_groupMaps.count();
beginInsertRows( QModelIndex(), newRow, newRow );
m_groupMaps << data;
endInsertRows();
return index( newRow, 0, QModelIndex() );
}
bool
QtGroupingProxy::removeGroup( const QModelIndex &idx )
{
beginRemoveRows( idx.parent(), idx.row(), idx.row() );
m_groupHash.remove( idx.row() );
m_groupMaps.removeAt( idx.row() );
m_parentCreateList.removeAt( idx.internalId() );
endRemoveRows();
//TODO: only true if all data could be unset.
return true;
}
bool
QtGroupingProxy::hasChildren( const QModelIndex &parent ) const
{
if( !parent.isValid() )
return true;
if( isGroup( parent ) )
return !m_groupHash.value( parent.row() ).isEmpty();
return sourceModel()->hasChildren( mapToSource( parent ) );
}
void
QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end )
{
if( parent != m_rootIndex )
{
//an item will be added to an original index, remap and pass it on
QModelIndex proxyParent = mapFromSource( parent );
beginInsertRows( proxyParent, start, end );
}
}
void
QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end )
{
if( parent == m_rootIndex )
{
//top level of the model changed, these new rows need to be put in groups
for( int modelRow = start; modelRow <= end ; modelRow++ )
{
addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootIndex ) );
}
}
else
{
//beginInsertRows had to be called in modelRowsAboutToBeInserted()
endInsertRows();
}
}
void
QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end )
{
if( parent == m_rootIndex )
{
QHash<quint32, QList<int> >::const_iterator i;
//HACK, we are going to call beginRemoveRows() multiple times without
// endRemoveRows() if a source index is in multiple groups.
// This can be a problem for some views/proxies, but Q*Views can handle it.
// TODO: investigate a queue for applying proxy model changes in the correct order
for( i = m_groupHash.constBegin(); i != m_groupHash.constEnd(); ++i )
{
int groupIndex = i.key();
const QList<int> &groupList = i.value();
QModelIndex proxyParent = index( groupIndex, 0 );
foreach( int originalRow, groupList )
{
if( originalRow >= start && originalRow <= end )
{
int proxyRow = groupList.indexOf( originalRow );
if( groupIndex == -1 ) //adjust for non-grouped (root level) original items
proxyRow += m_groupMaps.count();
//TODO: optimize for continues original rows in the same group
beginRemoveRows( proxyParent, proxyRow, proxyRow );
}
}
}
}
else
{
//child item(s) of an original item will be removed, remap and pass it on
// qDebug() << parent;
QModelIndex proxyParent = mapFromSource( parent );
// qDebug() << proxyParent;
beginRemoveRows( proxyParent, start, end );
}
}
void
QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end )
{
if( parent == m_rootIndex )
{
//TODO: can be optimised by iterating over m_groupHash and checking start <= r < end
//rather than increasing i we change the stored sourceRows in-place and reuse argument start
//X-times (where X = end - start).
for( int i = start; i <= end; i++ )
{
//HACK: we are going to iterate the hash in reverse so calls to endRemoveRows()
// are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved()
//NOTE: easier to do reverse with java style iterator
QMutableHashIterator<quint32, QList<int> > it( m_groupHash );
it.toBack();
while( it.hasPrevious() )
{
it.previous();
int groupIndex = it.key();
//has to be a modifiable reference for remove and replace operations
QList<int> &groupList = it.value();
int rowIndex = groupList.indexOf( start );
if( rowIndex != -1 )
{
QModelIndex proxyParent = index( groupIndex, 0 );
groupList.removeAt( rowIndex );
}
//Now decrement all source rows that are after the removed row
for( int j = 0; j < groupList.count(); j++ )
{
int sourceRow = groupList.at( j );
if( sourceRow > start )
groupList.replace( j, sourceRow-1 );
}
if( rowIndex != -1)
endRemoveRows(); //end remove operation only after group was updated.
}
}
return;
}
//beginRemoveRows had to be called in modelRowsAboutToBeRemoved();
endRemoveRows();
}
void
QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
{
//TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly
QModelIndex proxyTopLeft = mapFromSource( topLeft );
if( !proxyTopLeft.isValid() )
return;
if( topLeft == bottomRight )
{
emit dataChanged( proxyTopLeft, proxyTopLeft );
}
else
{
QModelIndex proxyBottomRight = mapFromSource( bottomRight );
emit dataChanged( proxyTopLeft, proxyBottomRight );
}
}
bool
QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const
{
foreach( const QModelIndex &index, list )
{
if( isGroup( index ) )
return true;
}
return false;
}
void
QtGroupingProxy::dumpGroups() const
{
qDebug() << "m_groupHash: ";
for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ )
{
qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex );
}
qDebug() << "m_groupMaps: ";
for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ )
qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex );
qDebug() << m_groupHash.value( -1 );
}
diff --git a/src/browsers/servicebrowser/ServiceBrowser.cpp b/src/browsers/servicebrowser/ServiceBrowser.cpp
index e4dfd00011..c26d043c5a 100644
--- a/src/browsers/servicebrowser/ServiceBrowser.cpp
+++ b/src/browsers/servicebrowser/ServiceBrowser.cpp
@@ -1,75 +1,76 @@
/****************************************************************************************
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ServiceBrowser.h"
#include "core/support/Debug.h"
#include <KStandardDirs>
ServiceBrowser * ServiceBrowser::s_instance = 0;
ServiceBrowser * ServiceBrowser::instance()
{
if ( s_instance == 0 )
s_instance = new ServiceBrowser( "internet" );
return s_instance;
}
ServiceBrowser::ServiceBrowser( const QString& name, QWidget* parent )
: BrowserCategoryList( name, parent, true )
{
debug() << "ServiceBrowser starting...";
setLongDescription( i18n( "The Internet browser lets you browse online sources of content that integrates directly into Amarok. Amarok ships with a number of these sources, but many more can be added using scripts." ) );
setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_internet.png" ) );
}
ServiceBrowser::~ServiceBrowser()
{
DEBUG_BLOCK
}
//TODO: This should be moved to the ScriptableServiceManager instead
void
ServiceBrowser::setScriptableServiceManager( ScriptableServiceManager * scriptableServiceManager )
{
m_scriptableServiceManager = scriptableServiceManager;
m_scriptableServiceManager->setParent( this );
- connect ( m_scriptableServiceManager, SIGNAL(addService(ServiceBase*)), this, SLOT(addService(ServiceBase*)) );
+ connect ( m_scriptableServiceManager, &ScriptableServiceManager::addService,
+ this, &ServiceBrowser::addService );
}
void
ServiceBrowser::resetService( const QString &name )
{
//What in the world is this for...
//Currently unused, but needed, in the future, for resetting a service based on config changes
//or the user choosing to reset the state of the service somehow.
Q_UNUSED( name );
}
void ServiceBrowser::addService( ServiceBase * service )
{
DEBUG_BLOCK
addCategory( service );
}
diff --git a/src/configdialog/ConfigDialog.cpp b/src/configdialog/ConfigDialog.cpp
index 56cf13290c..13065a168c 100644
--- a/src/configdialog/ConfigDialog.cpp
+++ b/src/configdialog/ConfigDialog.cpp
@@ -1,229 +1,229 @@
/****************************************************************************************
* Copyright (c) 2004-2008 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ConfigDialog"
#include "ConfigDialog.h"
#include "amarokconfig.h"
#include "configdialog/dialogs/CollectionConfig.h"
#include "configdialog/dialogs/DatabaseConfig.h"
#include "configdialog/dialogs/GeneralConfig.h"
#include "configdialog/dialogs/MetadataConfig.h"
#include "configdialog/dialogs/NotificationsConfig.h"
#include "configdialog/dialogs/PlaybackConfig.h"
#include "configdialog/dialogs/PluginsConfig.h"
#include "configdialog/dialogs/ScriptsConfig.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <KLocalizedString>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
QString Amarok2ConfigDialog::s_currentPage = "GeneralConfig";
//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC
//////////////////////////////////////////////////////////////////////////////////////////
Amarok2ConfigDialog::Amarok2ConfigDialog( QWidget *parent, const char* name, KConfigSkeleton *config )
: KConfigDialog( parent, name, config )
{
DEBUG_BLOCK
setAttribute( Qt::WA_DeleteOnClose );
ConfigDialogBase *general = new GeneralConfig( this );
ConfigDialogBase *collection = new CollectionConfig( this );
ConfigDialogBase *metadata = new MetadataConfig( this );
ConfigDialogBase *playback = new PlaybackConfig( this );
ConfigDialogBase *notify = new NotificationsConfig( this );
ConfigDialogBase *database = new DatabaseConfig( this, config );
ConfigDialogBase *plugins = new PluginsConfig( this );
ConfigDialogBase *scripts = new ScriptsConfig( this );
//ConfigDialogBase* mediadevice = new MediadeviceConfig( this );
addPage( general, i18nc( "Miscellaneous settings", "General" ), "preferences-other-amarok", i18n( "Configure General Options" ) );
addPage( collection, i18n( "Local Collection" ), "drive-harddisk", i18n( "Configure Local Collection" ) );
addPage( metadata, i18n( "Metadata" ), "amarok_playcount", i18n( "Configure Metadata Handling" ) );
addPage( playback, i18n( "Playback" ), "preferences-media-playback-amarok", i18n( "Configure Playback" ) );
addPage( notify, i18n( "Notifications" ), "preferences-indicator-amarok", i18n( "Configure Notifications" ) );
addPage( database, i18n( "Database" ), "server-database", i18n( "Configure Database" ) );
addPage( plugins, i18n( "Plugins" ), "preferences-plugin", i18n( "Configure Plugins" ) );
addPage( scripts, i18n( "Scripts" ), "preferences-plugin-script", i18n( "Configure Scripts" ) );
//addPage( mediadevice, i18n( "Media Devices" ), "preferences-multimedia-player-amarok", i18n( "Configure Portable Player Support" ) );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Apply);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &Amarok2ConfigDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &Amarok2ConfigDialog::reject);
mainLayout->addWidget(buttonBox);
KWindowConfig::restoreWindowSize(windowHandle(), Amarok::config( "ConfigDialog" ));
}
Amarok2ConfigDialog::~Amarok2ConfigDialog()
{
DEBUG_BLOCK
KPageWidgetItem* pageItem = currentPage();
foreach( ConfigDialogBase *configPage, m_pageList )
{
if( m_pageMap[configPage] == pageItem )
{
s_currentPage = configPage->metaObject()->className();
break;
}
}
KConfigGroup config = Amarok::config( "ConfigDialog" );
KWindowConfig::saveWindowSize(windowHandle(), config);
AmarokConfig::self()->writeConfig();
}
void Amarok2ConfigDialog::updateButtons() //SLOT
{
DEBUG_BLOCK
buttonBox()->button(QDialogButtonBox::Apply)->setEnabled( hasChanged() );
}
/** Reimplemented from KConfigDialog */
void Amarok2ConfigDialog::addPage( ConfigDialogBase *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage )
{
- connect( page, SIGNAL(settingsChanged(QString)), this, SIGNAL(settingsChanged(QString)) );
+ connect( page, &ConfigDialogBase::settingsChanged, this, &Amarok2ConfigDialog::settingsChanged );
// Add the widget pointer to our list, for later reference
m_pageList << page;
KPageWidgetItem *pageWidget = KConfigDialog::addPage( page, itemName, pixmapName, header, manage );
m_pageMap.insert( page, pageWidget );
}
void Amarok2ConfigDialog::show( QString page )
{
if( page.isNull() )
{
page = s_currentPage;
}
foreach( ConfigDialogBase *configPage, m_pageList )
{
if( configPage->metaObject()->className() == page )
{
KPageWidgetItem *pageItem = m_pageMap[configPage];
KConfigDialog::setCurrentPage( pageItem );
break;
}
}
KConfigDialog::show();
raise();
activateWindow();
}
//////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED SLOTS
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Update the settings from the dialog.
* Example use: User clicks Ok or Apply button in a configure dialog.
* REIMPLEMENTED
*/
void Amarok2ConfigDialog::updateSettings()
{
foreach( ConfigDialogBase* page, m_pageList )
page->updateSettings();
}
/**
* Update the configuration-widgets in the dialog based on Amarok's current settings.
* Example use: Initialisation of dialog.
* Example use: User clicks Reset button in a configure dialog.
* REIMPLEMENTED
*/
void Amarok2ConfigDialog::updateWidgets()
{
foreach( ConfigDialogBase* page, m_pageList )
page->updateWidgets();
}
/**
* Update the configuration-widgets in the dialog based on the default values for Amarok's settings.
* Example use: User clicks Defaults button in a configure dialog.
* REIMPLEMENTED
*/
void Amarok2ConfigDialog::updateWidgetsDefault()
{
foreach( ConfigDialogBase* page, m_pageList )
page->updateWidgetsDefault();
}
//////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED
//////////////////////////////////////////////////////////////////////////////////////////
/**
* @return true if any configuration items we are managing changed from Amarok's stored settings
* We manage the engine combo box and some of the OSD settings
* REIMPLEMENTED
*/
bool Amarok2ConfigDialog::hasChanged()
{
DEBUG_BLOCK
bool changed = false;
foreach( ConfigDialogBase* page, m_pageList )
if( page->hasChanged() ) {
changed = true;
debug() << "Changed: " << page->metaObject()->className();
}
return changed;
}
/** REIMPLEMENTED */
bool Amarok2ConfigDialog::isDefault()
{
bool def = false;
foreach( ConfigDialogBase* page, m_pageList )
if( page->isDefault() )
def = true;
return def;
}
//////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE
//////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/configdialog/dialogs/CollectionConfig.cpp b/src/configdialog/dialogs/CollectionConfig.cpp
index 75d2daa3c5..99a4402f8d 100644
--- a/src/configdialog/dialogs/CollectionConfig.cpp
+++ b/src/configdialog/dialogs/CollectionConfig.cpp
@@ -1,70 +1,76 @@
/****************************************************************************************
* Copyright (c) 2004-2007 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CollectionConfig.h"
#include "amarokconfig.h"
#include <config.h>
+#include "configdialog/ConfigDialog.h"
#include "core/support/Amarok.h"
#include "core-impl/collections/db/sql/SqlCollection.h"
#include "dialogs/CollectionSetup.h"
CollectionConfig::CollectionConfig( QWidget* parent )
: ConfigDialogBase( parent )
{
m_collectionSetup = new CollectionSetup( this );
- connect( m_collectionSetup, SIGNAL(changed()), parent, SLOT(updateButtons()) );
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget( m_collectionSetup );
setLayout( layout );
KConfigGroup transcodeGroup = Amarok::config( Collections::SQL_TRANSCODING_GROUP_NAME );
m_collectionSetup->transcodingConfig()->fillInChoices( Transcoding::Configuration::fromConfigGroup( transcodeGroup ) );
- connect( m_collectionSetup->transcodingConfig(), SIGNAL(currentIndexChanged(int)), parent, SLOT(updateButtons()) );
+
+ if (auto dialog = qobject_cast<Amarok2ConfigDialog*>(parent))
+ {
+ connect( m_collectionSetup, &CollectionSetup::changed, dialog, &Amarok2ConfigDialog::updateButtons );
+ connect( m_collectionSetup->transcodingConfig(), QOverload<int>::of(&QComboBox::currentIndexChanged),
+ dialog, &Amarok2ConfigDialog::updateButtons );
+ }
}
CollectionConfig::~CollectionConfig()
{}
///////////////////////////////////////////////////////////////
// REIMPLEMENTED METHODS from ConfigDialogBase
///////////////////////////////////////////////////////////////
bool
CollectionConfig::hasChanged()
{
DEBUG_BLOCK
return m_collectionSetup->hasChanged() || m_collectionSetup->transcodingConfig()->hasChanged();
}
bool
CollectionConfig::isDefault()
{
return false;
}
void
CollectionConfig::updateSettings()
{
m_collectionSetup->writeConfig();
KConfigGroup transcodeGroup = Amarok::config( Collections::SQL_TRANSCODING_GROUP_NAME );
m_collectionSetup->transcodingConfig()->currentChoice().saveToConfigGroup( transcodeGroup );
}
diff --git a/src/configdialog/dialogs/DatabaseConfig.cpp b/src/configdialog/dialogs/DatabaseConfig.cpp
index cb3a3eee9f..40febe7795 100644
--- a/src/configdialog/dialogs/DatabaseConfig.cpp
+++ b/src/configdialog/dialogs/DatabaseConfig.cpp
@@ -1,175 +1,175 @@
/****************************************************************************************
* Copyright (c) 2009 John Atkinson <john@fauxnetic.co.uk> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DatabaseConfig.h"
#include <PluginManager.h>
#include <core/support/Amarok.h>
#include <core/support/Debug.h>
#include <KConfigDialogManager>
#include <KMessageBox>
#include <KCMultiDialog>
DatabaseConfig::DatabaseConfig( QWidget* parent, KConfigSkeleton *config )
: ConfigDialogBase( parent )
, m_configManager( new KConfigDialogManager( this, config ) )
{
setupUi( this );
// Fix some weird tab orderness
setTabOrder( kcfg_Host, kcfg_Port ); // host to port
setTabOrder( kcfg_Port, kcfg_User ); // port to username
setTabOrder( kcfg_User, kcfg_Password ); // username to password
setTabOrder( kcfg_Password, kcfg_Database ); // password to database
// enable the test button if one of the plugin factories has a correct testSettings slot
// get all storage factories
QList<Plugins::PluginFactory*> factories;
factories = Plugins::PluginManager::instance()->factories( Plugins::PluginManager::Storage );
bool testFunctionAvailable = false;
foreach( Plugins::PluginFactory* factory, factories )
{
// check the meta object if there is a testSettings slot available
if( factory->metaObject()->
indexOfMethod( QMetaObject::normalizedSignature("testSettings(QString, QString, QString, int, QString)" ) ) >= 0 )
testFunctionAvailable = true;
}
button_Test->setEnabled( testFunctionAvailable );
// connect slots
- connect( kcfg_UseServer, SIGNAL(stateChanged(int)), SLOT(toggleExternalConfigAvailable(int)) );
+ connect( kcfg_UseServer, &QCheckBox::stateChanged, this, &DatabaseConfig::toggleExternalConfigAvailable );
- connect( kcfg_Database, SIGNAL(textChanged(QString)), SLOT(updateSQLQuery()) );
- connect( kcfg_User, SIGNAL(textChanged(QString)), SLOT(updateSQLQuery()) );
- connect( button_Test, SIGNAL(clicked(bool)), SLOT(testDatabaseConnection()));
+ connect( kcfg_Database, &QLineEdit::textChanged, this, &DatabaseConfig::updateSQLQuery );
+ connect( kcfg_User, &QLineEdit::textChanged, this, &DatabaseConfig::updateSQLQuery );
+ connect( button_Test, &QAbstractButton::clicked, this, &DatabaseConfig::testDatabaseConnection );
toggleExternalConfigAvailable( kcfg_UseServer->checkState() );
updateSQLQuery();
m_configManager->addWidget( this );
}
DatabaseConfig::~DatabaseConfig()
{}
void
DatabaseConfig::toggleExternalConfigAvailable( const int checkBoxState ) //SLOT
{
group_Connection->setEnabled( checkBoxState == Qt::Checked );
}
void
DatabaseConfig::testDatabaseConnection() //SLOT
{
// get all storage factories
QList<Plugins::PluginFactory*> factories;
factories = Plugins::PluginManager::instance()->factories( Plugins::PluginManager::Storage );
// try if they have a testSettings slot that we can call
foreach( Plugins::PluginFactory* factory, factories )
{
bool callSucceeded = false;
QStringList connectionErrors;
callSucceeded = QMetaObject::invokeMethod( factory,
"testSettings",
Q_RETURN_ARG( QStringList, connectionErrors ),
Q_ARG( QString, kcfg_Host->text() ),
Q_ARG( QString, kcfg_User->text() ),
Q_ARG( QString, kcfg_Password->text() ),
Q_ARG( int, kcfg_Port->text().toInt() ),
Q_ARG( QString, kcfg_Database->text() )
);
if( callSucceeded )
{
if( connectionErrors.isEmpty() )
KMessageBox::messageBox( this, KMessageBox::Information,
i18n( "Amarok was able to establish a successful connection to the database." ),
i18n( "Success" ) );
else
KMessageBox::error( this, i18n( "The amarok database reported "
"the following errors:\n%1\nIn most cases you will need to resolve "
"these errors before Amarok will run properly." ).
arg( connectionErrors.join( "\n" ) ),
i18n( "Database Error" ));
}
}
}
///////////////////////////////////////////////////////////////
// REIMPLEMENTED METHODS from ConfigDialogBase
///////////////////////////////////////////////////////////////
bool
DatabaseConfig::hasChanged()
{
return false;
}
bool
DatabaseConfig::isDefault()
{
return false;
}
void
DatabaseConfig::updateSettings()
{
if( m_configManager->hasChanged() )
KMessageBox::messageBox( 0, KMessageBox::Information,
i18n( "Changes to database settings only take\neffect after Amarok is restarted." ),
i18n( "Database settings changed" ) );
}
///////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////
void
DatabaseConfig::updateSQLQuery() //SLOT
{
QString query;
if( isSQLInfoPresent() )
{
// Query template:
// GRANT ALL ON amarokdb.* TO 'amarokuser'@'localhost' IDENTIFIED BY 'mypassword'; FLUSH PRIVILEGES;
// Don't print the actual password!
const QString examplePassword = i18nc( "A default password for insertion into an example SQL command (so as not to print the real one). To be manually replaced by the user.", "password" );
query = QString( "CREATE DATABASE %1;\nGRANT ALL PRIVILEGES ON %1.* TO '%2' IDENTIFIED BY '%3'; FLUSH PRIVILEGES;" )
.arg( kcfg_Database->text() )
.arg( kcfg_User->text() )
.arg( examplePassword );
}
text_SQL->setPlainText( query );
}
inline bool
DatabaseConfig::isSQLInfoPresent() const
{
return !kcfg_Database->text().isEmpty() && !kcfg_User->text().isEmpty() && !kcfg_Host->text().isEmpty();
}
diff --git a/src/configdialog/dialogs/ExcludedLabelsDialog.cpp b/src/configdialog/dialogs/ExcludedLabelsDialog.cpp
index d29d2de087..6e5095c073 100644
--- a/src/configdialog/dialogs/ExcludedLabelsDialog.cpp
+++ b/src/configdialog/dialogs/ExcludedLabelsDialog.cpp
@@ -1,135 +1,135 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ExcludedLabelsDialog.h"
#include "core/meta/Meta.h"
#include "core/collections/QueryMaker.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "statsyncing/Config.h"
#include <KLineEdit>
#include <KLocalizedString>
#include <QGridLayout>
#include <QLabel>
#include <QListWidget>
#include <QToolButton>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
ExcludedLabelsDialog::ExcludedLabelsDialog( StatSyncing::Config *config, QWidget *parent,
Qt::WFlags flags )
: QDialog( parent, flags )
, m_statSyncingConfig( config )
{
Q_ASSERT( m_statSyncingConfig );
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
setupUi(mainWidget);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &ExcludedLabelsDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &ExcludedLabelsDialog::reject);
setWindowTitle( i18n( "Excluded Labels" ) );
mainLayout->addWidget(buttonBox);
addLabels( config->excludedLabels(), true );
Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker();
qm->setQueryType( Collections::QueryMaker::Label );
qm->setAutoDelete( true );
- connect( qm, SIGNAL(newResultReady(Meta::LabelList)),
- SLOT(slowNewResultReady(Meta::LabelList)) );
+ connect( qm, &Collections::QueryMaker::newLabelsReady,
+ this, &ExcludedLabelsDialog::slowNewResultReady );
qm->run();
- connect( addButton, SIGNAL(clicked(bool)), SLOT(slotAddExcludedLabel()) );
- connect( addLabelLine, SIGNAL(returnPressed(QString)), SLOT(slotAddExcludedLabel()) );
- connect(okButton, SIGNAL(clicked()), SLOT(slotSaveToConfig()) );
+ connect( addButton, &QAbstractButton::clicked, this, &ExcludedLabelsDialog::slotAddExcludedLabel );
+ connect( addLabelLine, &KLineEdit::returnPressed, this, &ExcludedLabelsDialog::slotAddExcludedLabel );
+ connect( okButton, &QAbstractButton::clicked, this, &ExcludedLabelsDialog::slotSaveToConfig );
}
void
ExcludedLabelsDialog::slowNewResultReady( const Meta::LabelList &labels )
{
foreach( const Meta::LabelPtr &label, labels )
addLabel( label->name() );
}
void
ExcludedLabelsDialog::slotAddExcludedLabel()
{
addLabel( addLabelLine->text(), true );
addLabelLine->setText( QString() );
}
void
ExcludedLabelsDialog::slotSaveToConfig()
{
QSet<QString> excluded;
foreach( const QListWidgetItem *item, listWidget->selectedItems() )
{
excluded.insert( item->text() );
}
m_statSyncingConfig->setExcludedLabels( excluded );
}
void
ExcludedLabelsDialog::addLabel( const QString &label, bool selected )
{
int count = listWidget->count();
for( int i = 0; i <= count; i++ )
{
QModelIndex idx;
if( i == count )
{
// reached end of the list
listWidget->addItem( label );
idx = listWidget->model()->index( i, 0 );
}
else if( listWidget->item( i )->text() == label )
{
// already in list
return;
}
else if( QString::localeAwareCompare( listWidget->item( i )->text(), label ) > 0 )
{
listWidget->insertItem( i, label );
idx = listWidget->model()->index( i, 0 );
}
// else continue to next iteration
// just inserted
if( idx.isValid() && selected )
listWidget->selectionModel()->select( idx, QItemSelectionModel::Select );
if( idx.isValid() )
return;
}
}
void
ExcludedLabelsDialog::addLabels( const QSet<QString> &labels, bool selected )
{
foreach( const QString &label, labels )
{
addLabel( label, selected );
}
}
diff --git a/src/configdialog/dialogs/MetadataConfig.cpp b/src/configdialog/dialogs/MetadataConfig.cpp
index 53fbbbd22a..17ee9f7358 100644
--- a/src/configdialog/dialogs/MetadataConfig.cpp
+++ b/src/configdialog/dialogs/MetadataConfig.cpp
@@ -1,310 +1,314 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MetadataConfig.h"
#include "amarokconfig.h"
+#include "configdialog/ConfigDialog.h"
#include "configdialog/dialogs/ExcludedLabelsDialog.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "statsyncing/Config.h"
#include "statsyncing/Controller.h"
#include "MetaValues.h"
MetadataConfig::MetadataConfig( QWidget *parent )
: ConfigDialogBase( parent )
{
- connect( this, SIGNAL(changed()), parent, SLOT(updateButtons()) );
+ if( auto p = static_cast<Amarok2ConfigDialog*>(parent) )
+ connect( this, &MetadataConfig::changed, p, &Amarok2ConfigDialog::updateButtons );
setupUi( this );
m_writeBackCoverDimensions->addItem(
i18nc("Maximum cover size option", "Small (200 px)" ), QVariant( 200 ) );
m_writeBackCoverDimensions->addItem(
i18nc("Maximum cover size option", "Medium (400 px)" ), QVariant( 400 ) );
m_writeBackCoverDimensions->addItem(
i18nc("Maximum cover size option", "Large (800 px)" ), QVariant( 800 ) );
m_writeBackCoverDimensions->addItem(
i18nc("Maximum cover size option", "Huge (1600 px)" ), QVariant( 1600 ) );
m_writeBack->setChecked( AmarokConfig::writeBack() );
m_writeBack->setVisible( false ); // probably not a usecase
m_writeBackStatistics->setChecked( AmarokConfig::writeBackStatistics() );
m_writeBackStatistics->setEnabled( m_writeBack->isChecked() );
m_writeBackCover->setChecked( AmarokConfig::writeBackCover() );
m_writeBackCover->setEnabled( m_writeBack->isChecked() );
if( m_writeBackCoverDimensions->findData( AmarokConfig::writeBackCoverDimensions() ) != -1 )
m_writeBackCoverDimensions->setCurrentIndex( m_writeBackCoverDimensions->findData( AmarokConfig::writeBackCoverDimensions() ) );
else
m_writeBackCoverDimensions->setCurrentIndex( 1 ); // medium
m_writeBackCoverDimensions->setEnabled( m_writeBackCover->isEnabled() && m_writeBackCover->isChecked() );
m_useCharsetDetector->setChecked( AmarokConfig::useCharsetDetector() );
- connect( m_writeBack, SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
- connect( m_writeBackStatistics, SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
- connect( m_writeBackCover, SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
- connect( m_writeBackCoverDimensions, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed()) );
- connect( m_useCharsetDetector, SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
+ connect( m_writeBack, &QCheckBox::toggled, this, &MetadataConfig::changed );
+ connect( m_writeBackStatistics, &QCheckBox::toggled, this, &MetadataConfig::changed );
+ connect( m_writeBackCover, &QCheckBox::toggled, this, &MetadataConfig::changed );
+ connect( m_writeBackCoverDimensions, QOverload<int>::of(&KComboBox::currentIndexChanged),
+ this, &MetadataConfig::changed );
+ connect( m_useCharsetDetector, &QCheckBox::toggled, this, &MetadataConfig::changed );
StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
StatSyncing::Config *config = controller ? controller->config() : 0;
m_statSyncingConfig = config;
m_statSyncingProvidersView->setModel( config );
m_synchronizeButton->setIcon( QIcon::fromTheme( "amarok_playcount" ) );
m_configureTargetButton->setIcon( QIcon::fromTheme( "configure" ) );
- connect( config, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SIGNAL(changed()) );
- connect( config, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(changed()) );
- connect( config, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(changed()) );
- connect( config, SIGNAL(modelReset()), SIGNAL(changed()) );
+ connect( config, &StatSyncing::Config::dataChanged, this, &MetadataConfig::changed );
+ connect( config, &StatSyncing::Config::rowsInserted, this, &MetadataConfig::changed );
+ connect( config, &StatSyncing::Config::rowsRemoved, this, &MetadataConfig::changed );
+ connect( config, &StatSyncing::Config::modelReset, this, &MetadataConfig::changed );
// Add target button
m_addTargetButton->setEnabled( controller && controller->hasProviderFactories() );
- connect( m_addTargetButton, SIGNAL(clicked(bool)), SLOT(slotCreateProviderDialog()) );
+ connect( m_addTargetButton, &QAbstractButton::clicked, this, &MetadataConfig::slotCreateProviderDialog );
// Configure target button
m_configureTargetButton->setEnabled( false );
- connect( m_statSyncingProvidersView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
- SLOT(slotUpdateProviderConfigureButton()) );
- connect( m_configureTargetButton, SIGNAL(clicked(bool)), SLOT(slotConfigureProvider()) );
+ connect( m_statSyncingProvidersView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &MetadataConfig::slotUpdateProviderConfigureButton );
+ connect( m_configureTargetButton, &QAbstractButton::clicked, this, &MetadataConfig::slotConfigureProvider );
// Forget target button
- connect( m_statSyncingProvidersView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
- SLOT(slotUpdateForgetButton()) );
- connect( m_forgetTargetsButton, SIGNAL(clicked(bool)), SLOT(slotForgetCollections()) );
+ connect( m_statSyncingProvidersView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &MetadataConfig::slotUpdateForgetButton );
+ connect( m_forgetTargetsButton, &QAbstractButton::clicked, this, &MetadataConfig::slotForgetCollections );
// Synchronize button
if( controller )
- connect( m_synchronizeButton, SIGNAL(clicked(bool)), controller, SLOT(synchronize()) );
+ connect( m_synchronizeButton, &QAbstractButton::clicked,
+ controller, &StatSyncing::Controller::synchronize );
else
m_synchronizeButton->setEnabled( false );
slotUpdateForgetButton();
const qint64 checkedFields = config ? config->checkedFields() : 0;
QMap<qint64, QString> i18nSyncLabels; // to avoid word puzzles, bug 334561
i18nSyncLabels.insert( Meta::valRating, i18nc( "Statistics sync checkbox label",
"Synchronize Ratings" ) );
i18nSyncLabels.insert( Meta::valFirstPlayed, i18nc( "Statistics sync checkbox label",
"Synchronize First Played Times" ) );
i18nSyncLabels.insert( Meta::valLastPlayed, i18nc( "Statistics sync checkbox label",
"Synchronize Last Played Times" ) );
i18nSyncLabels.insert( Meta::valPlaycount, i18nc( "Statistics sync checkbox label",
"Synchronize Playcounts" ) );
i18nSyncLabels.insert( Meta::valLabel, i18nc( "Statistics sync checkbox label",
"Synchronize Labels" ) );
foreach( qint64 field, StatSyncing::Controller::availableFields() )
{
QString name = i18nSyncLabels.value( field );
if( name.isEmpty() )
{
name = i18nc( "%1 is field name such as Play Count (this string is only used "
"as a fallback)", "Synchronize %1", Meta::i18nForField( field ) );
warning() << Q_FUNC_INFO << "no explicit traslation for" << name << "using fallback";
}
QCheckBox *checkBox = new QCheckBox( name );
if( field == Meta::valLabel ) // special case, we want plural:
{
QHBoxLayout *lineLayout = new QHBoxLayout();
QLabel *button = new QLabel();
button->setObjectName( "configureLabelExceptions" );
- connect( button, SIGNAL(linkActivated(QString)),
- SLOT(slotConfigureExcludedLabels()) );
+ connect( button, &QLabel::linkActivated,
+ this, &MetadataConfig::slotConfigureExcludedLabels );
lineLayout->addWidget( checkBox );
lineLayout->addWidget( button );
lineLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum ) );
m_statSyncingFieldsLayout->addLayout( lineLayout );
slotUpdateConfigureExcludedLabelsLabel();
}
else
{
m_statSyncingFieldsLayout->addWidget( checkBox );
}
checkBox->setCheckState( ( field & checkedFields ) ? Qt::Checked : Qt::Unchecked );
checkBox->setProperty( "field", field );
- connect( checkBox, SIGNAL(stateChanged(int)), SIGNAL(changed()) );
+ connect( checkBox, &QCheckBox::stateChanged, this, &MetadataConfig::changed );
}
}
MetadataConfig::~MetadataConfig()
{
if( m_statSyncingConfig )
m_statSyncingConfig.data()->read(); // reset unsaved changes
}
bool
MetadataConfig::isDefault()
{
return false;
}
bool
MetadataConfig::hasChanged()
{
// a bit hacky, but updating enabled status here does the trick
m_writeBackStatistics->setEnabled( m_writeBack->isChecked() );
m_writeBackCover->setEnabled( m_writeBack->isChecked() );
m_writeBackCoverDimensions->setEnabled( m_writeBackCover->isEnabled() && m_writeBackCover->isChecked() );
return
m_writeBack->isChecked() != AmarokConfig::writeBack() ||
m_writeBackStatistics->isChecked() != AmarokConfig::writeBackStatistics() ||
m_writeBackCover->isChecked() != AmarokConfig::writeBackCover() ||
writeBackCoverDimensions() != AmarokConfig::writeBackCoverDimensions() ||
m_useCharsetDetector->isChecked() != AmarokConfig::useCharsetDetector() ||
( m_statSyncingConfig.data() ? ( checkedFields() != m_statSyncingConfig.data()->checkedFields() ) : false ) ||
( m_statSyncingConfig.data() ? m_statSyncingConfig.data()->hasChanged() : false );
}
void
MetadataConfig::updateSettings()
{
AmarokConfig::setWriteBack( m_writeBack->isChecked() );
AmarokConfig::setWriteBackStatistics( m_writeBackStatistics->isChecked() );
AmarokConfig::setWriteBackCover( m_writeBackCover->isChecked() );
if( writeBackCoverDimensions() > 0 )
AmarokConfig::setWriteBackCoverDimensions( writeBackCoverDimensions() );
AmarokConfig::setUseCharsetDetector( m_useCharsetDetector->isChecked() );
if( m_statSyncingConfig )
{
m_statSyncingConfig.data()->setCheckedFields( checkedFields() );
m_statSyncingConfig.data()->save();
}
}
void
MetadataConfig::slotForgetCollections()
{
if( !m_statSyncingConfig )
return;
foreach( const QModelIndex &idx, m_statSyncingProvidersView->selectionModel()->selectedIndexes() )
{
QString id = idx.data( StatSyncing::Config::ProviderIdRole ).toString();
m_statSyncingConfig.data()->forgetProvider( id );
}
}
void
MetadataConfig::slotUpdateForgetButton()
{
QItemSelectionModel *selectionModel = m_statSyncingProvidersView->selectionModel();
// note: hasSelection() and selection() gives false positives!
m_forgetTargetsButton->setEnabled( !selectionModel->selectedIndexes().isEmpty() );
}
void
MetadataConfig::slotUpdateConfigureExcludedLabelsLabel()
{
QLabel *label = findChild<QLabel *>( "configureLabelExceptions" );
if( !label || !m_statSyncingConfig )
{
warning() << __PRETTY_FUNCTION__ << "label or m_statSyncingConfig is null!";
return;
}
int exceptions = m_statSyncingConfig.data()->excludedLabels().count();
QString begin = "<a href='dummy'>";
QString end = "</a>";
label->setText( i18np( "(%2one exception%3)", "(%2%1 exceptions%3)", exceptions,
begin, end ) );
}
void
MetadataConfig::slotConfigureExcludedLabels()
{
ExcludedLabelsDialog dialog( m_statSyncingConfig.data(), this );
if( dialog.exec() == QDialog::Accepted )
{
slotUpdateConfigureExcludedLabelsLabel();
emit changed();
}
}
void
MetadataConfig::slotConfigureProvider()
{
StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
if( controller )
{
QModelIndexList selected = m_statSyncingProvidersView->selectionModel()->selectedIndexes();
if( selected.size() == 1 )
{
const QString id = selected.front().data( StatSyncing::Config::ProviderIdRole ).toString();
QWidget *dialog = controller->providerConfigDialog( id );
if( dialog )
{
dialog->show();
dialog->activateWindow();
dialog->raise();
}
}
}
}
void
MetadataConfig::slotUpdateProviderConfigureButton()
{
QModelIndexList selected = m_statSyncingProvidersView->selectionModel()->selectedIndexes();
StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
if( selected.size() != 1 || !controller )
{
m_configureTargetButton->setEnabled( false );
}
else
{
const QString id = selected.front().data( StatSyncing::Config::ProviderIdRole ).toString();
m_configureTargetButton->setEnabled( controller->providerIsConfigurable( id ) );
}
}
void
MetadataConfig::slotCreateProviderDialog()
{
StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
if( controller )
{
QWidget *dialog = controller->providerCreationDialog();
if( dialog )
{
dialog->show();
dialog->activateWindow();
dialog->raise();
}
}
}
int
MetadataConfig::writeBackCoverDimensions() const
{
return m_writeBackCoverDimensions->itemData( m_writeBackCoverDimensions->currentIndex() ).toInt();
}
qint64
MetadataConfig::checkedFields() const
{
qint64 ret = 0;
foreach( QCheckBox *checkBox, m_statSyncingFieldsLayout->parentWidget()->findChildren<QCheckBox *>() )
{
if( checkBox->isChecked() && checkBox->property( "field" ).canConvert<qint64>() )
ret |= checkBox->property( "field" ).value<qint64>();
}
return ret;
}
diff --git a/src/configdialog/dialogs/NotificationsConfig.cpp b/src/configdialog/dialogs/NotificationsConfig.cpp
index 7c33ad7c50..edd8c044f2 100644
--- a/src/configdialog/dialogs/NotificationsConfig.cpp
+++ b/src/configdialog/dialogs/NotificationsConfig.cpp
@@ -1,185 +1,187 @@
/****************************************************************************************
* Copyright (c) 2004-2007 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2004 Frederik Holljen <fh@ez.no> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "NotificationsConfig.h"
#include "amarokconfig.h"
+#include "configdialog/ConfigDialog.h"
#include "core/support/Debug.h"
#include "KNotificationBackend.h"
#include <KWindowSystem>
#include <QDesktopWidget>
NotificationsConfig::NotificationsConfig( QWidget* parent )
: ConfigDialogBase( parent )
, m_oldAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) )
, m_oldYOffset( AmarokConfig::osdYOffset() )
{
setupUi( this );
- connect( this, SIGNAL(changed()), parent, SLOT(updateButtons()) );
+ if (auto dialog = qobject_cast<Amarok2ConfigDialog*>(parent))
+ connect( this, &NotificationsConfig::changed, dialog, &Amarok2ConfigDialog::updateButtons );
m_osdPreview = new OSDPreviewWidget( this ); //must be child!!!
m_osdPreview->setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
m_osdPreview->setYOffset( AmarokConfig::osdYOffset() );
m_osdPreview->setFontScale( AmarokConfig::osdFontScaling() );
m_osdPreview->setTranslucent( AmarokConfig::osdUseTranslucency() );
#ifdef Q_WS_MAC
QCheckBox* growl = new QCheckBox( i18n( "Use Growl for notifications" ), this );
growl->setChecked( AmarokConfig::growlEnabled() );
gridLayout_5->addWidget( growl, 2, 0, 1, 1 );
- connect( growl, SIGNAL(toggled(bool)),
- this, SLOT(setGrowlEnabled(bool)) );
+ connect( growl, &QCheckBox::toggled,
+ this, &NotificationsConfig::setGrowlEnabled );
#endif
// Enable/disable the translucency option depending on availablity of desktop compositing
kcfg_OsdUseTranslucency->setEnabled( KWindowSystem::compositingActive() );
- connect( m_osdPreview, SIGNAL(positionChanged()), SLOT(slotPositionChanged()) );
+ connect( m_osdPreview, &OSDPreviewWidget::positionChanged, this, &NotificationsConfig::slotPositionChanged );
const int numScreens = QApplication::desktop()->numScreens();
for( int i = 0; i < numScreens; i++ )
kcfg_OsdScreen->addItem( QString::number( i ) );
- connect( kcfg_OsdTextColor, SIGNAL(changed(QColor)),
- m_osdPreview, SLOT(setTextColor(QColor)) );
- connect( kcfg_OsdUseCustomColors, SIGNAL(toggled(bool)),
- this, SLOT(useCustomColorsToggled(bool)) );
- connect( kcfg_OsdScreen, SIGNAL(activated(int)),
- m_osdPreview, SLOT(setScreen(int)) );
- connect( kcfg_OsdEnabled, SIGNAL(toggled(bool)),
- m_osdPreview, SLOT(setVisible(bool)) );
- connect( kcfg_OsdUseTranslucency, SIGNAL(toggled(bool)),
- m_osdPreview, SLOT(setTranslucent(bool)) );
- connect( kcfg_OsdFontScaling, SIGNAL(valueChanged(int)),
- m_osdPreview, SLOT(setFontScale(int)) );
+ connect( kcfg_OsdTextColor, &KColorButton::changed,
+ m_osdPreview, &OSDPreviewWidget::setTextColor );
+ connect( kcfg_OsdUseCustomColors, &QGroupBox::toggled,
+ this, &NotificationsConfig::useCustomColorsToggled );
+ connect( kcfg_OsdScreen, QOverload<int>::of(&KComboBox::activated),
+ m_osdPreview, &OSDPreviewWidget::setScreen );
+ connect( kcfg_OsdEnabled, &QGroupBox::toggled,
+ m_osdPreview, &OSDPreviewWidget::setVisible );
+ connect( kcfg_OsdUseTranslucency, &QCheckBox::toggled,
+ m_osdPreview, &OSDPreviewWidget::setTranslucent );
+ connect( kcfg_OsdFontScaling, QOverload<int>::of(&QSpinBox::valueChanged),
+ m_osdPreview, &OSDPreviewWidget::setFontScale );
/*
Amarok::QStringx text = i18n(
"<h3>Tags Displayed in OSD</h3>"
"You can use the following tokens:"
"<ul>"
"<li>Title - %1"
"<li>Album - %2"
"<li>Artist - %3"
"<li>Genre - %4"
"<li>Bitrate - %5"
"<li>Year - %6"
"<li>Track Length - %7"
"<li>Track Number - %8"
"<li>Filename - %9"
"<li>Directory - %10"
"<li>Type - %11"
"<li>Comment - %12"
"<li>Score - %13"
"<li>Playcount - %14"
"<li>Disc Number - %15"
"<li>Rating - %16"
"<li>Elapsed Time - %17"
"</ul>"
"If you surround sections of text that contain a token with curly-braces, that section will be hidden if the token is empty, for example:"
"<pre>%18</pre>"
"Will not show <b>Score: <i>%score</i></b> if the track has no score." );
*/
}
NotificationsConfig::~NotificationsConfig()
{}
///////////////////////////////////////////////////////////////
// REIMPLEMENTED METHODS from ConfigDialogBase
///////////////////////////////////////////////////////////////
bool
NotificationsConfig::hasChanged()
{
DEBUG_BLOCK
return ( m_osdPreview->alignment() != m_oldAlignment ) || ( m_osdPreview->yOffset() != m_oldYOffset );
}
bool
NotificationsConfig::isDefault()
{
return false;
}
void
NotificationsConfig::updateSettings()
{
DEBUG_BLOCK
AmarokConfig::setOsdAlignment( m_osdPreview->alignment() );
AmarokConfig::setOsdYOffset( m_osdPreview->yOffset() );
AmarokConfig::setOsdUseTranslucency( kcfg_OsdUseTranslucency->isChecked() );
Amarok::OSD::instance()->setEnabled( kcfg_OsdEnabled->isChecked() );
Amarok::KNotificationBackend::instance()->setEnabled( kcfg_KNotifyEnabled->isChecked() );
emit settingsChanged( QString() );
}
///////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////
void
NotificationsConfig::slotPositionChanged()
{
DEBUG_BLOCK
kcfg_OsdScreen->blockSignals( true );
kcfg_OsdScreen->setCurrentIndex( m_osdPreview->screen() );
kcfg_OsdScreen->blockSignals( false );
// Update button states (e.g. "Apply")
emit changed();
}
void
NotificationsConfig::hideEvent( QHideEvent* )
{
m_osdPreview->hide();
}
void
NotificationsConfig::showEvent( QShowEvent* )
{
useCustomColorsToggled( kcfg_OsdUseCustomColors->isChecked() );
m_osdPreview->setScreen( kcfg_OsdScreen->currentIndex() );
m_osdPreview->setVisible( kcfg_OsdEnabled->isChecked() );
}
void
NotificationsConfig::setGrowlEnabled( bool enable )
{
DEBUG_BLOCK
AmarokConfig::setGrowlEnabled( enable );
}
void
NotificationsConfig::useCustomColorsToggled( bool on )
{
m_osdPreview->setUseCustomColors( on, kcfg_OsdTextColor->color() );
}
diff --git a/src/configdialog/dialogs/PlaybackConfig.cpp b/src/configdialog/dialogs/PlaybackConfig.cpp
index 96f030315d..d772985123 100644
--- a/src/configdialog/dialogs/PlaybackConfig.cpp
+++ b/src/configdialog/dialogs/PlaybackConfig.cpp
@@ -1,108 +1,108 @@
/****************************************************************************************
* Copyright (c) 2004-2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaybackConfig.h"
#include "amarokconfig.h"
#include "core/support/Amarok.h"
#include "ActionClasses.h"
#include "EngineController.h"
#include "core/support/Debug.h"
#include <KCMultiDialog>
#include <kmessagebox.h>
PlaybackConfig::PlaybackConfig( QWidget* parent )
: ConfigDialogBase( parent )
{
setupUi( this );
EngineController *engine = EngineController::instance();
Q_ASSERT( engine );
if( !engine->supportsFadeout() )
{
const QString toolTip = i18n( "Current Phonon backend does not support volume fading" );
kcfg_FadeoutOnStop->setEnabled( false );
kcfg_FadeoutOnStop->setToolTip( toolTip );
kcfg_FadeoutOnPause->setEnabled( false );
kcfg_FadeoutOnPause->setToolTip( toolTip );
fadeoutLengthLabel->setEnabled( false );
fadeoutLengthLabel->setToolTip( toolTip );
kcfg_FadeoutLength->setEnabled( false );
kcfg_FadeoutLength->setToolTip( toolTip );
}
- connect( findChild<QPushButton*>( "pushButtonPhonon" ), SIGNAL(clicked()), SLOT(configurePhonon()) );
- connect( kcfg_FadeoutOnStop, SIGNAL(toggled(bool)), SLOT(setFadeoutState()) );
- connect( kcfg_FadeoutOnPause, SIGNAL(toggled(bool)), SLOT(setFadeoutState()) );
+ connect( findChild<QPushButton*>( "pushButtonPhonon" ), &QAbstractButton::clicked, this, &PlaybackConfig::configurePhonon );
+ connect( kcfg_FadeoutOnStop, &QCheckBox::toggled, this, &PlaybackConfig::setFadeoutState );
+ connect( kcfg_FadeoutOnPause, &QCheckBox::toggled, this, &PlaybackConfig::setFadeoutState );
}
PlaybackConfig::~PlaybackConfig()
{}
///////////////////////////////////////////////////////////////
// REIMPLEMENTED METHODS from ConfigDialogBase
///////////////////////////////////////////////////////////////
bool
PlaybackConfig::hasChanged()
{
return false;
}
bool
PlaybackConfig::isDefault()
{
return false;
}
void
PlaybackConfig::updateSettings()
{}
///////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////
void
PlaybackConfig::configurePhonon() //SLOT
{
DEBUG_BLOCK
KCMultiDialog KCM;
KCM.setWindowTitle( i18n( "Sound System - Amarok" ) );
KCM.addModule( "kcm_phonon" );
KCM.exec();
}
void
PlaybackConfig::setFadeoutState() //SLOT
{
if( !EngineController::instance()->supportsFadeout() )
return;
const bool enabled = kcfg_FadeoutOnPause->isChecked() || kcfg_FadeoutOnStop->isChecked();
fadeoutLengthLabel->setEnabled( enabled );
kcfg_FadeoutLength->setEnabled( enabled );
}
diff --git a/src/configdialog/dialogs/PluginsConfig.cpp b/src/configdialog/dialogs/PluginsConfig.cpp
index 33c656eba6..9ef9a694d8 100644
--- a/src/configdialog/dialogs/PluginsConfig.cpp
+++ b/src/configdialog/dialogs/PluginsConfig.cpp
@@ -1,88 +1,91 @@
/****************************************************************************************
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PluginsConfig"
#include "PluginsConfig.h"
+#include "configdialog/ConfigDialog.h"
#include "core/support/Debug.h"
#include "services/ServiceBase.h"
#include "PluginManager.h"
#include <KPluginInfo>
#include <KPluginSelector>
#include <QVBoxLayout>
PluginsConfig::PluginsConfig( QWidget *parent )
: ConfigDialogBase( parent )
, m_configChanged( false )
{
DEBUG_BLOCK
m_selector = new KPluginSelector( this );
m_selector->setSizePolicy( QSizePolicy:: Expanding, QSizePolicy::Expanding );
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setMargin( 0 );
layout->addWidget( m_selector );
m_selector->addPlugins( The::pluginManager()->plugins( Plugins::PluginManager::Collection ),
KPluginSelector::ReadConfigFile, i18n("Collections"), "Collection" );
m_selector->addPlugins( The::pluginManager()->plugins( Plugins::PluginManager::Service ),
KPluginSelector::ReadConfigFile, i18n("Internet Services"), "Service" );
m_selector->addPlugins( The::pluginManager()->plugins( Plugins::PluginManager::Importer ),
KPluginSelector::ReadConfigFile, i18n("Statistics importers"), "Importer" );
- connect( m_selector, SIGNAL(changed(bool)), SLOT(slotConfigChanged(bool)) );
- connect( m_selector, SIGNAL(changed(bool)), parent, SLOT(updateButtons()) );
+ connect( m_selector, &KPluginSelector::changed, this, &PluginsConfig::slotConfigChanged );
+
+ if (auto dialog = qobject_cast<Amarok2ConfigDialog*>(parent))
+ connect( m_selector, &KPluginSelector::changed, dialog, &Amarok2ConfigDialog::updateButtons );
}
PluginsConfig::~PluginsConfig()
{}
void PluginsConfig::updateSettings()
{
DEBUG_BLOCK
if( m_configChanged )
{
debug() << "config changed";
m_selector->save();
// check if any services were disabled and needs to be removed, or any
// that are hidden needs to be enabled
The::pluginManager()->checkPluginEnabledStates();
}
}
bool PluginsConfig::hasChanged()
{
return m_configChanged;
}
bool PluginsConfig::isDefault()
{
return false;
}
void PluginsConfig::slotConfigChanged( bool changed )
{
m_configChanged = changed;
if( changed )
debug() << "config changed";
}
diff --git a/src/configdialog/dialogs/ScriptSelector.cpp b/src/configdialog/dialogs/ScriptSelector.cpp
index 1b33fe936d..a623494823 100644
--- a/src/configdialog/dialogs/ScriptSelector.cpp
+++ b/src/configdialog/dialogs/ScriptSelector.cpp
@@ -1,119 +1,119 @@
/****************************************************************************************
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ScriptSelector.h"
#include "core/support/Debug.h"
#include <KCategorizedView>
#include <KLocale>
#include <KLineEdit>
#include <KPluginInfo>
#include <KMessageBox>
#include <QScrollBar>
// uber-hacky, this whole thing, make our own script selector?
ScriptSelector::ScriptSelector( QWidget * parent )
: KPluginSelector( parent )
, m_scriptCount( 0 )
{
m_lineEdit = this->findChild<KLineEdit*>();
if( m_lineEdit )
{
m_lineEdit->setClickMessage( i18n( "Search Scripts" ) );
- connect( m_lineEdit, SIGNAL(textChanged(QString)), SLOT(slotFiltered(QString)) );
+ connect( m_lineEdit, &QLineEdit::textChanged, this, &ScriptSelector::slotFiltered );
}
m_listView = this->findChild<KCategorizedView*>();
}
ScriptSelector::~ScriptSelector()
{}
void
ScriptSelector::setVerticalPosition( int position )
{
m_listView->verticalScrollBar()->setSliderPosition( position );
}
int
ScriptSelector::verticalPosition()
{
return m_listView->verticalScrollBar()->sliderPosition();
}
QString
ScriptSelector::filter()
{
return m_lineEdit->text();
}
void
ScriptSelector::setFilter( const QString &filter )
{
m_lineEdit->setText( filter );
}
void
ScriptSelector::addScripts( QList<KPluginInfo> pluginInfoList,
PluginLoadMethod pluginLoadMethod,
const QString &categoryName,
const QString &categoryKey,
const KSharedConfig::Ptr &config )
{
DEBUG_BLOCK
qSort( pluginInfoList.begin(), pluginInfoList.end()
, []( const KPluginInfo &left, const KPluginInfo &right ){ return left.name() < right.name(); } );
addPlugins( pluginInfoList, pluginLoadMethod, categoryName, categoryKey, config );
foreach( const KPluginInfo &plugin, pluginInfoList )
{
m_scriptCount++;
m_scripts[m_scriptCount] = plugin.pluginName();
}
}
QString
ScriptSelector::currentItem() const
{
DEBUG_BLOCK
QItemSelectionModel *selModel = m_listView->selectionModel();
const QModelIndexList selIndexes = selModel->selectedIndexes();
if( !selIndexes.isEmpty() )
{
QModelIndex currentIndex = selIndexes[0];
if( currentIndex.isValid() )
{
debug() << "row: " << currentIndex.row() + 1; //the index start from 1
debug() << "name: "<< m_scripts[currentIndex.row() + 1];
return m_scripts[currentIndex.row() + 1];
}
}
return QString();
}
void
ScriptSelector::slotFiltered( const QString &filter )
{
if( filter.isEmpty() )
emit filtered( false );
else
emit filtered( true );
}
diff --git a/src/configdialog/dialogs/ScriptsConfig.cpp b/src/configdialog/dialogs/ScriptsConfig.cpp
index 41adc33cab..9df73056b1 100644
--- a/src/configdialog/dialogs/ScriptsConfig.cpp
+++ b/src/configdialog/dialogs/ScriptsConfig.cpp
@@ -1,304 +1,310 @@
/****************************************************************************************
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ScriptsConfig"
#include "ScriptsConfig.h"
#include "amarokconfig.h"
+#include "configdialog/ConfigDialog.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "scripting/scriptmanager/ScriptManager.h"
#include "ScriptSelector.h"
#include "ui_ScriptsConfig.h"
#include <KFileDialog>
#include <KMessageBox>
#include <KNS3/DownloadDialog>
#include <KPluginInfo>
#include <KPluginSelector>
#include <KStandardDirs>
#include <KTar>
#include <KZip>
#include <QTemporaryFile>
#include <QTimer>
#include <QVBoxLayout>
#include <QScrollBar>
#include <QMimeDatabase>
#include <QMimeType>
ScriptsConfig::ScriptsConfig( QWidget *parent )
: ConfigDialogBase( parent )
, m_configChanged( false )
, m_parent( parent )
, m_oldSelector( 0 )
{
DEBUG_BLOCK
Ui::ScriptsConfig gui;
gui.setupUi( this );
m_uninstallButton = gui.uninstallButton;
m_timer = new QTimer(this);
- connect( m_timer, SIGNAL(timeout()), this, SLOT(slotUpdateScripts()) );
+ connect( m_timer, &QTimer::timeout, this, &ScriptsConfig::slotUpdateScripts );
m_timer->setInterval( 200 );
// Load config
gui.kcfg_AutoUpdateScripts->setChecked( AmarokConfig::autoUpdateScripts() );
gui.manageButton->setIcon( QIcon::fromTheme( "get-hot-new-stuff-amarok" ) );
- connect( gui.manageButton, SIGNAL(clicked()), SLOT(slotManageScripts()) );
- connect( gui.installButton, SIGNAL(clicked(bool)), SLOT(installLocalScript()) );
+ connect( gui.manageButton, &QAbstractButton::clicked,
+ this, &ScriptsConfig::slotManageScripts );
+ connect( gui.installButton, &QAbstractButton::clicked,
+ this, &ScriptsConfig::installLocalScript );
m_selector = gui.scriptSelector;
m_verticalLayout = gui.verticalLayout;
slotReloadScriptSelector();
- connect( gui.reloadButton, SIGNAL(clicked(bool)), m_timer, SLOT(start()) );
- connect( gui.uninstallButton, SIGNAL(clicked(bool)), this, SLOT(slotUninstallScript()) );
+ connect( gui.reloadButton, &QAbstractButton::clicked, m_timer, QOverload<>::of(&QTimer::start) );
+ connect( gui.uninstallButton, &QAbstractButton::clicked, this, &ScriptsConfig::slotUninstallScript );
- connect( ScriptManager::instance(), SIGNAL(scriptsChanged()), SLOT(slotReloadScriptSelector()) );
+ connect( ScriptManager::instance(), &ScriptManager::scriptsChanged,
+ this, &ScriptsConfig::slotReloadScriptSelector );
this->setEnabled( AmarokConfig::enableScripts() );
}
ScriptsConfig::~ScriptsConfig()
{}
void
ScriptsConfig::slotManageScripts()
{
QStringList updateScriptsList;
KNS3::DownloadDialog dialog("amarok.knsrc", this);
dialog.exec();
if( !dialog.installedEntries().isEmpty() || !dialog.changedEntries().isEmpty() )
m_timer->start();
}
void
ScriptsConfig::updateSettings()
{
DEBUG_BLOCK
if( m_configChanged )
{
m_selector->save();
ScriptManager::instance()->configChanged( true );
}
}
bool
ScriptsConfig::hasChanged()
{
return m_configChanged;
}
bool
ScriptsConfig::isDefault()
{
return false;
}
void
ScriptsConfig::slotConfigChanged( bool changed )
{
m_configChanged = changed;
if( changed )
debug() << "config changed";
}
void
ScriptsConfig::installLocalScript()
{
DEBUG_BLOCK
// where's this config stored anyway, use amarokconfig instead?
// the script can actually be updated if you get the folder name right
int response = KMessageBox::warningContinueCancel( this, i18n( "Manually installed scripts "
"cannot be automatically updated, continue?" ), QString(), KStandardGuiItem::cont()
, KStandardGuiItem::cancel(), "manualScriptInstallWarning" );
if( response == KMessageBox::Cancel )
return;
QString filePath = KFileDialog::getOpenFileName( QUrl(), QString(), this, i18n( "Select Archived Script" ) );
if( filePath.isEmpty() )
return;
QString fileName = QFileInfo( filePath ).fileName();
QMimeDatabase db;
QMimeType mimeType = db.mimeTypeForFile( filePath );
QScopedPointer<KArchive> archive;
if( mimeType.inherits( "application/zip" ) )
archive.reset( new KZip( filePath ) );
else
archive.reset( new KTar( filePath ) );
if( !archive || !archive->open( QIODevice::ReadOnly ) )
{
KMessageBox::error( this, i18n( "Invalid Archive" ) );
return;
}
QString destination = KGlobal::dirs()->saveLocation( "data", QString("amarok/scripts/") + fileName + "/" , false );
const KArchiveDirectory* const archiveDir = archive->directory();
const QDir dir( destination );
const KArchiveFile *specFile = findSpecFile( archiveDir );
if( !specFile )
{
KMessageBox::error( this, i18n( "Invalid Script File" ) );
return;
}
QTemporaryFile tempFile;
tempFile.open();
QIODevice *device = specFile->createDevice();
tempFile.write( device->readAll() );
delete device;
tempFile.close();
KPluginInfo newScriptInfo( tempFile.fileName() );
if( !newScriptInfo.isValid() )
{
KMessageBox::error( this, i18n( "Invalid Script File" ) );
return;
}
if( ScriptManager::instance()->m_scripts.contains( newScriptInfo.pluginName() ) )
{
QString existingVersion = ScriptManager::instance()->m_scripts[ newScriptInfo.pluginName() ]->info().version();
QString message = i18n( "Another script with the name %1 already exists\nExisting Script's "
"Version: %1\nSelected Script's Version: %2", newScriptInfo.version()
, existingVersion, newScriptInfo.version() );
KMessageBox::error( this, message );
return;
}
for( int i = 1; dir.exists( destination ); ++i )
destination += i;
dir.mkpath( destination );
archiveDir->copyTo( destination );
KMessageBox::information( this, i18n( "The script %1 was successfully installed", newScriptInfo.name() ) );
m_timer->start();
}
void
ScriptsConfig::slotReloadScriptSelector()
{
DEBUG_BLOCK
m_oldSelector = m_selector;
m_selector = new ScriptSelector( this );
QString key = QLatin1String( "Generic" );
m_selector->addScripts( ScriptManager::instance()->scripts( key ),
KPluginSelector::ReadConfigFile, i18n("Generic"), key );
key = QLatin1String( "Lyrics" );
m_selector->addScripts( ScriptManager::instance()->scripts( key ),
KPluginSelector::ReadConfigFile, i18n("Lyrics"), key );
key = QLatin1String( "Scriptable Service" );
m_selector->addScripts( ScriptManager::instance()->scripts( key ),
KPluginSelector::ReadConfigFile, i18n("Scriptable Service"), key );
- connect( m_selector, SIGNAL(changed(bool)), SLOT(slotConfigChanged(bool)) );
- connect( m_selector, SIGNAL(changed(bool)), m_parent, SLOT(updateButtons()) );
- connect( m_selector, SIGNAL(filtered(bool)), m_uninstallButton, SLOT(setDisabled(bool)) );
+ connect( m_selector, &ScriptSelector::changed, this, &ScriptsConfig::slotConfigChanged );
+ connect( m_selector, &ScriptSelector::filtered, m_uninstallButton, &QPushButton::setDisabled );
+
+ if (auto dialog = qobject_cast<Amarok2ConfigDialog*>(m_parent))
+ connect( m_selector, &ScriptSelector::changed, dialog, &Amarok2ConfigDialog::updateButtons );
m_verticalLayout->insertWidget( 0, m_selector );
m_verticalLayout->removeWidget( m_oldSelector );
#pragma message("PORTME KF5: Line to port here")
//m_selector->setFilter( m_oldSelector->filter() );
- QTimer::singleShot( 0, this, SLOT(restoreScrollBar()) );
+ QTimer::singleShot( 0, this, &ScriptsConfig::restoreScrollBar );
}
void
ScriptsConfig::restoreScrollBar()
{
if( !m_oldSelector )
return;
m_selector->setVerticalPosition( m_oldSelector->verticalPosition() );
m_oldSelector->deleteLater();
}
void
ScriptsConfig::slotUpdateScripts()
{
m_timer->stop();
ScriptManager::instance()->updateAllScripts();
}
void
ScriptsConfig::slotUninstallScript()
{
DEBUG_BLOCK
if( !ScriptManager::instance()->m_scripts.contains( m_selector->currentItem() ) )
return;
ScriptItem *item = ScriptManager::instance()->m_scripts.value( m_selector->currentItem() );
int response = KMessageBox::warningContinueCancel( this, i18n( "You are advised to only uninstall manually "
"installed scripts using this button." ) );
if( response == KMessageBox::Cancel )
return;
QRegExp regex( "(.*apps/amarok/scripts/.+/).*script.spec" );
regex.indexIn( item->info().entryPath() );
qDebug() << "About to remove folder " << regex.cap( 1 );
removeDir( regex.cap( 1 ) );
m_timer->start();
}
const KArchiveFile*
ScriptsConfig::findSpecFile( const KArchiveDirectory *dir ) const
{
foreach( const QString &entry, dir->entries() )
{
if( dir->entry( entry )->isFile() )
{
if( entry == "script.spec" )
return static_cast<const KArchiveFile*>( dir->entry( entry ) );
}
else
{
if( entry != "." && entry != ".." )
{
const KArchiveDirectory *subDir = static_cast<const KArchiveDirectory*>( dir->entry( entry ) );
if( subDir )
{
const KArchiveFile *file = findSpecFile( subDir );
if( !file )
continue;
return file;
}
}
}
}
return 0;
}
void
ScriptsConfig::removeDir( const QString &dirPath ) const
{
QDir dir( dirPath );
if( dir.exists( dirPath ) )
{
foreach( const QFileInfo &info, dir.entryInfoList( QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files ) )
{
if( info.isDir() )
removeDir( info.absoluteFilePath() );
else
QFile::remove( info.absoluteFilePath() );
}
dir.rmdir( dirPath );
}
}
diff --git a/src/context/ToolbarView.cpp b/src/context/ToolbarView.cpp
index a6f62e4ec5..6cd0beda65 100644
--- a/src/context/ToolbarView.cpp
+++ b/src/context/ToolbarView.cpp
@@ -1,267 +1,267 @@
/****************************************************************************************
* Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ToolbarView.h"
#include "Containment.h"
#include "core/support/Debug.h"
#include "PaletteHandler.h"
#include "toolbar/AppletItemOverlay.h"
#include "toolbar/AppletToolbarAppletItem.h"
#include "toolbar/AppletToolbar.h"
#include <kfiledialog.h>
#include <kstandarddirs.h>
#include <plasma/applet.h>
#include <plasma/containment.h>
#include <plasma/packagestructure.h>
#include <plasma/theme.h>
#include <QApplication>
#include <QDBusInterface>
#include <QGraphicsLinearLayout>
#include <QGraphicsScene>
#include <QPalette>
#include <QSizePolicy>
#include <QWidget>
#include <QTimer>
#define TOOLBAR_X_OFFSET 2000
#define TOOLBAR_SCENE_PADDING 2
Context::ToolbarView::ToolbarView( Plasma::Containment* containment, QGraphicsScene* scene, QWidget* parent )
: QGraphicsView( scene, parent )
, m_height( 36 )
, m_cont( containment )
{
setObjectName( "ContextToolbarView" );
setFixedHeight( m_height );
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
setAutoFillBackground( true );
setContentsMargins( 0, 0, 0, 0 );
setFrameStyle( QFrame::NoFrame );
applyStyleSheet();
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(applyStyleSheet()) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &Context::ToolbarView::applyStyleSheet );
//Padding required to prevent view scrolling, probably caused by the 1px ridge
setSceneRect( TOOLBAR_X_OFFSET, 0, size().width()-TOOLBAR_SCENE_PADDING,
size().height()-TOOLBAR_SCENE_PADDING );
setInteractive( true );
setAcceptDrops( true );
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
// now we create the toolbar
m_toolbar = new AppletToolbar(0);
scene->addItem(m_toolbar.data());
m_toolbar.data()->setContainment( qobject_cast<Context::Containment *>(containment) );
m_toolbar.data()->setZValue( m_toolbar.data()->zValue() + 1000 );
m_toolbar.data()->setPos( TOOLBAR_X_OFFSET, 0 );
- connect( m_toolbar.data(), SIGNAL(configModeToggled()), SLOT(toggleConfigMode()) );
- connect( m_toolbar.data(), SIGNAL(hideAppletExplorer()), SIGNAL(hideAppletExplorer()) );
- connect( m_toolbar.data(), SIGNAL(showAppletExplorer()), SIGNAL(showAppletExplorer()) );
+ connect( m_toolbar.data(), &Context::AppletToolbar::configModeToggled, this, &Context::ToolbarView::toggleConfigMode );
+ connect( m_toolbar.data(), &Context::AppletToolbar::hideAppletExplorer, this, &Context::ToolbarView::hideAppletExplorer );
+ connect( m_toolbar.data(), &Context::AppletToolbar::showAppletExplorer, this, &Context::ToolbarView::showAppletExplorer );
Context::Containment* cont = dynamic_cast< Context::Containment* >( containment );
if( cont )
{
- connect( cont, SIGNAL(appletAdded(Plasma::Applet*,int)), m_toolbar.data(), SLOT(appletAdded(Plasma::Applet*,int)) );
- connect( m_toolbar.data(), SIGNAL(appletAddedToToolbar(Plasma::Applet*,int)), this, SLOT(appletAdded(Plasma::Applet*,int)) );
- connect( cont, SIGNAL(appletRemoved(Plasma::Applet*)), this, SLOT(appletRemoved(Plasma::Applet*)) );
- connect( m_toolbar.data(), SIGNAL(showApplet(Plasma::Applet*)), cont, SLOT(showApplet(Plasma::Applet*)) );
- connect( m_toolbar.data(), SIGNAL(moveApplet(Plasma::Applet*,int,int)), cont, SLOT(moveApplet(Plasma::Applet*,int,int)) );
+ connect( cont, &Context::Containment::appletAdded, m_toolbar.data(), &Context::AppletToolbar::appletAdded );
+ connect( m_toolbar.data(), &Context::AppletToolbar::appletAddedToToolbar, this, &Context::ToolbarView::appletAdded );
+ connect( cont, &Context::Containment::appletRemoved, this, &Context::ToolbarView::appletRemoved );
+ connect( m_toolbar.data(), &Context::AppletToolbar::showApplet, cont, &Context::Containment::showApplet );
+ connect( m_toolbar.data(), &Context::AppletToolbar::moveApplet, cont, &Context::Containment::moveApplet );
}
}
Context::ToolbarView::~ToolbarView()
{
delete m_toolbar.data();
}
void
Context::ToolbarView::resizeEvent( QResizeEvent *event )
{
Q_UNUSED( event )
setSceneRect( TOOLBAR_X_OFFSET, 0, size().width()-TOOLBAR_SCENE_PADDING,
size().height()-TOOLBAR_SCENE_PADDING );
if( m_toolbar )
m_toolbar.data()->setGeometry( sceneRect() );
}
void
Context::ToolbarView::dragEnterEvent( QDragEnterEvent *event )
{
Q_UNUSED( event )
}
void
Context::ToolbarView::dragMoveEvent( QDragMoveEvent *event )
{
Q_UNUSED( event )
}
void
Context::ToolbarView::dragLeaveEvent( QDragLeaveEvent *event )
{
Q_UNUSED( event )
}
void
Context::ToolbarView::applyStyleSheet() // SLOT
{
DEBUG_BLOCK
const QPalette palette = QApplication::palette();
setStyleSheet( QString( "QFrame#ContextToolbarView { border: 1px ridge %1; " \
"background-color: %2; color: %3; border-radius: 3px; }" \
"QLabel { color: %3; }" )
.arg( palette.color( QPalette::Active, QPalette::Window ).name() )
.arg( The::paletteHandler()->highlightColor().name() )
.arg( palette.color( QPalette::Active, QPalette::HighlightedText ).name() )
);
}
void
Context::ToolbarView::toggleConfigMode()
{
DEBUG_BLOCK
if( m_toolbar.data()->configEnabled() ) // set up config stuff
{
debug() << "got config enabled, creating all the move overlays";
// now add the overlays that handle the drag-n-dropping
QColor overlayColor( Plasma::Theme::defaultTheme()->color( Plasma::Theme::BackgroundColor ) );
QBrush overlayBrush( overlayColor );
QPalette p( palette() );
p.setBrush( QPalette::Window, overlayBrush );
/* for( int i = 0; i < m_toolbar->appletLayout()->count(); i++ )
{
debug() << "item" << i << "has geometry:" << m_toolbar->appletLayout()->itemAt( i )->geometry();
Context::AppletToolbarAddItem* item = dynamic_cast< Context::AppletToolbarAddItem* >( m_toolbar->appletLayout()->itemAt( i ) );
if( item )
debug() << "add item has boundingRect:" << item->boundingRect() << "and geom:" << item->geometry() << "and sizehint" << item->effectiveSizeHint( Qt::PreferredSize );
} */
for( int i = 0; i < m_toolbar.data()->appletLayout()->count(); i++ )
{
debug() << "creating a move overlay";
Context::AppletToolbarAppletItem* item = dynamic_cast< Context::AppletToolbarAppletItem* >( m_toolbar.data()->appletLayout()->itemAt( i ) );
if( item )
{
Context::AppletItemOverlay *moveOverlay = new Context::AppletItemOverlay( item, m_toolbar.data()->appletLayout(), this );
- connect( moveOverlay, SIGNAL(moveApplet(Plasma::Applet*,int,int)), m_cont, SLOT(moveApplet(Plasma::Applet*,int,int)) );
- connect( moveOverlay, SIGNAL(moveApplet(Plasma::Applet*,int,int)), this, SLOT(refreshOverlays()) );
- connect( moveOverlay, SIGNAL(deleteApplet(Plasma::Applet*)), this, SLOT(appletRemoved(Plasma::Applet*)) );
+ connect( moveOverlay, &Context::AppletItemOverlay::moveApplet, m_cont, &Context::Containment::moveApplet );
+ connect( moveOverlay, &Context::AppletItemOverlay::moveApplet, this, &Context::ToolbarView::refreshOverlays );
+ connect( moveOverlay, &Context::AppletItemOverlay::deleteApplet, this, &Context::ToolbarView::appletRemoved );
moveOverlay->setPalette( p );
moveOverlay->show();
moveOverlay->raise();
m_moveOverlays << moveOverlay;
debug() << moveOverlay << moveOverlay->geometry();
}
}
} else
{
debug() << "removing all the move overlays";
foreach( Context::AppletItemOverlay *moveOverlay, m_moveOverlays )
moveOverlay->deleteLater();
m_moveOverlays.clear();
}
}
void
Context::ToolbarView::appletRemoved( Plasma::Applet* applet )
{
DEBUG_BLOCK
foreach( Context::AppletItemOverlay* overlay, m_moveOverlays )
{
if( overlay->applet()->applet() == applet )
{
m_moveOverlays.removeAll( overlay );
debug() << "got an overlay to remove";
}
}
m_toolbar.data()->appletRemoved( applet );
applet->deleteLater();
}
void
Context::ToolbarView::appletAdded( Plasma::Applet* applet, int loc )
{
DEBUG_BLOCK
Q_UNUSED( applet )
Q_UNUSED( loc )
if( m_toolbar.data()->configEnabled() )
recreateOverlays();
}
void
Context::ToolbarView::refreshOverlays()
{
}
void
Context::ToolbarView::recreateOverlays()
{
DEBUG_BLOCK
foreach( Context::AppletItemOverlay *moveOverlay, m_moveOverlays )
moveOverlay->deleteLater();
m_moveOverlays.clear();
QColor overlayColor( Plasma::Theme::defaultTheme()->color( Plasma::Theme::BackgroundColor ) );
QBrush overlayBrush( overlayColor );
QPalette p( palette() );
p.setBrush( QPalette::Window, overlayBrush );
for( int i = 0; i < m_toolbar.data()->appletLayout()->count(); i++ )
{
debug() << "creating a move overlay";
Context::AppletToolbarAppletItem* item = dynamic_cast< Context::AppletToolbarAppletItem* >( m_toolbar.data()->appletLayout()->itemAt( i ) );
if( item )
{
Context::AppletItemOverlay *moveOverlay = new Context::AppletItemOverlay( item, m_toolbar.data()->appletLayout(), this );
- connect( moveOverlay, SIGNAL(moveApplet(Plasma::Applet*,int,int)), m_cont, SLOT(moveApplet(Plasma::Applet*,int,int)) );
- connect( moveOverlay, SIGNAL(moveApplet(Plasma::Applet*,int,int)), this, SLOT(refreshOverlays()) );
- connect( moveOverlay, SIGNAL(deleteApplet(Plasma::Applet*)), this, SLOT(appletRemoved(Plasma::Applet*)) );
+ connect( moveOverlay, &Context::AppletItemOverlay::moveApplet, m_cont, &Context::Containment::moveApplet );
+ connect( moveOverlay, &Context::AppletItemOverlay::moveApplet, this, &Context::ToolbarView::refreshOverlays );
+ connect( moveOverlay, &Context::AppletItemOverlay::deleteApplet, this, &Context::ToolbarView::appletRemoved );
moveOverlay->setPalette( p );
moveOverlay->show();
moveOverlay->raise();
m_moveOverlays << moveOverlay;
debug() << moveOverlay << moveOverlay->geometry();
}
}
}
void
Context::ToolbarView::refreshSycoca()
{
QDBusInterface dbus("org.kde.kded", "/kbuildsycoca", "org.kde.kbuildsycoca");
dbus.call(QDBus::Block, "recreate");
recreateOverlays();
}
diff --git a/src/context/popupdropper/libpud/PopupDropper.cpp b/src/context/popupdropper/libpud/PopupDropper.cpp
index 4bbc3084b9..b013a27f9a 100644
--- a/src/context/popupdropper/libpud/PopupDropper.cpp
+++ b/src/context/popupdropper/libpud/PopupDropper.cpp
@@ -1,979 +1,979 @@
/***************************************************************************
* Copyright (c) 2008 Jeff Mitchell <mitchell@kde.org> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "PopupDropper.h"
#include "PopupDropper_p.h"
#include "PopupDropperItem.h"
#include <QtDebug>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QSvgRenderer>
#include <QAction>
#include <QMenu>
#include <QPalette>
#include <QTimeLine>
#include <QWidget>
PopupDropperPrivate::PopupDropperPrivate( PopupDropper* parent, bool sa, QWidget* widget )
: QObject( parent )
, standalone( sa )
, widget( widget )
, scene( 0 )
, view( 0 )
, fade( PopupDropper::FadeInOut )
, fadeHideTimer()
, fadeShowTimer()
, fadeInTime( 800 )
, fadeOutTime( 300 )
, deleteTimer()
, deleteTimeout( 1000 )
, frameMax( 30 )
, windowColor( 0, 0, 0, 64 )
, windowBackgroundBrush()
, baseTextColor( Qt::white )
, hoveredTextColor( Qt::blue )
, hoveredBorderPen()
, hoveredFillBrush()
, file()
, sharedRenderer( 0 )
, horizontalOffset( 30 )
, pdiItems()
, overlayLevel( 1 )
, entered( false )
, submenuMap()
, submenu( false )
, allItems()
, quitOnDragLeave( false )
, onTop( true )
, widgetRect()
, queuedHide( false )
, q( parent )
{
if( widget )
widgetRect = widget->rect();
windowBackgroundBrush.setColor( windowColor );
hoveredBorderPen.setColor( Qt::blue );
hoveredBorderPen.setWidth( 2 );
hoveredBorderPen.setStyle( Qt::SolidLine );
QColor hoveredFillColor = QColor( Qt::blue );
hoveredFillColor.setAlpha( 32 );
hoveredFillBrush.setColor( hoveredFillColor );
hoveredFillBrush.setStyle( Qt::SolidPattern );
scene = new QGraphicsScene( ( sa ? 0 : parent ) );
view = new PopupDropperView( parent, scene, ( sa ? 0 : widget ) );
//qDebug() << "on create, view size = " << view->size();
deleteTimer.setSingleShot( true );
fadeHideTimer.setDirection( QTimeLine::Backward );
- connect( &fadeHideTimer, SIGNAL(frameChanged(int)), this, SLOT(fadeHideTimerFrameChanged(int)) );
- connect( &fadeShowTimer, SIGNAL(frameChanged(int)), this, SLOT(fadeShowTimerFrameChanged(int)) );
- connect( &fadeHideTimer, SIGNAL(finished()), this, SLOT(fadeHideTimerFinished()) );
- connect( &fadeShowTimer, SIGNAL(finished()), this, SLOT(fadeShowTimerFinished()) );
- connect( &deleteTimer, SIGNAL(timeout()), this, SLOT(deleteTimerFinished()) );
+ connect( &fadeHideTimer, &QTimeLine::frameChanged, this, &PopupDropperPrivate::fadeHideTimerFrameChanged );
+ connect( &fadeShowTimer, &QTimeLine::frameChanged, this, &PopupDropperPrivate::fadeShowTimerFrameChanged );
+ connect( &fadeHideTimer, &QTimeLine::finished, this, &PopupDropperPrivate::fadeHideTimerFinished );
+ connect( &fadeShowTimer, &QTimeLine::finished, this, &PopupDropperPrivate::fadeShowTimerFinished );
+ connect( &deleteTimer, &QTimer::timeout, this, &PopupDropperPrivate::deleteTimerFinished );
}
PopupDropperPrivate::~PopupDropperPrivate()
{
}
void PopupDropperPrivate::newSceneView( PopupDropper* pud )
{
scene->deleteLater();
scene = new QGraphicsScene( pud );
//qDebug() << "new scene width in newSceneView = " << scene->width();
view = new PopupDropperView( pud, scene, widget );
//qDebug() << "on create, view size = " << view->size();
}
void PopupDropperPrivate::setParent( QObject* parent )
{
QObject::setParent( parent );
q = static_cast<PopupDropper*>( parent );
}
void PopupDropperPrivate::fadeHideTimerFrameChanged( int frame ) //SLOT
{
if( fadeHideTimer.state() == QTimeLine::Running )
{
qreal val = ( frame * 1.0 ) / frameMax;
QColor color = windowColor;
int alpha = (int)( color.alpha() * val );
color.setAlpha( alpha );
q->setPalette( color );
foreach( PopupDropperItem* pdi, pdiItems )
pdi->setSubitemOpacity( val );
}
}
void PopupDropperPrivate::fadeShowTimerFrameChanged( int frame ) //SLOT
{
if( fadeShowTimer.state() == QTimeLine::Running )
{
qreal val = ( frame * 1.0 ) / frameMax;
QColor color = windowColor;
int alpha = (int)( color.alpha() * val );
color.setAlpha( alpha );
q->setPalette( color );
foreach( PopupDropperItem* pdi, pdiItems )
pdi->setSubitemOpacity( val );
}
}
void PopupDropperPrivate::fadeHideTimerFinished() //SLOT
{
view->hide();
//qDebug() << "Emitting fadeHideFinished in d pointer " << this;
emit q->fadeHideFinished();
}
void PopupDropperPrivate::fadeShowTimerFinished() //SLOT
{
q->setPalette( windowColor );
queuedHide = false;
foreach( PopupDropperItem* pdi, pdiItems )
pdi->setSubitemOpacity( 1.0 );
}
void PopupDropperPrivate::dragEntered()
{
//qDebug() << "PopupDropperPrivate::dragEntered";
q->updateAllOverlays();
}
void PopupDropperPrivate::dragLeft()
{
//qDebug() << "PopupDropperPrivate::dragLeft()";
//qDebug() << "PUD to be hidden or not hidden = " << q;
if( view->entered() && quitOnDragLeave )
{
view->setAcceptDrops( false );
//qDebug() << "View entered, hiding";
- connect( q, SIGNAL(fadeHideFinished()), q, SLOT(subtractOverlay()) );
+ connect( q, &PopupDropper::fadeHideFinished, q, &PopupDropper::subtractOverlay );
q->hide();
}
q->updateAllOverlays();
}
void PopupDropperPrivate::startDeleteTimer()
{
if( deleteTimeout == 0 )
return;
view->setEntered( false );
//qDebug() << "Starting delete timer";
deleteTimer.start( deleteTimeout );
}
void PopupDropperPrivate::deleteTimerFinished() //SLOT
{
//qDebug() << "Delete Timer Finished";
if( !view->entered() && quitOnDragLeave )
{
- connect( q, SIGNAL(fadeHideFinished()), q, SLOT(subtractOverlay()) );
+ connect( q, &PopupDropper::fadeHideFinished, q, &PopupDropper::subtractOverlay );
q->hide();
}
}
void PopupDropperPrivate::reposItems()
{
qreal partitionsize, my_min, my_max;
//qDebug() << "allItems.size() = " << allItems.size();
int counter = 0;
for( int i = 0; i < allItems.size(); i++ )
{
//qDebug() << "item " << i;
int verticalmargin = 5;
partitionsize = scene->height() / pdiItems.size(); //gives partition size...now center in this area
my_min = ( counter * partitionsize ) + verticalmargin;
my_max = ( ( counter + 1 ) * partitionsize ) - verticalmargin;
//qDebug() << "my_min = " << my_min << ", my_max = " << my_max;
PopupDropperItem* pItem = dynamic_cast<PopupDropperItem*>( allItems.at( i ) );
QGraphicsLineItem* qglItem = 0;
if( pItem )
{
pItem->setPopupDropper( q ); //safety
//qDebug() << "item " << i << " is a PDI ";
//If the svgElementRect is too high, resize it to fit
pItem->svgElementRect().setHeight( my_max - my_min - ( 2 * verticalmargin ) );
pItem->setPos( 0, my_min );
pItem->borderRectItem()->setRect( 0 - pItem->borderWidth(), 0, scene->width() + 2*pItem->borderWidth(), my_max - my_min );
pItem->scaleAndReposSvgItem();
pItem->reposTextItem();
pItem->reposHoverFillRects();
pItem->update();
//qDebug() << "size of view frame = " << view->size();
++counter;
}
else if( ( qglItem = dynamic_cast<QGraphicsLineItem*>( allItems.at( i ) ) ) )
{
//qDebug() << "item " << i << " is a QGLI";
qglItem->setLine( horizontalOffset, (my_max-partitionsize), scene->width() - horizontalOffset, (my_max-partitionsize) );
}
}
}
bool PopupDropperPrivate::amIOnTop( PopupDropperView* pdv )
{
if( onTop && pdv == view )
return true;
return false;
}
//////////////////////////////////////////////////////////////
PopupDropper::PopupDropper( QWidget *parent, bool standalone )
: QObject( parent )
, d( new PopupDropperPrivate( this, standalone, parent ) )
{
if( !parent )
{
parent = d->view;
d->widget = parent;
}
QObject::setParent( parent );
initOverlay( parent );
setColors( d->windowColor, d->baseTextColor, d->hoveredTextColor, d->hoveredBorderPen.color(), d->hoveredFillBrush.color() );
d->sharedRenderer = new QSvgRenderer( this );
d->overlayLevel = 1;
//qDebug() << "Popup Dropper created!";
}
PopupDropper::~PopupDropper()
{
//qDebug() << "Popup Dropper destroyed!";
}
int PopupDropper::overlayLevel() const
{
return d->overlayLevel;
}
void PopupDropper::initOverlay( QWidget* parent, PopupDropperPrivate* priv )
{
PopupDropperPrivate *pdp = priv ? priv : d;
//qDebug() << "PUD Overlay being created, d pointer is " << d;
pdp->scene->setSceneRect( QRectF( parent->rect() ) );
//qDebug() << "Scene width = " << pdp->scene->width();
pdp->scene->setItemIndexMethod( QGraphicsScene::NoIndex );
pdp->view->setFixedSize( parent->size() );
pdp->view->setLineWidth( 0 );
pdp->view->setFrameStyle( QFrame::NoFrame );
pdp->view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
pdp->view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
pdp->view->setBackgroundRole( QPalette::Window );
pdp->view->setAutoFillBackground( true );
pdp->fadeHideTimer.setFrameRange( 0, pdp->frameMax );
pdp->fadeHideTimer.setUpdateInterval( 20 ); // 50 fps
pdp->fadeShowTimer.setFrameRange( 0, pdp->frameMax );
pdp->fadeShowTimer.setUpdateInterval( 20 ); // 50 fps
}
void PopupDropper::addOverlay()
{
d->onTop = false;
m_viewStack.push( d );
PopupDropperPrivate* old_d = d;
d = new PopupDropperPrivate( this, false, old_d->view );
d->sharedRenderer = old_d->sharedRenderer;
//qDebug() << "Adding overlay: ";
initOverlay( old_d->view );
setColors( d->windowColor, d->baseTextColor, d->hoveredTextColor, d->hoveredBorderPen.color(), d->hoveredFillBrush.color() );
d->quitOnDragLeave = true;
d->overlayLevel = old_d->overlayLevel + 1;
old_d->view->deactivateHover();
}
//note: Used when activating a submenu; does not set default colors, should have done that when creating submenu
void PopupDropper::addOverlay( PopupDropperPrivate* newD )
{
d->onTop = false;
//qDebug() << "right before push, m_viewStack.size() is " << m_viewStack.size();
m_viewStack.push( d );
//qDebug() << "right after push, m_viewStack.size() is " << m_viewStack.size();
PopupDropperPrivate* old_d = d;
d = newD;
d->onTop = true;
d->sharedRenderer = old_d->sharedRenderer;
d->quitOnDragLeave = true;
d->overlayLevel = old_d->overlayLevel + 1;
//qDebug() << "new d d->overlayLevel = " << d->overlayLevel;
//qDebug() << "in PopupDropper " << this;
}
//NOTE: just like you have to show the overlay when adding, this function does not hide the overlay; you must do that yourself
//with hide() (which will hide just one level)
bool PopupDropper::subtractOverlay()
{
//qDebug() << "subtractOverlay, with d pointer " << d;
- disconnect( this, SLOT(subtractOverlay()) );
+ disconnect( this, &PopupDropper::fadeHideFinished, this, &PopupDropper::subtractOverlay );
while( !isHidden() && d->fadeHideTimer.state() == QTimeLine::Running )
{
//qDebug() << "singleshotting subtractOverlay";
- QTimer::singleShot( 0, this, SLOT(subtractOverlay()) );
+ QTimer::singleShot( 0, this, &PopupDropper::subtractOverlay );
return false;
}
//qDebug() << "in PopupDropper " << this;
//qDebug() << "overlay d->overlayLevel = " << d->overlayLevel;
if( d->overlayLevel == 1 )
return false;
PopupDropper::Fading currFadeValue = d->fade;
d->fade = PopupDropper::NoFade;
d->onTop = false;
PopupDropperPrivate* old_d = d;
//qDebug() << "right before pop, m_viewStack.size() is " << m_viewStack.size();
d = m_viewStack.pop();
d->onTop = true;
if( !old_d->submenu )
{
//qDebug() << "not submenu, deleting";
old_d->deleteLater();
}
else
{
foreach( QGraphicsItem* item, old_d->pdiItems )
old_d->scene->removeItem( item );
//qDebug() << "not deleting, submenu";
old_d->fade = currFadeValue;
old_d->view->resetView();
}
d->startDeleteTimer();
return true;
}
PopupDropperItem* PopupDropper::addSubmenu( PopupDropper** pd, const QString &text )
{
//qDebug() << "addSubmenu, this is " << this << " and passed-in PopupDropper is " << (*pd);
if( !(*pd) )
{
qWarning() << "Did not pass in a valid PUD!";
return 0;
}
PopupDropperPrivate* newD = (*pd)->d;
newD->submenu = true;
newD->widget = d->widget;
newD->setParent( this );
foreach( PopupDropperItem* item, newD->pdiItems )
newD->scene->removeItem( item );
newD->newSceneView( this );
initOverlay( d->widget, newD );
PopupDropperItem* pdi = new PopupDropperItem();
QAction* action = new QAction( text, this );
- connect( action, SIGNAL(hovered()), this, SLOT(activateSubmenu()) );
+ connect( action, &QAction::hovered, this, &PopupDropper::activateSubmenu );
pdi->setAction( action );
pdi->setSubmenuTrigger( true );
pdi->setHoverIndicatorShowStyle( PopupDropperItem::OnHover );
d->submenuMap[action] = newD;
delete (*pd);
(*pd) = 0;
foreach( PopupDropperItem* item, newD->pdiItems )
item->setPopupDropper( this );
//qDebug() << "d->submenuMap[pda] = " << d->submenuMap[pda];
addItem( pdi );
return pdi;
}
void PopupDropper::activateSubmenu()
{
//qDebug() << "Sender is " << QObject::sender();
if( isHidden() || d->fadeHideTimer.state() == QTimeLine::Running )
return;
PopupDropperPrivate* oldd = d;
addOverlay( d->submenuMap[static_cast<QAction*>(QObject::sender())] );
//qDebug() << "d->pdiItems.size() = " << d->pdiItems.size() << " for " << d;
foreach( PopupDropperItem* item, d->pdiItems )
addItem( item, false, false );
oldd->view->deactivateHover();
show();
}
bool PopupDropper::addMenu( const QMenu *menu )
{
Q_UNUSED(menu)
if( !menu )
return false;
if( menu->actions().size() == 0 )
return true;
PopupDropperItem *pdi = 0;
foreach( QAction *action, menu->actions() )
{
if( !action->menu() )
{
pdi = new PopupDropperItem();
pdi->setAction( action );
addItem( pdi );
}
else
{
PopupDropper *pd = new PopupDropper( 0 );
bool success = pd->addMenu( action->menu() );
if( success )
pdi = addSubmenu( &pd, action->text() );
}
pdi = 0;
}
return true;
}
bool PopupDropper::standalone() const
{
return d->standalone;
}
void PopupDropper::show()
{
if( !d->sharedRenderer )
{
//qDebug() << "No shared renderer set!";
return;
}
if( d->widget && d->widget->rect() != d->widgetRect )
{
d->widgetRect = d->widget->rect();
d->scene->setSceneRect( d->widget->rect() );
d->view->setFixedSize( d->widget->size() );
update();
}
//qDebug() << "Showing PopupDropper";
d->fadeShowTimer.stop();
if( ( d->fade == PopupDropper::FadeIn || d->fade == PopupDropper::FadeInOut ) && d->fadeInTime > 0 )
{
//qDebug() << "Animating!";
d->fadeShowTimer.setDuration( d->fadeInTime );
d->fadeShowTimer.setCurrentTime( 0 );
d->fadeShowTimer.setCurveShape( QTimeLine::EaseOutCurve );
QColor color = d->windowColor;
color.setAlpha( 0 );
setPalette( color );
foreach( PopupDropperItem* pdi, d->pdiItems )
pdi->setSubitemOpacity( 0.0 );
d->fadeShowTimer.start();
//qDebug() << "Timer started";
}
d->view->show();
}
void PopupDropper::showAllOverlays()
{
show();
for( int i = m_viewStack.size() - 1; i >= 0; --i )
{
PopupDropperPrivate* pdi = m_viewStack.at( i );
if( pdi != d )
d->view->show();
}
}
//returns immediately!
void PopupDropper::hide()
{
//qDebug() << "PopupDropper::hide entered, d pointer is = " << d;
//if hide is called and the view is already hidden, it's likely spurious
if( isHidden() )
{
//qDebug() << "ishidden, returning";
return;
}
//queuedHide is to make sure that fadeShowTimerFinished executes before this next hide()
if( d->fadeShowTimer.state() == QTimeLine::Running )
{
//qDebug() << "show timer running, queueing hide";
d->fadeShowTimer.stop();
d->queuedHide = true;
- QTimer::singleShot( 0, d, SLOT(fadeShowTimerFinished()) );
- QTimer::singleShot( 0, this, SLOT(hide()) );
+ QTimer::singleShot( 0, d, &PopupDropperPrivate::fadeShowTimerFinished );
+ QTimer::singleShot( 0, this, &PopupDropper::hide );
return;
}
//queuedHide will be set to false from fadeShowTimerFinished...so if this came up first,
//then wait
if( d->fadeHideTimer.state() == QTimeLine::Running || d->queuedHide )
{
//qDebug() << "hide timer running or queued hide";
- QTimer::singleShot( 0, this, SLOT(hide()) );
+ QTimer::singleShot( 0, this, &PopupDropper::hide );
return;
}
if( ( d->fade == PopupDropper::FadeOut || d->fade == PopupDropper::FadeInOut ) && d->fadeOutTime > 0 )
{
//qDebug() << "Starting fade out";
d->fadeHideTimer.setDuration( d->fadeOutTime );
d->fadeHideTimer.setCurveShape( QTimeLine::LinearCurve );
d->fadeHideTimer.start();
//qDebug() << "Timer started";
return;
}
else //time is zero, or no fade
{
//qDebug() << "time is zero, or no fade";
- QTimer::singleShot( 0, d, SLOT(fadeHideTimerFinished()) );
+ QTimer::singleShot( 0, d, &PopupDropperPrivate::fadeHideTimerFinished );
return;
}
}
void PopupDropper::hideAllOverlays()
{
//qDebug() << "Entered hideAllOverlays";
- connect( this, SIGNAL(fadeHideFinished()), this, SLOT(slotHideAllOverlays()) );
+ connect( this, &PopupDropper::fadeHideFinished, this, &PopupDropper::slotHideAllOverlays );
hide();
//qDebug() << "Leaving hideAllOverlays";
}
void PopupDropper::slotHideAllOverlays()
{
//qDebug() << "Entered slotHideAllOverlays()";
- disconnect( this, SIGNAL(fadeHideFinished()), this, SLOT(slotHideAllOverlays()) );
+ disconnect( this, &PopupDropper::fadeHideFinished, this, &PopupDropper::slotHideAllOverlays );
//qDebug() << "m_viewStack.size() = " << m_viewStack.size();
for( int i = m_viewStack.size() - 1; i >= 0; --i )
{
PopupDropperPrivate* pdp = m_viewStack.at( i );
//qDebug() << "checking pdp = " << (QObject*)pdp << ", d is " << (QObject*)d;
if( pdp != d )
pdp->view->hide();
}
//qDebug() << "Leaving slotHideAllOverlays";
}
void PopupDropper::update()
{
d->reposItems();
d->view->update();
}
void PopupDropper::updateAllOverlays()
{
for( int i = m_viewStack.size() - 1; i >= 0; --i )
{
PopupDropperPrivate* pdp = m_viewStack.at( i );
pdp->view->update();
}
d->view->update();
}
bool PopupDropper::isHidden() const
{
return d->view->isHidden();
}
void PopupDropper::clear()
{
while( !isHidden() && d->fadeHideTimer.state() == QTimeLine::Running )
{
- QTimer::singleShot(0, this, SLOT(clear()) );
+ QTimer::singleShot(0, this, &PopupDropper::clear );
return;
}
//qDebug() << "Clear happening!";
- disconnect( this, SLOT(clear()) );
+// disconnect( this, 0, this, &PopupDropper::clear ); Unused at this point.
do
{
foreach( QGraphicsItem* item, d->allItems )
{
if( dynamic_cast<PopupDropperItem*>(item) )
{
if( dynamic_cast<PopupDropperItem*>(item)->isSubmenuTrigger() )
{
//qDebug() << "Disconnecting action";
- disconnect( dynamic_cast<PopupDropperItem*>(item)->action(), SIGNAL(hovered()), this, SLOT(activateSubmenu()) );
+ disconnect( dynamic_cast<PopupDropperItem*>(item)->action(), &QAction::hovered, this, &PopupDropper::activateSubmenu );
}
dynamic_cast<PopupDropperItem*>(item)->deleteLater();
}
else
delete item;
}
d->pdiItems.clear();
d->allItems.clear();
//qDebug() << "Size of pdiItems is now " << d->pdiItems.size();
//qDebug() << "Size of allItems is now " << d->allItems.size();
d->view->hide();
d->view->resetView();
} while ( subtractOverlay() );
}
bool PopupDropper::isEmpty( bool allItems ) const
{
if( allItems )
return d->allItems.empty();
else
return d->pdiItems.empty();
}
bool PopupDropper::quitOnDragLeave() const
{
return d->quitOnDragLeave;
}
void PopupDropper::setQuitOnDragLeave( bool quit )
{
d->quitOnDragLeave = quit;
}
int PopupDropper::fadeInTime() const
{
return d->fadeInTime;
}
void PopupDropper::setFadeInTime( const int msecs )
{
d->fadeInTime = msecs;
}
int PopupDropper::fadeOutTime() const
{
return d->fadeOutTime;
}
void PopupDropper::setFadeOutTime( const int msecs )
{
d->fadeOutTime = msecs;
}
PopupDropper::Fading PopupDropper::fading() const
{
return d->fade;
}
void PopupDropper::setFading( PopupDropper::Fading fade )
{
d->fade = fade;
}
const QTimeLine* PopupDropper::fadeHideTimer() const
{
return &d->fadeHideTimer;
}
const QTimeLine* PopupDropper::fadeShowTimer() const
{
return &d->fadeShowTimer;
}
int PopupDropper::deleteTimeout() const
{
return d->deleteTimeout;
}
void PopupDropper::setDeleteTimeout( int msecs )
{
d->deleteTimeout = msecs;
}
QColor PopupDropper::windowColor() const
{
return d->windowColor;
}
void PopupDropper::setWindowColor( const QColor &window )
{
d->windowColor = window;
setPalette( window );
}
QBrush PopupDropper::windowBackgroundBrush() const
{
return d->windowBackgroundBrush;
}
void PopupDropper::setWindowBackgroundBrush( const QBrush &window )
{
d->windowBackgroundBrush = window;
d->view->setBackgroundBrush( window );
}
QColor PopupDropper::baseTextColor() const
{
return d->baseTextColor;
}
void PopupDropper::setBaseTextColor( const QColor &text )
{
d->baseTextColor = text;
foreach( PopupDropperItem *item, d->pdiItems )
item->setBaseTextColor( text );
}
QColor PopupDropper::hoveredTextColor() const
{
return d->hoveredTextColor;
}
void PopupDropper::setHoveredTextColor( const QColor &text )
{
d->hoveredTextColor = text;
foreach( PopupDropperItem *item, d->pdiItems )
item->setHoveredTextColor( text );
}
QPen PopupDropper::hoveredBorderPen() const
{
return d->hoveredBorderPen;
}
void PopupDropper::setHoveredBorderPen( const QPen &border )
{
d->hoveredBorderPen = border;
foreach( PopupDropperItem *item, d->pdiItems )
item->setHoveredBorderPen( border );
}
QBrush PopupDropper::hoveredFillBrush() const
{
return d->hoveredFillBrush;
}
void PopupDropper::setHoveredFillBrush( const QBrush &fill )
{
d->hoveredFillBrush = fill;
foreach( PopupDropperItem *item, d->pdiItems )
item->setHoveredFillBrush( fill );
}
void PopupDropper::setColors( const QColor &window, const QColor &baseText, const QColor &hoveredText, const QColor &hoveredBorder, const QColor &hoveredFill )
{
d->windowColor = window;
d->baseTextColor = baseText;
d->hoveredTextColor = hoveredText;
d->hoveredBorderPen.setColor( hoveredBorder );
d->hoveredFillBrush.setColor( hoveredFill );
setPalette( window, baseText, hoveredText, hoveredBorder, hoveredFill );
}
void PopupDropper::setPalette( const QColor &window )
{
QPalette p = d->view->palette();
p.setColor( QPalette::Window, window );
d->view->setPalette( p );
updateAllOverlays();
}
void PopupDropper::setPalette( const QColor &window, const QColor &baseText, const QColor &hoveredText, const QColor &hoveredBorder, const QColor &hoveredFill )
{
QPalette p = d->view->palette();
p.setColor( QPalette::Window, window );
d->view->setPalette( p );
QPen pen;
QBrush brush;
foreach( PopupDropperItem *item, d->pdiItems )
{
item->setBaseTextColor( baseText );
item->setHoveredTextColor( hoveredText );
pen = item->hoveredBorderPen();
pen.setColor( hoveredBorder );
item->setHoveredBorderPen( pen );
brush = item->hoveredFillBrush();
brush.setColor( hoveredFill );
item->setHoveredFillBrush( brush );
}
updateAllOverlays();
}
QString PopupDropper::windowTitle() const
{
return d->view->windowTitle();
}
void PopupDropper::setWindowTitle( const QString &title )
{
d->view->setWindowTitle( title );
d->view->update();
}
QString PopupDropper::svgFile() const
{
return d->file;
}
void PopupDropper::setSvgFile( const QString &file )
{
if( d->sharedRenderer )
{
if( !d->sharedRenderer->load( file ) )
qWarning() << "Could not load SVG file " << file;
else
{
d->file = file;
//qDebug() << "Loaded SVG file!";
}
}
else
qWarning() << "No shared renderer!";
}
QSvgRenderer* PopupDropper::svgRenderer()
{
return d->sharedRenderer;
}
void PopupDropper::setSvgRenderer( QSvgRenderer *renderer )
{
d->sharedRenderer = renderer;
}
int PopupDropper::horizontalOffset() const
{
return d->horizontalOffset;
}
void PopupDropper::setHorizontalOffset( int pixels )
{
d->horizontalOffset = pixels;
}
const QSize PopupDropper::viewSize() const
{
if( d && d->view )
return d->view->size();
else
return QSize( 0, 0 );
}
void PopupDropper::addItem( PopupDropperItem *item, bool useSharedRenderer )
{
addItem( item, useSharedRenderer, true );
}
void PopupDropper::addItem( PopupDropperItem *item, bool useSharedRenderer, bool appendToList )
{
//qDebug() << "adding item";
//FIXME: Make separators do something graphical instead of just ignoring them
PopupDropperItem *pItem = static_cast<PopupDropperItem*>( item );
if( pItem->isSeparator() )
return;
if( useSharedRenderer )
pItem->setSharedRenderer( d->sharedRenderer );
//qDebug() << "Checking appendToList";
if( appendToList )
{
d->pdiItems.append( pItem );
d->allItems.append( pItem );
//qDebug() << "pdiItems list is now size " << d->pdiItems.size() << " for " << d;
//qDebug() << "allItems list is now size " << d->allItems.size() << " for " << d;
}
if( !pItem->textItem() )
{
QGraphicsTextItem *textItem = new QGraphicsTextItem( pItem->text(), pItem );
pItem->setTextItem( textItem );
if( !pItem->customBaseTextColor() || !pItem->baseTextColor().isValid() )
{
pItem->setBaseTextColor( d->baseTextColor );
//qDebug() << "Using PD's base text color";
}
else
{
//qDebug() << "Using the item's base text color";
pItem->textItem()->setDefaultTextColor( pItem->baseTextColor() );
}
if( !pItem->customHoveredTextColor() )
pItem->setHoveredTextColor( d->hoveredTextColor );
}
if( !pItem->borderRectItem() )
{
QGraphicsRectItem *borderRectItem = new QGraphicsRectItem( pItem );
borderRectItem->setZValue( -5 );
pItem->setBorderRectItem( borderRectItem );
if( !pItem->customHoveredBorderPen() )
pItem->setHoveredBorderPen( d->hoveredBorderPen );
if( !pItem->customHoveredFillBrush() )
pItem->setHoveredFillBrush( d->hoveredFillBrush );
}
d->reposItems();
pItem->setPopupDropper( this );
d->scene->addItem( pItem );
}
QList<PopupDropperItem*> PopupDropper::items() const
{
QList<PopupDropperItem*> list;
foreach( PopupDropperItem *item, d->pdiItems )
list.append( item );
return list;
}
//Won't currently work for > 1 level of submenu!
//TODO: Figure out a better way. (Does anything else work > 1 level?)
QList<PopupDropperItem*> PopupDropper::submenuItems( const PopupDropperItem *item ) const
{
QList<PopupDropperItem*> list;
if( !item || !item->isSubmenuTrigger() || !d->submenuMap.contains( item->action() ) )
return list;
PopupDropperPrivate *pdp = d->submenuMap[item->action()];
foreach( PopupDropperItem *pdi, pdp->pdiItems )
list.append( pdi );
return list;
}
//Goes through and calls the callback on all items, including submenuItems
//which can be adjusted differently by checking isSubmenuTrigger()
void PopupDropper::forEachItem( void callback(void*) )
{
forEachItemPrivate( d, callback );
}
void PopupDropper::forEachItemPrivate( PopupDropperPrivate *pdp, void callback(void* item) )
{
foreach( PopupDropperItem *item, pdp->pdiItems )
callback( item );
foreach( QAction *action, pdp->submenuMap.keys() )
forEachItemPrivate( pdp->submenuMap[action], callback );
}
void PopupDropper::addSeparator( PopupDropperItem* separator )
{
if( !separator )
{
//qDebug() << "Action is not a separator!";
return;
}
separator->setSeparator( true );
if( separator->separatorStyle() == PopupDropperItem::TextSeparator )
{
//qDebug() << "Separator style is text";
addItem( separator );
}
//qDebug() << "Separator style is line";
QPen linePen;
if( separator && separator->hasLineSeparatorPen() )
linePen = separator->lineSeparatorPen();
else
{
linePen.setWidth( 2 );
linePen.setCapStyle( Qt::RoundCap );
linePen.setStyle( Qt::DotLine );
linePen.setColor( QColor( 255, 255, 255 ) );
}
//qDebug() << "scene width = " << d->scene->width() << ", horizontalOffset = " << d->horizontalOffset;
//qDebug() << "right side = " << qreal(d->scene->width() - d->horizontalOffset);
QGraphicsLineItem* lineItem = new QGraphicsLineItem( 0, 0, 0, 0 );
d->allItems.append( lineItem );
lineItem->setPen( linePen );
d->reposItems();
d->scene->addItem( lineItem );
}
diff --git a/src/context/popupdropper/libpud/PopupDropperItem.cpp b/src/context/popupdropper/libpud/PopupDropperItem.cpp
index 71b2ea6ead..957346a3d9 100644
--- a/src/context/popupdropper/libpud/PopupDropperItem.cpp
+++ b/src/context/popupdropper/libpud/PopupDropperItem.cpp
@@ -1,884 +1,884 @@
/***************************************************************************
* Copyright (c) 2008 Jeff Mitchell <mitchell@kde.org> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "PopupDropperItem.h"
#include "PopupDropperItem_p.h"
#include "PopupDropper.h"
#include <QtDebug>
#include <QSvgRenderer>
#include <QGraphicsSvgItem>
#include <QAction>
#include <QFont>
///////////////////////////////////////////////////////////
PopupDropperItemPrivate::PopupDropperItemPrivate( PopupDropperItem *parent )
: action( 0 )
, text( QString() )
, hoverTimer( 500, parent )
, elementId( QString() )
, textItem( 0 )
, borderRectItem( 0 )
, svgItem( 0 )
, hoverIndicatorRectItem( 0 )
, hoverIndicatorRectFillItem( 0 )
, borderWidth( 2 )
, hoverIndicatorRectWidth( 15 )
, font()
, submenuTrigger( false )
, baseTextColor()
, hoveredTextColor()
, hoveredBorderPen()
, hoveredFillBrush()
, hoverIndicatorRectFillBrush()
, hoveredOver( false )
, customBaseTextColor( false )
, customHoveredTextColor( false )
, customHoveredBorderPen( false )
, customHoveredFillBrush( false )
, subitemOpacity( 0.0 )
, file( QString() )
, svgElementRect( 0, 0, 50, 50 )
, sharedRenderer( 0 )
, horizontalOffset( 30 )
, textOffset( 30 )
, separator( false )
, hasLineSeparatorPen( false )
, lineSeparatorPen()
, hoverIndicatorShowStyle( PopupDropperItem::Never )
, orientation( PopupDropperItem::Left )
, textProtection( PopupDropperItem::ScaleFont )
, separatorStyle( PopupDropperItem::TextSeparator )
, pd( 0 )
, q( parent )
{
hoverTimer.setFrameRange( 0, 30 );
hoverTimer.setUpdateInterval( 20 ); // 50 fps
q->setAcceptDrops( true );
hoverIndicatorRectFillBrush.setColor( Qt::white );
hoveredBorderPen.setWidth( borderWidth );
hoveredBorderPen.setColor( Qt::white );
hoveredBorderPen.setStyle( Qt::SolidLine );
hoveredFillBrush.setColor( Qt::white );
hoveredFillBrush.setStyle( Qt::SolidPattern );
}
PopupDropperItemPrivate::~PopupDropperItemPrivate()
{
}
///////////////////////////////////////////////////////////
PopupDropperItem::PopupDropperItem( QGraphicsItem *parent )
: QObject()
, QAbstractGraphicsShapeItem( parent )
, d( new PopupDropperItemPrivate( this ) )
{
- connect( &d->hoverTimer, SIGNAL(finished()), this, SLOT(hoverFinished()) );
- connect( &d->hoverTimer, SIGNAL(frameChanged(int)), this, SLOT(hoverFrameChanged(int)) );
+ connect( &d->hoverTimer, &QTimeLine::finished, this, &PopupDropperItem::hoverFinished );
+ connect( &d->hoverTimer, &QTimeLine::frameChanged, this, &PopupDropperItem::hoverFrameChanged );
}
PopupDropperItem::PopupDropperItem( const QString &file, QGraphicsItem *parent )
: QObject()
, QAbstractGraphicsShapeItem( parent )
, d( new PopupDropperItemPrivate( this ) )
{
d->file = file;
- connect( &d->hoverTimer, SIGNAL(finished()), this, SLOT(hoverFinished()) );
- connect( &d->hoverTimer, SIGNAL(frameChanged(int)), this, SLOT(hoverFrameChanged(int)) );
+ connect( &d->hoverTimer, &QTimeLine::finished, this, &PopupDropperItem::hoverFinished );
+ connect( &d->hoverTimer, &QTimeLine::frameChanged, this, &PopupDropperItem::hoverFrameChanged );
}
PopupDropperItem::~PopupDropperItem()
{
delete d;
}
void PopupDropperItem::show()
{
}
QAction* PopupDropperItem::action() const
{
return d->action;
}
void PopupDropperItem::setAction( QAction *action )
{
if( !action )
return;
//note that this also sets the text
d->action = action;
d->text = action->text();
if( action )
{
if( !d->svgItem )
{
if( !d->file.isEmpty() )
d->svgItem = new QGraphicsSvgItem( d->file, this );
else
d->svgItem = new QGraphicsSvgItem( this );
}
if( d->sharedRenderer )
d->svgItem->setSharedRenderer( d->sharedRenderer );
if( d->elementId.isEmpty() )
d->elementId = action->property( "popupdropper_svg_id" ).toString();
if( !d->elementId.isEmpty() )
{
if( d->svgItem->renderer() && d->svgItem->renderer()->elementExists( d->elementId ) )
d->svgItem->setElementId( d->elementId );
}
if( !d->svgItem->elementId().isEmpty() && d->svgItem->renderer()->elementExists( d->svgItem->elementId() ) )
d->svgItem->show();
else
d->svgItem->hide();
if( action->isSeparator() )
d->separator = true;
scaleAndReposSvgItem();
d->hoverIndicatorRectItem = new QGraphicsRectItem( this );
QPen pen = d->hoveredBorderPen;
QColor color( pen.color() );
color.setAlpha( 255 );
pen.setColor( color );
d->hoverIndicatorRectItem->setPen( pen );
QBrush brush = d->hoverIndicatorRectItem->brush();
brush.setStyle( Qt::NoBrush );
d->hoverIndicatorRectItem->setBrush( brush );
d->hoverIndicatorRectFillItem = new QGraphicsRectItem( this );
pen = d->hoverIndicatorRectFillItem->pen();
pen.setStyle( Qt::NoPen );
d->hoverIndicatorRectFillItem->setPen( pen );
d->hoverIndicatorRectFillBrush.setStyle( Qt::SolidPattern );
if( d->hoverIndicatorShowStyle == PopupDropperItem::AlwaysShow )
d->hoverIndicatorRectItem->show();
else
d->hoverIndicatorRectItem->hide();
d->hoverIndicatorRectFillItem->hide();
reposHoverFillRects();
}
if( d->pd )
d->pd->updateAllOverlays();
}
PopupDropperItem::HoverIndicatorShowStyle PopupDropperItem::hoverIndicatorShowStyle() const
{
return d->hoverIndicatorShowStyle;
}
void PopupDropperItem::setHoverIndicatorShowStyle( HoverIndicatorShowStyle hover )
{
d->hoverIndicatorShowStyle = hover;
if( !d->hoveredOver )
{
if( d->hoverIndicatorShowStyle == PopupDropperItem::AlwaysShow )
d->hoverIndicatorRectItem->show();
else
d->hoverIndicatorRectItem->hide();
}
}
PopupDropperItem::Orientation PopupDropperItem::orientation() const
{
return d->orientation;
}
void PopupDropperItem::setOrientation( const Orientation orientation )
{
d->orientation = orientation;
fullUpdate();
}
PopupDropperItem::TextProtection PopupDropperItem::textProtection() const
{
return d->textProtection;
}
void PopupDropperItem::setTextProtection( const TextProtection textProtection )
{
d->textProtection = textProtection;
fullUpdate();
}
QString PopupDropperItem::text() const
{
return d->text;
}
void PopupDropperItem::setText( const QString &text )
{
d->text = text;
if( d->textItem )
d->textItem->setHtml( text );
reposTextItem();
}
QFont PopupDropperItem::font() const
{
return d->font;
}
void PopupDropperItem::setFont( const QFont &font )
{
d->font = font;
if( d->textItem )
d->textItem->setFont( font );
reposTextItem();
}
QColor PopupDropperItem::baseTextColor() const
{
return d->baseTextColor;
}
void PopupDropperItem::setBaseTextColor( const QColor &color )
{
if( !d->hoveredOver && d->textItem )
d->textItem->setDefaultTextColor( color );
d->baseTextColor = color;
d->customBaseTextColor = true;
}
QColor PopupDropperItem::hoveredTextColor() const
{
return d->hoveredTextColor;
}
void PopupDropperItem::setHoveredTextColor( const QColor &color )
{
if( d->textItem && d->hoveredOver && d->hoverTimer.state() != QTimeLine::Running )
d->textItem->setDefaultTextColor( color );
d->hoveredTextColor = color;
d->customHoveredTextColor = true;
}
QPen PopupDropperItem::hoveredBorderPen() const
{
return d->hoveredBorderPen;
}
void PopupDropperItem::setHoveredBorderPen( const QPen &pen )
{
d->hoveredBorderPen = pen;
d->customHoveredBorderPen = true;
if( d->borderRectItem && ( !d->hoveredOver || ( d->hoveredOver && d->hoverTimer.state() != QTimeLine::Running ) ) )
{
QPen borderPen = pen;
if( !d->hoveredOver )
{
QColor pencolor = borderPen.color();
pencolor.setAlpha( 0 );
borderPen.setColor( pencolor );
}
d->borderRectItem->setPen( borderPen );
}
if( d->hoverIndicatorRectItem && ( !d->hoveredOver || ( d->hoveredOver && d->hoverTimer.state() != QTimeLine::Running ) ) )
{
QPen borderPen = d->hoveredBorderPen;
QColor color = borderPen.color();
color.setAlpha( 255 );
borderPen.setColor( color );
d->hoverIndicatorRectItem->setPen( borderPen );
}
}
QBrush PopupDropperItem::hoveredFillBrush() const
{
return d->hoveredFillBrush;
}
void PopupDropperItem::setHoveredFillBrush( const QBrush &brush )
{
d->hoveredFillBrush = brush;
d->customHoveredFillBrush = true;
if( d->borderRectItem && ( !d->hoveredOver || ( d->hoveredOver && d->hoverTimer.state() != QTimeLine::Running ) ) )
{
QBrush borderBrush = brush;
if( !d->hoveredOver )
{
QColor brushColor = borderBrush.color();
brushColor.setAlpha( 0 );
borderBrush.setColor( brushColor );
}
d->borderRectItem->setBrush( borderBrush );
}
}
QBrush PopupDropperItem::hoverIndicatorFillBrush() const
{
return d->hoverIndicatorRectFillBrush;
}
void PopupDropperItem::setHoverIndicatorFillBrush( const QBrush &brush )
{
d->hoverIndicatorRectFillBrush = brush;
if( d->hoverIndicatorRectFillItem && ( d->hoveredOver && d->hoverTimer.state() != QTimeLine::Running ) )
d->hoverIndicatorRectFillItem->setBrush( d->hoverIndicatorRectFillBrush );
}
bool PopupDropperItem::customBaseTextColor() const
{
return d->customBaseTextColor;
}
bool PopupDropperItem::customHoveredTextColor() const
{
return d->customHoveredTextColor;
}
bool PopupDropperItem::customHoveredBorderPen() const
{
return d->customHoveredBorderPen;
}
bool PopupDropperItem::customHoveredFillBrush() const
{
return d->customHoveredFillBrush;
}
qreal PopupDropperItem::subitemOpacity() const
{
return d->subitemOpacity;
}
void PopupDropperItem::setSubitemOpacity( qreal opacity )
{
if( d->svgItem )
d->svgItem->setOpacity( opacity );
if( d->textItem )
d->textItem->setOpacity( opacity );
if( d->borderRectItem )
d->borderRectItem->setOpacity( opacity );
if( d->hoverIndicatorRectItem )
d->hoverIndicatorRectItem->setOpacity( opacity );
if( d->hoverIndicatorRectFillItem )
d->hoverIndicatorRectFillItem->setOpacity( opacity );
}
QGraphicsTextItem* PopupDropperItem::textItem() const
{
return d->textItem;
}
void PopupDropperItem::setTextItem( QGraphicsTextItem *textItem )
{
d->textItem = textItem;
if( d->textItem )
d->textItem->setHtml( d->text );
}
QGraphicsRectItem* PopupDropperItem::borderRectItem() const
{
return d->borderRectItem;
}
void PopupDropperItem::setBorderRectItem( QGraphicsRectItem *borderRectItem )
{
if( !borderRectItem )
return;
d->borderRectItem = borderRectItem;
if( !d->hoveredOver )
{
QPen pen = d->hoveredBorderPen;
QColor color = pen.color();
color.setAlpha( 0 );
pen.setColor( color );
d->borderRectItem->setPen( pen );
QBrush brush = d->hoveredFillBrush;
color = brush.color();
color.setAlpha( 0 );
brush.setColor( color );
d->borderRectItem->setBrush( brush );
}
}
QGraphicsSvgItem* PopupDropperItem::svgItem() const
{
return d->svgItem;
}
void PopupDropperItem::scaleAndReposSvgItem()
{
if( !d->svgItem || !d->borderRectItem )
return;
if( d->separator )
{
d->svgItem->scale( 0, 0 );
d->svgItem->setPos( 0, 0 );
return;
}
//Need to scale if it is too tall or wide
qreal maxheight = d->svgElementRect.height() - ( 2 * d->borderRectItem->pen().width() );
qreal maxwidth = d->svgElementRect.width() - ( 2 * d->borderRectItem->pen().width() );
qreal vertScaleValue = maxheight / d->svgItem->sceneBoundingRect().height();
qreal horizScaleValue = maxwidth / d->svgItem->sceneBoundingRect().width();
qreal scaleValue = vertScaleValue < horizScaleValue ? vertScaleValue : horizScaleValue;
d->svgItem->scale( scaleValue, scaleValue );
qreal item_center = ( d->borderRectItem->sceneBoundingRect().height() / 2 ) + d->borderRectItem->pos().y();
if( d->orientation == PopupDropperItem::Left )
{
d->svgItem->setPos( d->horizontalOffset, item_center - ( d->svgElementRect.height() / 2 ) );
}
else
{
int rightside;
if( !d->pd || d->pd->viewSize().width() == 0 )
rightside = sceneBoundingRect().width();
else
rightside = d->pd->viewSize().width();
d->svgItem->setPos(
rightside
- d->svgItem->sceneBoundingRect().width()
- d->horizontalOffset
, item_center - ( d->svgElementRect.height() / 2 ) );
}
}
void PopupDropperItem::reposTextItem()
{
if( !d->textItem || !d->borderRectItem )
return;
d->textItem->setFont( d->font );
qreal item_vert_center = ( d->borderRectItem->sceneBoundingRect().height() / 2 ) + d->borderRectItem->pos().y();
if( d->separator )
{
if( d->text.isEmpty() )
return;
qreal width = d->textItem->textWidth();
if( width > d->borderRectItem->sceneBoundingRect().width() )
d->textItem->setTextWidth( d->borderRectItem->sceneBoundingRect().width() );
qreal offset = ( d->borderRectItem->sceneBoundingRect().width() - width ) / 2;
d->textItem->setPos( offset, item_vert_center - ( d->textItem->sceneBoundingRect().height() / 2 ) );
return;
}
int offsetPos = d->horizontalOffset + d->textOffset + d->svgElementRect.width();
d->textItem->setPos(
( d->orientation == PopupDropperItem::Left
? offsetPos
: 0
)
, item_vert_center - ( d->textItem->sceneBoundingRect().height() / 2 ) );
if( d->textProtection == PopupDropperItem::ScaleFont )
{
QFontMetrics fm( d->textItem->font() );
qreal desiredWidth = d->borderRectItem->sceneBoundingRect().width() - offsetPos;
while( d->textItem->font().pointSize() > 1 &&
( fm.width( d->textItem->toPlainText() ) > desiredWidth ||
fm.height() > d->textItem->boundingRect().height() ) )
{
QFont font = d->textItem->font();
font.setPointSize( font.pointSize() - 1 );
d->textItem->setFont( font );
fm = QFontMetrics( font );
}
}
else if( d->textProtection == PopupDropperItem::MultiLine &&
( d->textItem->textWidth() == -1 ||
d->textItem->textWidth() > ( d->borderRectItem->sceneBoundingRect().width() - offsetPos )
)
)
{
d->textItem->setTextWidth( d->borderRectItem->sceneBoundingRect().width() - offsetPos );
reposTextItem();
}
}
void PopupDropperItem::reposHoverFillRects()
{
if( !d->hoverIndicatorRectItem || !d->hoverIndicatorRectFillItem || !d->textItem || !d->borderRectItem )
return;
if( d->separator )
{
d->hoverIndicatorRectItem->setRect( 0, 0, 0, 0 );
d->hoverIndicatorRectFillItem->setRect( 0, 0, 0, 0 );
return;
}
//qDebug() << "\n\nPUDItem boundingRect().width() = " << boundingRect().width();
qreal startx, starty, endx, endy, item_center;
//int rightside = d->borderRectItem ? d->borderRectItem->boundingRect().width() : boundingRect().width();
if( d->orientation == PopupDropperItem::Left )
{
startx = d->horizontalOffset
- d->hoverIndicatorRectWidth
- ( 2 * d->hoverIndicatorRectItem->pen().width() );
}
else
{
int rightside = (!d->pd || d->pd->viewSize().width() == 0 )
? sceneBoundingRect().width()
: d->pd->viewSize().width();
//qDebug() << "right side = " << rightside;
startx = rightside - d->horizontalOffset
+ d->hoverIndicatorRectWidth
- ( 2 * d->hoverIndicatorRectItem->pen().width() );
}
item_center = ( d->borderRectItem->sceneBoundingRect().height() / 2 ) + d->borderRectItem->pos().y();
starty = item_center - ( d->svgElementRect.height() / 2 );
endx = d->hoverIndicatorRectWidth - ( 2 * d->hoverIndicatorRectItem->pen().width() );
endy = d->svgElementRect.height();
QRectF indicatorRect( startx, starty, endx, endy );
d->hoverIndicatorRectItem->setRect( indicatorRect );
QRectF indicatorFillRect(
indicatorRect.left() + d->hoverIndicatorRectItem->pen().width()
, indicatorRect.bottom() - d->hoverIndicatorRectItem->pen().width()
, indicatorRect.width() - ( 2 * d->hoverIndicatorRectItem->pen().width() )
, 0 );
d->hoverIndicatorRectFillItem->setRect( indicatorFillRect );
}
QSvgRenderer* PopupDropperItem::sharedRenderer() const
{
return d->sharedRenderer;
}
void PopupDropperItem::setSharedRenderer( QSvgRenderer *renderer )
{
d->sharedRenderer = renderer;
if( renderer && d->svgItem )
{
d->svgItem->setSharedRenderer( renderer );
d->svgItem->setElementId( d->elementId );
if( !d->svgItem->elementId().isEmpty() && d->svgItem->renderer()->elementExists( d->svgItem->elementId() ) )
{
d->svgItem->show();
fullUpdate();
}
}
}
QString PopupDropperItem::elementId() const
{
return d->elementId;
}
void PopupDropperItem::setElementId( const QString &id )
{
//qDebug() << "Element ID being set: " << id;
d->elementId = id;
if( id.isEmpty() )
{
d->svgItem->hide();
fullUpdate();
}
else if( d->svgItem && d->svgItem->renderer() && d->svgItem->renderer()->elementExists( id ))
{
d->svgItem->setElementId( id );
d->svgItem->show();
fullUpdate();
}
}
QRect PopupDropperItem::svgElementRect() const
{
return d->svgElementRect;
}
void PopupDropperItem::setSvgElementRect( const QRect &rect )
{
d->svgElementRect = rect;
}
int PopupDropperItem::horizontalOffset() const
{
return d->horizontalOffset;
}
void PopupDropperItem::setHorizontalOffset( int offset )
{
d->horizontalOffset = offset;
}
int PopupDropperItem::textOffset() const
{
return d->textOffset;
}
void PopupDropperItem::setTextOffset( int offset )
{
d->textOffset = offset;
}
bool PopupDropperItem::isSeparator() const
{
return d->separator;
}
void PopupDropperItem::setSeparator( bool separator )
{
d->separator = separator;
}
PopupDropperItem::SeparatorStyle PopupDropperItem::separatorStyle() const
{
return d->separatorStyle;
}
void PopupDropperItem::setSeparatorStyle( SeparatorStyle style )
{
d->separatorStyle = style;
}
bool PopupDropperItem::hasLineSeparatorPen() const
{
return d->hasLineSeparatorPen;
}
QPen PopupDropperItem::lineSeparatorPen() const
{
return d->lineSeparatorPen;
}
void PopupDropperItem::setLineSeparatorPen( const QPen &pen )
{
d->lineSeparatorPen = pen;
}
void PopupDropperItem::clearLineSeparatorPen()
{
d->lineSeparatorPen = QPen();
d->hasLineSeparatorPen = false;
}
int PopupDropperItem::hoverMsecs() const
{
return d->hoverTimer.duration();
}
void PopupDropperItem::setHoverMsecs( const int msecs )
{
d->hoverTimer.setDuration( msecs );
}
void PopupDropperItem::hoverEntered()
{
if( d->hoverIndicatorRectItem && d->hoverIndicatorRectFillItem && d->hoverIndicatorShowStyle != PopupDropperItem::Never )
{
d->hoverIndicatorRectFillItem->show();
}
d->hoverTimer.stop();
d->hoverTimer.setDirection( QTimeLine::Forward );
d->hoveredOver = true;
d->hoverTimer.start();
}
void PopupDropperItem::hoverLeft()
{
d->hoverTimer.stop();
d->hoverTimer.setDirection( QTimeLine::Backward );
d->hoveredOver = false;
if( d->hoverTimer.currentFrame() != 0 )
d->hoverTimer.start();
}
int PopupDropperItem::borderWidth() const
{
return d->borderWidth;
}
void PopupDropperItem::setBorderWidth( int borderWidth )
{
d->borderWidth = borderWidth;
d->hoveredBorderPen.setWidth( borderWidth );
if( d->borderRectItem )
{
d->borderRectItem->setPen( d->hoveredBorderPen );
}
}
int PopupDropperItem::hoverIndicatorRectWidth() const
{
return d->hoverIndicatorRectWidth;
}
void PopupDropperItem::setHoverIndicatorRectWidth( int hoverIndicatorRectWidth )
{
d->hoverIndicatorRectWidth = hoverIndicatorRectWidth;
if( d->hoverIndicatorRectItem )
{
QPen pen = d->hoverIndicatorRectItem->pen();
pen.setWidth( d->hoverIndicatorRectWidth );
d->hoverIndicatorRectItem->setPen( pen );
}
}
bool PopupDropperItem::isSubmenuTrigger() const
{
return d->submenuTrigger;
}
void PopupDropperItem::setSubmenuTrigger( bool trigger )
{
d->submenuTrigger = trigger;
}
void PopupDropperItem::setPopupDropper( PopupDropper* pd )
{
d->pd = pd;
}
//bool PopupDropperItem::operator<( const PopupDropperItem &other ) const
//{
// return d->text < other.text();
//}
void PopupDropperItem::dropped( QDropEvent *event ) //virtual SLOT
{
Q_UNUSED( event );
d->hoverTimer.stop();
//qDebug() << "PopupDropperItem drop detected";
if( d->action )
{
//qDebug() << "Triggering action";
d->action->activate( QAction::Trigger );
}
}
void PopupDropperItem::hoverFinished() //SLOT
{
if( d->separator )
return;
//qDebug() << "direction = forwards ? " << ( d->hoverTimer.direction() == QTimeLine::Forward ? "yes" : "no" );
if( d->action && d->hoverTimer.direction() == QTimeLine::Forward )
d->action->activate( QAction::Hover );
if( d->hoverTimer.direction() == QTimeLine::Forward )
d->textItem->setDefaultTextColor( d->hoveredTextColor );
else
d->textItem->setDefaultTextColor( d->baseTextColor );
//Something is messed up in QTimeLine...I get hoverFinished immediately after doing a hoverLeft, but only sometimes...hence the check
//to see if the timeline isn't running
if( d->hoverIndicatorRectFillItem && d->hoverTimer.state() == QTimeLine::NotRunning && d->hoverTimer.direction() == QTimeLine::Backward )
{
d->hoverIndicatorRectFillItem->hide();
if( d->hoverIndicatorRectItem && d->hoverIndicatorShowStyle != PopupDropperItem::AlwaysShow )
d->hoverIndicatorRectItem->hide();
}
if( d->pd )
d->pd->updateAllOverlays();
}
void PopupDropperItem::hoverFrameChanged( int frame ) //SLOT
{
if( d->separator )
return;
//qDebug() << "hoverFrameChanged for " << static_cast<QObject*>(this) << ", frame = " << frame;
int range = d->hoverTimer.endFrame() - d->hoverTimer.startFrame();
qreal multiplier = ( 1.0 * frame ) / range;
int r = (int)( ( d->hoveredTextColor.red() - d->baseTextColor.red() ) * multiplier ) + d->baseTextColor.red();
int g = (int)( ( d->hoveredTextColor.green() - d->baseTextColor.green() ) * multiplier ) + d->baseTextColor.green();
int b = (int)( ( d->hoveredTextColor.blue() - d->baseTextColor.blue() ) * multiplier ) + d->baseTextColor.blue();
int a = (int)( ( d->hoveredTextColor.alpha() - d->baseTextColor.alpha() ) * multiplier ) + d->baseTextColor.alpha();
d->textItem->setDefaultTextColor( QColor( r, g, b, a ) );
QColor borderColor = d->hoveredBorderPen.color();
borderColor.setAlpha( (int)( borderColor.alpha() * multiplier ) );
QPen pen = d->borderRectItem->pen();
pen.setColor( borderColor );
d->borderRectItem->setPen( pen );
if( d->hoverIndicatorRectItem && d->hoverIndicatorShowStyle == PopupDropperItem::OnHover )
{
d->hoverIndicatorRectItem->setPen( pen );
d->hoverIndicatorRectItem->show();
}
QColor fillColor = d->hoveredFillBrush.color();
QBrush brush = d->borderRectItem->brush();
fillColor.setAlpha( (int)( fillColor.alpha() * multiplier ) );
brush.setColor( fillColor );
d->borderRectItem->setBrush( brush );
if( d->hoverIndicatorRectItem && d->hoverIndicatorRectFillItem && d->hoverIndicatorShowStyle != PopupDropperItem::Never )
{
int hoverIndicatorPenWidth = d->hoverIndicatorRectItem->pen().width();
QRectF rect = d->hoverIndicatorRectFillItem->rect();
QRectF outerRect = d->hoverIndicatorRectItem->rect();
rect.setTop( ( multiplier * -1 * ( outerRect.bottom() - outerRect.top() - ( 2 * hoverIndicatorPenWidth ) ) )
+ outerRect.bottom()
- hoverIndicatorPenWidth );
d->hoverIndicatorRectFillItem->setRect( rect );
d->hoverIndicatorRectFillItem->setBrush( d->hoverIndicatorRectFillBrush );
d->hoverIndicatorRectFillItem->show();
//qDebug() << "hoverIndicatorRectFillItem = " << d->hoverIndicatorRectFillItem;
}
if( d->pd )
d->pd->updateAllOverlays();
}
void PopupDropperItem::fullUpdate()
{
scaleAndReposSvgItem();
reposTextItem();
reposHoverFillRects();
if( d->pd )
d->pd->updateAllOverlays();
}
QRectF PopupDropperItem::boundingRect() const
{
if( d->borderRectItem )
return d->borderRectItem->boundingRect();
else if( d->pd && d->pd->viewSize().width() != 0 )
return QRectF( 0, 0, d->pd->viewSize().width(), d->svgElementRect.height() );
else
return QRectF( 0, 0, d->svgElementRect.width(), d->svgElementRect.height() );
}
void PopupDropperItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )
{
Q_UNUSED( painter )
Q_UNUSED( option )
Q_UNUSED( widget )
return;
}
diff --git a/src/context/popupdropper/libpud/PopupDropper_p.h b/src/context/popupdropper/libpud/PopupDropper_p.h
index d335f2ff0f..f20f523171 100644
--- a/src/context/popupdropper/libpud/PopupDropper_p.h
+++ b/src/context/popupdropper/libpud/PopupDropper_p.h
@@ -1,99 +1,99 @@
/***************************************************************************
* Copyright (c) 2008 Jeff Mitchell <mitchell@kde.org> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#ifndef POPUPDROPPER_P_H
#define POPUPDROPPER_P_H
#include <QList>
#include <QMap>
#include <QObject>
#include <QRectF>
#include <QTimeLine>
#include <QTimer>
#include "PopupDropper.h"
#include "PopupDropperView.h"
class QSvgRenderer;
class QWidget;
class PopupDropperPrivate : public QObject
{
Q_OBJECT
public:
PopupDropperPrivate( PopupDropper* parent, bool sa, QWidget* widget );
~PopupDropperPrivate();
void newSceneView( PopupDropper* pud );
void setParent( QObject* parent );
bool standalone;
QWidget* widget;
QGraphicsScene* scene;
PopupDropperView* view;
PopupDropper::Fading fade;
QTimeLine fadeHideTimer;
QTimeLine fadeShowTimer;
int fadeInTime;
int fadeOutTime;
QTimer deleteTimer;
int deleteTimeout;
int frameMax;
QColor windowColor;
QBrush windowBackgroundBrush;
QColor baseTextColor;
QColor hoveredTextColor;
QPen hoveredBorderPen;
QBrush hoveredFillBrush;
QString file;
QSvgRenderer* sharedRenderer;
int horizontalOffset;
QList<PopupDropperItem*> pdiItems;
int overlayLevel;
bool entered;
QMap<QAction*, PopupDropperPrivate*> submenuMap;
bool submenu;
QList<QGraphicsItem*> allItems;
bool quitOnDragLeave;
bool onTop;
QRectF widgetRect;
//queuedHide: To prevent multiple hide() from being sent if it's already being hidden
bool queuedHide;
void dragLeft();
void dragEntered();
void startDeleteTimer();
void reposItems();
bool amIOnTop( PopupDropperView* pdv );
-private Q_SLOTS:
+public Q_SLOTS:
void fadeHideTimerFrameChanged( int frame );
void fadeShowTimerFrameChanged( int frame );
void fadeShowTimerFinished();
void fadeHideTimerFinished();
void deleteTimerFinished();
private:
PopupDropper* q;
};
#endif //POPUPDROPPER_P_H
diff --git a/src/core-impl/capabilities/AlbumActionsCapability.cpp b/src/core-impl/capabilities/AlbumActionsCapability.cpp
index 8f92e1a699..2d657b49ff 100644
--- a/src/core-impl/capabilities/AlbumActionsCapability.cpp
+++ b/src/core-impl/capabilities/AlbumActionsCapability.cpp
@@ -1,88 +1,88 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AlbumActionsCapability.h"
#include "core/meta/Meta.h"
#include "covermanager/CoverFetchingActions.h"
#include <QIcon>
#include <KLocalizedString>
class CompilationAction : public QAction
{
Q_OBJECT
public:
CompilationAction( QObject* parent, Meta::AlbumPtr album )
: QAction( parent )
, m_album( album )
{
- connect( this, SIGNAL(triggered(bool)), SLOT(slotTriggered()) );
+ connect( this, &CompilationAction::triggered, this, &CompilationAction::slotTriggered );
if( m_album->isCompilation() )
{
setIcon( QIcon::fromTheme( "filename-artist-amarok" ) );
setText( i18n( "Do not show under Various Artists" ) );
}
else
{
setIcon( QIcon::fromTheme( "similarartists-amarok" ) );
setText( i18n( "Show under Various Artists" ) );
}
setEnabled( m_album->canUpdateCompilation() );
}
private slots:
void slotTriggered()
{
if( !m_album->canUpdateCompilation() )
return;
m_album->setCompilation( !m_album->isCompilation() );
}
private:
Meta::AlbumPtr m_album;
};
using namespace Capabilities;
AlbumActionsCapability::AlbumActionsCapability( Meta::AlbumPtr album, QList<QAction *> actions )
: ActionsCapability()
{
m_actions.append( new DisplayCoverAction( 0, album ) );
m_actions.append( new FetchCoverAction( 0, album ) );
m_actions.append( new SetCustomCoverAction( 0, album ) );
m_actions.append( new UnsetCoverAction( 0, album ) );
QAction *separator = new QAction( 0 );
separator->setSeparator( true );
m_actions.append( separator );
m_actions.append( new CompilationAction( 0, album ) );
if( actions.isEmpty() )
return;
separator = new QAction( 0 );
separator->setSeparator( true );
m_actions.append( separator );
m_actions.append( actions );
}
AlbumActionsCapability::~AlbumActionsCapability()
{
// nothing to do
}
#include "AlbumActionsCapability.moc"
diff --git a/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp b/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp
index 849f47b13f..660f57de17 100644
--- a/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp
+++ b/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp
@@ -1,57 +1,57 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MultiSourceCapabilityImpl.h"
#include "core/support/Debug.h"
using namespace Capabilities;
MultiSourceCapabilityImpl::MultiSourceCapabilityImpl(Meta::MultiTrack * track)
: Capabilities::MultiSourceCapability()
, m_track( track )
{
//forward from track, as there might be several instances of MultiSourceCapabilityImpl active for one track.
- connect( m_track, SIGNAL(urlChanged(QUrl)), this, SIGNAL(urlChanged(QUrl)) );
+ connect( m_track, &Meta::MultiTrack::urlChanged, this, &MultiSourceCapabilityImpl::urlChanged );
}
MultiSourceCapabilityImpl::~MultiSourceCapabilityImpl()
{
}
QStringList
MultiSourceCapabilityImpl::sources() const
{
return m_track->sources();
}
void
MultiSourceCapabilityImpl::setSource( int source )
{
m_track->setSource( source );
}
int
MultiSourceCapabilityImpl::current() const
{
return m_track->current();
}
QUrl
MultiSourceCapabilityImpl::nextUrl() const
{
return m_track->nextUrl();
}
diff --git a/src/core-impl/collections/aggregate/AggregateCollection.cpp b/src/core-impl/collections/aggregate/AggregateCollection.cpp
index 2d0bb1fb01..7ed30f44a3 100644
--- a/src/core-impl/collections/aggregate/AggregateCollection.cpp
+++ b/src/core-impl/collections/aggregate/AggregateCollection.cpp
@@ -1,526 +1,526 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AggregateCollection"
#include "AggregateCollection.h"
#include "core/support/Debug.h"
#include "core-impl/collections/aggregate/AggregateMeta.h"
#include "core-impl/collections/aggregate/AggregateQueryMaker.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <QIcon>
#include <QReadLocker>
#include <QTimer>
using namespace Collections;
AggregateCollection::AggregateCollection()
: Collections::Collection()
{
QTimer *timer = new QTimer( this );
timer->setSingleShot( false );
timer->setInterval( 60000 ); //clean up every 60 seconds
- connect( timer, SIGNAL(timeout()), this, SLOT(emptyCache()) );
+ connect( timer, &QTimer::timeout, this, &AggregateCollection::emptyCache );
timer->start();
}
AggregateCollection::~AggregateCollection()
{
}
QString
AggregateCollection::prettyName() const
{
return i18nc( "Name of the virtual collection that merges tracks from all collections",
"Aggregate Collection" );
}
QIcon
AggregateCollection::icon() const
{
return QIcon::fromTheme("drive-harddisk");
}
bool
AggregateCollection::possiblyContainsTrack( const QUrl &url ) const
{
foreach( Collections::Collection *collection, m_idCollectionMap )
{
if( collection->possiblyContainsTrack( url ) )
return true;
}
return false;
}
Meta::TrackPtr
AggregateCollection::trackForUrl( const QUrl &url )
{
foreach( Collections::Collection *collection, m_idCollectionMap )
{
Meta::TrackPtr track = collection->trackForUrl( url );
if( track )
{
//theoretically we should now query the other collections for the same track
//not sure how to do that yet though...
return Meta::TrackPtr( getTrack( track ) );
}
}
return Meta::TrackPtr();
}
QueryMaker*
AggregateCollection::queryMaker()
{
QList<QueryMaker*> list;
foreach( Collections::Collection *collection, m_idCollectionMap )
{
list.append( collection->queryMaker() );
}
return new Collections::AggregateQueryMaker( this, list );
}
QString
AggregateCollection::collectionId() const
{
// do we need more than one AggregateCollection?
return QLatin1String( "AggregateCollection" );
}
void
AggregateCollection::addCollection( Collections::Collection *collection, CollectionManager::CollectionStatus status )
{
if( !collection )
return;
if( !( status & CollectionManager::CollectionViewable ) )
return;
m_idCollectionMap.insert( collection->collectionId(), collection );
//TODO
emit updated();
}
void
-AggregateCollection::removeCollection( const QString &collectionId )
+AggregateCollection::removeCollectionById( const QString &collectionId )
{
m_idCollectionMap.remove( collectionId );
emit updated();
}
void
AggregateCollection::removeCollection( Collections::Collection *collection )
{
m_idCollectionMap.remove( collection->collectionId() );
emit updated();
}
void
AggregateCollection::slotUpdated()
{
//TODO
emit updated();
}
void
AggregateCollection::removeYear( const QString &name )
{
m_yearLock.lockForWrite();
m_yearMap.remove( name );
m_yearLock.unlock();
}
Meta::AggreagateYear*
AggregateCollection::getYear( Meta::YearPtr year )
{
m_yearLock.lockForRead();
if( m_yearMap.contains( year->name() ) )
{
KSharedPtr<Meta::AggreagateYear> aggregateYear = m_yearMap.value( year->name() );
aggregateYear->add( year );
m_yearLock.unlock();
return aggregateYear.data();
}
else
{
m_yearLock.unlock();
m_yearLock.lockForWrite();
//we might create two year instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggreagateYear *aggregateYear = new Meta::AggreagateYear( this, year );
m_yearMap.insert( year->name(), KSharedPtr<Meta::AggreagateYear>( aggregateYear ) );
m_yearLock.unlock();
return aggregateYear;
}
}
void
AggregateCollection::setYear( Meta::AggreagateYear *year )
{
m_yearLock.lockForWrite();
m_yearMap.insert( year->name(), KSharedPtr<Meta::AggreagateYear>( year ) );
m_yearLock.unlock();
}
bool
AggregateCollection::hasYear( const QString &name )
{
QReadLocker locker( &m_yearLock );
return m_yearMap.contains( name );
}
void
AggregateCollection::removeGenre( const QString &name )
{
m_genreLock.lockForWrite();
m_genreMap.remove( name );
m_genreLock.unlock();
}
Meta::AggregateGenre*
AggregateCollection::getGenre( Meta::GenrePtr genre )
{
m_genreLock.lockForRead();
if( m_genreMap.contains( genre->name() ) )
{
KSharedPtr<Meta::AggregateGenre> aggregateGenre = m_genreMap.value( genre->name() );
aggregateGenre->add( genre );
m_genreLock.unlock();
return aggregateGenre.data();
}
else
{
m_genreLock.unlock();
m_genreLock.lockForWrite();
//we might create two instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggregateGenre *aggregateGenre = new Meta::AggregateGenre( this, genre );
m_genreMap.insert( genre->name(), KSharedPtr<Meta::AggregateGenre>( aggregateGenre ) );
m_genreLock.unlock();
return aggregateGenre;
}
}
void
AggregateCollection::setGenre( Meta::AggregateGenre *genre )
{
m_genreLock.lockForWrite();
m_genreMap.insert( genre->name(), KSharedPtr<Meta::AggregateGenre>( genre ) );
m_genreLock.unlock();
}
bool
AggregateCollection::hasGenre( const QString &genre )
{
QReadLocker locker( &m_genreLock );
return m_genreMap.contains( genre );
}
void
AggregateCollection::removeComposer( const QString &name )
{
m_composerLock.lockForWrite();
m_composerMap.remove( name );
m_composerLock.unlock();
}
Meta::AggregateComposer*
AggregateCollection::getComposer( Meta::ComposerPtr composer )
{
m_composerLock.lockForRead();
if( m_composerMap.contains( composer->name() ) )
{
KSharedPtr<Meta::AggregateComposer> aggregateComposer = m_composerMap.value( composer->name() );
aggregateComposer->add( composer );
m_composerLock.unlock();
return aggregateComposer.data();
}
else
{
m_composerLock.unlock();
m_composerLock.lockForWrite();
//we might create two instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggregateComposer *aggregateComposer = new Meta::AggregateComposer( this, composer );
m_composerMap.insert( composer->name(), KSharedPtr<Meta::AggregateComposer>( aggregateComposer ) );
m_composerLock.unlock();
return aggregateComposer;
}
}
void
AggregateCollection::setComposer( Meta::AggregateComposer *composer )
{
m_composerLock.lockForWrite();
m_composerMap.insert( composer->name(), KSharedPtr<Meta::AggregateComposer>( composer ) );
m_composerLock.unlock();
}
bool
AggregateCollection::hasComposer( const QString &name )
{
QReadLocker locker( &m_composerLock );
return m_composerMap.contains( name );
}
void
AggregateCollection::removeArtist( const QString &name )
{
m_artistLock.lockForWrite();
m_artistMap.remove( name );
m_artistLock.unlock();
}
Meta::AggregateArtist*
AggregateCollection::getArtist( Meta::ArtistPtr artist )
{
m_artistLock.lockForRead();
if( m_artistMap.contains( artist->name() ) )
{
KSharedPtr<Meta::AggregateArtist> aggregateArtist = m_artistMap.value( artist->name() );
aggregateArtist->add( artist );
m_artistLock.unlock();
return aggregateArtist.data();
}
else
{
m_artistLock.unlock();
m_artistLock.lockForWrite();
//we might create two instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggregateArtist *aggregateArtist = new Meta::AggregateArtist( this, artist );
m_artistMap.insert( artist->name(), KSharedPtr<Meta::AggregateArtist>( aggregateArtist ) );
m_artistLock.unlock();
return aggregateArtist;
}
}
void
AggregateCollection::setArtist( Meta::AggregateArtist *artist )
{
m_artistLock.lockForWrite();
m_artistMap.insert( artist->name(), KSharedPtr<Meta::AggregateArtist>( artist ) );
m_artistLock.unlock();
}
bool
AggregateCollection::hasArtist( const QString &artist )
{
QReadLocker locker( &m_artistLock );
return m_artistMap.contains( artist );
}
void
AggregateCollection::removeAlbum( const QString &album, const QString &albumartist )
{
Meta::AlbumKey key( album, albumartist );
m_albumLock.lockForWrite();
m_albumMap.remove( key );
m_albumLock.unlock();
}
Meta::AggregateAlbum*
AggregateCollection::getAlbum( Meta::AlbumPtr album )
{
Meta::AlbumKey key( album );
m_albumLock.lockForRead();
if( m_albumMap.contains( key ) )
{
KSharedPtr<Meta::AggregateAlbum> aggregateAlbum = m_albumMap.value( key );
aggregateAlbum->add( album );
m_albumLock.unlock();
return aggregateAlbum.data();
}
else
{
m_albumLock.unlock();
m_albumLock.lockForWrite();
//we might create two instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggregateAlbum *aggregateAlbum = new Meta::AggregateAlbum( this, album );
m_albumMap.insert( key, KSharedPtr<Meta::AggregateAlbum>( aggregateAlbum ) );
m_albumLock.unlock();
return aggregateAlbum;
}
}
void
AggregateCollection::setAlbum( Meta::AggregateAlbum *album )
{
m_albumLock.lockForWrite();
m_albumMap.insert( Meta::AlbumKey( Meta::AlbumPtr( album ) ),
KSharedPtr<Meta::AggregateAlbum>( album ) );
m_albumLock.unlock();
}
bool
AggregateCollection::hasAlbum( const QString &album, const QString &albumArtist )
{
QReadLocker locker( &m_albumLock );
return m_albumMap.contains( Meta::AlbumKey( album, albumArtist ) );
}
void
AggregateCollection::removeTrack( const Meta::TrackKey &key )
{
m_trackLock.lockForWrite();
m_trackMap.remove( key );
m_trackLock.unlock();
}
Meta::AggregateTrack*
AggregateCollection::getTrack( Meta::TrackPtr track )
{
const Meta::TrackKey key( track );
m_trackLock.lockForRead();
if( m_trackMap.contains( key ) )
{
KSharedPtr<Meta::AggregateTrack> aggregateTrack = m_trackMap.value( key );
aggregateTrack->add( track );
m_trackLock.unlock();
return aggregateTrack.data();
}
else
{
m_trackLock.unlock();
m_trackLock.lockForWrite();
//we might create two instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggregateTrack *aggregateTrack = new Meta::AggregateTrack( this, track );
m_trackMap.insert( key, KSharedPtr<Meta::AggregateTrack>( aggregateTrack ) );
m_trackLock.unlock();
return aggregateTrack;
}
}
void
AggregateCollection::setTrack( Meta::AggregateTrack *track )
{
Meta::TrackPtr ptr( track );
const Meta::TrackKey key( ptr );
m_trackLock.lockForWrite();
m_trackMap.insert( key, KSharedPtr<Meta::AggregateTrack>( track ) );
m_trackLock.unlock();
}
bool
AggregateCollection::hasTrack( const Meta::TrackKey &key )
{
QReadLocker locker( &m_trackLock );
return m_trackMap.contains( key );
}
bool
AggregateCollection::hasLabel( const QString &name )
{
QReadLocker locker( &m_labelLock );
return m_labelMap.contains( name );
}
void
AggregateCollection::removeLabel( const QString &name )
{
QWriteLocker locker( &m_labelLock );
m_labelMap.remove( name );
}
Meta::AggregateLabel*
AggregateCollection::getLabel( Meta::LabelPtr label )
{
m_labelLock.lockForRead();
if( m_labelMap.contains( label->name() ) )
{
KSharedPtr<Meta::AggregateLabel> aggregateLabel = m_labelMap.value( label->name() );
aggregateLabel->add( label );
m_labelLock.unlock();
return aggregateLabel.data();
}
else
{
m_labelLock.unlock();
m_labelLock.lockForWrite();
//we might create two year instances with the same name here,
//which would show some weird behaviour in other places
Meta::AggregateLabel *aggregateLabel = new Meta::AggregateLabel( this, label );
m_labelMap.insert( label->name(), KSharedPtr<Meta::AggregateLabel>( aggregateLabel ) );
m_labelLock.unlock();
return aggregateLabel;
}
}
void
AggregateCollection::setLabel( Meta::AggregateLabel *label )
{
QWriteLocker locker( &m_labelLock );
m_labelMap.insert( label->name(), KSharedPtr<Meta::AggregateLabel>( label ) );
}
void
AggregateCollection::emptyCache()
{
bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer, hasLabel;
hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = hasLabel = false;
//try to avoid possible deadlocks by aborting when we can't get all locks
if ( ( hasTrack = m_trackLock.tryLockForWrite() )
&& ( hasAlbum = m_albumLock.tryLockForWrite() )
&& ( hasArtist = m_artistLock.tryLockForWrite() )
&& ( hasYear = m_yearLock.tryLockForWrite() )
&& ( hasGenre = m_genreLock.tryLockForWrite() )
&& ( hasComposer = m_composerLock.tryLockForWrite() )
&& ( hasLabel = m_labelLock.tryLockForWrite() ) )
{
//this very simple garbage collector doesn't handle cyclic object graphs
//so care has to be taken to make sure that we are not dealing with a cyclic graph
//by invalidating the tracks cache on all objects
#define foreachInvalidateCache( Type, RealType, x ) \
for( QMutableHashIterator<int,Type > iter(x); iter.hasNext(); ) \
RealType::staticCast( iter.next().value() )->invalidateCache()
//elem.count() == 2 is correct because elem is one pointer to the object
//and the other is stored in the hash map (except for m_trackMap, where
//another refence is stored in m_uidMap
#define foreachCollectGarbage( Key, Type, RefCount, x ) \
for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
{ \
Type elem = iter.next().value(); \
if( elem.count() == RefCount ) \
iter.remove(); \
}
foreachCollectGarbage( Meta::TrackKey, KSharedPtr<Meta::AggregateTrack>, 2, m_trackMap )
//run before artist so that album artist pointers can be garbage collected
foreachCollectGarbage( Meta::AlbumKey, KSharedPtr<Meta::AggregateAlbum>, 2, m_albumMap )
foreachCollectGarbage( QString, KSharedPtr<Meta::AggregateArtist>, 2, m_artistMap )
foreachCollectGarbage( QString, KSharedPtr<Meta::AggregateGenre>, 2, m_genreMap )
foreachCollectGarbage( QString, KSharedPtr<Meta::AggregateComposer>, 2, m_composerMap )
foreachCollectGarbage( QString, KSharedPtr<Meta::AggreagateYear>, 2, m_yearMap )
foreachCollectGarbage( QString, KSharedPtr<Meta::AggregateLabel>, 2, m_labelMap )
}
//make sure to unlock all necessary locks
//important: calling unlock() on an unlocked mutex gives an undefined result
//unlocking a mutex locked by another thread results in an error, so be careful
if( hasTrack ) m_trackLock.unlock();
if( hasAlbum ) m_albumLock.unlock();
if( hasArtist ) m_artistLock.unlock();
if( hasYear ) m_yearLock.unlock();
if( hasGenre ) m_genreLock.unlock();
if( hasComposer ) m_composerLock.unlock();
if( hasLabel ) m_labelLock.unlock();
}
diff --git a/src/core-impl/collections/aggregate/AggregateCollection.h b/src/core-impl/collections/aggregate/AggregateCollection.h
index 4b2eb0466a..a7301db520 100644
--- a/src/core-impl/collections/aggregate/AggregateCollection.h
+++ b/src/core-impl/collections/aggregate/AggregateCollection.h
@@ -1,131 +1,131 @@
/*
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef AGGREGATECOLLECTION_H
#define AGGREGATECOLLECTION_H
#include "core/collections/Collection.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core/meta/forward_declarations.h"
#include "core/meta/support/MetaKeys.h"
#include <QString>
#include <QHash>
#include <QReadWriteLock>
#include <KLocalizedString>
namespace Meta {
class AggreagateYear;
class AggregateTrack;
class AggregateArtist;
class AggregateAlbum;
class AggregateGenre;
class AggregateComposer;
class AggregateLabel;
}
namespace Collections {
class AMAROK_EXPORT AggregateCollection : public Collections::Collection
{
Q_OBJECT
public:
AggregateCollection();
~AggregateCollection();
// Collections::Collection methods
virtual QString prettyName() const;
virtual QIcon icon() const;
virtual bool possiblyContainsTrack( const QUrl &url ) const;
virtual Meta::TrackPtr trackForUrl( const QUrl &url );
virtual QueryMaker* queryMaker();
virtual QString collectionId() const;
// AggregateCollection methods
void removeTrack( const Meta::TrackKey &key );
Meta::AggregateTrack* getTrack( Meta::TrackPtr track );
void setTrack( Meta::AggregateTrack *track );
bool hasTrack( const Meta::TrackKey &key );
void removeAlbum( const QString &album, const QString &albumArtist );
Meta::AggregateAlbum* getAlbum( Meta::AlbumPtr album );
void setAlbum( Meta::AggregateAlbum *album );
bool hasAlbum( const QString &album, const QString &albumArtist );
void removeArtist( const QString &artist );
Meta::AggregateArtist* getArtist( Meta::ArtistPtr artist );
void setArtist( Meta::AggregateArtist *artist );
bool hasArtist( const QString &artist );
void removeGenre( const QString &genre );
Meta::AggregateGenre* getGenre( Meta::GenrePtr genre );
void setGenre( Meta::AggregateGenre *genre );
bool hasGenre( const QString &genre );
void removeComposer( const QString &name );
Meta::AggregateComposer* getComposer( Meta::ComposerPtr composer );
void setComposer( Meta::AggregateComposer *composer );
bool hasComposer( const QString &name );
bool hasYear( const QString &name );
void removeYear( const QString &name );
Meta::AggreagateYear* getYear( Meta::YearPtr year );
void setYear( Meta::AggreagateYear *year );
bool hasLabel( const QString &name );
void removeLabel( const QString &name );
Meta::AggregateLabel* getLabel( Meta::LabelPtr label );
void setLabel( Meta::AggregateLabel *label );
public Q_SLOTS:
- void removeCollection( const QString &collectionId );
+ void removeCollectionById( const QString &collectionId );
void removeCollection( Collections::Collection *collection );
void addCollection( Collections::Collection *collection, CollectionManager::CollectionStatus status );
void slotUpdated();
private Q_SLOTS:
void emptyCache();
private:
QHash<QString, Collections::Collection*> m_idCollectionMap;
QHash<QString, KSharedPtr<Meta::AggreagateYear> > m_yearMap;
QHash<QString, KSharedPtr<Meta::AggregateGenre> > m_genreMap;
QHash<QString, KSharedPtr<Meta::AggregateComposer> > m_composerMap;
QHash<QString, KSharedPtr<Meta::AggregateArtist> > m_artistMap;
QHash<Meta::AlbumKey, KSharedPtr<Meta::AggregateAlbum> > m_albumMap;
QHash<Meta::TrackKey, KSharedPtr<Meta::AggregateTrack> > m_trackMap;
QHash<QString, KSharedPtr<Meta::AggregateLabel> > m_labelMap;
QReadWriteLock m_yearLock;
QReadWriteLock m_genreLock;
QReadWriteLock m_composerLock;
QReadWriteLock m_artistLock;
QReadWriteLock m_albumLock;
QReadWriteLock m_trackLock;
QReadWriteLock m_labelLock;
};
} //namespace Collections
#endif
diff --git a/src/core-impl/collections/aggregate/AggregateMeta.cpp b/src/core-impl/collections/aggregate/AggregateMeta.cpp
index 64539832f6..40436a8f22 100644
--- a/src/core-impl/collections/aggregate/AggregateMeta.cpp
+++ b/src/core-impl/collections/aggregate/AggregateMeta.cpp
@@ -1,1532 +1,1532 @@
/****************************************************************************************
* Copyright (c) 2009,2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AggregateMeta"
#include "AggregateMeta.h"
#include "SvgHandler.h"
#include "core/meta/TrackEditor.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Debug.h"
#include "core-impl/collections/aggregate/AggregateCollection.h"
#include <QDateTime>
#include <QSet>
#include <QTimer>
namespace Meta
{
#define FORWARD( call ) { foreach( TrackEditorPtr e, m_editors ) { e->call; } \
- if( !m_batchMode ) QTimer::singleShot( 0, m_collection, SLOT(slotUpdated()) ); }
+ if( !m_batchMode ) QTimer::singleShot( 0, m_collection, &Collections::AggregateCollection::slotUpdated ); }
class AggregateTrackEditor : public TrackEditor
{
public:
AggregateTrackEditor( Collections::AggregateCollection *coll, const QList<TrackEditorPtr> &editors )
: TrackEditor()
, m_batchMode( false )
, m_collection( coll )
, m_editors( editors )
{}
void beginUpdate()
{
m_batchMode = true;
foreach( TrackEditorPtr ec, m_editors ) ec->beginUpdate();
}
void endUpdate()
{
foreach( TrackEditorPtr ec, m_editors ) ec->endUpdate();
m_batchMode = false;
- QTimer::singleShot( 0, m_collection, SLOT(slotUpdated()) );
+ QTimer::singleShot( 0, m_collection, &Collections::AggregateCollection::slotUpdated );
}
void setComment( const QString &newComment ) { FORWARD( setComment( newComment ) ) }
void setTrackNumber( int newTrackNumber ) { FORWARD( setTrackNumber( newTrackNumber ) ) }
void setDiscNumber( int newDiscNumber ) { FORWARD( setDiscNumber( newDiscNumber ) ) }
void setBpm( const qreal newBpm ) { FORWARD( setBpm( newBpm ) ) }
void setTitle( const QString &newTitle ) { FORWARD( setTitle( newTitle ) ) }
void setArtist( const QString &newArtist ) { FORWARD( setArtist( newArtist ) ) }
void setAlbum( const QString &newAlbum ) { FORWARD( setAlbum( newAlbum ) ) }
void setAlbumArtist( const QString &newAlbumArtist ) { FORWARD( setAlbumArtist ( newAlbumArtist ) ) }
void setGenre( const QString &newGenre ) { FORWARD( setGenre( newGenre ) ) }
void setComposer( const QString &newComposer ) { FORWARD( setComposer( newComposer ) ) }
void setYear( int newYear ) { FORWARD( setYear( newYear ) ) }
private:
bool m_batchMode;
Collections::AggregateCollection *m_collection;
QList<TrackEditorPtr> m_editors;
};
#undef FORWARD
AggregateTrack::AggregateTrack( Collections::AggregateCollection *coll, const TrackPtr &track )
: Track()
, Observer()
, m_collection( coll )
, m_name( track->name() )
, m_album( 0 )
, m_artist( 0 )
, m_genre( 0 )
, m_composer( 0 )
, m_year( 0 )
{
subscribeTo( track );
m_tracks.append( track );
if( track->album() )
m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) );
if( track->artist() )
m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) );
if( track->genre() )
m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) );
if( track->composer() )
m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) );
if( track->year() )
m_year = Meta::YearPtr( m_collection->getYear( track->year() ) );
}
AggregateTrack::~AggregateTrack()
{
}
QString
AggregateTrack::name() const
{
return m_name;
}
QString
AggregateTrack::prettyName() const
{
return m_name;
}
QString
AggregateTrack::sortableName() const
{
if( !m_tracks.isEmpty() )
return m_tracks.first()->sortableName();
return m_name;
}
QUrl
AggregateTrack::playableUrl() const
{
Meta::TrackPtr bestPlayableTrack;
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->isPlayable() )
{
bool local = track->playableUrl().isLocalFile();
if( local )
{
bestPlayableTrack = track;
break;
}
else
{
//we might want to add some more sophisticated logic to figure out
//the best remote track to play, but this works for now
bestPlayableTrack = track;
}
}
}
if( bestPlayableTrack )
return bestPlayableTrack->playableUrl();
return QUrl();
}
QString
AggregateTrack::prettyUrl() const
{
if( m_tracks.count() == 1 )
{
return m_tracks.first()->prettyUrl();
}
else
{
return QString();
}
}
QString
AggregateTrack::uidUrl() const
{
// this is where it gets interesting
// a uidUrl for a AggregateTrack probably has to be generated
// from the parts of the key in AggregateCollection
// need to think about this some more
return QString();
}
QString
AggregateTrack::notPlayableReason() const
{
QStringList reasons;
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( !track->isPlayable() )
reasons.append( track->notPlayableReason() );
else
return QString(); // no reason if at least one playable
}
return reasons.join( QString( ", " ) );
}
Meta::AlbumPtr
AggregateTrack::album() const
{
return m_album;
}
Meta::ArtistPtr
AggregateTrack::artist() const
{
return m_artist;
}
Meta::ComposerPtr
AggregateTrack::composer() const
{
return m_composer;
}
Meta::GenrePtr
AggregateTrack::genre() const
{
return m_genre;
}
Meta::YearPtr
AggregateTrack::year() const
{
return m_year;
}
QString
AggregateTrack::comment() const
{
//try to return something sensible here...
//do not show a comment if the internal tracks disagree about the comment
QString comment;
if( !m_tracks.isEmpty() )
comment = m_tracks.first()->comment();
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->comment() != comment )
{
comment.clear();
break;
}
}
return comment;
}
qreal
AggregateTrack::bpm() const
{
//Similar to comment(), try to return something sensible here...
//do not show a common bpm value if the internal tracks disagree about the bpm
qreal bpm = -1.0;
if( !m_tracks.isEmpty() )
bpm = m_tracks.first()->bpm();
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->bpm() != bpm )
{
bpm = -1.0;
break;
}
}
return bpm;
}
double
AggregateTrack::score() const
{
//again, multiple ways to implement this method:
//return the maximum score, the minimum score, the average
//the score of the track with the maximum play count,
//or an average weighted by play count. And probably a couple of ways that
//I cannot think of right now...
//implementing the weighted average here...
double weightedSum = 0.0;
int totalCount = 0;
foreach( const Meta::TrackPtr &track, m_tracks )
{
ConstStatisticsPtr statistics = track->statistics();
totalCount += statistics->playCount();
weightedSum += statistics->playCount() * statistics->score();
}
if( totalCount )
return weightedSum / totalCount;
return 0.0;
}
void
AggregateTrack::setScore( double newScore )
{
foreach( Meta::TrackPtr track, m_tracks )
{
track->statistics()->setScore( newScore );
}
}
int
AggregateTrack::rating() const
{
//yay, multiple options again. As this has to be defined by the user, let's take
//the maximum here.
int result = 0;
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->statistics()->rating() > result )
result = track->statistics()->rating();
}
return result;
}
void
AggregateTrack::setRating( int newRating )
{
foreach( Meta::TrackPtr track, m_tracks )
{
track->statistics()->setRating( newRating );
}
}
QDateTime
AggregateTrack::firstPlayed() const
{
QDateTime result;
foreach( const Meta::TrackPtr &track, m_tracks )
{
ConstStatisticsPtr statistics = track->statistics();
//use the track's firstPlayed value if it represents an earlier timestamp than
//the current result, or use it directly if result has not been set yet
//this should result in the earliest timestamp for first play of all internal
//tracks being returned
if( ( statistics->firstPlayed().isValid() && result.isValid() && statistics->firstPlayed() < result ) ||
( statistics->firstPlayed().isValid() && !result.isValid() ) )
{
result = statistics->firstPlayed();
}
}
return result;
}
void
AggregateTrack::setFirstPlayed( const QDateTime &date )
{
foreach( Meta::TrackPtr track, m_tracks )
{
// only "lower" the first played
Meta::StatisticsPtr trackStats = track->statistics();
if( !trackStats->firstPlayed().isValid() ||
trackStats->firstPlayed() > date )
{
trackStats->setFirstPlayed( date );
}
}
}
QDateTime
AggregateTrack::lastPlayed() const
{
QDateTime result;
//return the latest timestamp. Easier than firstPlayed because we do not have to
//care about 0.
//when are we going to perform the refactoring as discussed in Berlin?
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->statistics()->lastPlayed() > result )
{
result = track->statistics()->lastPlayed();
}
}
return result;
}
void
AggregateTrack::setLastPlayed(const QDateTime& date)
{
foreach( Meta::TrackPtr track, m_tracks )
{
// only "raise" the last played
Meta::StatisticsPtr trackStats = track->statistics();
if( !trackStats->lastPlayed().isValid() ||
trackStats->lastPlayed() < date )
{
trackStats->setLastPlayed( date );
}
}
}
int
AggregateTrack::playCount() const
{
// show the maximum of all play counts.
int result = 0;
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->statistics()->playCount() > result )
{
result = track->statistics()->playCount();
}
}
return result;
}
void
AggregateTrack::setPlayCount( int newPlayCount )
{
Q_UNUSED( newPlayCount )
// no safe thing to do here. Notice we override finishedPlaying()
}
void
AggregateTrack::finishedPlaying( double playedFraction )
{
foreach( Meta::TrackPtr track, m_tracks )
{
track->finishedPlaying( playedFraction );
}
}
qint64
AggregateTrack::length() const
{
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->length() )
return track->length();
}
return 0;
}
int
AggregateTrack::filesize() const
{
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->filesize() )
{
return track->filesize();
}
}
return 0;
}
int
AggregateTrack::sampleRate() const
{
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->sampleRate() )
return track->sampleRate();
}
return 0;
}
int
AggregateTrack::bitrate() const
{
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( track->bitrate() )
return track->bitrate();
}
return 0;
}
QDateTime
AggregateTrack::createDate() const
{
QDateTime result;
foreach( const Meta::TrackPtr &track, m_tracks )
{
//use the track's firstPlayed value if it represents an earlier timestamp than
//the current result, or use it directly if result has not been set yet
//this should result in the earliest timestamp for first play of all internal
//tracks being returned
if( ( track->createDate().isValid() && result.isValid() && track->createDate() < result ) ||
( track->createDate().isValid() && !result.isValid() ) )
{
result = track->createDate();
}
}
return result;
}
int
AggregateTrack::trackNumber() const
{
int result = 0;
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( ( !result && track->trackNumber() ) || ( result && result == track->trackNumber() ) )
{
result = track->trackNumber();
}
else if( result && result != track->trackNumber() )
{
//tracks disagree about the tracknumber
return 0;
}
}
return result;
}
int
AggregateTrack::discNumber() const
{
int result = 0;
foreach( const Meta::TrackPtr &track, m_tracks )
{
if( ( !result && track->discNumber() ) || ( result && result == track->discNumber() ) )
{
result = track->discNumber();
}
else if( result && result != track->discNumber() )
{
//tracks disagree about the disc number
return 0;
}
}
return result;
}
QString
AggregateTrack::type() const
{
if( m_tracks.size() == 1 )
{
return m_tracks.first()->type();
}
else
{
//TODO: figure something out
return QString();
}
}
Collections::Collection*
AggregateTrack::collection() const
{
return m_collection;
}
bool
AggregateTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
if( m_tracks.count() == 1 )
// if we aggregate only one track, simply return the tracks capability directly
return m_tracks.first()->hasCapabilityInterface( type );
else
return false;
}
Capabilities::Capability*
AggregateTrack::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_tracks.count() == 1 )
return m_tracks.first()->createCapabilityInterface( type );
else
return 0;
}
TrackEditorPtr
AggregateTrack::editor()
{
if( m_tracks.count() == 1 )
return m_tracks.first()->editor();
QList<Meta::TrackEditorPtr> editors;
foreach( Meta::TrackPtr track, m_tracks )
{
Meta::TrackEditorPtr ec = track->editor();
if( ec )
editors << ec;
else
return TrackEditorPtr();
}
return TrackEditorPtr( new AggregateTrackEditor( m_collection, editors ) );
}
void
AggregateTrack::addLabel( const QString &label )
{
foreach( Meta::TrackPtr track, m_tracks )
{
track->addLabel( label );
}
}
void
AggregateTrack::addLabel( const Meta::LabelPtr &label )
{
foreach( Meta::TrackPtr track, m_tracks )
{
track->addLabel( label );
}
}
void
AggregateTrack::removeLabel( const Meta::LabelPtr &label )
{
foreach( Meta::TrackPtr track, m_tracks )
{
track->removeLabel( label );
}
}
Meta::LabelList
AggregateTrack::labels() const
{
QSet<AggregateLabel *> aggregateLabels;
foreach( const Meta::TrackPtr &track, m_tracks )
{
foreach( Meta::LabelPtr label, track->labels() )
{
aggregateLabels.insert( m_collection->getLabel( label ) );
}
}
Meta::LabelList result;
foreach( AggregateLabel *label, aggregateLabels )
{
result << Meta::LabelPtr( label );
}
return result;
}
StatisticsPtr
AggregateTrack::statistics()
{
return StatisticsPtr( this );
}
void
AggregateTrack::add( const Meta::TrackPtr &track )
{
if( !track || m_tracks.contains( track ) )
return;
m_tracks.append( track );
subscribeTo( track );
notifyObservers();
}
void
AggregateTrack::metadataChanged( Meta::TrackPtr track )
{
if( !track )
return;
if( !m_tracks.contains( track ) )
{
//why are we subscribed?
unsubscribeFrom( track );
return;
}
const TrackKey myKey( Meta::TrackPtr( this ) );
const TrackKey otherKey( track );
if( myKey == otherKey )
{
//no key relevant metadata did change
notifyObservers();
return;
}
else
{
if( m_tracks.size() == 1 )
{
if( m_collection->hasTrack( otherKey ) )
{
unsubscribeFrom( track );
m_collection->getTrack( track );
m_tracks.removeAll( track );
m_collection->removeTrack( myKey );
return; //do not notify observers, this track is not valid anymore!
}
else
{
m_name = track->name();
if( track->album() )
m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) );
if( track->artist() )
m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) );
if( track->genre() )
m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) );
if( track->composer() )
m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) );
if( track->year() )
m_year = Meta::YearPtr( m_collection->getYear( track->year() ) );
m_collection->setTrack( this );
m_collection->removeTrack( myKey );
}
}
else
{
unsubscribeFrom( track );
m_collection->getTrack( track );
m_tracks.removeAll( track );
}
notifyObservers();
}
}
AggregateAlbum::AggregateAlbum( Collections::AggregateCollection *coll, Meta::AlbumPtr album )
: Meta::Album()
, Meta::Observer()
, m_collection( coll )
, m_name( album->name() )
{
m_albums.append( album );
if( album->hasAlbumArtist() )
m_albumArtist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) );
}
AggregateAlbum::~AggregateAlbum()
{
}
QString
AggregateAlbum::name() const
{
return m_name;
}
QString
AggregateAlbum::prettyName() const
{
return m_name;
}
QString
AggregateAlbum::sortableName() const
{
if( !m_albums.isEmpty() )
return m_albums.first()->sortableName();
return m_name;
}
Meta::TrackList
AggregateAlbum::tracks()
{
QSet<AggregateTrack*> tracks;
foreach( Meta::AlbumPtr album, m_albums )
{
Meta::TrackList tmp = album->tracks();
foreach( const Meta::TrackPtr &track, tmp )
{
tracks.insert( m_collection->getTrack( track ) );
}
}
Meta::TrackList result;
foreach( AggregateTrack *track, tracks )
{
result.append( Meta::TrackPtr( track ) );
}
return result;
}
Meta::ArtistPtr
AggregateAlbum::albumArtist() const
{
return m_albumArtist;
}
bool
AggregateAlbum::isCompilation() const
{
return m_albumArtist.isNull();
}
bool
AggregateAlbum::hasAlbumArtist() const
{
return !m_albumArtist.isNull();
}
bool
AggregateAlbum::hasCapabilityInterface(Capabilities::Capability::Type type ) const
{
if( m_albums.count() == 1 )
{
return m_albums.first()->hasCapabilityInterface( type );
}
else
{
return false;
}
}
Capabilities::Capability*
AggregateAlbum::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_albums.count() == 1 )
{
return m_albums.first()->createCapabilityInterface( type );
}
else
{
return 0;
}
}
void
AggregateAlbum::add( Meta::AlbumPtr album )
{
if( !album || m_albums.contains( album ) )
return;
m_albums.append( album );
subscribeTo( album );
notifyObservers();
}
bool
AggregateAlbum::hasImage( int size ) const
{
foreach( const Meta::AlbumPtr &album, m_albums )
{
if( album->hasImage( size ) )
return true;
}
return false;
}
QImage
AggregateAlbum::image( int size ) const
{
foreach( Meta::AlbumPtr album, m_albums )
{
if( album->hasImage( size ) )
{
return album->image( size );
}
}
return Meta::Album::image( size );
}
QUrl
AggregateAlbum::imageLocation( int size )
{
foreach( Meta::AlbumPtr album, m_albums )
{
if( album->hasImage( size ) )
{
QUrl url = album->imageLocation( size );
if( url.isValid() )
{
return url;
}
}
}
return QUrl();
}
QPixmap
AggregateAlbum::imageWithBorder( int size, int borderWidth )
{
foreach( Meta::AlbumPtr album, m_albums )
{
if( album->hasImage( size ) )
{
return The::svgHandler()->imageWithBorder( album, size, borderWidth );
}
}
return QPixmap();
}
bool
AggregateAlbum::canUpdateImage() const
{
if( m_albums.count() == 0 )
return false;
foreach( const Meta::AlbumPtr &album, m_albums )
{
//we can only update the image for all albusm at the same time
if( !album->canUpdateImage() )
return false;
}
return true;
}
void
AggregateAlbum::setImage( const QImage &image )
{
foreach( Meta::AlbumPtr album, m_albums )
{
album->setImage( image );
}
}
void
AggregateAlbum::removeImage()
{
foreach( Meta::AlbumPtr album, m_albums )
{
album->removeImage();
}
}
void
AggregateAlbum::setSuppressImageAutoFetch( bool suppress )
{
foreach( Meta::AlbumPtr album, m_albums )
{
album->setSuppressImageAutoFetch( suppress );
}
}
bool
AggregateAlbum::suppressImageAutoFetch() const
{
foreach( const Meta::AlbumPtr &album, m_albums )
{
if( !album->suppressImageAutoFetch() )
return false;
}
return true;
}
void
AggregateAlbum::metadataChanged( Meta::AlbumPtr album )
{
if( !album || !m_albums.contains( album ) )
return;
if( album->name() != m_name ||
hasAlbumArtist() != album->hasAlbumArtist() ||
( hasAlbumArtist() && m_albumArtist->name() != album->albumArtist()->name() ) )
{
if( m_albums.count() > 1 )
{
m_collection->getAlbum( album );
unsubscribeFrom( album );
m_albums.removeAll( album );
}
else
{
Meta::ArtistPtr albumartist;
if( album->hasAlbumArtist() )
albumartist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) );
QString artistname = m_albumArtist ? m_albumArtist->name() : QString();
m_collection->removeAlbum( m_name, artistname );
m_name = album->name();
m_albumArtist = albumartist;
m_collection->setAlbum( this );
}
}
notifyObservers();
}
AggregateArtist::AggregateArtist( Collections::AggregateCollection *coll, Meta::ArtistPtr artist )
: Meta::Artist()
, Meta::Observer()
, m_collection( coll )
, m_name( artist->name() )
{
m_artists.append( artist );
subscribeTo( artist );
}
AggregateArtist::~AggregateArtist()
{
}
QString
AggregateArtist::name() const
{
return m_name;
}
QString
AggregateArtist::prettyName() const
{
return m_name;
}
QString
AggregateArtist::sortableName() const
{
if( !m_artists.isEmpty() )
return m_artists.first()->sortableName();
return m_name;
}
Meta::TrackList
AggregateArtist::tracks()
{
QSet<AggregateTrack*> tracks;
foreach( Meta::ArtistPtr artist, m_artists )
{
Meta::TrackList tmp = artist->tracks();
foreach( const Meta::TrackPtr &track, tmp )
{
tracks.insert( m_collection->getTrack( track ) );
}
}
Meta::TrackList result;
foreach( AggregateTrack *track, tracks )
{
result.append( Meta::TrackPtr( track ) );
}
return result;
}
bool
AggregateArtist::hasCapabilityInterface(Capabilities::Capability::Type type ) const
{
if( m_artists.count() == 1 )
{
return m_artists.first()->hasCapabilityInterface( type );
}
else
{
return false;
}
}
Capabilities::Capability*
AggregateArtist::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_artists.count() == 1 )
{
return m_artists.first()->createCapabilityInterface( type );
}
else
{
return 0;
}
}
void
AggregateArtist::add( Meta::ArtistPtr artist )
{
if( !artist || m_artists.contains( artist ) )
return;
m_artists.append( artist );
subscribeTo( artist );
notifyObservers();
}
void
AggregateArtist::metadataChanged( Meta::ArtistPtr artist )
{
if( !artist || !m_artists.contains( artist ) )
return;
if( artist->name() != m_name )
{
if( m_artists.count() > 1 )
{
m_collection->getArtist( artist );
unsubscribeFrom( artist );
m_artists.removeAll( artist );
}
else
{
//possible race condition here:
//if another thread creates an Artist with the new name
//we will have two instances that have the same name!
//TODO: figure out a way around that
//the race condition is a problem for all other metadataChanged methods too
m_collection->removeArtist( m_name );
m_name = artist->name();
m_collection->setArtist( this );
}
}
notifyObservers();
}
AggregateGenre::AggregateGenre( Collections::AggregateCollection *coll, Meta::GenrePtr genre )
: Meta::Genre()
, Meta::Observer()
, m_collection( coll )
, m_name( genre->name() )
{
m_genres.append( genre );
subscribeTo( genre );
}
AggregateGenre::~AggregateGenre()
{
}
QString
AggregateGenre::name() const
{
return m_name;
}
QString
AggregateGenre::prettyName() const
{
return m_name;
}
QString
AggregateGenre::sortableName() const
{
if( !m_genres.isEmpty() )
return m_genres.first()->sortableName();
return m_name;
}
Meta::TrackList
AggregateGenre::tracks()
{
QSet<AggregateTrack*> tracks;
foreach( Meta::GenrePtr genre, m_genres )
{
Meta::TrackList tmp = genre->tracks();
foreach( const Meta::TrackPtr &track, tmp )
{
tracks.insert( m_collection->getTrack( track ) );
}
}
Meta::TrackList result;
foreach( AggregateTrack *track, tracks )
{
result.append( Meta::TrackPtr( track ) );
}
return result;
}
bool
AggregateGenre::hasCapabilityInterface(Capabilities::Capability::Type type ) const
{
if( m_genres.count() == 1 )
{
return m_genres.first()->hasCapabilityInterface( type );
}
else
{
return false;
}
}
Capabilities::Capability*
AggregateGenre::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_genres.count() == 1 )
{
return m_genres.first()->createCapabilityInterface( type );
}
else
{
return 0;
}
}
void
AggregateGenre::add( Meta::GenrePtr genre )
{
if( !genre || m_genres.contains( genre ) )
return;
m_genres.append( genre );
subscribeTo( genre );
notifyObservers();
}
void
AggregateGenre::metadataChanged( Meta::GenrePtr genre )
{
if( !genre || !m_genres.contains( genre ) )
return;
if( genre->name() != m_name )
{
if( m_genres.count() > 1 )
{
m_collection->getGenre( genre );
unsubscribeFrom( genre );
m_genres.removeAll( genre );
}
else
{
m_collection->removeGenre( m_name );
m_collection->setGenre( this );
m_name = genre->name();
}
}
notifyObservers();
}
AggregateComposer::AggregateComposer( Collections::AggregateCollection *coll, Meta::ComposerPtr composer )
: Meta::Composer()
, Meta::Observer()
, m_collection( coll )
, m_name( composer->name() )
{
m_composers.append( composer );
subscribeTo( composer );
}
AggregateComposer::~AggregateComposer()
{
}
QString
AggregateComposer::name() const
{
return m_name;
}
QString
AggregateComposer::prettyName() const
{
return m_name;
}
QString
AggregateComposer::sortableName() const
{
if( !m_composers.isEmpty() )
return m_composers.first()->sortableName();
return m_name;
}
Meta::TrackList
AggregateComposer::tracks()
{
QSet<AggregateTrack*> tracks;
foreach( Meta::ComposerPtr composer, m_composers )
{
Meta::TrackList tmp = composer->tracks();
foreach( const Meta::TrackPtr &track, tmp )
{
tracks.insert( m_collection->getTrack( track ) );
}
}
Meta::TrackList result;
foreach( AggregateTrack *track, tracks )
{
result.append( Meta::TrackPtr( track ) );
}
return result;
}
bool
AggregateComposer::hasCapabilityInterface(Capabilities::Capability::Type type ) const
{
if( m_composers.count() == 1 )
{
return m_composers.first()->hasCapabilityInterface( type );
}
else
{
return false;
}
}
Capabilities::Capability*
AggregateComposer::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_composers.count() == 1 )
{
return m_composers.first()->createCapabilityInterface( type );
}
else
{
return 0;
}
}
void
AggregateComposer::add( Meta::ComposerPtr composer )
{
if( !composer || m_composers.contains( composer ) )
return;
m_composers.append( composer );
subscribeTo( composer );
notifyObservers();
}
void
AggregateComposer::metadataChanged( Meta::ComposerPtr composer )
{
if( !composer || !m_composers.contains( composer ) )
return;
if( composer->name() != m_name )
{
if( m_composers.count() > 1 )
{
m_collection->getComposer( composer );
unsubscribeFrom( composer );
m_composers.removeAll( composer );
}
else
{
m_collection->removeComposer( m_name );
m_collection->setComposer( this );
m_name = composer->name();
}
}
notifyObservers();
}
AggreagateYear::AggreagateYear( Collections::AggregateCollection *coll, Meta::YearPtr year )
: Meta::Year()
, Meta::Observer()
, m_collection( coll )
, m_name( year->name() )
{
m_years.append( year );
subscribeTo( year );
}
AggreagateYear::~AggreagateYear()
{
//nothing to do
}
QString
AggreagateYear::name() const
{
return m_name;
}
QString
AggreagateYear::prettyName() const
{
return m_name;
}
QString
AggreagateYear::sortableName() const
{
if( !m_years.isEmpty() )
return m_years.first()->sortableName();
return m_name;
}
Meta::TrackList
AggreagateYear::tracks()
{
QSet<AggregateTrack*> tracks;
foreach( Meta::YearPtr year, m_years )
{
Meta::TrackList tmp = year->tracks();
foreach( const Meta::TrackPtr &track, tmp )
{
tracks.insert( m_collection->getTrack( track ) );
}
}
Meta::TrackList result;
foreach( AggregateTrack *track, tracks )
{
result.append( Meta::TrackPtr( track ) );
}
return result;
}
bool
AggreagateYear::hasCapabilityInterface(Capabilities::Capability::Type type ) const
{
if( m_years.count() == 1 )
{
return m_years.first()->hasCapabilityInterface( type );
}
else
{
return false;
}
}
Capabilities::Capability*
AggreagateYear::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_years.count() == 1 )
{
return m_years.first()->createCapabilityInterface( type );
}
else
{
return 0;
}
}
void
AggreagateYear::add( Meta::YearPtr year )
{
if( !year || m_years.contains( year ) )
return;
m_years.append( year );
subscribeTo( year );
notifyObservers();
}
void
AggreagateYear::metadataChanged( Meta::YearPtr year )
{
if( !year || !m_years.contains( year ) )
return;
if( year->name() != m_name )
{
if( m_years.count() > 1 )
{
m_collection->getYear( year );
unsubscribeFrom( year );
m_years.removeAll( year );
}
else
{
if( m_collection->hasYear( year->name() ) )
{
unsubscribeFrom( year );
m_collection->getYear( year );
m_years.removeAll( year );
m_collection->removeYear( m_name );
return; //do NOT notify observers, the instance is not valid anymore!
}
else
{
// be careful with the ordering of instructions here
// AggregateCollection uses KSharedPtr internally
// so we have to make sure that there is more than one pointer
// to this instance by registering this instance under the new name
// before removing the old one. Otherwise kSharedPtr might delete this
// instance in removeYear()
QString tmpName = m_name;
m_name = year->name();
m_collection->setYear( this );
m_collection->removeYear( tmpName );
}
}
}
notifyObservers();
}
AggregateLabel::AggregateLabel( Collections::AggregateCollection *coll, const Meta::LabelPtr &label )
: Meta::Label()
, m_collection( coll )
, m_name( label->name() )
{
m_labels.append( label );
Q_UNUSED(m_collection); // might be needed later
}
AggregateLabel::~AggregateLabel()
{
//nothing to do
}
QString
AggregateLabel::name() const
{
return m_name;
}
QString
AggregateLabel::prettyName() const
{
return m_name;
}
QString
AggregateLabel::sortableName() const
{
if( !m_labels.isEmpty() )
return m_labels.first()->sortableName();
return m_name;
}
bool
AggregateLabel::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
if( m_labels.count() == 1 )
{
return m_labels.first()->hasCapabilityInterface( type );
}
else
{
return false;
}
}
Capabilities::Capability*
AggregateLabel::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_labels.count() == 1 )
{
return m_labels.first()->createCapabilityInterface( type );
}
else
{
return 0;
}
}
void
AggregateLabel::add( const Meta::LabelPtr &label )
{
if( !label || m_labels.contains( label ) )
return;
m_labels.append( label );
}
} //namespace Meta
diff --git a/src/core-impl/collections/aggregate/AggregateQueryMaker.cpp b/src/core-impl/collections/aggregate/AggregateQueryMaker.cpp
index 1c7fa9078c..7b4cd29382 100644
--- a/src/core-impl/collections/aggregate/AggregateQueryMaker.cpp
+++ b/src/core-impl/collections/aggregate/AggregateQueryMaker.cpp
@@ -1,546 +1,560 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AggregateQueryMaker"
#include "AggregateQueryMaker.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core-impl/collections/aggregate/AggregateCollection.h"
#include "core-impl/collections/support/MemoryCustomValue.h"
#include "core-impl/collections/support/MemoryQueryMakerHelper.h"
#include <QMetaEnum>
#include <QMetaObject>
+#include <typeinfo>
+
using namespace Collections;
AggregateQueryMaker::AggregateQueryMaker( AggregateCollection *collection, const QList<QueryMaker*> &queryMakers )
: QueryMaker()
, m_collection( collection )
, m_builders( queryMakers )
, m_queryDoneCount( 0 )
, m_maxResultSize( -1 )
, m_queryType( QueryMaker::None )
, m_orderDescending( false )
, m_orderField( 0 )
, m_orderByNumberField( false )
, m_queryDoneCountMutex()
{
foreach( QueryMaker *b, m_builders )
{
- connect( b, SIGNAL(queryDone()), this, SLOT(slotQueryDone()) );
- connect( b, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(slotNewResultReady(Meta::TrackList)), Qt::QueuedConnection );
- connect( b, SIGNAL(newResultReady(Meta::ArtistList)), this, SLOT(slotNewResultReady(Meta::ArtistList)), Qt::QueuedConnection );
- connect( b, SIGNAL(newResultReady(Meta::AlbumList)), this, SLOT(slotNewResultReady(Meta::AlbumList)), Qt::QueuedConnection );
- connect( b, SIGNAL(newResultReady(Meta::GenreList)), this, SLOT(slotNewResultReady(Meta::GenreList)), Qt::QueuedConnection );
- connect( b, SIGNAL(newResultReady(Meta::ComposerList)), this, SLOT(slotNewResultReady(Meta::ComposerList)), Qt::QueuedConnection );
- connect( b, SIGNAL(newResultReady(Meta::YearList)), this, SLOT(slotNewResultReady(Meta::YearList)), Qt::QueuedConnection );
- connect( b, SIGNAL(newResultReady(Meta::LabelList)), this, SLOT(slotNewResultReady(Meta::LabelList)), Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::queryDone, this, &AggregateQueryMaker::slotQueryDone );
+ connect( b, &Collections::QueryMaker::newTracksReady, this, &AggregateQueryMaker::slotNewTracksReady, Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::newArtistsReady, this, &AggregateQueryMaker::slotNewArtistsReady, Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::newAlbumsReady, this, &AggregateQueryMaker::slotNewAlbumsReady, Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::newGenresReady, this, &AggregateQueryMaker::slotNewGenresReady, Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::newComposersReady, this, &AggregateQueryMaker::slotNewComposersReady, Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::newYearsReady, this, &AggregateQueryMaker::slotNewYearsReady, Qt::QueuedConnection );
+ connect( b, &Collections::QueryMaker::newLabelsReady, this, &AggregateQueryMaker::slotNewLabelsReady, Qt::QueuedConnection );
}
}
AggregateQueryMaker::~AggregateQueryMaker()
{
qDeleteAll( m_returnFunctions );
qDeleteAll( m_returnValues );
qDeleteAll( m_builders );
}
void
AggregateQueryMaker::run()
{
foreach( QueryMaker *b, m_builders )
b->run();
}
void
AggregateQueryMaker::abortQuery()
{
foreach( QueryMaker *b, m_builders )
b->abortQuery();
}
QueryMaker*
AggregateQueryMaker::setQueryType( QueryType type )
{
m_queryType = type;
if( type != QueryMaker::Custom )
{
foreach( QueryMaker *b, m_builders )
b->setQueryType( type );
return this;
}
else
{
// we cannot forward custom queries as there is no way to integrate the results
// delivered by the QueryMakers. Instead we ask for tracks that match the criterias,
// and then generate the custom result similar to MemoryQueryMaker.
// And yes, this means that we will load all tracks when we simply want the count of tracks
// in the collection. It might be necessary to add some specific logic for that case.
// On second thought, there is no way around loading all objects, as we want to operate on distinct
// elements (for some value of distinct) in AggregateCollection. We can only figure out what the union
// of all elements is after loading them in memory
foreach( QueryMaker *b, m_builders )
b->setQueryType( QueryMaker::Track );
return this;
}
}
QueryMaker*
AggregateQueryMaker::addReturnValue( qint64 value )
{
//do not forward this call, see comment in setQueryType()
m_returnValues.append( CustomValueFactory::returnValue( value ) );
return this;
}
QueryMaker*
AggregateQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
//do not forward this call, see comment in setQueryType()
m_returnFunctions.append( CustomValueFactory::returnFunction( function, value ) );
return this;
}
QueryMaker*
AggregateQueryMaker::orderBy( qint64 value, bool descending )
{
m_orderField = value;
m_orderDescending = descending;
//copied from MemoryQueryMaker. TODO: think of a sensible place to put this code
switch( value )
{
case Meta::valYear:
case Meta::valTrackNr:
case Meta::valDiscNr:
case Meta::valBpm:
case Meta::valLength:
case Meta::valBitrate:
case Meta::valSamplerate:
case Meta::valFilesize:
case Meta::valFormat:
case Meta::valCreateDate:
case Meta::valScore:
case Meta::valRating:
case Meta::valFirstPlayed:
case Meta::valLastPlayed:
case Meta::valPlaycount:
case Meta::valModified:
{
m_orderByNumberField = true;
break;
}
default:
m_orderByNumberField = false;
}
foreach( QueryMaker *b, m_builders )
b->orderBy( value, descending );
return this;
}
QueryMaker*
AggregateQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
foreach( QueryMaker *b, m_builders )
b->addFilter( value, filter, matchBegin, matchEnd );
return this;
}
QueryMaker*
AggregateQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
foreach( QueryMaker *b, m_builders )
b->excludeFilter( value, filter, matchBegin, matchEnd );
return this;
}
QueryMaker*
AggregateQueryMaker::addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
foreach( QueryMaker *b, m_builders )
b->addNumberFilter( value, filter, compare);
return this;
}
QueryMaker*
AggregateQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
foreach( QueryMaker *b, m_builders )
b->excludeNumberFilter( value, filter, compare );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::TrackPtr &track )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( track );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::ArtistPtr &artist, QueryMaker::ArtistMatchBehaviour behaviour )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( artist, behaviour );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( album );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( genre );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( composer );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::YearPtr &year )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( year );
return this;
}
QueryMaker*
AggregateQueryMaker::addMatch( const Meta::LabelPtr &label )
{
foreach( QueryMaker *b, m_builders )
b->addMatch( label );
return this;
}
QueryMaker*
AggregateQueryMaker::limitMaxResultSize( int size )
{
//forward the call so the m_builders do not have to do work
//that we definitely know is unnecessary (like returning more than size results)
//we have to limit the combined result of all m_builders nevertheless
m_maxResultSize = size;
foreach( QueryMaker *b, m_builders )
b->limitMaxResultSize( size );
return this;
}
QueryMaker*
AggregateQueryMaker::beginAnd()
{
foreach( QueryMaker *b, m_builders )
b->beginAnd();
return this;
}
QueryMaker*
AggregateQueryMaker::beginOr()
{
foreach( QueryMaker *b, m_builders )
b->beginOr();
return this;
}
QueryMaker*
AggregateQueryMaker::endAndOr()
{
foreach( QueryMaker *b, m_builders )
b->endAndOr();
return this;
}
QueryMaker*
AggregateQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
foreach( QueryMaker *b, m_builders )
b->setAlbumQueryMode( mode );
return this;
}
QueryMaker*
AggregateQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
foreach( QueryMaker *b, m_builders )
b->setLabelQueryMode( mode );
return this;
}
void
AggregateQueryMaker::slotQueryDone()
{
m_queryDoneCountMutex.lock();
m_queryDoneCount++;
if ( m_queryDoneCount == m_builders.size() )
{
//make sure we don't give control to code outside this class while holding the lock
m_queryDoneCountMutex.unlock();
handleResult();
emit queryDone();
}
else
{
m_queryDoneCountMutex.unlock();
}
}
-template <class PointerType>
-void AggregateQueryMaker::emitProperResult( const QList<PointerType>& list )
-{
- QList<PointerType> resultList = list;
-
- if ( m_maxResultSize >= 0 && resultList.count() > m_maxResultSize )
- resultList = resultList.mid( 0, m_maxResultSize );
-
- emit newResultReady( list );
-}
-
void
AggregateQueryMaker::handleResult()
{
//copied from MemoryQueryMaker::handleResult()
switch( m_queryType )
{
case QueryMaker::Custom :
{
QStringList result;
Meta::TrackList tracks;
foreach( KSharedPtr<Meta::AggregateTrack> track, m_tracks )
{
tracks.append( Meta::TrackPtr::staticCast( track ) );
}
if( !m_returnFunctions.empty() )
{
//no sorting necessary
foreach( CustomReturnFunction *function, m_returnFunctions )
{
result.append( function->value( tracks ) );
}
}
else if( !m_returnValues.empty() )
{
if( m_orderField )
{
if( m_orderByNumberField )
tracks = MemoryQueryMakerHelper::orderListByNumber( tracks, m_orderField, m_orderDescending );
else
tracks = MemoryQueryMakerHelper::orderListByString( tracks, m_orderField, m_orderDescending );
}
int count = 0;
foreach( const Meta::TrackPtr &track, tracks )
{
if ( m_maxResultSize >= 0 && count == m_maxResultSize )
break;
foreach( CustomReturnValue *value, m_returnValues )
{
result.append( value->value( track ) );
}
count++;
}
}
emit newResultReady( result );
break;
}
case QueryMaker::Track :
{
Meta::TrackList tracks;
foreach( KSharedPtr<Meta::AggregateTrack> track, m_tracks )
{
tracks.append( Meta::TrackPtr::staticCast( track ) );
}
if( m_orderField )
{
if( m_orderByNumberField )
tracks = MemoryQueryMakerHelper::orderListByNumber( tracks, m_orderField, m_orderDescending );
else
tracks = MemoryQueryMakerHelper::orderListByString( tracks, m_orderField, m_orderDescending );
}
- emitProperResult<Meta::TrackPtr>( tracks );
+ if ( m_maxResultSize >= 0 && tracks.count() > m_maxResultSize )
+ tracks = tracks.mid( 0, m_maxResultSize );
+
+ emit newTracksReady(tracks);
break;
}
case QueryMaker::Album :
{
Meta::AlbumList albums;
foreach( KSharedPtr<Meta::AggregateAlbum> album, m_albums )
{
albums.append( Meta::AlbumPtr::staticCast( album ) );
}
albums = MemoryQueryMakerHelper::orderListByName<Meta::AlbumPtr>( albums, m_orderDescending );
- emitProperResult<Meta::AlbumPtr>( albums );
+ if ( m_maxResultSize >= 0 && albums.count() > m_maxResultSize )
+ albums = albums.mid( 0, m_maxResultSize );
+
+ emit newAlbumsReady(albums);
break;
}
case QueryMaker::Artist :
case QueryMaker::AlbumArtist :
{
Meta::ArtistList artists;
foreach( KSharedPtr<Meta::AggregateArtist> artist, m_artists )
{
artists.append( Meta::ArtistPtr::staticCast( artist ) );
}
artists = MemoryQueryMakerHelper::orderListByName<Meta::ArtistPtr>( artists, m_orderDescending );
- emitProperResult<Meta::ArtistPtr>( artists );
+
+ if ( m_maxResultSize >= 0 && artists.count() > m_maxResultSize )
+ artists = artists.mid( 0, m_maxResultSize );
+
+ emit newArtistsReady(artists);
break;
}
case QueryMaker::Composer :
{
Meta::ComposerList composers;
foreach( KSharedPtr<Meta::AggregateComposer> composer, m_composers )
{
composers.append( Meta::ComposerPtr::staticCast( composer ) );
}
composers = MemoryQueryMakerHelper::orderListByName<Meta::ComposerPtr>( composers, m_orderDescending );
- emitProperResult<Meta::ComposerPtr>( composers );
+ if ( m_maxResultSize >= 0 && composers.count() > m_maxResultSize )
+ composers = composers.mid( 0, m_maxResultSize );
+
+ emit newComposersReady(composers);
break;
}
case QueryMaker::Genre :
{
Meta::GenreList genres;
foreach( KSharedPtr<Meta::AggregateGenre> genre, m_genres )
{
genres.append( Meta::GenrePtr::staticCast( genre ) );
}
genres = MemoryQueryMakerHelper::orderListByName<Meta::GenrePtr>( genres, m_orderDescending );
- emitProperResult<Meta::GenrePtr>( genres );
+ if ( m_maxResultSize >= 0 && genres.count() > m_maxResultSize )
+ genres = genres.mid( 0, m_maxResultSize );
+
+ emit newGenresReady(genres);
break;
}
case QueryMaker::Year :
{
Meta::YearList years;
foreach( KSharedPtr<Meta::AggreagateYear> year, m_years )
{
years.append( Meta::YearPtr::staticCast( year ) );
}
//years have to be ordered as numbers, but orderListByNumber does not work for Meta::YearPtrs
if( m_orderField == Meta::valYear )
{
years = MemoryQueryMakerHelper::orderListByYear( years, m_orderDescending );
}
- emitProperResult<Meta::YearPtr>( years );
+ if ( m_maxResultSize >= 0 && years.count() > m_maxResultSize )
+ years = years.mid( 0, m_maxResultSize );
+
+ emit newYearsReady(years);
break;
}
case QueryMaker::Label :
{
Meta::LabelList labels;
foreach( KSharedPtr<Meta::AggregateLabel> label, m_labels )
{
labels.append( Meta::LabelPtr::staticCast( label ) );
}
labels = MemoryQueryMakerHelper::orderListByName<Meta::LabelPtr>( labels, m_orderDescending );
- emitProperResult<Meta::LabelPtr>( labels );
+
+ if ( m_maxResultSize >= 0 && labels.count() > m_maxResultSize )
+ labels = labels.mid( 0, m_maxResultSize );
+
+ emit newLabelsReady(labels);
break;
}
case QueryMaker::None :
//nothing to do
break;
}
m_tracks.clear();
m_albums.clear();
m_artists.clear();
m_composers.clear();
m_genres.clear();
m_years.clear();
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::TrackList &tracks )
+AggregateQueryMaker::slotNewTracksReady( const Meta::TrackList &tracks )
{
foreach( const Meta::TrackPtr &track, tracks )
{
m_tracks.insert( KSharedPtr<Meta::AggregateTrack>( m_collection->getTrack( track ) ) );
}
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::ArtistList &artists )
+AggregateQueryMaker::slotNewArtistsReady( const Meta::ArtistList &artists )
{
foreach( const Meta::ArtistPtr &artist, artists )
{
m_artists.insert( KSharedPtr<Meta::AggregateArtist>( m_collection->getArtist( artist ) ) );
}
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::AlbumList &albums )
+AggregateQueryMaker::slotNewAlbumsReady( const Meta::AlbumList &albums )
{
foreach( const Meta::AlbumPtr &album, albums )
{
m_albums.insert( KSharedPtr<Meta::AggregateAlbum>( m_collection->getAlbum( album ) ) );
}
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::GenreList &genres )
+AggregateQueryMaker::slotNewGenresReady( const Meta::GenreList &genres )
{
foreach( const Meta::GenrePtr &genre, genres )
{
m_genres.insert( KSharedPtr<Meta::AggregateGenre>( m_collection->getGenre( genre ) ) );
}
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::ComposerList &composers )
+AggregateQueryMaker::slotNewComposersReady( const Meta::ComposerList &composers )
{
foreach( const Meta::ComposerPtr &composer, composers )
{
m_composers.insert( KSharedPtr<Meta::AggregateComposer>( m_collection->getComposer( composer ) ) );
}
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::YearList &years )
+AggregateQueryMaker::slotNewYearsReady( const Meta::YearList &years )
{
foreach( const Meta::YearPtr &year, years )
{
m_years.insert( KSharedPtr<Meta::AggreagateYear>( m_collection->getYear( year ) ) );
}
}
void
-AggregateQueryMaker::slotNewResultReady( const Meta::LabelList &labels )
+AggregateQueryMaker::slotNewLabelsReady( const Meta::LabelList &labels )
{
foreach( const Meta::LabelPtr &label, labels )
{
m_labels.insert( KSharedPtr<Meta::AggregateLabel>( m_collection->getLabel( label ) ) );
}
}
diff --git a/src/core-impl/collections/aggregate/AggregateQueryMaker.h b/src/core-impl/collections/aggregate/AggregateQueryMaker.h
index 5c2089cb6f..53fb13cc3c 100644
--- a/src/core-impl/collections/aggregate/AggregateQueryMaker.h
+++ b/src/core-impl/collections/aggregate/AggregateQueryMaker.h
@@ -1,120 +1,117 @@
/*
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef AGGREGATEQUERYMAKER_H
#define AGGREGATEQUERYMAKER_H
#include "core/collections/QueryMaker.h"
#include "core/collections/Collection.h"
#include "core-impl/collections/aggregate/AggregateMeta.h"
#include <QList>
#include <QMutex>
#include <QSet>
#include <KSharedPtr>
class CustomReturnFunction;
class CustomReturnValue;
namespace Collections
{
class AMAROK_EXPORT AggregateQueryMaker : public QueryMaker
{
Q_OBJECT
public:
AggregateQueryMaker( Collections::AggregateCollection *collection, const QList<QueryMaker*> &queryMakers );
~AggregateQueryMaker();
virtual void run();
virtual void abortQuery();
virtual QueryMaker* setQueryType( QueryType type );
virtual QueryMaker* addReturnValue( qint64 value);
virtual QueryMaker* addReturnFunction( ReturnFunction function, qint64 value );
virtual QueryMaker* orderBy( qint64 value, bool descending = false );
virtual QueryMaker* addMatch( const Meta::TrackPtr &track );
virtual QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists );
virtual QueryMaker* addMatch( const Meta::AlbumPtr &album );
virtual QueryMaker* addMatch( const Meta::ComposerPtr &composer );
virtual QueryMaker* addMatch( const Meta::GenrePtr &genre );
virtual QueryMaker* addMatch( const Meta::YearPtr &year );
virtual QueryMaker* addMatch( const Meta::LabelPtr &label );
virtual QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd );
virtual QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd );
virtual QueryMaker* addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare );
virtual QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare );
virtual QueryMaker* limitMaxResultSize( int size );
virtual QueryMaker* beginAnd();
virtual QueryMaker* beginOr();
virtual QueryMaker* endAndOr();
virtual QueryMaker* setAlbumQueryMode( AlbumQueryMode mode );
virtual QueryMaker* setLabelQueryMode( LabelQueryMode mode );
private:
- template <class PointerType>
- void emitProperResult( const QList<PointerType> &list );
-
void handleResult();
private Q_SLOTS:
void slotQueryDone();
- void slotNewResultReady( const Meta::TrackList &tracks );
- void slotNewResultReady( const Meta::ArtistList &artists );
- void slotNewResultReady( const Meta::AlbumList &albums );
- void slotNewResultReady( const Meta::GenreList &genres );
- void slotNewResultReady( const Meta::ComposerList &composers );
- void slotNewResultReady( const Meta::YearList &years );
- void slotNewResultReady( const Meta::LabelList &labels );
+ void slotNewTracksReady( const Meta::TrackList &tracks );
+ void slotNewArtistsReady( const Meta::ArtistList &artists );
+ void slotNewAlbumsReady( const Meta::AlbumList &albums );
+ void slotNewGenresReady( const Meta::GenreList &genres );
+ void slotNewComposersReady( const Meta::ComposerList &composers );
+ void slotNewYearsReady( const Meta::YearList &years );
+ void slotNewLabelsReady( const Meta::LabelList &labels );
private:
AggregateCollection *m_collection;
QList<QueryMaker*> m_builders;
int m_queryDoneCount;
bool m_returnDataPointers;
int m_maxResultSize;
QueryType m_queryType;
bool m_orderDescending;
qint64 m_orderField;
bool m_orderByNumberField;
QMutex m_queryDoneCountMutex;
// store AggregateCollection meta stuff using KSharedPtr,
// otherwise AggregateCollection might delete it (as soon as it gets garbage collection)
QSet<KSharedPtr<Meta::AggregateTrack> > m_tracks;
QSet<KSharedPtr<Meta::AggregateArtist> > m_artists;
QSet<KSharedPtr<Meta::AggregateAlbum> > m_albums;
QSet<KSharedPtr<Meta::AggregateGenre> > m_genres;
QSet<KSharedPtr<Meta::AggregateComposer> > m_composers;
QSet<KSharedPtr<Meta::AggreagateYear> > m_years;
QSet<KSharedPtr<Meta::AggregateLabel> > m_labels;
QList<CustomReturnFunction*> m_returnFunctions;
QList<CustomReturnValue*> m_returnValues;
};
}
#endif /* AGGREGATEQUERYMAKER_H */
diff --git a/src/core-impl/collections/audiocd/AudioCdCollection.cpp b/src/core-impl/collections/audiocd/AudioCdCollection.cpp
index a8c8ac2b93..c69674ce72 100644
--- a/src/core-impl/collections/audiocd/AudioCdCollection.cpp
+++ b/src/core-impl/collections/audiocd/AudioCdCollection.cpp
@@ -1,636 +1,635 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Seb Ruiz <ruiz@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AudioCdCollection"
#include "AudioCdCollection.h"
#include "MainWindow.h"
#include "amarokconfig.h"
#include "AudioCdCollectionLocation.h"
#include "AudioCdMeta.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/collections/support/MemoryQueryMaker.h"
#include "covermanager/CoverFetcher.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "MediaDeviceMonitor.h"
#include "MemoryQueryMaker.h"
#include "SvgHandler.h"
#include "handler/AudioCdHandler.h"
#include "support/AudioCdConnectionAssistant.h"
#include "support/AudioCdDeviceInfo.h"
#include <KIO/Job>
#include <KIO/NetAccess>
#include <KIO/UDSEntry>
#include <solid/device.h>
#include <solid/opticaldrive.h>
#include <KConfigGroup>
#include <KEncodingProber>
-
+#include <KPluginFactory>
#include <KSharedConfig>
#include <KUrl>
#include <QDir>
#include <QTextCodec>
using namespace Collections;
AMAROK_EXPORT_COLLECTION( AudioCdCollectionFactory, audiocdcollection )
static const QString unknownCddbId( "unknown" );
AudioCdCollectionFactory::AudioCdCollectionFactory( QObject *parent, const QVariantList &args )
: MediaDeviceCollectionFactory<AudioCdCollection>( parent, args, new AudioCdConnectionAssistant() )
{
m_info = KPluginInfo( "amarok_collection-audiocdcollection.desktop" );
}
AudioCdCollection::AudioCdCollection( MediaDeviceInfo* info )
: MediaDeviceCollection()
, m_encodingFormat( OGG )
{
DEBUG_BLOCK
// so that `amarok --cdplay` works:
- connect( this, SIGNAL(collectionReady(Collections::Collection*)),
- SLOT(checkForStartPlayRequest()) );
+ connect( this, &AudioCdCollection::collectionReady,
+ this, &AudioCdCollection::checkForStartPlayRequest );
debug() << "Getting Audio CD info";
AudioCdDeviceInfo *cdInfo = qobject_cast<AudioCdDeviceInfo *>( info );
m_udi = cdInfo->udi();
m_device = cdInfo->device();
readAudioCdSettings();
m_handler = new Meta::AudioCdHandler( this );
}
AudioCdCollection::~AudioCdCollection()
{
}
QUrl
AudioCdCollection::audiocdUrl( const QString &path ) const
{
QUrl url("audiocd:/");
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + '/' + ( path ));
if( !m_device.isEmpty() )
url.addQueryItem( "device", m_device );
return url;
}
void
AudioCdCollection::readCd()
{
DEBUG_BLOCK
//get the CDDB info file if possible.
KIO::ListJob *listJob = KIO::listRecursive( audiocdUrl(), KIO::HideProgressInfo, false );
- connect( listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
- this, SLOT(audioCdEntries(KIO::Job*,KIO::UDSEntryList)) );
- connect( listJob, SIGNAL(result(KJob*)), SLOT(slotEntriesJobDone(KJob*)) );
+ connect( listJob, &KIO::ListJob::entries, this, &AudioCdCollection::audioCdEntries );
+ connect( listJob, &KIO::ListJob::result, this, &AudioCdCollection::slotEntriesJobDone );
}
void
AudioCdCollection::audioCdEntries( KIO::Job *job, const KIO::UDSEntryList &list )
{
DEBUG_BLOCK
Q_UNUSED( job )
for( KIO::UDSEntryList::ConstIterator it = list.begin(); it != list.end(); ++it )
{
const KIO::UDSEntry &entry = *it;
QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
if( name.endsWith( QLatin1String(".txt") ) )
m_cddbTextFiles.insert( entry.numberValue( KIO::UDSEntry::UDS_SIZE ), audiocdUrl( name ) );
}
}
void
AudioCdCollection::slotEntriesJobDone( KJob *job )
{
DEBUG_BLOCK
if( job->error() )
warning() << __PRETTY_FUNCTION__ << job->errorString() << job->errorText();
if( m_cddbTextFiles.isEmpty() )
{
warning() << __PRETTY_FUNCTION__ << "haven't found .txt file under audiocd:/, but continuing";
noInfoAvailable();
return;
}
int biggestTextFile = m_cddbTextFiles.keys().last();
QUrl url = m_cddbTextFiles.value( biggestTextFile );
m_cddbTextFiles.clear(); // save memory
KIO::StoredTransferJob *tjob = KIO::storedGet( url, KIO::NoReload, KIO::HideProgressInfo );
- connect( tjob, SIGNAL(result(KJob*)), SLOT(infoFetchComplete(KJob*)) );
+ connect( tjob, &KIO::StoredTransferJob::result, this, &AudioCdCollection::infoFetchComplete );
}
void
AudioCdCollection::infoFetchComplete( KJob *job )
{
DEBUG_BLOCK
if( job->error() )
{
error() << job->error() << job->errorString() << job->errorText();
job->deleteLater();
noInfoAvailable();
return;
}
KIO::StoredTransferJob *tjob = static_cast<KIO::StoredTransferJob*>( job );
QString cddbInfo = tjob->data();
KEncodingProber prober;
KEncodingProber::ProberState result = prober.feed( tjob->data() );
if( result != KEncodingProber::NotMe )
cddbInfo = QTextCodec::codecForName( prober.encoding() )->toUnicode( tjob->data() );
debug() << "Encoding: " << prober.encoding();
debug() << "got cddb info: " << cddbInfo;
if (cddbInfo.length() == 0) {
job->deleteLater();
noInfoAvailable();
return;
}
int startIndex;
int endIndex;
QString artist;
QString album;
QString year;
QString genre;
startIndex = cddbInfo.indexOf( "DTITLE=", 0 );
if ( startIndex != -1 )
{
startIndex += 7;
endIndex = cddbInfo.indexOf( "\n", startIndex );
QString compoundTitle = cddbInfo.mid( startIndex, endIndex - startIndex );
debug() << "compoundTitle: " << compoundTitle;
QStringList compoundTitleList = compoundTitle.split( " / " );
artist = compoundTitleList.at( 0 );
album = compoundTitleList.at( 1 );
}
Meta::AudioCdArtistPtr artistPtr = Meta::AudioCdArtistPtr( new Meta::AudioCdArtist( artist ) );
memoryCollection()->addArtist( Meta::ArtistPtr::staticCast( artistPtr ) );
Meta::AudioCdComposerPtr composerPtr = Meta::AudioCdComposerPtr( new Meta::AudioCdComposer( QString() ) );
memoryCollection()->addComposer( Meta::ComposerPtr::staticCast( composerPtr ) );
Meta::AudioCdAlbumPtr albumPtr = Meta::AudioCdAlbumPtr( new Meta::AudioCdAlbum( album ) );
albumPtr->setAlbumArtist( artistPtr );
memoryCollection()->addAlbum( Meta::AlbumPtr::staticCast( albumPtr ) );
startIndex = cddbInfo.indexOf( "DYEAR=", 0 );
if ( startIndex != -1 )
{
startIndex += 6;
endIndex = cddbInfo.indexOf( "\n", startIndex );
year = cddbInfo.mid( startIndex, endIndex - startIndex );
}
Meta::AudioCdYearPtr yearPtr = Meta::AudioCdYearPtr( new Meta::AudioCdYear( year ) );
memoryCollection()->addYear( Meta::YearPtr::staticCast( yearPtr ) );
startIndex = cddbInfo.indexOf( "DGENRE=", 0 );
if ( startIndex != -1 )
{
startIndex += 7;
endIndex = cddbInfo.indexOf( "\n", startIndex );
genre = cddbInfo.mid( startIndex, endIndex - startIndex );
}
Meta::AudioCdGenrePtr genrePtr = Meta::AudioCdGenrePtr( new Meta::AudioCdGenre( genre ) );
memoryCollection()->addGenre( Meta::GenrePtr::staticCast( genrePtr ) );
m_discCddbId = unknownCddbId;
startIndex = cddbInfo.indexOf( "DISCID=", 0 );
if ( startIndex != -1 )
{
startIndex += 7;
endIndex = cddbInfo.indexOf( "\n", startIndex );
m_discCddbId = cddbInfo.mid( startIndex, endIndex - startIndex );
}
//MediaDeviceMonitor::instance()->setCurrentCdId( m_discCddbId );
//get the list of tracknames
startIndex = cddbInfo.indexOf( "TTITLE0=", 0 );
if ( startIndex != -1 )
{
endIndex = cddbInfo.indexOf( "\nEXTD=", startIndex );
QString tracksBlock = cddbInfo.mid( startIndex, endIndex - startIndex );
debug() << "Tracks block: " << tracksBlock;
QStringList tracksBlockList = tracksBlock.split( '\n' );
int numberOfTracks = tracksBlockList.count();
for ( int i = 0; i < numberOfTracks; i++ )
{
QString prefix = "TTITLE" + QString::number( i ) + '=';
debug() << "prefix: " << prefix;
QString trackName = tracksBlockList.at( i );
trackName = trackName.remove( prefix );
QString trackArtist;
//check if a track artist is included in the track name:
if ( trackName.contains( " / " ) )
{
QStringList trackArtistList = trackName.split( " / " );
trackName = trackArtistList.at( 1 );
trackArtist = trackArtistList.at( 0 );
}
debug() << "Track name: " << trackName;
QString padding = (i + 1) < 10 ? "0" : QString();
QString baseFileName = m_fileNamePattern;
debug() << "Track Base File Name (before): " << baseFileName;
baseFileName.replace( "%{title}", trackName, Qt::CaseInsensitive );
baseFileName.replace( "%{number}", padding + QString::number( i + 1 ), Qt::CaseInsensitive );
baseFileName.replace( "%{albumtitle}", album, Qt::CaseInsensitive );
baseFileName.replace( "%{trackartist}", trackArtist, Qt::CaseInsensitive );
baseFileName.replace( "%{albumartist}", artist, Qt::CaseInsensitive );
baseFileName.replace( "%{year}", year, Qt::CaseInsensitive );
baseFileName.replace( "%{genre}", genre, Qt::CaseInsensitive );
//we hack the url so the engine controller knows what track on the CD to play..
QUrl baseUrl = audiocdUrl( m_discCddbId + '/' + QString::number( i + 1 ) );
debug() << "Track Base File Name (after): " << baseFileName;
debug() << "Track url: " << baseUrl;
Meta::AudioCdTrackPtr trackPtr = Meta::AudioCdTrackPtr( new Meta::AudioCdTrack( this, trackName, baseUrl ) );
trackPtr->setTrackNumber( i + 1 );
trackPtr->setFileNameBase( baseFileName );
trackPtr->setLength( trackLength( i + 1 ) );
memoryCollection()->addTrack( Meta::TrackPtr::staticCast( trackPtr ) );
artistPtr->addTrack( trackPtr );
if ( trackArtist.isEmpty() )
trackPtr->setArtist( artistPtr );
else
{
albumPtr->setCompilation( true );
Meta::AudioCdArtistPtr trackArtistPtr = Meta::AudioCdArtistPtr( new Meta::AudioCdArtist( trackArtist ) );
trackArtistPtr->addTrack( trackPtr );
trackPtr->setArtist( trackArtistPtr );
}
composerPtr->addTrack( trackPtr );
trackPtr->setComposer( composerPtr );
albumPtr->addTrack( trackPtr );
trackPtr->setAlbum( albumPtr );
genrePtr->addTrack( trackPtr );
trackPtr->setGenre( genrePtr );
yearPtr->addTrack( trackPtr );
trackPtr->setYear( yearPtr );
}
}
//lets see if we can find a cover for the album:
if( AmarokConfig::autoGetCoverArt() )
The::coverFetcher()->queueAlbum( Meta::AlbumPtr::staticCast( albumPtr ) );
updateProxyTracks();
emit collectionReady( this );
}
void
AudioCdCollection::checkForStartPlayRequest()
{
//be nice and check if MainWindow is just aching for an audio cd to start playing
if( The::mainWindow()->isWaitingForCd() )
{
debug() << "Tell MainWindow to start playing us immediately.";
The::mainWindow()->playAudioCd();
}
}
QString
AudioCdCollection::trackBaseFileName( int i ) const
{
return QString( "Track%1" ).arg( i, 2, 10, QChar('0') );
}
QString
AudioCdCollection::trackWavFileName( int i ) const
{
return trackBaseFileName( i ) + ".wav";
}
QString
AudioCdCollection::trackDisplayName( int i ) const
{
return i18n( "Track" ) + ' ' + QString::number( i );
}
qint64
AudioCdCollection::trackLength( int i ) const
{
QUrl kioUrl = audiocdUrl( trackWavFileName( i ) );
KIO::UDSEntry uds;
if ( KIO::NetAccess::stat(kioUrl, uds, NULL) )
{
qint64 samples = (uds.numberValue(KIO::UDSEntry::UDS_SIZE, 44) - 44) / 4;
return (samples - 44) * 10 / 441;
}
return 0;
}
QString
AudioCdCollection::collectionId() const
{
return QLatin1String( "AudioCd" );
}
QString
AudioCdCollection::prettyName() const
{
return i18n( "Audio CD" );
}
QIcon
AudioCdCollection::icon() const
{
return QIcon::fromTheme( "media-optical-audio" );
}
void
AudioCdCollection::cdRemoved()
{
emit remove();
}
QString
AudioCdCollection::encodingFormat() const
{
switch( m_encodingFormat )
{
case WAV:
return "wav";
case FLAC:
return "flac";
case OGG:
return "ogg";
case MP3:
return "mp3";
}
return QString();
}
QString
AudioCdCollection::copyableFilePath( const QString &fileName ) const
{
switch( m_encodingFormat )
{
case WAV:
return audiocdUrl( fileName ).url();
case FLAC:
return audiocdUrl( "FLAC/" + fileName ).url();
case OGG:
return audiocdUrl( "Ogg Vorbis/" + fileName ).url();
case MP3:
return audiocdUrl( "MP3/" + fileName ).url();
}
return QString();
}
void
AudioCdCollection::setEncodingFormat( int format ) const
{
m_encodingFormat = format;
}
CollectionLocation *
AudioCdCollection::location()
{
return new AudioCdCollectionLocation( this );
}
void
AudioCdCollection::eject()
{
DEBUG_BLOCK
//we need to do a quick check if we are currently playing from this cd, if so, stop playback and then eject
Meta::TrackPtr track = The::engineController()->currentTrack();
if ( track )
{
if( track->playableUrl().url().startsWith( "audiocd:/" ) )
The::engineController()->stop();
}
Solid::Device device = Solid::Device( m_udi );
Solid::OpticalDrive *drive = device.parent().as<Solid::OpticalDrive>();
if( drive )
drive->eject();
else
debug() << "disc has no drive";
}
void
AudioCdCollection::noInfoAvailable()
{
DEBUG_BLOCK
m_discCddbId = unknownCddbId;
//MediaDeviceMonitor::instance()->setCurrentCdId( m_discCddbId );
QString artist = i18n( "Unknown" );
QString album = i18n( "Unknown" );
QString year = i18n( "Unknown" );
QString genre = i18n( "Unknown" );
Meta::AudioCdArtistPtr artistPtr = Meta::AudioCdArtistPtr( new Meta::AudioCdArtist( artist ) );
memoryCollection()->addArtist( Meta::ArtistPtr::staticCast( artistPtr ) );
Meta::AudioCdComposerPtr composerPtr = Meta::AudioCdComposerPtr( new Meta::AudioCdComposer( QString() ) );
memoryCollection()->addComposer( Meta::ComposerPtr::staticCast( composerPtr ) );
Meta::AudioCdAlbumPtr albumPtr = Meta::AudioCdAlbumPtr( new Meta::AudioCdAlbum( album ) );
albumPtr->setAlbumArtist( artistPtr );
memoryCollection()->addAlbum( Meta::AlbumPtr::staticCast( albumPtr ) );
Meta::AudioCdYearPtr yearPtr = Meta::AudioCdYearPtr( new Meta::AudioCdYear( year ) );
memoryCollection()->addYear( Meta::YearPtr::staticCast( yearPtr ) );
Meta::AudioCdGenrePtr genrePtr = Meta::AudioCdGenrePtr( new Meta::AudioCdGenre( genre ) );
memoryCollection()->addGenre( Meta::GenrePtr::staticCast( genrePtr ) );
int i = 1;
QString trackWav = trackWavFileName( i );
// This will find also data tracks on mixed CDs:
// a better way to discover the available audio tracks should be found
while( KIO::NetAccess::exists( audiocdUrl( trackWav ), KIO::NetAccess::SourceSide, 0 ) )
{
debug() << "got track url: " << audiocdUrl( trackWav );
//we hack the url so the engine controller knows what track on the CD to play..
QUrl baseUrl = audiocdUrl( m_discCddbId + '/' + QString::number( i ) );
Meta::AudioCdTrackPtr trackPtr = Meta::AudioCdTrackPtr( new Meta::AudioCdTrack( this, trackDisplayName( i ), baseUrl ) );
trackPtr->setTrackNumber( i );
trackPtr->setFileNameBase( trackBaseFileName( i ) );
trackPtr->setLength( trackLength( i ) );
memoryCollection()->addTrack( Meta::TrackPtr::staticCast( trackPtr ) );
artistPtr->addTrack( trackPtr );
trackPtr->setArtist( artistPtr );
composerPtr->addTrack( trackPtr );
trackPtr->setComposer( composerPtr );
albumPtr->addTrack( trackPtr );
trackPtr->setAlbum( albumPtr );
genrePtr->addTrack( trackPtr );
trackPtr->setGenre( genrePtr );
yearPtr->addTrack( trackPtr );
trackPtr->setYear( yearPtr );
i++;
trackWav = trackWavFileName( i );
}
updateProxyTracks();
emit collectionReady( this );
}
void
AudioCdCollection::readAudioCdSettings()
{
KSharedConfigPtr conf = KSharedConfig::openConfig( "kcmaudiocdrc" );
KConfigGroup filenameConf = conf->group( "FileName" );
m_fileNamePattern = filenameConf.readEntry( "file_name_template", "%{trackartist} - %{number} - %{title}" );
m_albumNamePattern = filenameConf.readEntry( "album_name_template", "%{albumartist} - %{albumtitle}" );
}
bool
AudioCdCollection::possiblyContainsTrack( const QUrl &url ) const
{
return url.scheme() == "audiocd";
}
Meta::TrackPtr
AudioCdCollection::trackForUrl( const QUrl &url )
{
QReadLocker locker( memoryCollection()->mapLock() );
if( memoryCollection()->trackMap().contains( url.url() ) )
return memoryCollection()->trackMap().value( url.url() );
QRegExp trackUrlScheme( "^audiocd:/([a-zA-Z0-9]*)/([0-9]{1,})" );
if( trackUrlScheme.indexIn( url.url() ) != 0 )
{
warning() << __PRETTY_FUNCTION__ << url.url() << "doesn't have correct scheme" << trackUrlScheme;
return Meta::TrackPtr();
}
const QString trackCddbId = trackUrlScheme.capturedTexts().value( 1 );
const int trackNumber = trackUrlScheme.capturedTexts().value( 2 ).toInt();
if( !trackCddbId.isEmpty() && trackCddbId != unknownCddbId &&
!m_discCddbId.isEmpty() && m_discCddbId != unknownCddbId &&
trackCddbId != m_discCddbId )
{
warning() << __PRETTY_FUNCTION__ << "track with cddbId" << trackCddbId
<< "doesn't match our cddbId" << m_discCddbId;
return Meta::TrackPtr();
}
foreach( const Meta::TrackPtr &track, memoryCollection()->trackMap() )
{
if( track->trackNumber() == trackNumber )
return track;
}
warning() << __PRETTY_FUNCTION__ << "track with number" << trackNumber << "not found";
return Meta::TrackPtr();
}
void
AudioCdCollection::updateProxyTracks()
{
foreach( const QUrl &url, m_proxyMap.keys() )
{
QString urlString = url.url().remove( "audiocd:/" );
const QStringList &parts = urlString.split( '/' );
if( parts.count() != 2 )
continue;
const QString &discId = parts.at( 0 );
if( discId != m_discCddbId )
continue;
const int trackNumber = parts.at( 1 ).toInt();
foreach( const Meta::TrackPtr &track, memoryCollection()->trackMap().values() )
{
if( track->trackNumber() == trackNumber )
{
m_proxyMap.value( url )->updateTrack( track );
}
}
}
m_proxyMap.clear();
}
void AudioCdCollection::startFullScan()
{
DEBUG_BLOCK
readCd();
}
diff --git a/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp b/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp
index 59d14ca174..fbcc03b3c8 100644
--- a/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp
+++ b/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp
@@ -1,89 +1,89 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AudioCdCollectionLocation.h"
#include "AudioCdMeta.h"
#include "core/support/Debug.h"
#include "FormatSelectionDialog.h"
using namespace Collections;
AudioCdCollectionLocation::AudioCdCollectionLocation( AudioCdCollection *parentCollection )
: CollectionLocation( parentCollection )
, m_collection( parentCollection )
{
}
AudioCdCollectionLocation::~AudioCdCollectionLocation()
{
}
void AudioCdCollectionLocation::getKIOCopyableUrls( const Meta::TrackList & tracks )
{
DEBUG_BLOCK
QMap<Meta::TrackPtr, QUrl> resultMap;
foreach( Meta::TrackPtr trackPtr, tracks )
{
Meta::AudioCdTrackPtr cdTrack = Meta::AudioCdTrackPtr::staticCast( trackPtr );
const QString path = m_collection->copyableFilePath( cdTrack->fileNameBase() + '.' + m_collection->encodingFormat() );
resultMap.insert( trackPtr, QUrl( path ) );
}
slotGetKIOCopyableUrlsDone( resultMap );
}
void AudioCdCollectionLocation::showSourceDialog( const Meta::TrackList &tracks, bool removeSources )
{
DEBUG_BLOCK
Q_UNUSED( tracks )
Q_UNUSED( removeSources )
FormatSelectionDialog * dlg = new FormatSelectionDialog();
- connect( dlg, SIGNAL(formatSelected(int)), this, SLOT(onFormatSelected(int)) );
- connect( dlg, SIGNAL(rejected()), this, SLOT(onCancel()) );
+ connect( dlg, &FormatSelectionDialog::formatSelected, this, &AudioCdCollectionLocation::onFormatSelected );
+ connect( dlg, &FormatSelectionDialog::rejected, this, &AudioCdCollectionLocation::onCancel );
dlg->show();
}
void AudioCdCollectionLocation::formatSelected( int format )
{
Q_UNUSED( format )
}
void AudioCdCollectionLocation::formatSelectionCancelled()
{
}
void AudioCdCollectionLocation::onFormatSelected( int format )
{
DEBUG_BLOCK
m_collection->setEncodingFormat( format );
slotShowSourceDialogDone();
}
void AudioCdCollectionLocation::onCancel()
{
DEBUG_BLOCK
abort();
}
diff --git a/src/core-impl/collections/audiocd/FormatSelectionDialog.cpp b/src/core-impl/collections/audiocd/FormatSelectionDialog.cpp
index ffa4a869a4..cbce8e0109 100644
--- a/src/core-impl/collections/audiocd/FormatSelectionDialog.cpp
+++ b/src/core-impl/collections/audiocd/FormatSelectionDialog.cpp
@@ -1,120 +1,120 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "FormatSelectionDialog.h"
#include "AudioCdCollection.h"
#include <KCMultiDialog>
FormatSelectionDialog::FormatSelectionDialog( QWidget *parent )
: QDialog( parent )
{
setupUi( this );
- connect( oggButton, SIGNAL(toggled(bool)), this, SLOT(selectionChanged(bool)) );
- connect( flacButton, SIGNAL(toggled(bool)), this, SLOT(selectionChanged(bool)) );
- connect( wavButton, SIGNAL(toggled(bool)), this, SLOT(selectionChanged(bool)) );
- connect( mp3Button, SIGNAL(toggled(bool)), this, SLOT(selectionChanged(bool)) );
+ connect( oggButton, &QRadioButton::toggled, this, &FormatSelectionDialog::selectionChanged );
+ connect( flacButton, &QRadioButton::toggled, this, &FormatSelectionDialog::selectionChanged );
+ connect( wavButton, &QRadioButton::toggled, this, &FormatSelectionDialog::selectionChanged );
+ connect( mp3Button, &QRadioButton::toggled, this, &FormatSelectionDialog::selectionChanged );
- connect( advancedButton, SIGNAL(clicked(bool)), this, SLOT(showAdvancedSettings()) );
+ connect( advancedButton, &QRadioButton::clicked, this, &FormatSelectionDialog::showAdvancedSettings );
//restore format from last time, if any.
KConfigGroup config = Amarok::config("Audio CD Collection");
QString format = config.readEntry( "Import Format", "ogg" );
if ( format.compare( "ogg", Qt::CaseInsensitive ) == 0 )
oggButton->setChecked( true );
else if ( format.compare( "flac", Qt::CaseInsensitive ) == 0 )
flacButton->setChecked( true );
else if ( format.compare( "wav", Qt::CaseInsensitive ) == 0 )
wavButton->setChecked( true );
else if ( format.compare( "mp3", Qt::CaseInsensitive ) == 0 )
mp3Button->setChecked( true );
}
FormatSelectionDialog::~FormatSelectionDialog()
{
}
void FormatSelectionDialog::selectionChanged( bool checked )
{
if ( !checked )
return;
if( sender() == oggButton )
{
descriptionLabel->setText( i18n( "Ogg Vorbis is a fully free and unencumbered compressed audio format that is perfect for storing your compressed music on your computer. The sound quality is slightly better than MP3 at the same bitrate. Note that not all mobile players support the Ogg Vorbis format." ) );
m_selectedFormat = Collections::AudioCdCollection::OGG;
}
else if( sender() == flacButton )
{
descriptionLabel->setText( i18n( "FLAC is a lossless compressed audio format free of any patents or license fees. It maintains perfect CD audio quality while reducing file size by about 50%. Because the filesize is much larger than Ogg Vorbis or MP3 it is not recommended if you want to transfer your music to a mobile player." ) );
m_selectedFormat = Collections::AudioCdCollection::FLAC;
}
else if( sender() == wavButton )
{
descriptionLabel->setText( i18n( "WAV is a basic, uncompressed audio file format. It takes up a lot of space but maintains perfect quality. It is generally not recommended unless you know what you are doing. If you want perfect quality, use FLAC instead." ) );
m_selectedFormat = Collections::AudioCdCollection::WAV;
}
else if( sender() == mp3Button )
{
descriptionLabel->setText( i18n( "MP3 is the de facto standard in compressed audio compatible with almost all mobile players. It is however non free and generally not recommended." ) );
m_selectedFormat = Collections::AudioCdCollection::MP3;
}
}
void FormatSelectionDialog::accept()
{
//store to config for next download:
QString format;
if( m_selectedFormat == Collections::AudioCdCollection::OGG )
format = "ogg";
else if( m_selectedFormat == Collections::AudioCdCollection::FLAC )
format = "flac";
else if( m_selectedFormat == Collections::AudioCdCollection::WAV )
format = "wav";
else if( m_selectedFormat == Collections::AudioCdCollection::MP3 )
format = "mp3";
KConfigGroup config = Amarok::config("Audio CD Collection");
config.writeEntry( "Import Format", format );
emit formatSelected( m_selectedFormat );
QDialog::accept();
}
void FormatSelectionDialog::showAdvancedSettings()
{
KCMultiDialog KCM;
KCM.setWindowTitle( i18n( "Audio CD settings - Amarok" ) );
KCM.addModule( "audiocd" );
KCM.exec();
}
diff --git a/src/core-impl/collections/daap/DaapCollection.cpp b/src/core-impl/collections/daap/DaapCollection.cpp
index caf8285a00..f901b6f757 100644
--- a/src/core-impl/collections/daap/DaapCollection.cpp
+++ b/src/core-impl/collections/daap/DaapCollection.cpp
@@ -1,317 +1,318 @@
/****************************************************************************************
* Copyright (c) 2006 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2006 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "DaapCollection"
#include "DaapCollection.h"
#include "amarokconfig.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "DaapMeta.h"
#include "MemoryQueryMaker.h"
#include "Reader.h"
#include <QStringList>
#include <QTimer>
-#include <KLocale>
+#include <KLocalizedString>
+#include <KPluginFactory>
#include <DNSSD/RemoteService>
#include <DNSSD/ServiceBase>
#include <DNSSD/ServiceBrowser>
using namespace Collections;
AMAROK_EXPORT_COLLECTION( DaapCollectionFactory, daapcollection )
DaapCollectionFactory::DaapCollectionFactory( QObject *parent, const QVariantList &args )
: Collections::CollectionFactory( parent, args )
, m_browser( 0 )
{
m_info = KPluginInfo( "amarok_collection-daapcollection.desktop" );
}
DaapCollectionFactory::~DaapCollectionFactory()
{
delete m_browser;
}
void
DaapCollectionFactory::init()
{
DEBUG_BLOCK
switch( KDNSSD::ServiceBrowser::isAvailable() )
{
case KDNSSD::ServiceBrowser::Working:
//don't block Amarok's startup by connecting to DAAP servers
QTimer::singleShot( 1000, this, SLOT(connectToManualServers()) );
m_browser = new KDNSSD::ServiceBrowser("_daap._tcp");
m_browser->setObjectName("daapServiceBrowser");
connect( m_browser, SIGNAL(serviceAdded(KDNSSD::RemoteService::Ptr)),
this, SLOT(foundDaap(KDNSSD::RemoteService::Ptr)) );
connect( m_browser, SIGNAL(serviceRemoved(KDNSSD::RemoteService::Ptr)),
this, SLOT(serverOffline(KDNSSD::RemoteService::Ptr)) );
m_browser->startBrowse();
break;
case KDNSSD::ServiceBrowser::Stopped:
debug() << "The Zeroconf daemon is not running";
break;
case KDNSSD::ServiceBrowser::Unsupported:
debug() << "Zeroconf support is not available";
break;
default:
debug() << "Unknown error with Zeroconf";
}
m_initialized = true;
}
void
DaapCollectionFactory::connectToManualServers()
{
DEBUG_BLOCK
QStringList sl = AmarokConfig::manuallyAddedServers();
foreach( const QString &server, sl )
{
debug() << "Adding server " << server;
QStringList current = server.split( ':', QString::KeepEmptyParts );
//handle invalid urls gracefully
if( current.count() < 2 )
continue;
QString host = current.first();
quint16 port = current.last().toUShort();
Amarok::Components::logger()->longMessage(
i18n( "Loading remote collection from host %1", host),
Amarok::Logger::Information );
int lookup_id = QHostInfo::lookupHost( host, this, SLOT(resolvedManualServerIp(QHostInfo)));
m_lookupHash.insert( lookup_id, port );
}
}
void
DaapCollectionFactory::serverOffline( KDNSSD::RemoteService::Ptr service )
{
DEBUG_BLOCK
QString key = serverKey( service.data()->hostName(), service.data()->port() );
if( m_collectionMap.contains( key ) )
{
QWeakPointer<DaapCollection> coll = m_collectionMap[ key ];
if( coll )
coll.data()->serverOffline(); //collection will be deleted by collectionmanager
else
warning() << "collection already null";
m_collectionMap.remove( key );
}
else
warning() << "removing non-existent service";
}
void
DaapCollectionFactory::foundDaap( KDNSSD::RemoteService::Ptr service )
{
DEBUG_BLOCK
connect( service.data(), SIGNAL(resolved(bool)), this, SLOT(resolvedDaap(bool)) );
service->resolveAsync();
}
void
DaapCollectionFactory::resolvedDaap( bool success )
{
const KDNSSD::RemoteService* service = dynamic_cast<const KDNSSD::RemoteService*>(sender());
if( !success || !service ) return;
debug() << service->serviceName() << ' ' << service->hostName() << ' ' << service->domain() << ' ' << service->type();
int lookup_id = QHostInfo::lookupHost( service->hostName(), this, SLOT(resolvedServiceIp(QHostInfo)));
m_lookupHash.insert( lookup_id, service->port() );
}
QString
DaapCollectionFactory::serverKey( const QString& host, quint16 port) const
{
return host + ':' + QString::number( port );
}
void
DaapCollectionFactory::slotCollectionReady()
{
DEBUG_BLOCK
DaapCollection *collection = dynamic_cast<DaapCollection*>( sender() );
if( collection )
{
disconnect( collection, SIGNAL(remove()), this, SLOT(slotCollectionDownloadFailed()) );
emit newCollection( collection );
}
}
void
DaapCollectionFactory::slotCollectionDownloadFailed()
{
DEBUG_BLOCK
DaapCollection *collection = qobject_cast<DaapCollection*>( sender() );
if( !collection )
return;
disconnect( collection, SIGNAL(collectionReady()), this, SLOT(slotCollectionReady()) );
foreach( const QWeakPointer< DaapCollection > &it, m_collectionMap )
{
if( it.data() == collection )
{
m_collectionMap.remove( m_collectionMap.key( it ) );
break;
}
}
collection->deleteLater();
}
void
DaapCollectionFactory::resolvedManualServerIp( QHostInfo hostInfo )
{
if ( !m_lookupHash.contains(hostInfo.lookupId()) )
return;
if ( hostInfo.addresses().isEmpty() )
return;
QString host = hostInfo.hostName();
QString ip = hostInfo.addresses().at(0).toString();
quint16 port = m_lookupHash.value( hostInfo.lookupId() );
//adding manual servers to the collectionMap doesn't make sense
DaapCollection *coll = new DaapCollection( host, ip, port );
connect( coll, SIGNAL(collectionReady()), SLOT(slotCollectionReady()) );
connect( coll, SIGNAL(remove()), SLOT(slotCollectionDownloadFailed()) );
}
void
DaapCollectionFactory::resolvedServiceIp( QHostInfo hostInfo )
{
DEBUG_BLOCK
// debug() << "got address:" << hostInfo.addresses() << "and lookup hash contains id" << hostInfo.lookupId() << "?" << m_lookupHash.contains(hostInfo.lookupId());
if ( !m_lookupHash.contains(hostInfo.lookupId()) )
return;
if ( hostInfo.addresses().isEmpty() )
return;
QString host = hostInfo.hostName();
QString ip = hostInfo.addresses().at(0).toString();
quint16 port = m_lookupHash.value( hostInfo.lookupId() );
// debug() << "already added server?" << m_collectionMap.contains(serverKey( host, port ));
if( m_collectionMap.contains(serverKey( host, port )) ) //same server from multiple interfaces
return;
// debug() << "creating daap collection with" << host << ip << port;
QWeakPointer<DaapCollection> coll( new DaapCollection( host, ip, port ) );
connect( coll.data(), SIGNAL(collectionReady()), SLOT(slotCollectionReady()) );
connect( coll.data(), SIGNAL(remove()), SLOT(slotCollectionDownloadFailed()) );
m_collectionMap.insert( serverKey( host, port ), coll.data() );
}
//DaapCollection
DaapCollection::DaapCollection( const QString &host, const QString &ip, quint16 port )
: Collection()
, m_host( host )
, m_port( port )
, m_ip( ip )
, m_reader( 0 )
, m_mc( new MemoryCollection() )
{
debug() << "Host: " << host << " port: " << port;
m_reader = new Daap::Reader( this, host, port, QString(), this, "DaapReader" );
connect( m_reader, SIGNAL(passwordRequired()), SLOT(passwordRequired()) );
connect( m_reader, SIGNAL(httpError(QString)), SLOT(httpError(QString)) );
m_reader->loginRequest();
}
DaapCollection::~DaapCollection()
{
}
QueryMaker*
DaapCollection::queryMaker()
{
return new MemoryQueryMaker( m_mc.toWeakRef(), collectionId() );
}
QString
DaapCollection::collectionId() const
{
return QString( "daap://" + m_ip + ':' ) + QString::number( m_port );
}
QString
DaapCollection::prettyName() const
{
QString host = m_host;
// No need to be overly verbose
if( host.endsWith( ".local" ) )
host = host.remove( QRegExp(".local$") );
return i18n("Music share at %1", host);
}
void
DaapCollection::passwordRequired()
{
//get password
QString password;
delete m_reader;
m_reader = new Daap::Reader( this, m_host, m_port, password, this, "DaapReader" );
connect( m_reader, SIGNAL(passwordRequired()), SLOT(passwordRequired()) );
connect( m_reader, SIGNAL(httpError(QString)), SLOT(httpError(QString)) );
m_reader->loginRequest();
}
void
DaapCollection::httpError( const QString &error )
{
DEBUG_BLOCK
debug() << "Http error in DaapReader: " << error;
emit remove();
}
void
DaapCollection::serverOffline()
{
emit remove();
}
void
DaapCollection::loadedDataFromServer()
{
DEBUG_BLOCK
emit collectionReady();
}
void
DaapCollection::parsingFailed()
{
DEBUG_BLOCK
emit remove();
}
diff --git a/src/core-impl/collections/db/DatabaseCollection.cpp b/src/core-impl/collections/db/DatabaseCollection.cpp
index 6c7bfa7e4c..edadef4a52 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 <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2010 Jeff Mitchell <mitchell@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "DatabaseCollection"
#include <KLocalizedString>
#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");
}
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, SIGNAL(deviceAdded(int)), this, SLOT(slotDeviceAdded(int)) );
- disconnect( mpm, SIGNAL(deviceRemoved(int)), this, SLOT(slotDeviceRemoved(int)) );
+ disconnect( mpm, &MountPointManager::deviceAdded, this, &DatabaseCollection::slotDeviceAdded );
+ disconnect( mpm, &MountPointManager::deviceRemoved, this, &DatabaseCollection::slotDeviceRemoved );
}
m_mpm = mpm;
- connect( mpm, SIGNAL(deviceAdded(int)), this, SLOT(slotDeviceAdded(int)) );
- connect( mpm, SIGNAL(deviceRemoved(int)), this, SLOT(slotDeviceRemoved(int)) );
+ 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();
emit updated();
}
}
void
DatabaseCollection::collectionUpdated()
{
QMutexLocker locker( &m_mutex );
if( m_blockUpdatedSignalCount == 0 )
{
m_updatedSignalRequested = false;
locker.unlock();
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<QUrl> 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<QUrl> urls;
foreach( const QString& path, m_collection->mountPointManager()->collectionFolders() )
urls.append( QUrl::fromLocalFile( path ) );
m_collection->scanManager()->requestScan( urls, GenericScanManager::UpdateScan );
}
else
{
QList<QUrl> 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/db/MountPointManager.cpp b/src/core-impl/collections/db/MountPointManager.cpp
index ee8a0a4e86..c7aacd95f3 100644
--- a/src/core-impl/collections/db/MountPointManager.cpp
+++ b/src/core-impl/collections/db/MountPointManager.cpp
@@ -1,421 +1,421 @@
/****************************************************************************************
* Copyright (c) 2006-2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MountPointManager"
#include "MountPointManager.h"
#include "MediaDeviceCache.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <core/storage/SqlStorage.h>
#include "core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h"
#include "core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h"
#include "core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h"
#include <KConfigGroup>
#include <Solid/Predicate>
#include <Solid/Device>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QList>
#include <QStringList>
#include <QTimer>
MountPointManager::MountPointManager( QObject *parent, SqlStorage *storage )
: QObject( parent )
, m_storage( storage )
, m_ready( false )
{
DEBUG_BLOCK
setObjectName( "MountPointManager" );
if ( !Amarok::config( "Collection" ).readEntry( "DynamicCollection", true ) )
{
debug() << "Dynamic Collection deactivated in amarokrc, not loading plugins, not connecting signals";
m_ready = true;
handleMusicLocation();
return;
}
- connect( MediaDeviceCache::instance(), SIGNAL(deviceAdded(QString)), SLOT(deviceAdded(QString)) );
- connect( MediaDeviceCache::instance(), SIGNAL(deviceRemoved(QString)), SLOT(deviceRemoved(QString)) );
+ connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceAdded, this, &MountPointManager::slotDeviceAdded );
+ connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceRemoved, this, &MountPointManager::slotDeviceRemoved );
createDeviceFactories();
}
void
MountPointManager::handleMusicLocation()
{
// For users who were using QDesktopServices::MusicLocation exclusively up
// to v2.2.2, which did not store the location into config.
// and also for versions up to 2.7-git that did write the Use MusicLocation entry
KConfigGroup folders = Amarok::config( "Collection Folders" );
const QString entryKey( "Use MusicLocation" );
if( !folders.hasKey( entryKey ) )
return; // good, already solved, nothing to do
// write the music location as another collection folder in this case
if( folders.readEntry( entryKey, false ) )
{
const QUrl musicUrl = QUrl::fromLocalFile(QDesktopServices::storageLocation( QDesktopServices::MusicLocation ));
const QString musicDir = musicUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QDir dir( musicDir );
if( dir.exists() && dir.isReadable() )
{
QStringList currentFolders = collectionFolders();
if( !currentFolders.contains( musicDir ) )
setCollectionFolders( currentFolders << musicDir );
}
}
folders.deleteEntry( entryKey ); // get rid of it for good
}
MountPointManager::~MountPointManager()
{
DEBUG_BLOCK
m_handlerMapMutex.lock();
foreach( DeviceHandler *dh, m_handlerMap )
delete dh;
m_handlerMapMutex.unlock();
// DeviceHandlerFactories are memory managed using QObject parentship
}
void
MountPointManager::createDeviceFactories()
{
DEBUG_BLOCK
QList<DeviceHandlerFactory*> factories;
factories << new MassStorageDeviceHandlerFactory( this );
factories << new NfsDeviceHandlerFactory( this );
factories << new SmbDeviceHandlerFactory( this );
foreach( DeviceHandlerFactory *factory, factories )
{
debug() << "Initializing DeviceHandlerFactory of type:" << factory->type();
if( factory->canCreateFromMedium() )
m_mediumFactories.append( factory );
else if (factory->canCreateFromConfig() )
m_remoteFactories.append( factory );
else //FIXME max: better error message
debug() << "Unknown DeviceHandlerFactory";
}
Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess );
QList<Solid::Device> devices = Solid::Device::listFromQuery( predicate );
foreach( const Solid::Device &device, devices )
createHandlerFromDevice( device, device.udi() );
m_ready = true;
handleMusicLocation();
}
int
MountPointManager::getIdForUrl( const QUrl &url )
{
int mountPointLength = 0;
int id = -1;
m_handlerMapMutex.lock();
foreach( DeviceHandler *dh, m_handlerMap )
{
if ( url.path().startsWith( dh->getDevicePath() ) && mountPointLength < dh->getDevicePath().length() )
{
id = m_handlerMap.key( dh );
mountPointLength = dh->getDevicePath().length();
}
}
m_handlerMapMutex.unlock();
if ( mountPointLength > 0 )
{
return id;
}
else
{
//default fallback if we could not identify the mount point.
//treat -1 as mount point / in all other methods
return -1;
}
}
bool
MountPointManager::isMounted( const int deviceId ) const
{
m_handlerMapMutex.lock();
const bool result = m_handlerMap.contains( deviceId );
m_handlerMapMutex.unlock();
return result;
}
QString
MountPointManager::getMountPointForId( const int id ) const
{
QString mountPoint;
if ( isMounted( id ) )
{
m_handlerMapMutex.lock();
mountPoint = m_handlerMap[id]->getDevicePath();
m_handlerMapMutex.unlock();
}
else
//TODO better error handling
mountPoint = '/';
return mountPoint;
}
QString
MountPointManager::getAbsolutePath( const int deviceId, const QString& relativePath ) const
{
// TODO: someone who clearly understands QUrl should clean this up.
QUrl rpath;
rpath.setPath( relativePath );
QUrl absolutePath;
// debug() << "id is " << deviceId << ", relative path is " << relativePath;
if ( deviceId == -1 )
{
#ifdef Q_OS_WIN32
absolutePath.setPath( rpath.toLocalFile() );
#else
absolutePath.setPath( "/" );
absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash);
absolutePath.setPath(absolutePath.path() + '/' + ( rpath.path() ));
#endif
absolutePath.setPath( QDir::cleanPath(absolutePath.path()) );
// debug() << "Deviceid is -1, using relative Path as absolute Path, returning " << absolutePath.path();
}
else
{
m_handlerMapMutex.lock();
if ( m_handlerMap.contains( deviceId ) )
{
m_handlerMap[deviceId]->getURL( absolutePath, rpath );
m_handlerMapMutex.unlock();
}
else
{
m_handlerMapMutex.unlock();
const QStringList lastMountPoint = m_storage->query(
QString( "SELECT lastmountpoint FROM devices WHERE id = %1" )
.arg( deviceId ) );
if ( lastMountPoint.count() == 0 )
{
//hmm, no device with that id in the DB...serious problem
warning() << "Device " << deviceId << " not in database, this should never happen!";
return getAbsolutePath( -1, relativePath );
}
else
{
absolutePath.setPath( lastMountPoint.first() );
absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash);
absolutePath.setPath(absolutePath.path() + '/' + ( rpath.path() ));
absolutePath.setPath( QDir::cleanPath(absolutePath.path()) );
//debug() << "Device " << deviceId << " not mounted, using last mount point and returning " << absolutePath.path();
}
}
}
if (QFileInfo(absolutePath.path()).isDir())
absolutePath.setPath( absolutePath.adjusted(QUrl::StripTrailingSlash).path() + '/' );
#ifdef Q_OS_WIN32
return absolutePath.toLocalFile();
#else
return absolutePath.path();
#endif
}
QString
MountPointManager::getRelativePath( const int deviceId, const QString& absolutePath ) const
{
QMutexLocker locker(&m_handlerMapMutex);
if ( deviceId != -1 && m_handlerMap.contains( deviceId ) )
{
//FIXME max: returns garbage if the absolute path is actually not under the device's mount point
return QDir( m_handlerMap[deviceId]->getDevicePath() ).relativeFilePath( absolutePath );
}
else
{
//TODO: better error handling
#ifdef Q_OS_WIN32
return QUrl( absolutePath ).toLocalFile();
#else
return QDir("/").relativeFilePath(absolutePath);
#endif
}
}
IdList
MountPointManager::getMountedDeviceIds() const
{
m_handlerMapMutex.lock();
IdList list( m_handlerMap.keys() );
m_handlerMapMutex.unlock();
list.append( -1 );
return list;
}
QStringList
MountPointManager::collectionFolders() const
{
if( !m_ready )
{
debug() << "requested collectionFolders from MountPointManager that is not yet ready";
return QStringList();
}
//TODO max: cache data
QStringList result;
KConfigGroup folders = Amarok::config( "Collection Folders" );
const IdList ids = getMountedDeviceIds();
foreach( int id, ids )
{
const QStringList rpaths = folders.readEntry( QString::number( id ), QStringList() );
foreach( const QString &strIt, rpaths )
{
const QUrl url = QUrl::fromLocalFile( ( strIt == "./" ) ? getMountPointForId( id ) : getAbsolutePath( id, strIt ) );
const QString absPath = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
if ( !result.contains( absPath ) )
result.append( absPath );
}
}
return result;
}
void
MountPointManager::setCollectionFolders( const QStringList &folders )
{
typedef QMap<int, QStringList> FolderMap;
KConfigGroup folderConf = Amarok::config( "Collection Folders" );
FolderMap folderMap;
foreach( const QString &folder, folders )
{
int id = getIdForUrl( QUrl::fromLocalFile(folder) );
const QString rpath = getRelativePath( id, folder );
if( folderMap.contains( id ) ) {
if( !folderMap[id].contains( rpath ) )
folderMap[id].append( rpath );
}
else
folderMap[id] = QStringList( rpath );
}
//make sure that collection folders on devices which are not in foldermap are deleted
IdList ids = getMountedDeviceIds();
foreach( int deviceId, ids )
{
if( !folderMap.contains( deviceId ) )
{
folderConf.deleteEntry( QString::number( deviceId ) );
}
}
QMapIterator<int, QStringList> i( folderMap );
while( i.hasNext() )
{
i.next();
folderConf.writeEntry( QString::number( i.key() ), i.value() );
}
}
void
-MountPointManager::deviceAdded( const QString &udi )
+MountPointManager::slotDeviceAdded( const QString &udi )
{
DEBUG_BLOCK
Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess );
QList<Solid::Device> devices = Solid::Device::listFromQuery( predicate );
//Looking for a specific udi in predicate seems flaky/buggy; the foreach loop barely
//takes any time, so just be safe
bool found = false;
debug() << "looking for udi " << udi;
foreach( const Solid::Device &device, devices )
{
if( device.udi() == udi )
{
createHandlerFromDevice( device, udi );
found = true;
}
}
if( !found )
debug() << "Did not find device from Solid for udi " << udi;
}
void
-MountPointManager::deviceRemoved( const QString &udi )
+MountPointManager::slotDeviceRemoved( const QString &udi )
{
DEBUG_BLOCK
m_handlerMapMutex.lock();
foreach( DeviceHandler *dh, m_handlerMap )
{
if( dh->deviceMatchesUdi( udi ) )
{
int key = m_handlerMap.key( dh );
m_handlerMap.remove( key );
delete dh;
debug() << "removed device " << key;
m_handlerMapMutex.unlock();
//we found the medium which was removed, so we can abort the loop
emit deviceRemoved( key );
return;
}
}
m_handlerMapMutex.unlock();
}
void MountPointManager::createHandlerFromDevice( const Solid::Device& device, const QString &udi )
{
DEBUG_BLOCK
if ( device.isValid() )
{
debug() << "Device added and mounted, checking handlers";
foreach( DeviceHandlerFactory *factory, m_mediumFactories )
{
if( factory->canHandle( device ) )
{
debug() << "found handler for " << udi;
DeviceHandler *handler = factory->createHandler( device, udi, m_storage );
if( !handler )
{
debug() << "Factory " << factory->type() << "could not create device handler";
break;
}
int key = handler->getDeviceID();
m_handlerMapMutex.lock();
if( m_handlerMap.contains( key ) )
{
debug() << "Key " << key << " already exists in handlerMap, replacing";
delete m_handlerMap[key];
m_handlerMap.remove( key );
}
m_handlerMap.insert( key, handler );
m_handlerMapMutex.unlock();
// debug() << "added device " << key << " with mount point " << volumeAccess->mountPoint();
emit deviceAdded( key );
break; //we found the added medium and don't have to check the other device handlers
}
else
debug() << "Factory can't handle device " << udi;
}
}
else
debug() << "Device not valid!";
}
diff --git a/src/core-impl/collections/db/MountPointManager.h b/src/core-impl/collections/db/MountPointManager.h
index 91c78f6a43..d57b7589b5 100644
--- a/src/core-impl/collections/db/MountPointManager.h
+++ b/src/core-impl/collections/db/MountPointManager.h
@@ -1,232 +1,232 @@
/****************************************************************************************
* Copyright (c) 2006-2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_MOUNTPOINTMANAGER_H
#define AMAROK_MOUNTPOINTMANAGER_H
#include "core-impl/collections/db/sql/amarok_sqlcollection_export.h"
#include <KSharedConfig>
#include <QMap>
#include <QMutex>
#include <QObject>
#include <QUrl>
class DeviceHandler;
class DeviceHandlerFactory;
class SqlStorage;
class QUrl;
namespace Solid {
class Device;
}
typedef QList<int> IdList;
typedef QList<DeviceHandlerFactory*> FactoryList;
typedef QMap<int, DeviceHandler*> HandlerMap;
class DeviceHandlerFactory : public QObject
{
Q_OBJECT
public:
DeviceHandlerFactory( QObject *parent ) : QObject( parent ) {}
virtual ~DeviceHandlerFactory() {}
/**
* checks whether a DeviceHandler subclass can handle a given Medium.
* @param volume the connected solid volume
* @return true if the DeviceHandler implementation can handle the medium,
* false otherwise
*/
virtual bool canHandle( const Solid::Device &device ) const = 0;
/**
* tells the MountPointManager whether it makes sense to ask the factory to
* create a Devicehandler when a new Medium was connected
* @return true if the factory can create DeviceHandlers from Medium instances
*/
virtual bool canCreateFromMedium() const = 0;
/**
* creates a DeviceHandler which represents the Medium.
* @param volume the Volume for which a DeviceHandler is required
* @return a DeviceHandler or 0 if the factory cannot handle the Medium
*/
virtual DeviceHandler* createHandler( const Solid::Device &device, const QString &udi, SqlStorage *s ) const = 0;
virtual bool canCreateFromConfig() const = 0;
virtual DeviceHandler* createHandler( KSharedConfigPtr c, SqlStorage *s ) const = 0;
/**
* returns the type of the DeviceHandler. Should be the same as the value used in
* ~/.kde/share/config/amarokrc
* @return a QString describing the type of the DeviceHandler
*/
virtual QString type() const = 0;
};
class DeviceHandler
{
public:
DeviceHandler() {}
virtual ~DeviceHandler() {}
virtual bool isAvailable() const = 0;
/**
* returns the type of the DeviceHandler. Should be the same as the value used in
* ~/.kde/share/config/amarokrc
* @return a QString describing the type of the DeviceHandler
*/
virtual QString type() const = 0;
/**
* returns an absolute path which is guaranteed to be playable by amarok's current engine. (based on an
* idea by andrewt512: this method would only be called when we actually want to play the file, not when we
* simply want to show it to the user. It could for example download a file using KIO and return a path to a
* temporary file. Needs some more thought and is not actually used at the moment.
* @param absolutePath
* @param relativePath
*/
virtual void getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ) = 0;
/**
* builds an absolute path from a relative path and DeviceHandler specific information. The absolute path
* is not necessarily playable! (based on an idea by andrewt512: allows better handling of files stored in remote * collections. this method would return a "pretty" URL which might not be playable by amarok's engines.
* @param absolutePath the not necessarily playbale absolute path
* @param relativePath the device specific relative path
*/
virtual void getURL( QUrl &absolutePath, const QUrl &relativePath ) = 0;
/**
* retrieves the unique database id of a given Medium. Implementations are responsible
* for generating a (sufficiently) unique value which identifies the Medium.
* Additionally, implementations must recognize unknown mediums and store the necessary
* information to recognize them the next time they are connected in the database.
* @return unique identifier which can be used as a foreign key to the media table.
*/
virtual int getDeviceID() = 0;
virtual const QString &getDevicePath() const = 0;
/**
* allows MountPointManager to check if a device handler handles a specific medium.
* @param m
* @return true if the device handler handles the Medium m
*/
virtual bool deviceMatchesUdi( const QString &udi ) const = 0;
};
/**
* @author Maximilian Kossick <maximilian.kossick@googlemail.com>
*/
class AMAROK_SQLCOLLECTION_EXPORT MountPointManager : public QObject
{
Q_OBJECT
public:
MountPointManager( QObject *parent, SqlStorage *storage );
~MountPointManager();
/**
*
* @param url
* @return
*/
virtual int getIdForUrl( const QUrl &url );
/**
*
* @param id
* @return
*/
virtual QString getMountPointForId( const int id ) const;
/**
* builds the absolute path from the mount point of the medium and the given relative
* path.
* @param deviceId the medium(device)'s unique id
* @param relativePath relative path on the medium
* @return the absolute path
*/
virtual QString getAbsolutePath( const int deviceId, const QString& relativePath ) const;
/**
* calculates a file's/directory's relative path on a given device.
* @param deviceId the unique id which identifies the device the file/directory is supposed to be on
* @param absolutePath the file's/directory's absolute path
* @param relativePath the calculated relative path
*/
virtual QString getRelativePath( const int deviceId, const QString& absolutePath ) const;
/**
* allows calling code to access the ids of all active devices
* @return the ids of all devices which are currently mounted or otherwise accessible
*/
virtual IdList getMountedDeviceIds() const;
virtual QStringList collectionFolders() const;
virtual void setCollectionFolders( const QStringList &folders );
Q_SIGNALS:
void deviceAdded( int id );
void deviceRemoved( int id );
private:
void createDeviceFactories();
/**
* Old Amarok versions used to have "Use MusicLocation" config option which caused
* problems (bug 316216). This method converts out of it and needs to be run after
* MountPointManager has initialized.
*/
void handleMusicLocation();
/**
* checks whether a medium identified by its unique database id is currently mounted.
* Note: does not handle deviceId = -1! It only checks real devices
* @param deviceId the mediums unique id
* @return true if the medium is mounted, false otherwise
*/
bool isMounted ( const int deviceId ) const;
SqlStorage *m_storage;
/**
* maps a device id to a mount point. does only work for mountable filesystems and needs to be
* changed for the real Dynamic Collection implementation.
*/
HandlerMap m_handlerMap;
mutable QMutex m_handlerMapMutex;
FactoryList m_mediumFactories;
FactoryList m_remoteFactories;
bool m_ready;
//Solid specific
void createHandlerFromDevice( const Solid::Device &device, const QString &udi );
private Q_SLOTS:
- void deviceAdded( const QString &udi );
- void deviceRemoved( const QString &udi );
+ void slotDeviceAdded( const QString &udi );
+ void slotDeviceRemoved( const QString &udi );
};
#define AMAROK_EXPORT_DEVICE_PLUGIN(libname, classname) \
K_PLUGIN_FACTORY(factory, registerPlugin<classname>();) \
K_EXPORT_PLUGIN(factory("amarok_device_" #libname))\
#endif
diff --git a/src/core-impl/collections/db/sql/SqlCollection.cpp b/src/core-impl/collections/db/sql/SqlCollection.cpp
index 5a7a639c35..edaae80a40 100644
--- a/src/core-impl/collections/db/sql/SqlCollection.cpp
+++ b/src/core-impl/collections/db/sql/SqlCollection.cpp
@@ -1,476 +1,478 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2008-2009 Jeff Mitchell <mitchell@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SqlCollection"
#include "SqlCollection.h"
#include "DefaultSqlQueryMakerFactory.h"
#include "DatabaseUpdater.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/capabilities/TranscodeCapability.h"
#include "core/transcoding/TranscodingController.h"
#include "core-impl/collections/db/MountPointManager.h"
#include "scanner/GenericScanManager.h"
#include "scanner/AbstractDirectoryWatcher.h"
#include "dialogs/OrganizeCollectionDialog.h"
#include "SqlCollectionLocation.h"
#include "SqlQueryMaker.h"
#include "SqlScanResultProcessor.h"
#include "SvgHandler.h"
#include "MainWindow.h"
#include "collectionscanner/BatchFile.h"
#include <KStandardDirs>
#include <QApplication>
#include <ThreadWeaver/ThreadWeaver>
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/Job>
#include <QApplication>
#include <QDir>
#include <KConfigGroup>
#include <KGlobal>
/** Concrete implementation of the directory watcher */
class SqlDirectoryWatcher : public AbstractDirectoryWatcher
{
public:
SqlDirectoryWatcher( Collections::SqlCollection* collection )
: AbstractDirectoryWatcher()
, m_collection( collection )
{ }
~SqlDirectoryWatcher()
{ }
protected:
QList<QString> collectionFolders()
{ return m_collection->mountPointManager()->collectionFolders(); }
Collections::SqlCollection* m_collection;
};
class SqlScanManager : public GenericScanManager
{
public:
SqlScanManager( Collections::SqlCollection* collection, QObject* parent )
: GenericScanManager( parent )
, m_collection( collection )
{ }
~SqlScanManager()
{ }
protected:
QList< QPair<QString, uint> > getKnownDirs()
{
QList< QPair<QString, uint> > result;
// -- get all (mounted) mount points
QList<int> idList = m_collection->mountPointManager()->getMountedDeviceIds();
// -- query all known directories
QString deviceIds;
foreach( int id, idList )
{
if( !deviceIds.isEmpty() )
deviceIds += ',';
deviceIds += QString::number( id );
}
QString query = QString( "SELECT deviceid, dir, changedate FROM directories WHERE deviceid IN (%1);" );
QStringList values = m_collection->sqlStorage()->query( query.arg( deviceIds ) );
for( QListIterator<QString> iter( values ); iter.hasNext(); )
{
int deviceid = iter.next().toInt();
QString dir = iter.next();
uint mtime = iter.next().toUInt();
QString folder = m_collection->mountPointManager()->getAbsolutePath( deviceid, dir );
result.append( QPair<QString, uint>( folder, mtime ) );
}
return result;
}
QString getBatchFile( const QStringList &scanDirsRequested )
{
// -- write the batch file
// the batch file contains the known modification dates so that the scanner only
// needs to report changed directories
QList<QPair<QString, uint> > knownDirs = getKnownDirs();
if( !knownDirs.isEmpty() )
{
QString path = KGlobal::dirs()->saveLocation( "data", QString("amarok/"), false ) + "amarokcollectionscanner_batchscan.xml";
while( QFile::exists( path ) )
path += '_';
CollectionScanner::BatchFile batchfile;
batchfile.setTimeDefinitions( knownDirs );
batchfile.setDirectories( scanDirsRequested );
if( !batchfile.write( path ) )
{
warning() << "Failed to write batch file" << path;
return QString();
}
return path;
}
return QString();
}
Collections::SqlCollection* m_collection;
};
namespace Collections {
class OrganizeCollectionDelegateImpl : public OrganizeCollectionDelegate
{
public:
OrganizeCollectionDelegateImpl()
: OrganizeCollectionDelegate()
, m_dialog( 0 )
, m_organizing( false ) {}
virtual ~ OrganizeCollectionDelegateImpl() { delete m_dialog; }
virtual void setTracks( const Meta::TrackList &tracks ) { m_tracks = tracks; }
virtual void setFolders( const QStringList &folders ) { m_folders = folders; }
virtual void setIsOrganizing( bool organizing ) { m_organizing = organizing; }
virtual void setTranscodingConfiguration( const Transcoding::Configuration &configuration )
{ m_targetFileExtension =
Amarok::Components::transcodingController()->format( configuration.encoder() )->fileExtension(); }
virtual void setCaption( const QString &caption ) { m_caption = caption; }
virtual void show()
{
m_dialog = new OrganizeCollectionDialog( m_tracks,
m_folders,
m_targetFileExtension,
The::mainWindow(), //parent
"", //name is unused
true, //modal
m_caption //caption
);
- connect( m_dialog, SIGNAL(accepted()), SIGNAL(accepted()) );
- connect( m_dialog, SIGNAL(rejected()), SIGNAL(rejected()) );
+ connect( m_dialog, &OrganizeCollectionDialog::accepted,
+ this, &OrganizeCollectionDelegateImpl::accepted );
+ connect( m_dialog, &OrganizeCollectionDialog::rejected,
+ this, &OrganizeCollectionDelegateImpl::rejected );
m_dialog->show();
}
virtual bool overwriteDestinations() const { return m_dialog->overwriteDestinations(); }
virtual QMap<Meta::TrackPtr, QString> destinations() const { return m_dialog->getDestinations(); }
private:
Meta::TrackList m_tracks;
QStringList m_folders;
OrganizeCollectionDialog *m_dialog;
bool m_organizing;
QString m_targetFileExtension;
QString m_caption;
};
class OrganizeCollectionDelegateFactoryImpl : public OrganizeCollectionDelegateFactory
{
public:
virtual OrganizeCollectionDelegate* createDelegate() { return new OrganizeCollectionDelegateImpl(); }
};
class SqlCollectionLocationFactoryImpl : public SqlCollectionLocationFactory
{
public:
SqlCollectionLocationFactoryImpl( SqlCollection *collection )
: SqlCollectionLocationFactory()
, m_collection( collection ) {}
SqlCollectionLocation *createSqlCollectionLocation() const
{
Q_ASSERT( m_collection );
SqlCollectionLocation *loc = new SqlCollectionLocation( m_collection );
loc->setOrganizeCollectionDelegateFactory( new OrganizeCollectionDelegateFactoryImpl() );
return loc;
}
Collections::SqlCollection *m_collection;
};
} //namespace Collections
using namespace Collections;
SqlCollection::SqlCollection( SqlStorage* storage )
: DatabaseCollection()
, m_registry( 0 )
, m_sqlStorage( storage )
, m_scanProcessor( 0 )
, m_directoryWatcher( 0 )
, m_collectionLocationFactory( 0 )
, m_queryMakerFactory( 0 )
{
qRegisterMetaType<TrackUrls>( "TrackUrls" );
qRegisterMetaType<ChangedTrackUrls>( "ChangedTrackUrls" );
// update database to current schema version; this must be run *before* MountPointManager
// is initialized or its handlers may try to insert
// into the database before it's created/updated!
DatabaseUpdater updater( this );
if( updater.needsUpdate() )
{
if( updater.schemaExists() ) // this is an update
{
KDialog dialog( 0, Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint );
QLabel label( i18n( "Updating Amarok database schema. Please don't terminate "
"Amarok now as it may result in database corruption." ) );
label.setWordWrap( true );
dialog.setMainWidget( &label );
dialog.setCaption( i18n( "Updating Amarok database schema" ) );
dialog.setButtons( KDialog::None );
dialog.setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
dialog.show();
dialog.raise();
// otherwise the splash screen doesn't load image and this dialog is not shown:
qApp->processEvents();
updater.update();
dialog.hide();
qApp->processEvents();
}
else // this is new schema creation
updater.update();
}
//perform a quick check of the database
updater.cleanupDatabase();
m_registry = new SqlRegistry( this );
m_collectionLocationFactory = new SqlCollectionLocationFactoryImpl( this );
m_queryMakerFactory = new DefaultSqlQueryMakerFactory( this );
// scanning
m_scanManager = new SqlScanManager( this, this );
m_scanProcessor = new SqlScanResultProcessor( m_scanManager, this, this );
m_directoryWatcher = new SqlDirectoryWatcher( this );
- connect( m_directoryWatcher, SIGNAL(done(ThreadWeaver::JobPointer)),
- m_directoryWatcher, SLOT(deleteLater()) ); // auto delete
- connect( m_directoryWatcher, SIGNAL(requestScan(QList<QUrl>,GenericScanManager::ScanType)),
- m_scanManager, SLOT(requestScan(QList<QUrl>,GenericScanManager::ScanType)) );
+ connect( m_directoryWatcher, &AbstractDirectoryWatcher::done,
+ m_directoryWatcher, &AbstractDirectoryWatcher::deleteLater ); // auto delete
+ connect( m_directoryWatcher, &AbstractDirectoryWatcher::requestScan,
+ m_scanManager, &GenericScanManager::requestScan );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(m_directoryWatcher) );
}
SqlCollection::~SqlCollection()
{
m_directoryWatcher->abort();
delete m_scanProcessor; // this prevents any further commits from the scanner
delete m_collectionLocationFactory;
delete m_queryMakerFactory;
delete m_sqlStorage;
delete m_registry;
}
QString
SqlCollection::uidUrlProtocol() const
{
return "amarok-sqltrackuid";
}
QString
SqlCollection::generateUidUrl( const QString &hash )
{
return uidUrlProtocol() + "://" + hash;
}
QueryMaker*
SqlCollection::queryMaker()
{
Q_ASSERT( m_queryMakerFactory );
return m_queryMakerFactory->createQueryMaker();
}
SqlRegistry*
SqlCollection::registry() const
{
Q_ASSERT( m_registry );
return m_registry;
}
SqlStorage*
SqlCollection::sqlStorage() const
{
Q_ASSERT( m_sqlStorage );
return m_sqlStorage;
}
bool
SqlCollection::possiblyContainsTrack( const QUrl &url ) const
{
if( url.isLocalFile() )
{
foreach( const QString &folder, collectionFolders() )
{
QUrl q = QUrl( folder );
if( q.isParentOf( url ) || q.matches( url , QUrl::StripTrailingSlash) )
return true;
}
return false;
}
else
return url.scheme() == uidUrlProtocol();
}
Meta::TrackPtr
SqlCollection::trackForUrl( const QUrl &url )
{
if( url.scheme() == uidUrlProtocol() )
return m_registry->getTrackFromUid( url.url() );
else if( url.scheme() == "file" )
return m_registry->getTrack( url.path() );
else
return Meta::TrackPtr();
}
Meta::TrackPtr
SqlCollection::getTrack( int deviceId, const QString &rpath, int directoryId, const QString &uidUrl )
{
return m_registry->getTrack( deviceId, rpath, directoryId, uidUrl );
}
Meta::TrackPtr
SqlCollection::getTrackFromUid( const QString &uniqueid )
{
return m_registry->getTrackFromUid( uniqueid );
}
Meta::AlbumPtr
SqlCollection::getAlbum( const QString &album, const QString &artist )
{
return m_registry->getAlbum( album, artist );
}
CollectionLocation*
SqlCollection::location()
{
Q_ASSERT( m_collectionLocationFactory );
return m_collectionLocationFactory->createSqlCollectionLocation();
}
void
SqlCollection::slotDeviceAdded( int id )
{
QString query = "select count(*) from tracks inner join urls on tracks.url = urls.id where urls.deviceid = %1";
QStringList rs = m_sqlStorage->query( query.arg( id ) );
if( !rs.isEmpty() )
{
int count = rs.first().toInt();
if( count > 0 )
{
collectionUpdated();
}
}
else
{
warning() << "Query " << query << "did not return a result! Is the database available?";
}
}
void
SqlCollection::slotDeviceRemoved( int id )
{
QString query = "select count(*) from tracks inner join urls on tracks.url = urls.id where urls.deviceid = %1";
QStringList rs = m_sqlStorage->query( query.arg( id ) );
if( !rs.isEmpty() )
{
int count = rs.first().toInt();
if( count > 0 )
{
collectionUpdated();
}
}
else
{
warning() << "Query " << query << "did not return a result! Is the database available?";
}
}
bool
SqlCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
switch( type )
{
case Capabilities::Capability::Transcode:
return true;
default:
return DatabaseCollection::hasCapabilityInterface( type );
}
}
Capabilities::Capability*
SqlCollection::createCapabilityInterface( Capabilities::Capability::Type type )
{
switch( type )
{
case Capabilities::Capability::Transcode:
return new SqlCollectionTranscodeCapability();
default:
return DatabaseCollection::createCapabilityInterface( type );
}
}
void
SqlCollection::dumpDatabaseContent()
{
DatabaseUpdater updater( this );
QStringList tables = m_sqlStorage->query( "select table_name from INFORMATION_SCHEMA.tables WHERE table_schema='amarok'" );
foreach( const QString &table, tables )
{
QString filePath =
QDir::home().absoluteFilePath( table + '-' + QDateTime::currentDateTime().toString( Qt::ISODate ) + ".csv" );
updater.writeCSVFile( table, filePath, true );
}
}
// ---------- SqlCollectionTranscodeCapability -------------
SqlCollectionTranscodeCapability::~SqlCollectionTranscodeCapability()
{
// nothing to do
}
Transcoding::Configuration
SqlCollectionTranscodeCapability::savedConfiguration()
{
KConfigGroup transcodeGroup = Amarok::config( SQL_TRANSCODING_GROUP_NAME );
return Transcoding::Configuration::fromConfigGroup( transcodeGroup );
}
void
SqlCollectionTranscodeCapability::setSavedConfiguration( const Transcoding::Configuration &configuration )
{
KConfigGroup transcodeGroup = Amarok::config( SQL_TRANSCODING_GROUP_NAME );
configuration.saveToConfigGroup( transcodeGroup );
transcodeGroup.sync();
}
diff --git a/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp b/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp
index 5ebbb56795..8a049f82ae 100644
--- a/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp
+++ b/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp
@@ -1,786 +1,787 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2010 Teo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SqlCollectionLocation"
#include "SqlCollectionLocation.h"
#include "MetaTagLib.h" // for getting the uid
#include "core/collections/CollectionLocationDelegate.h"
#include <core/storage/SqlStorage.h>
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core/transcoding/TranscodingController.h"
#include "core-impl/collections/db/MountPointManager.h"
#include "core-impl/collections/db/sql/SqlCollection.h"
#include "core-impl/collections/db/sql/SqlMeta.h"
#include "transcoding/TranscodingJob.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <kdiskfreespaceinfo.h>
#include <KFileItem>
#include <kjob.h>
#include <KSharedPtr>
#include <kio/job.h>
#include <kio/jobclasses.h>
#include <kio/deletejob.h>
#include <KConfigGroup>
#include <KLocalizedString>
using namespace Collections;
SqlCollectionLocation::SqlCollectionLocation( SqlCollection *collection )
: CollectionLocation( collection )
, m_collection( collection )
, m_delegateFactory( 0 )
, m_overwriteFiles( false )
, m_transferjob( )
{
//nothing to do
}
SqlCollectionLocation::~SqlCollectionLocation()
{
//nothing to do
delete m_delegateFactory;
}
QString
SqlCollectionLocation::prettyLocation() const
{
return i18n( "Local Collection" );
}
QStringList
SqlCollectionLocation::actualLocation() const
{
return m_collection->mountPointManager()->collectionFolders();
}
bool
SqlCollectionLocation::isWritable() const
{
// TODO: This function is also called when removing files to check
// if the tracks can be removed. In such a case we should not check the space
// The collection is writable if there exists a path that has more than
// 500 MB free space.
bool path_exists_with_space = false;
bool path_exists_writable = false;
QStringList folders = actualLocation();
foreach( const QString &path, folders )
{
float used = KDiskFreeSpaceInfo::freeSpaceInfo( path ).used();
float total = KDiskFreeSpaceInfo::freeSpaceInfo( path ).size();
if( total <= 0 ) // protect against div by zero
continue; //How did this happen?
float free_space = total - used;
if( free_space >= 500*1000*1000 ) // ~500 megabytes
path_exists_with_space = true;
QFileInfo info( path );
if( info.isWritable() )
path_exists_writable = true;
}
return path_exists_with_space && path_exists_writable;
}
bool
SqlCollectionLocation::isOrganizable() const
{
return true;
}
bool
SqlCollectionLocation::remove( const Meta::TrackPtr &track )
{
DEBUG_BLOCK
Q_ASSERT( track );
if( track->inCollection() &&
track->collection()->collectionId() == m_collection->collectionId() )
{
bool removed;
QUrl src = track->playableUrl();
if( isGoingToRemoveSources() ) // is organize operation?
{
SqlCollectionLocation* destinationloc = qobject_cast<SqlCollectionLocation*>( destination() );
if( destinationloc )
{
src = destinationloc->m_originalUrls[track];
if( src == track->playableUrl() )
return false;
}
}
// we are going to delete it from the database only if is no longer on disk
removed = !QFile::exists( src.path() );
if( removed )
static_cast<Meta::SqlTrack*>(const_cast<Meta::Track*>(track.data()))->remove();
return removed;
}
else
{
debug() << "Remove Failed";
return false;
}
}
bool
SqlCollectionLocation::insert( const Meta::TrackPtr &track, const QString &url )
{
if( !QFile::exists( url ) )
{
warning() << Q_FUNC_INFO << "file" << url << "does not exist, not inserting into db";
return false;
}
// -- the target url
SqlRegistry *registry = m_collection->registry();
int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(url) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, url );
int directoryId = registry->getDirectory( QFileInfo( url ).path() );
// -- the track uid (we can't use the original one from the old collection)
Meta::FieldHash fileTags = Meta::Tag::readTags( url );
QString uid = fileTags.value( Meta::valUniqueId ).toString();
uid = m_collection->generateUidUrl( uid ); // add the right prefix
// -- the track from the registry
Meta::SqlTrackPtr metaTrack;
metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrackFromUid( uid ) );
if( metaTrack )
{
warning() << "Location is inserting a file with the same uid as an already existing one.";
metaTrack->setUrl( deviceId, rpath, directoryId );
} else
metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrack( deviceId, rpath, directoryId, uid ) );
Meta::ConstStatisticsPtr origStats = track->statistics();
// -- set the values
metaTrack->setWriteFile( false ); // no need to write the tags back
metaTrack->beginUpdate();
if( !track->name().isEmpty() )
metaTrack->setTitle( track->name() );
if( track->album() )
metaTrack->setAlbum( track->album()->name() );
if( track->artist() )
metaTrack->setArtist( track->artist()->name() );
if( track->composer() )
metaTrack->setComposer( track->composer()->name() );
if( track->year() && track->year()->year() > 0 )
metaTrack->setYear( track->year()->year() );
if( track->genre() )
metaTrack->setGenre( track->genre()->name() );
if( track->bpm() > 0 )
metaTrack->setBpm( track->bpm() );
if( !track->comment().isEmpty() )
metaTrack->setComment( track->comment() );
if( origStats->score() > 0 )
metaTrack->setScore( origStats->score() );
if( origStats->rating() > 0 )
metaTrack->setRating( origStats->rating() );
/* These tags change when transcoding. Prefer to read those from file */
if( fileTags.value( Meta::valLength, 0 ).toLongLong() > 0 )
metaTrack->setLength( fileTags.value( Meta::valLength ).value<qint64>() );
else if( track->length() > 0 )
metaTrack->setLength( track->length() );
// the filesize is updated every time after the
// file is changed. Doesn't make sense to set it.
if( fileTags.value( Meta::valSamplerate, 0 ).toInt() > 0 )
metaTrack->setSampleRate( fileTags.value( Meta::valSamplerate ).toInt() );
else if( track->sampleRate() > 0 )
metaTrack->setSampleRate( track->sampleRate() );
if( fileTags.value( Meta::valBitrate, 0 ).toInt() > 0 )
metaTrack->setBitrate( fileTags.value( Meta::valBitrate ).toInt() );
else if( track->bitrate() > 0 )
metaTrack->setBitrate( track->bitrate() );
// createDate is already set in Track constructor
if( track->modifyDate().isValid() )
metaTrack->setModifyDate( track->modifyDate() );
if( track->trackNumber() > 0 )
metaTrack->setTrackNumber( track->trackNumber() );
if( track->discNumber() > 0 )
metaTrack->setDiscNumber( track->discNumber() );
if( origStats->lastPlayed().isValid() )
metaTrack->setLastPlayed( origStats->lastPlayed() );
if( origStats->firstPlayed().isValid() )
metaTrack->setFirstPlayed( origStats->firstPlayed() );
if( origStats->playCount() > 0 )
metaTrack->setPlayCount( origStats->playCount() );
Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain,
Meta::ReplayGain_Track_Peak,
Meta::ReplayGain_Album_Gain,
Meta::ReplayGain_Album_Peak };
for( int i=0; i<4; i++ )
if( track->replayGain( modes[i] ) != 0 )
metaTrack->setReplayGain( modes[i], track->replayGain( modes[i] ) );
Meta::LabelList labels = track->labels();
foreach( Meta::LabelPtr label, labels )
metaTrack->addLabel( label );
if( fileTags.value( Meta::valFormat, int(Amarok::Unknown) ).toInt() != int(Amarok::Unknown) )
metaTrack->setType( Amarok::FileType( fileTags.value( Meta::valFormat ).toInt() ) );
else if( Amarok::FileTypeSupport::fileType( track->type() ) != Amarok::Unknown )
metaTrack->setType( Amarok::FileTypeSupport::fileType( track->type() ) );
// Used to be updated after changes commit to prevent crash on NULL pointer access
// if metaTrack had no album.
if( track->album() && metaTrack->album() )
{
if( track->album()->hasAlbumArtist() && !metaTrack->album()->hasAlbumArtist() )
metaTrack->setAlbumArtist( track->album()->albumArtist()->name() );
if( track->album()->hasImage() && !metaTrack->album()->hasImage() )
metaTrack->album()->setImage( track->album()->image() );
}
metaTrack->endUpdate();
metaTrack->setWriteFile( true );
// we have a first shot at the meta data (expecially ratings and playcounts from media
// collections) but we still need to trigger the collection scanner
// to get the album and other meta data correct.
// TODO m_collection->directoryWatcher()->delayedIncrementalScan( QFileInfo(url).path() );
return true;
}
void
SqlCollectionLocation::showDestinationDialog( const Meta::TrackList &tracks,
bool removeSources,
const Transcoding::Configuration &configuration )
{
DEBUG_BLOCK
setGoingToRemoveSources( removeSources );
KIO::filesize_t transferSize = 0;
foreach( Meta::TrackPtr track, tracks )
transferSize += track->filesize();
QStringList actual_folders = actualLocation(); // the folders in the collection
QStringList available_folders; // the folders which have freespace available
foreach(QString path, actual_folders)
{
if( path.isEmpty() )
continue;
debug() << "Path" << path;
KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo( path );
if( !spaceInfo.isValid() )
continue;
KIO::filesize_t totalCapacity = spaceInfo.size();
KIO::filesize_t used = spaceInfo.used();
KIO::filesize_t freeSpace = totalCapacity - used;
debug() << "used:" << used;
debug() << "total:" << totalCapacity;
debug() << "Free space" << freeSpace;
debug() << "transfersize" << transferSize;
if( totalCapacity <= 0 ) // protect against div by zero
continue; //How did this happen?
QFileInfo info( path );
// since bad things happen when drives become totally full
// we make sure there is at least ~500MB left
// finally, ensure the path is writable
debug() << ( freeSpace - transferSize );
if( ( freeSpace - transferSize ) > 1024*1024*500 && info.isWritable() )
available_folders << path;
}
if( available_folders.size() <= 0 )
{
debug() << "No space available or not writable";
CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
abort();
return;
}
OrganizeCollectionDelegate *delegate = m_delegateFactory->createDelegate();
delegate->setTracks( tracks );
delegate->setFolders( available_folders );
delegate->setIsOrganizing( ( collection() == source()->collection() ) );
delegate->setTranscodingConfiguration( configuration );
delegate->setCaption( operationText( configuration ) );
- connect( delegate, SIGNAL(accepted()), SLOT(slotDialogAccepted()) );
- connect( delegate, SIGNAL(rejected()), SLOT(slotDialogRejected()) );
+ connect( delegate, &OrganizeCollectionDelegate::accepted, this, &SqlCollectionLocation::slotDialogAccepted );
+ connect( delegate, &OrganizeCollectionDelegate::rejected, this, &SqlCollectionLocation::slotDialogRejected );
delegate->show();
}
void
SqlCollectionLocation::slotDialogAccepted()
{
DEBUG_BLOCK
sender()->deleteLater();
OrganizeCollectionDelegate *ocDelegate = qobject_cast<OrganizeCollectionDelegate*>( sender() );
m_destinations = ocDelegate->destinations();
m_overwriteFiles = ocDelegate->overwriteDestinations();
if( isGoingToRemoveSources() )
{
CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
const bool del = delegate->reallyMove( this, m_destinations.keys() );
if( !del )
{
abort();
return;
}
}
slotShowDestinationDialogDone();
}
void
SqlCollectionLocation::slotDialogRejected()
{
DEBUG_BLOCK
sender()->deleteLater();
abort();
}
void
SqlCollectionLocation::slotJobFinished( KJob *job )
{
DEBUG_BLOCK
Meta::TrackPtr track = m_jobs.value( job );
if( job->error() && job->error() != KIO::ERR_FILE_ALREADY_EXIST )
{
//TODO: proper error handling
warning() << "An error occurred when copying a file: " << job->errorString();
source()->transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) );
m_destinations.remove( track );
}
else
source()->transferSuccessful( track );
m_jobs.remove( job );
job->deleteLater();
}
void
SqlCollectionLocation::slotRemoveJobFinished( KJob *job )
{
DEBUG_BLOCK
Meta::TrackPtr track = m_removejobs.value( job );
if( job->error() )
{
//TODO: proper error handling
warning() << "An error occurred when removing a file: " << job->errorString();
}
// -- remove the track from the database if it's gone
if( !QFile(track->playableUrl().path()).exists() )
{
// Remove the track from the database
remove( track );
//we assume that KIO works correctly...
transferSuccessful( track );
}
else
{
transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) );
}
m_removejobs.remove( job );
job->deleteLater();
if( !startNextRemoveJob() )
{
slotRemoveOperationFinished();
}
}
void SqlCollectionLocation::slotTransferJobFinished( KJob* job )
{
DEBUG_BLOCK
if( job->error() )
{
debug() << job->errorText();
}
// filter the list of destinations to only include tracks
// that were successfully copied
foreach( const Meta::TrackPtr &track, m_destinations.keys() )
{
if( QFile::exists( m_destinations[ track ] ) )
insert( track, m_destinations[ track ] );
m_originalUrls[track] = track->playableUrl();
}
debug () << "m_originalUrls" << m_originalUrls;
slotCopyOperationFinished();
}
void SqlCollectionLocation::slotTransferJobAborted()
{
DEBUG_BLOCK
if( !m_transferjob )
return;
m_transferjob->kill();
// filter the list of destinations to only include tracks
// that were successfully copied
foreach( const Meta::TrackPtr &track, m_destinations.keys() )
{
if( QFile::exists( m_destinations[ track ] ) )
insert( track, m_destinations[ track ] ); // was already copied, so have to insert it in the db
m_originalUrls[track] = track->playableUrl();
}
abort();
}
void
SqlCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration )
{
DEBUG_BLOCK
m_sources = sources;
QString statusBarTxt = operationInProgressText( configuration, sources.count() );
m_transferjob = new TransferJob( this, configuration );
Amarok::Components::logger()->newProgressOperation( m_transferjob, statusBarTxt, this,
SLOT(slotTransferJobAborted()) );
- connect( m_transferjob, SIGNAL(result(KJob*)), SLOT(slotTransferJobFinished(KJob*)) );
+ connect( m_transferjob, &Collections::TransferJob::result,
+ this, &SqlCollectionLocation::slotTransferJobFinished );
m_transferjob->start();
}
void
SqlCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources )
{
DEBUG_BLOCK
m_removetracks = sources;
if( !startNextRemoveJob() ) //this signal needs to be called no matter what, even if there are no job finishes to call it
slotRemoveOperationFinished();
}
void
SqlCollectionLocation::setOrganizeCollectionDelegateFactory( OrganizeCollectionDelegateFactory *fac )
{
m_delegateFactory = fac;
}
bool SqlCollectionLocation::startNextJob( const Transcoding::Configuration configuration )
{
DEBUG_BLOCK
if( !m_sources.isEmpty() )
{
Meta::TrackPtr track = m_sources.keys().first();
QUrl src = m_sources.take( track );
QUrl dest = QUrl::fromLocalFile(m_destinations[ track ]);
dest.setPath( QDir::cleanPath(dest.path()) );
src.setPath( QDir::cleanPath(src.path()) );
bool hasMoodFile = QFile::exists( moodFile( src ).toLocalFile() );
bool isJustCopy = configuration.isJustCopy( track );
if( isJustCopy )
debug() << "copying from " << src << " to " << dest;
else
debug() << "transcoding from " << src << " to " << dest;
KFileItem srcInfo( src );
if( !srcInfo.isFile() )
{
warning() << "Source track" << src << "was no file";
source()->transferError( track, i18n( "Source track does not exist: %1", src.toDisplayString() ) );
return true; // Attempt to copy/move the next item in m_sources
}
QFileInfo destInfo( dest.toDisplayString() );
QDir dir = destInfo.dir();
if( !dir.exists() )
{
if( !dir.mkpath( "." ) )
{
warning() << "Could not create directory " << dir;
source()->transferError(track, i18n( "Could not create directory: %1", dir.path() ) );
return true; // Attempt to copy/move the next item in m_sources
}
}
KIO::JobFlags flags;
if( isJustCopy )
{
flags = KIO::HideProgressInfo;
if( m_overwriteFiles )
{
flags |= KIO::Overwrite;
}
}
KJob *job = 0;
KJob *moodJob = 0;
if( src.matches( dest, QUrl::StripTrailingSlash ) )
{
warning() << "move to itself found: " << destInfo.absoluteFilePath();
m_transferjob->slotJobFinished( 0 );
if( m_sources.isEmpty() )
return false;
return true;
}
else if( isGoingToRemoveSources() && source()->collection() == collection() )
{
debug() << "moving!";
job = KIO::file_move( src, dest, -1, flags );
if( hasMoodFile )
{
QUrl moodSrc = moodFile( src );
QUrl moodDest = moodFile( dest );
moodJob = KIO::file_move( moodSrc, moodDest, -1, flags );
}
}
else
{
//later on in the case that remove is called, the file will be deleted because we didn't apply moveByDestination to the track
if( isJustCopy )
job = KIO::file_copy( src, dest, -1, flags );
else
{
QString destPath = dest.path();
destPath.truncate( dest.path().lastIndexOf( '.' ) + 1 );
destPath.append( Amarok::Components::transcodingController()->
format( configuration.encoder() )->fileExtension() );
dest.setPath( destPath );
job = new Transcoding::Job( src, dest, configuration, this );
job->start();
}
if( hasMoodFile )
{
QUrl moodSrc = moodFile( src );
QUrl moodDest = moodFile( dest );
moodJob = KIO::file_copy( moodSrc, moodDest, -1, flags );
}
}
if( job ) //just to be safe
{
- connect( job, SIGNAL(result(KJob*)), SLOT(slotJobFinished(KJob*)) );
- connect( job, SIGNAL(result(KJob*)), m_transferjob, SLOT(slotJobFinished(KJob*)) );
+ connect( job, &KJob::result, this, &SqlCollectionLocation::slotJobFinished );
+ connect( job, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished );
m_transferjob->addSubjob( job );
if( moodJob )
{
- connect( moodJob, SIGNAL(result(KJob*)), m_transferjob, SLOT(slotJobFinished(KJob*)) );
+ connect( moodJob, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished );
m_transferjob->addSubjob( moodJob );
}
QString name = track->prettyName();
if( track->artist() )
name = QString( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() );
if( isJustCopy )
m_transferjob->emitInfo( i18n( "Transferring: %1", name ) );
else
m_transferjob->emitInfo( i18n( "Transcoding: %1", name ) );
m_jobs.insert( job, track );
return true;
}
debug() << "JOB NULL OMG!11";
}
return false;
}
bool SqlCollectionLocation::startNextRemoveJob()
{
DEBUG_BLOCK
while ( !m_removetracks.isEmpty() )
{
Meta::TrackPtr track = m_removetracks.takeFirst();
// QUrl src = track->playableUrl();
QUrl src = track->playableUrl();
QUrl srcMoodFile = moodFile( src );
debug() << "isGoingToRemoveSources() " << isGoingToRemoveSources();
if( isGoingToRemoveSources() && destination() ) // is organize operation?
{
SqlCollectionLocation* destinationloc = dynamic_cast<SqlCollectionLocation*>( destination() );
// src = destinationloc->m_originalUrls[track];
if( destinationloc && src == QUrl::fromUserInput(destinationloc->m_destinations[track]) ) {
debug() << "src == dst ("<<src<<")";
continue;
}
}
src.setPath( QDir::cleanPath(src.path()) );
debug() << "deleting " << src;
KIO::DeleteJob *job = KIO::del( src, KIO::HideProgressInfo );
if( job ) //just to be safe
{
if( QFile::exists( srcMoodFile.toLocalFile() ) )
KIO::del( srcMoodFile, KIO::HideProgressInfo );
- connect( job, SIGNAL(result(KJob*)), SLOT(slotRemoveJobFinished(KJob*)) );
+ connect( job, &KIO::DeleteJob::result, this, &SqlCollectionLocation::slotRemoveJobFinished );
QString name = track->prettyName();
if( track->artist() )
name = QString( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() );
Amarok::Components::logger()->newProgressOperation( job, i18n( "Removing: %1", name ) );
m_removejobs.insert( job, track );
return true;
}
break;
}
return false;
}
QUrl
SqlCollectionLocation::moodFile( const QUrl &track ) const
{
QUrl moodPath = track;
moodPath = moodPath.adjusted(QUrl::RemoveFilename);
moodPath.setPath(moodPath.path() + '.' + moodPath.fileName().replace( QRegExp( "(\\.\\w{2,5})$" ), ".mood" ) );
return moodPath;
}
TransferJob::TransferJob( SqlCollectionLocation * location, const Transcoding::Configuration & configuration )
: KCompositeJob( 0 )
, m_location( location )
, m_killed( false )
, m_transcodeFormat( configuration )
{
setCapabilities( KJob::Killable );
debug() << "TransferJob::TransferJob";
}
bool TransferJob::addSubjob( KJob* job )
{
- connect( job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)),
- this, SLOT(propagateProcessedAmount(KJob*,KJob::Unit,qulonglong)) );
+ connect( job, SIGNAL(processedAmount(KJob*, KJob::Unit, qulonglong)),
+ this, SLOT(propagateProcessedAmount(KJob*, KJob::Unit, qulonglong)) );
//KCompositeJob::addSubjob doesn't handle progress reporting.
return KCompositeJob::addSubjob( job );
}
void TransferJob::emitInfo(const QString& message)
{
emit infoMessage( this, message );
}
void TransferJob::slotResult( KJob *job )
{
// When copying without overwriting some files might already be
// there and it is not a reason for stopping entire transfer.
if ( job->error() == KIO::ERR_FILE_ALREADY_EXIST )
removeSubjob( job );
else
KCompositeJob::slotResult( job );
}
void TransferJob::start()
{
DEBUG_BLOCK
if( m_location == 0 )
{
setError( 1 );
setErrorText( "Location is null!" );
emitResult();
return;
}
- QTimer::singleShot( 0, this, SLOT(doWork()) );
+ QTimer::singleShot( 0, this, &TransferJob::doWork );
}
void TransferJob::doWork()
{
DEBUG_BLOCK
setTotalAmount( KJob::Files, m_location->m_sources.size() );
setTotalAmount( KJob::Bytes, m_location->m_sources.size() * 1000 );
setProcessedAmount( KJob::Files, 0 );
if( !m_location->startNextJob( m_transcodeFormat ) )
{
if( !hasSubjobs() )
emitResult();
}
}
void TransferJob::slotJobFinished( KJob* job )
{
DEBUG_BLOCK
if( job )
removeSubjob( job );
if( m_killed )
{
debug() << "slotJobFinished entered, but it should be killed!";
return;
}
setProcessedAmount( KJob::Files, processedAmount( KJob::Files ) + 1 );
emitPercent( processedAmount( KJob::Files ) * 1000, totalAmount( KJob::Bytes ) );
debug() << "processed" << processedAmount( KJob::Files ) << " totalAmount" << totalAmount( KJob::Files );
if( !m_location->startNextJob( m_transcodeFormat ) )
{
debug() << "sources empty";
// don't quit if there are still subjobs
if( !hasSubjobs() )
emitResult();
else
debug() << "have subjobs";
}
}
bool TransferJob::doKill()
{
DEBUG_BLOCK
m_killed = true;
foreach( KJob* job, subjobs() )
{
job->kill();
}
clearSubjobs();
return KJob::doKill();
}
void TransferJob::propagateProcessedAmount( KJob *job, KJob::Unit unit, qulonglong amount ) //SLOT
{
if( unit == KJob::Bytes )
{
qulonglong currentJobAmount = ( static_cast< qreal >( amount ) / job->totalAmount( KJob::Bytes ) ) * 1000;
setProcessedAmount( KJob::Bytes, processedAmount( KJob::Files ) * 1000 + currentJobAmount );
emitPercent( processedAmount( KJob::Bytes ), totalAmount( KJob::Bytes ) );
}
}
diff --git a/src/core-impl/collections/db/sql/SqlMeta.cpp b/src/core-impl/collections/db/sql/SqlMeta.cpp
index 0088671e92..b744ae7f94 100644
--- a/src/core-impl/collections/db/sql/SqlMeta.cpp
+++ b/src/core-impl/collections/db/sql/SqlMeta.cpp
@@ -1,2244 +1,2244 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Daniel Winter <dw@danielwinter.de> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SqlMeta"
#include "SqlMeta.h"
#include "amarokconfig.h"
#include "SqlCapabilities.h"
#include "SqlCollection.h"
#include "SqlQueryMaker.h"
#include "SqlRegistry.h"
#include "SqlReadLabelCapability.h"
#include "SqlWriteLabelCapability.h"
#include "MetaTagLib.h" // for getting an embedded cover
#include "amarokurls/BookmarkMetaActions.h"
#include <core/storage/SqlStorage.h>
#include "core/meta/support/MetaUtility.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/capabilities/BookmarkThisCapability.h"
#include "core-impl/capabilities/AlbumActionsCapability.h"
#include "core-impl/collections/db/MountPointManager.h"
#include "core-impl/collections/support/ArtistHelper.h"
#include "core-impl/collections/support/jobs/WriteTagsJob.h"
#include "covermanager/CoverCache.h"
#include "covermanager/CoverFetcher.h"
#include <QAction>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QMultiHash>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <QMutexLocker>
#include <QCryptographicHash>
#include <KCodecs>
#include <KLocale>
#include <KSharedPtr>
#include <KMD5>
#include <ThreadWeaver/Queue>
// additional constants
namespace Meta
{
static const qint64 valAlbumId = valCustom + 1;
}
using namespace Meta;
QString
SqlTrack::getTrackReturnValues()
{
//do not use any weird column names that contains commas: this will break getTrackReturnValuesCount()
// NOTE: when changing this, always check that SqlTrack::TrackReturnIndex enum remains valid
return "urls.id, urls.deviceid, urls.rpath, urls.directory, urls.uniqueid, "
"tracks.id, tracks.title, tracks.comment, "
"tracks.tracknumber, tracks.discnumber, "
"statistics.score, statistics.rating, "
"tracks.bitrate, tracks.length, "
"tracks.filesize, tracks.samplerate, "
"statistics.id, "
"statistics.createdate, statistics.accessdate, "
"statistics.playcount, tracks.filetype, tracks.bpm, "
"tracks.createdate, tracks.modifydate, tracks.albumgain, tracks.albumpeakgain, "
"tracks.trackgain, tracks.trackpeakgain, "
"artists.name, artists.id, " // TODO: just reading the id should be sufficient
"albums.name, albums.id, albums.artist, " // TODO: again here
"genres.name, genres.id, " // TODO: again here
"composers.name, composers.id, " // TODO: again here
"years.name, years.id"; // TODO: again here
}
QString
SqlTrack::getTrackJoinConditions()
{
return "LEFT JOIN tracks ON urls.id = tracks.url "
"LEFT JOIN statistics ON urls.id = statistics.url "
"LEFT JOIN artists ON tracks.artist = artists.id "
"LEFT JOIN albums ON tracks.album = albums.id "
"LEFT JOIN genres ON tracks.genre = genres.id "
"LEFT JOIN composers ON tracks.composer = composers.id "
"LEFT JOIN years ON tracks.year = years.id";
}
int
SqlTrack::getTrackReturnValueCount()
{
static int count = getTrackReturnValues().split( ',' ).count();
return count;
}
SqlTrack::SqlTrack( Collections::SqlCollection *collection, int deviceId,
const QString &rpath, int directoryId, const QString uidUrl )
: Track()
, m_collection( collection )
, m_batchUpdate( 0 )
, m_writeFile( true )
, m_labelsInCache( false )
{
m_batchUpdate = 1; // I don't want commits yet
m_urlId = -1; // this will be set with the first database write
m_trackId = -1; // this will be set with the first database write
m_statisticsId = -1;
setUrl( deviceId, rpath, directoryId );
m_url = QUrl::fromUserInput(m_cache.value( Meta::valUrl ).toString()); // SqlRegistry already has this url
setUidUrl( uidUrl );
m_uid = m_cache.value( Meta::valUniqueId ).toString(); // SqlRegistry already has this uid
// ensure that these values get a correct database id
m_cache.insert( Meta::valAlbum, QVariant() );
m_cache.insert( Meta::valArtist, QVariant() );
m_cache.insert( Meta::valComposer, QVariant() );
m_cache.insert( Meta::valYear, QVariant() );
m_cache.insert( Meta::valGenre, QVariant() );
m_trackNumber = 0;
m_discNumber = 0;
m_score = 0;
m_rating = 0;
m_bitrate = 0;
m_length = 0;
m_filesize = 0;
m_sampleRate = 0;
m_playCount = 0;
m_bpm = 0.0;
m_createDate = QDateTime::currentDateTime();
m_cache.insert( Meta::valCreateDate, m_createDate ); // ensure that the created date is written the next time
m_trackGain = 0.0;
m_trackPeakGain = 0.0;
m_albumGain = 0.0;
m_albumPeakGain = 0.0;
m_batchUpdate = 0; // reset in-batch-update without committing
m_filetype = Amarok::Unknown;
}
SqlTrack::SqlTrack( Collections::SqlCollection *collection, const QStringList &result )
: Track()
, m_collection( collection )
, m_batchUpdate( 0 )
, m_writeFile( true )
, m_labelsInCache( false )
{
QStringList::ConstIterator iter = result.constBegin();
m_urlId = (*(iter++)).toInt();
Q_ASSERT( m_urlId > 0 && "refusing to create SqlTrack with non-positive urlId, please file a bug" );
m_deviceId = (*(iter++)).toInt();
Q_ASSERT( m_deviceId != 0 && "refusing to create SqlTrack with zero deviceId, please file a bug" );
m_rpath = *(iter++);
m_directoryId = (*(iter++)).toInt();
Q_ASSERT( m_directoryId > 0 && "refusing to create SqlTrack with non-positive directoryId, please file a bug" );
m_url = QUrl::fromUserInput( m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) );
m_uid = *(iter++);
m_trackId = (*(iter++)).toInt();
m_title = *(iter++);
m_comment = *(iter++);
m_trackNumber = (*(iter++)).toInt();
m_discNumber = (*(iter++)).toInt();
m_score = (*(iter++)).toDouble();
m_rating = (*(iter++)).toInt();
m_bitrate = (*(iter++)).toInt();
m_length = (*(iter++)).toInt();
m_filesize = (*(iter++)).toInt();
m_sampleRate = (*(iter++)).toInt();
m_statisticsId = (*(iter++)).toInt();
uint time = (*(iter++)).toUInt();
if( time > 0 )
m_firstPlayed = QDateTime::fromTime_t(time);
time = (*(iter++)).toUInt();
if( time > 0 )
m_lastPlayed = QDateTime::fromTime_t(time);
m_playCount = (*(iter++)).toInt();
m_filetype = Amarok::FileType( (*(iter++)).toInt() );
m_bpm = (*(iter++)).toFloat();
m_createDate = QDateTime::fromTime_t((*(iter++)).toUInt());
m_modifyDate = QDateTime::fromTime_t((*(iter++)).toUInt());
// if there is no track gain, we assume a gain of 0
// if there is no album gain, we use the track gain
QString albumGain = *(iter++);
QString albumPeakGain = *(iter++);
m_trackGain = (*(iter++)).toDouble();
m_trackPeakGain = (*(iter++)).toDouble();
if ( albumGain.isEmpty() )
{
m_albumGain = m_trackGain;
m_albumPeakGain = m_trackPeakGain;
}
else
{
m_albumGain = albumGain.toDouble();
m_albumPeakGain = albumPeakGain.toDouble();
}
SqlRegistry* registry = m_collection->registry();
QString artist = *(iter++);
int artistId = (*(iter++)).toInt();
if( artistId > 0 )
m_artist = registry->getArtist( artistId, artist );
QString album = *(iter++);
int albumId =(*(iter++)).toInt();
int albumArtistId = (*(iter++)).toInt();
if( albumId > 0 ) // sanity check
m_album = registry->getAlbum( albumId, album, albumArtistId );
QString genre = *(iter++);
int genreId = (*(iter++)).toInt();
if( genreId > 0 ) // sanity check
m_genre = registry->getGenre( genreId, genre );
QString composer = *(iter++);
int composerId = (*(iter++)).toInt();
if( composerId > 0 ) // sanity check
m_composer = registry->getComposer( composerId, composer );
QString year = *(iter++);
int yearId = (*(iter++)).toInt();
if( yearId > 0 ) // sanity check
m_year = registry->getYear( year.toInt(), yearId );
//Q_ASSERT_X( iter == result.constEnd(), "SqlTrack( Collections::SqlCollection*, QStringList )", "number of expected fields did not match number of actual fields: expected " + result.size() );
}
SqlTrack::~SqlTrack()
{
QWriteLocker locker( &m_lock );
if( !m_cache.isEmpty() )
warning() << "Destroying track with unwritten meta information." << m_title << "cache:" << m_cache;
if( m_batchUpdate )
warning() << "Destroying track with unclosed batch update." << m_title;
}
QString
SqlTrack::name() const
{
QReadLocker locker( &m_lock );
return m_title;
}
QString
SqlTrack::prettyName() const
{
if ( !name().isEmpty() )
return name();
return prettyTitle( m_url.fileName() );
}
void
SqlTrack::setTitle( const QString &newTitle )
{
QWriteLocker locker( &m_lock );
if ( m_title != newTitle )
commitIfInNonBatchUpdate( Meta::valTitle, newTitle );
}
QUrl
SqlTrack::playableUrl() const
{
QReadLocker locker( &m_lock );
return m_url;
}
QString
SqlTrack::prettyUrl() const
{
QReadLocker locker( &m_lock );
return m_url.path();
}
void
SqlTrack::setUrl( int deviceId, const QString &rpath, int directoryId )
{
QWriteLocker locker( &m_lock );
if( m_deviceId == deviceId &&
m_rpath == rpath &&
m_directoryId == directoryId )
return;
m_deviceId = deviceId;
m_rpath = rpath;
m_directoryId = directoryId;
commitIfInNonBatchUpdate( Meta::valUrl,
m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) );
}
QString
SqlTrack::uidUrl() const
{
QReadLocker locker( &m_lock );
return m_uid;
}
void
SqlTrack::setUidUrl( const QString &uid )
{
QWriteLocker locker( &m_lock );
// -- ensure that the uid starts with the collections protocol (amarok-sqltrackuid)
QString newid = uid;
QString protocol;
if( m_collection )
protocol = m_collection->uidUrlProtocol()+"://";
if( !newid.startsWith( protocol ) )
newid.prepend( protocol );
m_cache.insert( Meta::valUniqueId, newid );
if( m_batchUpdate == 0 )
{
debug() << "setting uidUrl manually...did you really mean to do this?";
commitIfInNonBatchUpdate();
}
}
QString
SqlTrack::notPlayableReason() const
{
return localFileNotPlayableReason( playableUrl().toLocalFile() );
}
bool
SqlTrack::isEditable() const
{
QReadLocker locker( &m_lock );
QFile::Permissions p = QFile::permissions( m_url.path() );
const bool editable = ( p & QFile::WriteUser ) || ( p & QFile::WriteGroup ) || ( p & QFile::WriteOther );
return m_collection && QFile::exists( m_url.path() ) && editable;
}
Meta::AlbumPtr
SqlTrack::album() const
{
QReadLocker locker( &m_lock );
return m_album;
}
void
SqlTrack::setAlbum( const QString &newAlbum )
{
QWriteLocker locker( &m_lock );
if( !m_album || m_album->name() != newAlbum )
commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum );
}
void
SqlTrack::setAlbum( int albumId )
{
QWriteLocker locker( &m_lock );
commitIfInNonBatchUpdate( Meta::valAlbumId, albumId );
}
Meta::ArtistPtr
SqlTrack::artist() const
{
QReadLocker locker( &m_lock );
return m_artist;
}
void
SqlTrack::setArtist( const QString &newArtist )
{
QWriteLocker locker( &m_lock );
if( !m_artist || m_artist->name() != newArtist )
commitIfInNonBatchUpdate( Meta::valArtist, newArtist );
}
void
SqlTrack::setAlbumArtist( const QString &newAlbumArtist )
{
if( m_album.isNull() )
return;
if( !newAlbumArtist.compare( "Various Artists", Qt::CaseInsensitive ) ||
!newAlbumArtist.compare( i18n( "Various Artists" ), Qt::CaseInsensitive ) )
{
commitIfInNonBatchUpdate( Meta::valCompilation, true );
}
else
{
m_cache.insert( Meta::valAlbumArtist, ArtistHelper::realTrackArtist( newAlbumArtist ) );
m_cache.insert( Meta::valCompilation, false );
commitIfInNonBatchUpdate();
}
}
Meta::ComposerPtr
SqlTrack::composer() const
{
QReadLocker locker( &m_lock );
return m_composer;
}
void
SqlTrack::setComposer( const QString &newComposer )
{
QWriteLocker locker( &m_lock );
if( !m_composer || m_composer->name() != newComposer )
commitIfInNonBatchUpdate( Meta::valComposer, newComposer );
}
Meta::YearPtr
SqlTrack::year() const
{
QReadLocker locker( &m_lock );
return m_year;
}
void
SqlTrack::setYear( int newYear )
{
QWriteLocker locker( &m_lock );
if( !m_year || m_year->year() != newYear )
commitIfInNonBatchUpdate( Meta::valYear, newYear );
}
Meta::GenrePtr
SqlTrack::genre() const
{
QReadLocker locker( &m_lock );
return m_genre;
}
void
SqlTrack::setGenre( const QString &newGenre )
{
QWriteLocker locker( &m_lock );
if( !m_genre || m_genre->name() != newGenre )
commitIfInNonBatchUpdate( Meta::valGenre, newGenre );
}
QString
SqlTrack::type() const
{
QReadLocker locker( &m_lock );
return m_url.isLocalFile()
? Amarok::FileTypeSupport::toString( m_filetype )
// don't localize. This is used in different files to identify streams, see EngineController quirks
: "stream";
}
void
SqlTrack::setType( Amarok::FileType newType )
{
QWriteLocker locker( &m_lock );
if ( m_filetype != newType )
commitIfInNonBatchUpdate( Meta::valFormat, int(newType) );
}
qreal
SqlTrack::bpm() const
{
QReadLocker locker( &m_lock );
return m_bpm;
}
void
SqlTrack::setBpm( const qreal newBpm )
{
QWriteLocker locker( &m_lock );
if ( m_bpm != newBpm )
commitIfInNonBatchUpdate( Meta::valBpm, newBpm );
}
QString
SqlTrack::comment() const
{
QReadLocker locker( &m_lock );
return m_comment;
}
void
SqlTrack::setComment( const QString &newComment )
{
QWriteLocker locker( &m_lock );
if( newComment != m_comment )
commitIfInNonBatchUpdate( Meta::valComment, newComment );
}
double
SqlTrack::score() const
{
QReadLocker locker( &m_lock );
return m_score;
}
void
SqlTrack::setScore( double newScore )
{
QWriteLocker locker( &m_lock );
newScore = qBound( double(0), newScore, double(100) );
if( qAbs( newScore - m_score ) > 0.001 ) // we don't commit for minimal changes
commitIfInNonBatchUpdate( Meta::valScore, newScore );
}
int
SqlTrack::rating() const
{
QReadLocker locker( &m_lock );
return m_rating;
}
void
SqlTrack::setRating( int newRating )
{
QWriteLocker locker( &m_lock );
newRating = qBound( 0, newRating, 10 );
if( newRating != m_rating )
commitIfInNonBatchUpdate( Meta::valRating, newRating );
}
qint64
SqlTrack::length() const
{
QReadLocker locker( &m_lock );
return m_length;
}
void
SqlTrack::setLength( qint64 newLength )
{
QWriteLocker locker( &m_lock );
if( newLength != m_length )
commitIfInNonBatchUpdate( Meta::valLength, newLength );
}
int
SqlTrack::filesize() const
{
QReadLocker locker( &m_lock );
return m_filesize;
}
int
SqlTrack::sampleRate() const
{
QReadLocker locker( &m_lock );
return m_sampleRate;
}
void
SqlTrack::setSampleRate( int newSampleRate )
{
QWriteLocker locker( &m_lock );
if( newSampleRate != m_sampleRate )
commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate );
}
int
SqlTrack::bitrate() const
{
QReadLocker locker( &m_lock );
return m_bitrate;
}
void
SqlTrack::setBitrate( int newBitrate )
{
QWriteLocker locker( &m_lock );
if( newBitrate != m_bitrate )
commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate );
}
QDateTime
SqlTrack::createDate() const
{
QReadLocker locker( &m_lock );
return m_createDate;
}
QDateTime
SqlTrack::modifyDate() const
{
QReadLocker locker( &m_lock );
return m_modifyDate;
}
void
SqlTrack::setModifyDate( const QDateTime &newTime )
{
QWriteLocker locker( &m_lock );
if( newTime != m_modifyDate )
commitIfInNonBatchUpdate( Meta::valModified, newTime );
}
int
SqlTrack::trackNumber() const
{
QReadLocker locker( &m_lock );
return m_trackNumber;
}
void
SqlTrack::setTrackNumber( int newTrackNumber )
{
QWriteLocker locker( &m_lock );
if( newTrackNumber != m_trackNumber )
commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber );
}
int
SqlTrack::discNumber() const
{
QReadLocker locker( &m_lock );
return m_discNumber;
}
void
SqlTrack::setDiscNumber( int newDiscNumber )
{
QWriteLocker locker( &m_lock );
if( newDiscNumber != m_discNumber )
commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber );
}
QDateTime
SqlTrack::lastPlayed() const
{
QReadLocker locker( &m_lock );
return m_lastPlayed;
}
void
SqlTrack::setLastPlayed( const QDateTime &newTime )
{
QWriteLocker locker( &m_lock );
if( newTime != m_lastPlayed )
commitIfInNonBatchUpdate( Meta::valLastPlayed, newTime );
}
QDateTime
SqlTrack::firstPlayed() const
{
QReadLocker locker( &m_lock );
return m_firstPlayed;
}
void
SqlTrack::setFirstPlayed( const QDateTime &newTime )
{
QWriteLocker locker( &m_lock );
if( newTime != m_firstPlayed )
commitIfInNonBatchUpdate( Meta::valFirstPlayed, newTime );
}
int
SqlTrack::playCount() const
{
QReadLocker locker( &m_lock );
return m_playCount;
}
void
SqlTrack::setPlayCount( const int newCount )
{
QWriteLocker locker( &m_lock );
if( newCount != m_playCount )
commitIfInNonBatchUpdate( Meta::valPlaycount, newCount );
}
qreal
SqlTrack::replayGain( ReplayGainTag mode ) const
{
QReadLocker locker(&(const_cast<SqlTrack*>(this)->m_lock));
switch( mode )
{
case Meta::ReplayGain_Track_Gain:
return m_trackGain;
case Meta::ReplayGain_Track_Peak:
return m_trackPeakGain;
case Meta::ReplayGain_Album_Gain:
return m_albumGain;
case Meta::ReplayGain_Album_Peak:
return m_albumPeakGain;
}
return 0.0;
}
void
SqlTrack::setReplayGain( Meta::ReplayGainTag mode, qreal value )
{
if( qAbs( value - replayGain( mode ) ) < 0.01 )
return;
{
QWriteLocker locker( &m_lock );
switch( mode )
{
case Meta::ReplayGain_Track_Gain:
m_cache.insert( Meta::valTrackGain, value );
break;
case Meta::ReplayGain_Track_Peak:
m_cache.insert( Meta::valTrackGainPeak, value );
break;
case Meta::ReplayGain_Album_Gain:
m_cache.insert( Meta::valAlbumGain, value );
break;
case Meta::ReplayGain_Album_Peak:
m_cache.insert( Meta::valAlbumGainPeak, value );
break;
}
commitIfInNonBatchUpdate();
}
}
void
SqlTrack::beginUpdate()
{
QWriteLocker locker( &m_lock );
m_batchUpdate++;
}
void
SqlTrack::endUpdate()
{
QWriteLocker locker( &m_lock );
Q_ASSERT( m_batchUpdate > 0 );
m_batchUpdate--;
commitIfInNonBatchUpdate();
}
void
SqlTrack::commitIfInNonBatchUpdate( qint64 field, const QVariant &value )
{
m_cache.insert( field, value );
commitIfInNonBatchUpdate();
}
void
SqlTrack::commitIfInNonBatchUpdate()
{
if( m_batchUpdate > 0 || m_cache.isEmpty() )
return; // nothing to do
// debug() << "SqlTrack::commitMetaDataChanges " << m_cache;
QString oldUid = m_uid;
// for all the following objects we need to invalidate the cache and
// notify the observers after the update
KSharedPtr<SqlArtist> oldArtist;
KSharedPtr<SqlArtist> newArtist;
KSharedPtr<SqlAlbum> oldAlbum;
KSharedPtr<SqlAlbum> newAlbum;
KSharedPtr<SqlComposer> oldComposer;
KSharedPtr<SqlComposer> newComposer;
KSharedPtr<SqlGenre> oldGenre;
KSharedPtr<SqlGenre> newGenre;
KSharedPtr<SqlYear> oldYear;
KSharedPtr<SqlYear> newYear;
if( m_cache.contains( Meta::valFormat ) )
m_filetype = Amarok::FileType(m_cache.value( Meta::valFormat ).toInt());
if( m_cache.contains( Meta::valTitle ) )
m_title = m_cache.value( Meta::valTitle ).toString();
if( m_cache.contains( Meta::valComment ) )
m_comment = m_cache.value( Meta::valComment ).toString();
if( m_cache.contains( Meta::valScore ) )
m_score = m_cache.value( Meta::valScore ).toDouble();
if( m_cache.contains( Meta::valRating ) )
m_rating = m_cache.value( Meta::valRating ).toInt();
if( m_cache.contains( Meta::valLength ) )
m_length = m_cache.value( Meta::valLength ).toLongLong();
if( m_cache.contains( Meta::valSamplerate ) )
m_sampleRate = m_cache.value( Meta::valSamplerate ).toInt();
if( m_cache.contains( Meta::valBitrate ) )
m_bitrate = m_cache.value( Meta::valBitrate ).toInt();
if( m_cache.contains( Meta::valFirstPlayed ) )
m_firstPlayed = m_cache.value( Meta::valFirstPlayed ).toDateTime();
if( m_cache.contains( Meta::valLastPlayed ) )
m_lastPlayed = m_cache.value( Meta::valLastPlayed ).toDateTime();
if( m_cache.contains( Meta::valTrackNr ) )
m_trackNumber = m_cache.value( Meta::valTrackNr ).toInt();
if( m_cache.contains( Meta::valDiscNr ) )
m_discNumber = m_cache.value( Meta::valDiscNr ).toInt();
if( m_cache.contains( Meta::valPlaycount ) )
m_playCount = m_cache.value( Meta::valPlaycount ).toInt();
if( m_cache.contains( Meta::valCreateDate ) )
m_createDate = m_cache.value( Meta::valCreateDate ).toDateTime();
if( m_cache.contains( Meta::valModified ) )
m_modifyDate = m_cache.value( Meta::valModified ).toDateTime();
if( m_cache.contains( Meta::valTrackGain ) )
m_trackGain = m_cache.value( Meta::valTrackGain ).toDouble();
if( m_cache.contains( Meta::valTrackGainPeak ) )
m_trackPeakGain = m_cache.value( Meta::valTrackGainPeak ).toDouble();
if( m_cache.contains( Meta::valAlbumGain ) )
m_albumGain = m_cache.value( Meta::valAlbumGain ).toDouble();
if( m_cache.contains( Meta::valAlbumGainPeak ) )
m_albumPeakGain = m_cache.value( Meta::valAlbumGainPeak ).toDouble();
if( m_cache.contains( Meta::valUrl ) )
{
// slight problem here: it is possible to set the url to the one of an already
// existing track, which is forbidden by the database
// At least the ScanResultProcessor handles this problem
QUrl oldUrl = m_url;
QUrl newUrl = QUrl::fromUserInput(m_cache.value( Meta::valUrl ).toString());
if( oldUrl != newUrl )
m_collection->registry()->updateCachedUrl( oldUrl.path(), newUrl.path() );
m_url = newUrl;
// debug() << "m_cache contains a new URL, setting m_url to " << m_url << " from " << oldUrl;
}
if( m_cache.contains( Meta::valArtist ) )
{
//invalidate cache of the old artist...
oldArtist = static_cast<SqlArtist*>(m_artist.data());
m_artist = m_collection->registry()->getArtist( m_cache.value( Meta::valArtist ).toString() );
//and the new one
newArtist = static_cast<SqlArtist*>(m_artist.data());
// if the current album is no compilation and we aren't changing
// the album anyway, then we need to create a new album with the
// new artist.
if( m_album )
{
bool supp = m_album->suppressImageAutoFetch();
m_album->setSuppressImageAutoFetch( true );
if( m_album->hasAlbumArtist() &&
m_album->albumArtist() == oldArtist &&
!m_cache.contains( Meta::valAlbum ) &&
!m_cache.contains( Meta::valAlbumId ) )
{
m_cache.insert( Meta::valAlbum, m_album->name() );
}
m_album->setSuppressImageAutoFetch( supp );
}
}
if( m_cache.contains( Meta::valAlbum ) ||
m_cache.contains( Meta::valAlbumId ) ||
m_cache.contains( Meta::valAlbumArtist ) )
{
oldAlbum = static_cast<SqlAlbum*>(m_album.data());
if( m_cache.contains( Meta::valAlbumId ) )
m_album = m_collection->registry()->getAlbum( m_cache.value( Meta::valAlbumId ).toInt() );
else
{
// the album should remain a compilation after renaming it
// TODO: we would need to use the artist helper
QString newArtistName;
if( m_cache.contains( Meta::valAlbumArtist ) )
newArtistName = m_cache.value( Meta::valAlbumArtist ).toString();
else if( oldAlbum && oldAlbum->isCompilation() && !oldAlbum->name().isEmpty() )
newArtistName.clear();
else if( oldAlbum && oldAlbum->hasAlbumArtist() )
newArtistName = oldAlbum->albumArtist()->name();
m_album = m_collection->registry()->getAlbum( m_cache.contains( Meta::valAlbum)
? m_cache.value( Meta::valAlbum ).toString()
: oldAlbum->name(),
newArtistName );
}
newAlbum = static_cast<SqlAlbum*>(m_album.data());
// due to the complex logic with artist and albumId it can happen that
// in the end we have the same album as before.
if( newAlbum == oldAlbum )
{
m_cache.remove( Meta::valAlbum );
m_cache.remove( Meta::valAlbumId );
m_cache.remove( Meta::valAlbumArtist );
oldAlbum.clear();
newAlbum.clear();
}
}
if( m_cache.contains( Meta::valComposer ) )
{
oldComposer = static_cast<SqlComposer*>(m_composer.data());
m_composer = m_collection->registry()->getComposer( m_cache.value( Meta::valComposer ).toString() );
newComposer = static_cast<SqlComposer*>(m_composer.data());
}
if( m_cache.contains( Meta::valGenre ) )
{
oldGenre = static_cast<SqlGenre*>(m_genre.data());
m_genre = m_collection->registry()->getGenre( m_cache.value( Meta::valGenre ).toString() );
newGenre = static_cast<SqlGenre*>(m_genre.data());
}
if( m_cache.contains( Meta::valYear ) )
{
oldYear = static_cast<SqlYear*>(m_year.data());
m_year = m_collection->registry()->getYear( m_cache.value( Meta::valYear ).toInt() );
newYear = static_cast<SqlYear*>(m_year.data());
}
if( m_cache.contains( Meta::valBpm ) )
m_bpm = m_cache.value( Meta::valBpm ).toDouble();
// --- write the file
if( m_writeFile && AmarokConfig::writeBack() )
{
Meta::Tag::writeTags( m_url.path(), m_cache, AmarokConfig::writeBackStatistics() );
// unique id may have changed
QString uid = Meta::Tag::readTags( m_url.path() ).value( Meta::valUniqueId ).toString();
if( !uid.isEmpty() )
m_cache[ Meta::valUniqueId ] = m_collection->generateUidUrl( uid );
}
// needs to be after writing to file; that may have changed generated uid
if( m_cache.contains( Meta::valUniqueId ) )
{
QString newUid = m_cache.value( Meta::valUniqueId ).toString();
if( oldUid != newUid && m_collection->registry()->updateCachedUid( oldUid, newUid ) )
m_uid = newUid;
}
//updating the fields might have changed the filesize
//read the current filesize so that we can update the db
QFile file( m_url.path() );
if( file.exists() )
{
if( m_filesize != file.size() )
{
m_cache.insert( Meta::valFilesize, file.size() );
m_filesize = file.size();
}
}
// --- add to the registry dirty list
SqlRegistry *registry = 0;
// prevent writing to the db when we don't know the directory, bug 322474. Note that
// m_urlId is created by registry->commitDirtyTracks() if there is none.
if( m_deviceId != 0 && m_directoryId > 0 )
{
registry = m_collection->registry();
QMutexLocker locker2( &registry->m_blockMutex );
registry->m_dirtyTracks.insert( Meta::SqlTrackPtr( this ) );
if( oldArtist )
registry->m_dirtyArtists.insert( oldArtist );
if( newArtist )
registry->m_dirtyArtists.insert( newArtist );
if( oldAlbum )
registry->m_dirtyAlbums.insert( oldAlbum );
if( newAlbum )
registry->m_dirtyAlbums.insert( newAlbum );
if( oldComposer )
registry->m_dirtyComposers.insert( oldComposer );
if( newComposer )
registry->m_dirtyComposers.insert( newComposer );
if( oldGenre )
registry->m_dirtyGenres.insert( oldGenre );
if( newGenre )
registry->m_dirtyGenres.insert( newGenre );
if( oldYear )
registry->m_dirtyYears.insert( oldYear );
if( newYear )
registry->m_dirtyYears.insert( newYear );
}
else
error() << Q_FUNC_INFO << "non-positive urlId, zero deviceId or non-positive"
<< "directoryId encountered in track" << m_url
<< "urlId:" << m_urlId << "deviceId:" << m_deviceId
<< "directoryId:" << m_directoryId << "- not writing back metadata"
<< "changes to the database.";
m_lock.unlock(); // or else we provoke a deadlock
// copy the image BUG: 203211 (we need to do it here or provoke a dead lock)
if( oldAlbum && newAlbum )
{
bool oldSupp = oldAlbum->suppressImageAutoFetch();
bool newSupp = newAlbum->suppressImageAutoFetch();
oldAlbum->setSuppressImageAutoFetch( true );
newAlbum->setSuppressImageAutoFetch( true );
if( oldAlbum->hasImage() && !newAlbum->hasImage() )
newAlbum->setImage( oldAlbum->imageLocation().path() );
oldAlbum->setSuppressImageAutoFetch( oldSupp );
newAlbum->setSuppressImageAutoFetch( newSupp );
}
if( registry )
registry->commitDirtyTracks(); // calls notifyObservers() as appropriate
else
notifyObservers();
m_lock.lockForWrite(); // reset back to state it was during call
if( m_uid != oldUid )
{
updatePlaylistsToDb( m_cache, oldUid );
updateEmbeddedCoversToDb( m_cache, oldUid );
}
// --- clean up
m_cache.clear();
}
void
SqlTrack::updatePlaylistsToDb( const FieldHash &fields, const QString &oldUid )
{
if( fields.isEmpty() )
return; // nothing to do
SqlStorage *storage = m_collection->sqlStorage();
QStringList tags;
// keep this in sync with SqlPlaylist::saveTracks()!
if( fields.contains( Meta::valUrl ) )
tags << QString( "url='%1'" ).arg( storage->escape( m_url.path() ) );
if( fields.contains( Meta::valTitle ) )
tags << QString( "title='%1'" ).arg( storage->escape( m_title ) );
if( fields.contains( Meta::valAlbum ) )
tags << QString( "album='%1'" ).arg( m_album ? storage->escape( m_album->prettyName() ) : "" );
if( fields.contains( Meta::valArtist ) )
tags << QString( "artist='%1'" ).arg( m_artist ? storage->escape( m_artist->prettyName() ) : "" );
if( fields.contains( Meta::valLength ) )
tags << QString( "length=%1").arg( QString::number( m_length ) );
if( fields.contains( Meta::valUniqueId ) )
{
// SqlPlaylist mirrors uniqueid to url, update it too, bug 312128
tags << QString( "url='%1'" ).arg( storage->escape( m_uid ) );
tags << QString( "uniqueid='%1'" ).arg( storage->escape( m_uid ) );
}
if( !tags.isEmpty() )
{
QString update = "UPDATE playlist_tracks SET %1 WHERE uniqueid = '%2';";
update = update.arg( tags.join( ", " ), storage->escape( oldUid ) );
storage->query( update );
}
}
void
SqlTrack::updateEmbeddedCoversToDb( const FieldHash &fields, const QString &oldUid )
{
if( fields.isEmpty() )
return; // nothing to do
SqlStorage *storage = m_collection->sqlStorage();
QString tags;
if( fields.contains( Meta::valUniqueId ) )
tags += QString( ",path='%1'" ).arg( storage->escape( m_uid ) );
if( !tags.isEmpty() )
{
tags = tags.remove(0, 1); // the first character is always a ','
QString update = "UPDATE images SET %1 WHERE path = '%2';";
update = update.arg( tags, storage->escape( oldUid ) );
storage->query( update );
}
}
QString
SqlTrack::prettyTitle( const QString &filename ) //static
{
QString s = filename; //just so the code is more readable
//remove .part extension if it exists
if (s.endsWith( ".part" ))
s = s.left( s.length() - 5 );
//remove file extension, s/_/ /g and decode %2f-like sequences
s = s.left( s.lastIndexOf( '.' ) ).replace( '_', ' ' );
s = QUrl::fromPercentEncoding( s.toAscii() );
return s;
}
bool
SqlTrack::inCollection() const
{
QReadLocker locker( &m_lock );
return m_trackId > 0;
}
Collections::Collection*
SqlTrack::collection() const
{
return m_collection;
}
QString
SqlTrack::cachedLyrics() const
{
/* We don't cache the string as it may be potentially very long */
QString query = QString( "SELECT lyrics FROM lyrics WHERE url = %1" ).arg( m_urlId );
QStringList result = m_collection->sqlStorage()->query( query );
if( result.isEmpty() )
return QString();
return result.first();
}
void
SqlTrack::setCachedLyrics( const QString &lyrics )
{
QString query = QString( "SELECT count(*) FROM lyrics WHERE url = %1").arg( m_urlId );
const QStringList queryResult = m_collection->sqlStorage()->query( query );
if( queryResult.isEmpty() )
return; // error in the query?
if( queryResult.first().toInt() == 0 )
{
QString insert = QString( "INSERT INTO lyrics( url, lyrics ) VALUES ( %1, '%2' )" )
.arg( QString::number( m_urlId ),
m_collection->sqlStorage()->escape( lyrics ) );
m_collection->sqlStorage()->insert( insert, "lyrics" );
}
else
{
QString update = QString( "UPDATE lyrics SET lyrics = '%1' WHERE url = %2" )
.arg( m_collection->sqlStorage()->escape( lyrics ),
QString::number( m_urlId ) );
m_collection->sqlStorage()->query( update );
}
notifyObservers();
}
bool
SqlTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
switch( type )
{
case Capabilities::Capability::Actions:
case Capabilities::Capability::Organisable:
case Capabilities::Capability::BookmarkThis:
case Capabilities::Capability::WriteTimecode:
case Capabilities::Capability::LoadTimecode:
case Capabilities::Capability::ReadLabel:
case Capabilities::Capability::WriteLabel:
case Capabilities::Capability::FindInSource:
return true;
default:
return Track::hasCapabilityInterface( type );
}
}
Capabilities::Capability*
SqlTrack::createCapabilityInterface( Capabilities::Capability::Type type )
{
switch( type )
{
case Capabilities::Capability::Actions:
{
QList<QAction*> actions;
//TODO These actions will hang around until m_collection is destructed.
// Find a better parent to avoid this memory leak.
//actions.append( new CopyToDeviceAction( m_collection, this ) );
return new Capabilities::ActionsCapability( actions );
}
case Capabilities::Capability::Organisable:
return new Capabilities::OrganiseCapabilityImpl( this );
case Capabilities::Capability::BookmarkThis:
return new Capabilities::BookmarkThisCapability( new BookmarkCurrentTrackPositionAction( 0 ) );
case Capabilities::Capability::WriteTimecode:
return new Capabilities::TimecodeWriteCapabilityImpl( this );
case Capabilities::Capability::LoadTimecode:
return new Capabilities::TimecodeLoadCapabilityImpl( this );
case Capabilities::Capability::ReadLabel:
return new Capabilities::SqlReadLabelCapability( this, sqlCollection()->sqlStorage() );
case Capabilities::Capability::WriteLabel:
return new Capabilities::SqlWriteLabelCapability( this, sqlCollection()->sqlStorage() );
case Capabilities::Capability::FindInSource:
return new Capabilities::FindInSourceCapabilityImpl( this );
default:
return Track::createCapabilityInterface( type );
}
}
void
SqlTrack::addLabel( const QString &label )
{
Meta::LabelPtr realLabel = m_collection->registry()->getLabel( label );
addLabel( realLabel );
}
void
SqlTrack::addLabel( const Meta::LabelPtr &label )
{
KSharedPtr<SqlLabel> sqlLabel = KSharedPtr<SqlLabel>::dynamicCast( label );
if( !sqlLabel )
{
Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() );
sqlLabel = KSharedPtr<SqlLabel>::dynamicCast( tmp );
}
if( sqlLabel )
{
QWriteLocker locker( &m_lock );
commitIfInNonBatchUpdate(); // we need to have a up-to-date m_urlId
if( m_urlId <= 0 )
{
warning() << "Track does not have an urlId.";
return;
}
QString countQuery = "SELECT COUNT(*) FROM urls_labels WHERE url = %1 AND label = %2;";
QStringList countRs = m_collection->sqlStorage()->query( countQuery.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ) );
if( !countRs.isEmpty() && countRs.first().toInt() == 0 )
{
QString insert = "INSERT INTO urls_labels(url,label) VALUES (%1,%2);";
m_collection->sqlStorage()->insert( insert.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ), "urls_labels" );
if( m_labelsInCache )
{
m_labelsCache.append( Meta::LabelPtr::staticCast( sqlLabel ) );
}
locker.unlock();
notifyObservers();
sqlLabel->invalidateCache();
}
}
}
int
SqlTrack::id() const
{
QReadLocker locker( &m_lock );
return m_trackId;
}
int
SqlTrack::urlId() const
{
QReadLocker locker( &m_lock );
return m_urlId;
}
void
SqlTrack::removeLabel( const Meta::LabelPtr &label )
{
KSharedPtr<SqlLabel> sqlLabel = KSharedPtr<SqlLabel>::dynamicCast( label );
if( !sqlLabel )
{
Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() );
sqlLabel = KSharedPtr<SqlLabel>::dynamicCast( tmp );
}
if( sqlLabel )
{
QString query = "DELETE FROM urls_labels WHERE label = %2 and url = (SELECT url FROM tracks WHERE id = %1);";
m_collection->sqlStorage()->query( query.arg( QString::number( m_trackId ), QString::number( sqlLabel->id() ) ) );
if( m_labelsInCache )
{
m_labelsCache.removeAll( Meta::LabelPtr::staticCast( sqlLabel ) );
}
notifyObservers();
sqlLabel->invalidateCache();
}
}
Meta::LabelList
SqlTrack::labels() const
{
{
QReadLocker locker( &m_lock );
if( m_labelsInCache )
return m_labelsCache;
}
if( !m_collection )
return Meta::LabelList();
// when running the query maker don't lock. might lead to deadlock via registry
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Label );
qm->addMatch( Meta::TrackPtr( const_cast<SqlTrack*>(this) ) );
qm->setBlocking( true );
qm->run();
{
QWriteLocker locker( &m_lock );
m_labelsInCache = true;
m_labelsCache = qm->labels();
delete qm;
return m_labelsCache;
}
}
TrackEditorPtr
SqlTrack::editor()
{
return TrackEditorPtr( isEditable() ? this : 0 );
}
StatisticsPtr
SqlTrack::statistics()
{
return StatisticsPtr( this );
}
void
SqlTrack::remove()
{
QWriteLocker locker( &m_lock );
m_cache.clear();
locker.unlock();
m_collection->registry()->removeTrack( m_urlId, m_uid );
// -- inform all albums, artist, years
#undef foreachInvalidateCache
#define INVALIDATE_AND_UPDATE(X) if( X ) \
{ \
X->invalidateCache(); \
X->notifyObservers(); \
}
INVALIDATE_AND_UPDATE(static_cast<Meta::SqlArtist*>(m_artist.data()));
INVALIDATE_AND_UPDATE(static_cast<Meta::SqlAlbum*>(m_album.data()));
INVALIDATE_AND_UPDATE(static_cast<Meta::SqlComposer*>(m_composer.data()));
INVALIDATE_AND_UPDATE(static_cast<Meta::SqlGenre*>(m_genre.data()));
INVALIDATE_AND_UPDATE(static_cast<Meta::SqlYear*>(m_year.data()));
#undef INVALIDATE_AND_UPDATE
m_artist = 0;
m_album = 0;
m_composer = 0;
m_genre = 0;
m_year = 0;
m_urlId = 0;
m_trackId = 0;
m_statisticsId = 0;
m_collection->collectionUpdated();
}
//---------------------- class Artist --------------------------
SqlArtist::SqlArtist( Collections::SqlCollection *collection, int id, const QString &name )
: Artist()
, m_collection( collection )
, m_id( id )
, m_name( name )
, m_tracksLoaded( false )
{
Q_ASSERT( m_collection );
Q_ASSERT( m_id > 0 );
}
Meta::SqlArtist::~SqlArtist()
{
}
void
SqlArtist::invalidateCache()
{
QMutexLocker locker( &m_mutex );
m_tracksLoaded = false;
m_tracks.clear();
}
TrackList
SqlArtist::tracks()
{
{
QMutexLocker locker( &m_mutex );
if( m_tracksLoaded )
return m_tracks;
}
// when running the query maker don't lock. might lead to deadlock via registry
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addMatch( Meta::ArtistPtr( this ) );
qm->setBlocking( true );
qm->run();
{
QMutexLocker locker( &m_mutex );
m_tracks = qm->tracks();
m_tracksLoaded = true;
delete qm;
return m_tracks;
}
}
bool
SqlArtist::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
switch( type )
{
case Capabilities::Capability::BookmarkThis:
return true;
default:
return Artist::hasCapabilityInterface( type );
}
}
Capabilities::Capability*
SqlArtist::createCapabilityInterface( Capabilities::Capability::Type type )
{
switch( type )
{
case Capabilities::Capability::BookmarkThis:
return new Capabilities::BookmarkThisCapability( new BookmarkArtistAction( 0, Meta::ArtistPtr( this ) ) );
default:
return Artist::createCapabilityInterface( type );
}
}
//--------------- class Album ---------------------------------
const QString SqlAlbum::AMAROK_UNSET_MAGIC = QString( "AMAROK_UNSET_MAGIC" );
SqlAlbum::SqlAlbum( Collections::SqlCollection *collection, int id, const QString &name, int artist )
: Album()
, m_collection( collection )
, m_name( name )
, m_id( id )
, m_artistId( artist )
, m_imageId( -1 )
, m_hasImage( false )
, m_hasImageChecked( false )
, m_unsetImageId( -1 )
, m_tracksLoaded( false )
, m_suppressAutoFetch( false )
, m_mutex( QMutex::Recursive )
{
Q_ASSERT( m_collection );
Q_ASSERT( m_id > 0 );
}
Meta::SqlAlbum::~SqlAlbum()
{
CoverCache::invalidateAlbum( this );
}
void
SqlAlbum::invalidateCache()
{
QMutexLocker locker( &m_mutex );
m_tracksLoaded = false;
m_hasImage = false;
m_hasImageChecked = false;
m_tracks.clear();
}
TrackList
SqlAlbum::tracks()
{
{
QMutexLocker locker( &m_mutex );
if( m_tracksLoaded )
return m_tracks;
}
// when running the query maker don't lock. might lead to deadlock via registry
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addMatch( Meta::AlbumPtr( this ) );
qm->orderBy( Meta::valDiscNr );
qm->orderBy( Meta::valTrackNr );
qm->orderBy( Meta::valTitle );
qm->setBlocking( true );
qm->run();
{
QMutexLocker locker( &m_mutex );
m_tracks = qm->tracks();
m_tracksLoaded = true;
delete qm;
return m_tracks;
}
}
// note for internal implementation:
// if hasImage returns true then m_imagePath is set
bool
SqlAlbum::hasImage( int size ) const
{
Q_UNUSED(size); // we have every size if we have an image at all
QMutexLocker locker( &m_mutex );
if( m_name.isEmpty() )
return false;
if( !m_hasImageChecked )
{
m_hasImageChecked = true;
const_cast<SqlAlbum*>( this )->largeImagePath();
// The user has explicitly set no cover
if( m_imagePath == AMAROK_UNSET_MAGIC )
m_hasImage = false;
// if we don't have an image but it was not explicitly blocked
else if( m_imagePath.isEmpty() )
{
// Cover fetching runs in another thread. If there is a retrieved cover
// then updateImage() gets called which updates the cache and alerts the
// subscribers. We use queueAlbum() because this runs the fetch as a
// background job and doesn't give an intruding popup asking for confirmation
if( !m_suppressAutoFetch && !m_name.isEmpty() && AmarokConfig::autoGetCoverArt() )
CoverFetcher::instance()->queueAlbum( AlbumPtr(const_cast<SqlAlbum *>(this)) );
m_hasImage = false;
}
else
m_hasImage = true;
}
return m_hasImage;
}
QImage
SqlAlbum::image( int size ) const
{
QMutexLocker locker( &m_mutex );
if( !hasImage() )
return Meta::Album::image( size );
// findCachedImage looks for a scaled version of the fullsize image
// which may have been saved on a previous lookup
QString cachedImagePath;
if( size <= 1 )
cachedImagePath = m_imagePath;
else
cachedImagePath = scaledDiskCachePath( size );
//FIXME this cache doesn't differentiate between shadowed/unshadowed
// a image exists. just load it.
if( !cachedImagePath.isEmpty() && QFile( cachedImagePath ).exists() )
{
QImage image( cachedImagePath );
if( image.isNull() )
return Meta::Album::image( size );
return image;
}
// no cached scaled image exists. Have to create it
QImage image;
// --- embedded cover
if( m_collection && m_imagePath.startsWith( m_collection->uidUrlProtocol() ) )
{
// -- check if we have a track with the given path as uid
Meta::TrackPtr track = m_collection->getTrackFromUid( m_imagePath );
if( track )
image = Meta::Tag::embeddedCover( track->playableUrl().path() );
}
// --- a normal path
if( image.isNull() )
image = QImage( m_imagePath );
if( image.isNull() )
return Meta::Album::image( size );
if( size > 1 && size < 1000 )
{
image = image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
image.save( cachedImagePath, "PNG" );
}
return image;
}
QUrl
SqlAlbum::imageLocation( int size )
{
if( !hasImage() )
return QUrl();
// findCachedImage looks for a scaled version of the fullsize image
// which may have been saved on a previous lookup
if( size <= 1 )
return QUrl::fromUserInput(m_imagePath);
QString cachedImagePath = scaledDiskCachePath( size );
if( cachedImagePath.isEmpty() )
return QUrl();
if( !QFile( cachedImagePath ).exists() )
{
// If we don't have the location, it's possible that we haven't tried to find the image yet
// So, let's look for it and just ignore the result
QImage i = image( size );
Q_UNUSED( i )
}
if( !QFile( cachedImagePath ).exists() )
return QUrl();
return QUrl::fromUserInput(cachedImagePath);
}
void
SqlAlbum::setImage( const QImage &image )
{
// the unnamed album is special. it will never have an image
if( m_name.isEmpty() )
return;
QMutexLocker locker( &m_mutex );
if( image.isNull() )
return;
// removeImage() will destroy all scaled cached versions of the artwork
// and remove references from the database if required.
removeImage();
QString path = largeDiskCachePath();
// make sure not to overwrite existing images
while( QFile(path).exists() )
path += '_'; // not that nice but it shouldn't happen that often.
image.save( path, "JPG" );
setImage( path );
locker.unlock();
notifyObservers();
// -- write back the album cover if allowed
if( AmarokConfig::writeBackCover() )
{
// - scale to cover to a sensible size
QImage scaledImage( image );
if( scaledImage.width() > AmarokConfig::writeBackCoverDimensions() || scaledImage.height() > AmarokConfig::writeBackCoverDimensions() )
scaledImage = scaledImage.scaled( AmarokConfig::writeBackCoverDimensions(), AmarokConfig::writeBackCoverDimensions(), Qt::KeepAspectRatio, Qt::SmoothTransformation );
// - set the image for each track
Meta::TrackList myTracks = tracks();
foreach( Meta::TrackPtr metaTrack, myTracks )
{
// the song needs to be at least one mb big or we won't set an image
// that means that the new image will increase the file size by less than 2%
if( metaTrack->filesize() > 1024l * 1024l )
{
Meta::FieldHash fields;
fields.insert( Meta::valImage, scaledImage );
WriteTagsJob *job = new WriteTagsJob( metaTrack->playableUrl().path(), fields );
- QObject::connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ QObject::connect( job, &WriteTagsJob::done, job, &WriteTagsJob::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
// note: we might want to update the track file size after writing the image
}
}
}
void
SqlAlbum::removeImage()
{
QMutexLocker locker( &m_mutex );
if( !hasImage() )
return;
// Update the database image path
// Set the album image to a magic value which will tell Amarok not to fetch it automatically
const int unsetId = unsetImageId();
QString query = "UPDATE albums SET image = %1 WHERE id = %2";
m_collection->sqlStorage()->query( query.arg( QString::number( unsetId ), QString::number( m_id ) ) );
// From here on we check if there are any remaining references to that particular image in the database
// If there aren't, then we should remove the image path from the database ( and possibly delete the file? )
// If there are, we need to leave it since other albums will reference this particular image path.
//
query = "SELECT count( albums.id ) FROM albums "
"WHERE albums.image = %1";
QStringList res = m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) );
if( !res.isEmpty() )
{
int references = res.first().toInt();
// If there are no more references to this particular image, then we should clean up
if( references <= 0 )
{
query = "DELETE FROM images WHERE id = %1";
m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) );
// remove the large cover only if it was cached.
QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) );
if( QFileInfo(m_imagePath).absoluteDir() == largeCoverDir )
QFile::remove( m_imagePath );
// remove all cache images
QString key = md5sum( QString(), QString(), m_imagePath );
QDir cacheDir( Amarok::saveLocation( "albumcovers/cache/" ) );
QStringList cacheFilter;
cacheFilter << QString( "*@" ) + key;
QStringList cachedImages = cacheDir.entryList( cacheFilter );
foreach( const QString &image, cachedImages )
{
bool r = QFile::remove( cacheDir.filePath( image ) );
debug() << "deleting cached image: " << image << " : " + ( r ? QString("ok") : QString("fail") );
}
CoverCache::invalidateAlbum( this );
}
}
m_imageId = -1;
m_imagePath.clear();
m_hasImage = false;
m_hasImageChecked = true;
locker.unlock();
notifyObservers();
}
int
SqlAlbum::unsetImageId() const
{
// Return the cached value if we have already done the lookup before
if( m_unsetImageId >= 0 )
return m_unsetImageId;
QString query = "SELECT id FROM images WHERE path = '%1'";
QStringList res = m_collection->sqlStorage()->query( query.arg( AMAROK_UNSET_MAGIC ) );
// We already have the AMAROK_UNSET_MAGIC variable in the database
if( !res.isEmpty() )
{
m_unsetImageId = res.first().toInt();
}
else
{
// We need to create this value
query = QString( "INSERT INTO images( path ) VALUES ( '%1' )" )
.arg( m_collection->sqlStorage()->escape( AMAROK_UNSET_MAGIC ) );
m_unsetImageId = m_collection->sqlStorage()->insert( query, "images" );
}
return m_unsetImageId;
}
bool
SqlAlbum::isCompilation() const
{
return !hasAlbumArtist();
}
bool
SqlAlbum::hasAlbumArtist() const
{
return !albumArtist().isNull();
}
Meta::ArtistPtr
SqlAlbum::albumArtist() const
{
if( m_artistId > 0 && !m_artist )
{
const_cast<SqlAlbum*>( this )->m_artist =
m_collection->registry()->getArtist( m_artistId );
}
return m_artist;
}
QByteArray
SqlAlbum::md5sum( const QString& artist, const QString& album, const QString& file ) const
{
// FIXME: names with unicode characters are not supported.
// FIXME: "The Beatles"."Collection" and "The"."Beatles Collection" will produce the same hash.
// FIXME: Correcting this now would invalidate all existing image stores.
KMD5 context( artist.toLower().toLocal8Bit() + album.toLower().toLocal8Bit() + file.toLocal8Bit() );
return context.hexDigest();
}
QString
SqlAlbum::largeDiskCachePath() const
{
// IMPROVEMENT: the large disk cache path could be human readable
const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString();
if( artist.isEmpty() && m_name.isEmpty() )
return QString();
QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) );
const QString key = md5sum( artist, m_name, QString() );
return largeCoverDir.filePath( key );
}
QString
SqlAlbum::scaledDiskCachePath( int size ) const
{
const QByteArray widthKey = QByteArray::number( size ) + '@';
QDir cacheCoverDir( Amarok::saveLocation( "albumcovers/cache/" ) );
QString key = md5sum( QString(), QString(), m_imagePath );
if( !cacheCoverDir.exists( widthKey + key ) )
{
// the correct location is empty
// check deprecated locations for the image cache and delete them
// (deleting the scaled image cache is fine)
const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString();
if( artist.isEmpty() && m_name.isEmpty() )
; // do nothing special
else
{
QString oldKey;
oldKey = md5sum( artist, m_name, m_imagePath );
if( cacheCoverDir.exists( widthKey + oldKey ) )
cacheCoverDir.remove( widthKey + oldKey );
oldKey = md5sum( artist, m_name, QString() );
if( cacheCoverDir.exists( widthKey + oldKey ) )
cacheCoverDir.remove( widthKey + oldKey );
}
}
return cacheCoverDir.filePath( widthKey + key );
}
QString
SqlAlbum::largeImagePath()
{
if( !m_collection )
return m_imagePath;
// Look up in the database
QString query = "SELECT images.id, images.path FROM images, albums WHERE albums.image = images.id AND albums.id = %1;"; // TODO: shouldn't we do a JOIN here?
QStringList res = m_collection->sqlStorage()->query( query.arg( m_id ) );
if( !res.isEmpty() )
{
m_imageId = res.at(0).toInt();
m_imagePath = res.at(1);
// explicitly deleted image
if( m_imagePath == AMAROK_UNSET_MAGIC )
return AMAROK_UNSET_MAGIC;
// embedded image (e.g. id3v2 APIC
// We store embedded images as unique ids in the database
// we will get the real image later on from the track.
if( m_imagePath.startsWith( m_collection->uidUrlProtocol()+"://" ) )
return m_imagePath;
// normal file
if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) )
return m_imagePath;
}
// After a rescan we currently lose all image information, so we need
// to check that we haven't already downloaded this image before.
m_imagePath = largeDiskCachePath();
if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) {
setImage(m_imagePath);
return m_imagePath;
}
m_imageId = -1;
m_imagePath.clear();
return m_imagePath;
}
// note: we won't notify the observers. we are a private function. the caller must do that.
void
SqlAlbum::setImage( const QString &path )
{
if( m_imagePath == path )
return;
if( m_name.isEmpty() ) // the empty album never has an image
return;
QMutexLocker locker( &m_mutex );
QString imagePath = path;
QString query = "SELECT id FROM images WHERE path = '%1'";
query = query.arg( m_collection->sqlStorage()->escape( imagePath ) );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
QString insert = QString( "INSERT INTO images( path ) VALUES ( '%1' )" )
.arg( m_collection->sqlStorage()->escape( imagePath ) );
m_imageId = m_collection->sqlStorage()->insert( insert, "images" );
}
else
m_imageId = res.first().toInt();
if( m_imageId >= 0 )
{
query = QString("UPDATE albums SET image = %1 WHERE albums.id = %2" )
.arg( QString::number( m_imageId ), QString::number( m_id ) );
m_collection->sqlStorage()->query( query );
m_imagePath = imagePath;
m_hasImage = true;
m_hasImageChecked = true;
CoverCache::invalidateAlbum( this );
}
}
/** Set the compilation flag.
* Actually it does not cange this album but instead moves
* the tracks to other albums (e.g. one with the same name which is a
* compilation)
* If the compilation flag is set to "false" then all songs
* with different artists will be moved to other albums, possibly even
* creating them.
*/
void
SqlAlbum::setCompilation( bool compilation )
{
if( m_name.isEmpty() )
return;
if( isCompilation() == compilation )
{
return;
}
else
{
m_collection->blockUpdatedSignal();
if( compilation )
{
// get the new compilation album
Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( name(), QString() );
KSharedPtr<SqlAlbum> sqlAlbum = KSharedPtr<SqlAlbum>::dynamicCast( metaAlbum );
Meta::FieldHash changes;
changes.insert( Meta::valCompilation, 1);
Meta::TrackList myTracks = tracks();
foreach( Meta::TrackPtr metaTrack, myTracks )
{
SqlTrack* sqlTrack = static_cast<SqlTrack*>(metaTrack.data());
// copy over the cover image
if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() )
sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() );
// move the track
sqlTrack->setAlbum( sqlAlbum->id() );
if( AmarokConfig::writeBack() )
Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes,
AmarokConfig::writeBackStatistics() );
}
/* TODO: delete all old tracks albums */
}
else
{
Meta::FieldHash changes;
changes.insert( Meta::valCompilation, 0);
Meta::TrackList myTracks = tracks();
foreach( Meta::TrackPtr metaTrack, myTracks )
{
SqlTrack* sqlTrack = static_cast<SqlTrack*>(metaTrack.data());
Meta::ArtistPtr trackArtist = sqlTrack->artist();
// get the new album
Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum(
sqlTrack->album()->name(),
trackArtist ? ArtistHelper::realTrackArtist( trackArtist->name() ) : QString() );
KSharedPtr<SqlAlbum> sqlAlbum = KSharedPtr<SqlAlbum>::dynamicCast( metaAlbum );
// copy over the cover image
if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() )
sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() );
// move the track
sqlTrack->setAlbum( sqlAlbum->id() );
if( AmarokConfig::writeBack() )
Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes,
AmarokConfig::writeBackStatistics() );
}
/* TODO //step 5: delete the original album, if necessary */
}
m_collection->unblockUpdatedSignal();
}
}
bool
SqlAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
if( m_name.isEmpty() )
return false;
switch( type )
{
case Capabilities::Capability::Actions:
case Capabilities::Capability::BookmarkThis:
return true;
default:
return Album::hasCapabilityInterface( type );
}
}
Capabilities::Capability*
SqlAlbum::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( m_name.isEmpty() )
return 0;
switch( type )
{
case Capabilities::Capability::Actions:
return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) );
case Capabilities::Capability::BookmarkThis:
return new Capabilities::BookmarkThisCapability( new BookmarkAlbumAction( 0, Meta::AlbumPtr( this ) ) );
default:
return Album::createCapabilityInterface( type );
}
}
//---------------SqlComposer---------------------------------
SqlComposer::SqlComposer( Collections::SqlCollection *collection, int id, const QString &name )
: Composer()
, m_collection( collection )
, m_id( id )
, m_name( name )
, m_tracksLoaded( false )
{
Q_ASSERT( m_collection );
Q_ASSERT( m_id > 0 );
}
void
SqlComposer::invalidateCache()
{
QMutexLocker locker( &m_mutex );
m_tracksLoaded = false;
m_tracks.clear();
}
TrackList
SqlComposer::tracks()
{
{
QMutexLocker locker( &m_mutex );
if( m_tracksLoaded )
return m_tracks;
}
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addMatch( Meta::ComposerPtr( this ) );
qm->setBlocking( true );
qm->run();
{
QMutexLocker locker( &m_mutex );
m_tracks = qm->tracks();
m_tracksLoaded = true;
delete qm;
return m_tracks;
}
}
//---------------SqlGenre---------------------------------
SqlGenre::SqlGenre( Collections::SqlCollection *collection, int id, const QString &name )
: Genre()
, m_collection( collection )
, m_id( id )
, m_name( name )
, m_tracksLoaded( false )
{
Q_ASSERT( m_collection );
Q_ASSERT( m_id > 0 );
}
void
SqlGenre::invalidateCache()
{
QMutexLocker locker( &m_mutex );
m_tracksLoaded = false;
m_tracks.clear();
}
TrackList
SqlGenre::tracks()
{
{
QMutexLocker locker( &m_mutex );
if( m_tracksLoaded )
return m_tracks;
}
// when running the query maker don't lock. might lead to deadlock via registry
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addMatch( Meta::GenrePtr( this ) );
qm->setBlocking( true );
qm->run();
{
QMutexLocker locker( &m_mutex );
m_tracks = qm->tracks();
m_tracksLoaded = true;
delete qm;
return m_tracks;
}
}
//---------------SqlYear---------------------------------
SqlYear::SqlYear( Collections::SqlCollection *collection, int id, int year)
: Year()
, m_collection( collection )
, m_id( id )
, m_year( year )
, m_tracksLoaded( false )
{
Q_ASSERT( m_collection );
Q_ASSERT( m_id > 0 );
}
void
SqlYear::invalidateCache()
{
QMutexLocker locker( &m_mutex );
m_tracksLoaded = false;
m_tracks.clear();
}
TrackList
SqlYear::tracks()
{
{
QMutexLocker locker( &m_mutex );
if( m_tracksLoaded )
return m_tracks;
}
// when running the query maker don't lock. might lead to deadlock via registry
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addMatch( Meta::YearPtr( this ) );
qm->setBlocking( true );
qm->run();
{
QMutexLocker locker( &m_mutex );
m_tracks = qm->tracks();
m_tracksLoaded = true;
delete qm;
return m_tracks;
}
}
//---------------SqlLabel---------------------------------
SqlLabel::SqlLabel( Collections::SqlCollection *collection, int id, const QString &name )
: Label()
, m_collection( collection )
, m_id( id )
, m_name( name )
, m_tracksLoaded( false )
{
Q_ASSERT( m_collection );
Q_ASSERT( m_id > 0 );
}
void
SqlLabel::invalidateCache()
{
QMutexLocker locker( &m_mutex );
m_tracksLoaded = false;
m_tracks.clear();
}
TrackList
SqlLabel::tracks()
{
{
QMutexLocker locker( &m_mutex );
if( m_tracksLoaded )
return m_tracks;
}
// when running the query maker don't lock. might lead to deadlock via registry
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addMatch( Meta::LabelPtr( this ) );
qm->setBlocking( true );
qm->run();
{
QMutexLocker locker( &m_mutex );
m_tracks = qm->tracks();
m_tracksLoaded = true;
delete qm;
return m_tracks;
}
}
diff --git a/src/core-impl/collections/db/sql/SqlQueryMaker.cpp b/src/core-impl/collections/db/sql/SqlQueryMaker.cpp
index d3e0bb2d6c..cb6abd13f0 100644
--- a/src/core-impl/collections/db/sql/SqlQueryMaker.cpp
+++ b/src/core-impl/collections/db/sql/SqlQueryMaker.cpp
@@ -1,1188 +1,1188 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Daniel Winter <dw@danielwinter.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SqlQueryMaker"
#include "SqlQueryMaker.h"
#include "SqlCollection.h"
#include "SqlQueryMakerInternal.h"
#include <core/storage/SqlStorage.h>
#include "core/support/Debug.h"
#include "core-impl/collections/db/MountPointManager.h"
#include <QWeakPointer>
#include <QStack>
#include <ThreadWeaver/Job>
#include <ThreadWeaver/ThreadWeaver>
#include <ThreadWeaver/Queue>
using namespace Collections;
class SqlWorkerThread : public QObject, public ThreadWeaver::Job
{
Q_OBJECT
public:
SqlWorkerThread( SqlQueryMakerInternal *queryMakerInternal )
: QObject()
, ThreadWeaver::Job()
, m_queryMakerInternal( queryMakerInternal )
, m_aborted( false )
{
//nothing to do
}
virtual ~SqlWorkerThread()
{
delete m_queryMakerInternal;
}
virtual void requestAbort()
{
m_aborted = true;
}
SqlQueryMakerInternal* queryMakerInternal() const
{
return m_queryMakerInternal;
}
protected:
virtual void run(ThreadWeaver::JobPointer self = QSharedPointer<ThreadWeaver::Job>(), ThreadWeaver::Thread *thread = 0)
{
Q_UNUSED(self);
Q_UNUSED(thread);
m_queryMakerInternal->run();
if( m_aborted )
setStatus(Status_Aborted);
else
setStatus(Status_Running);
}
void defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
private:
SqlQueryMakerInternal *m_queryMakerInternal;
bool m_aborted;
Q_SIGNALS:
/** This signal is emitted when this job is being processed by a thread. */
void started(ThreadWeaver::JobPointer);
/** This signal is emitted when the job has been finished (no matter if it succeeded or not). */
void done(ThreadWeaver::JobPointer);
/** This job has failed.
* This signal is emitted when success() returns false after the job is executed. */
void failed(ThreadWeaver::JobPointer);
};
struct SqlQueryMaker::Private
{
enum { TAGS_TAB = 1, ARTIST_TAB = 2, ALBUM_TAB = 4, GENRE_TAB = 8, COMPOSER_TAB = 16, YEAR_TAB = 32, STATISTICS_TAB = 64, URLS_TAB = 128, ALBUMARTIST_TAB = 256, LABELS_TAB = 1024 };
int linkedTables;
QueryMaker::QueryType queryType;
QString query;
QString queryReturnValues;
QString queryFrom;
QString queryMatch;
QString queryFilter;
QString queryOrderBy;
bool withoutDuplicates;
int maxResultSize;
AlbumQueryMode albumMode;
LabelQueryMode labelMode;
SqlWorkerThread *worker;
QStack<bool> andStack;
QStringList blockingCustomData;
Meta::TrackList blockingTracks;
Meta::AlbumList blockingAlbums;
Meta::ArtistList blockingArtists;
Meta::GenreList blockingGenres;
Meta::ComposerList blockingComposers;
Meta::YearList blockingYears;
Meta::LabelList blockingLabels;
bool blocking;
bool used;
qint64 returnValueType;
};
SqlQueryMaker::SqlQueryMaker( SqlCollection* collection )
: QueryMaker()
, m_collection( collection )
, d( new Private )
{
d->worker = 0;
d->queryType = QueryMaker::None;
d->linkedTables = 0;
d->withoutDuplicates = false;
d->albumMode = AllAlbums;
d->labelMode = QueryMaker::NoConstraint;
d->maxResultSize = -1;
d->andStack.clear();
d->andStack.push( true ); //and is default
d->blocking = false;
d->used = false;
d->returnValueType = 0;
}
SqlQueryMaker::~SqlQueryMaker()
{
disconnect();
abortQuery();
if( d->worker )
{
d->worker->deleteLater();
}
delete d;
}
void
SqlQueryMaker::abortQuery()
{
if( d->worker )
{
d->worker->requestAbort();
d->worker->disconnect( this );
if( d->worker->queryMakerInternal() )
d->worker->queryMakerInternal()->disconnect( this );
}
}
void
SqlQueryMaker::run()
{
if( d->queryType == QueryMaker::None || (d->blocking && d->used) )
{
debug() << "sql querymaker used without reset or initialization" << endl;
return; //better error handling?
}
if( d->worker && !d->worker->isFinished() )
{
//the worker thread seems to be running
//TODO: wait or job to complete
}
else
{
SqlQueryMakerInternal *qmi = new SqlQueryMakerInternal( m_collection );
qmi->setQuery( query() );
qmi->setQueryType( d->queryType );
if ( !d->blocking )
{
- connect( qmi, SIGNAL(newResultReady(Meta::AlbumList)), SIGNAL(newResultReady(Meta::AlbumList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::ArtistList)), SIGNAL(newResultReady(Meta::ArtistList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::GenreList)), SIGNAL(newResultReady(Meta::GenreList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::ComposerList)), SIGNAL(newResultReady(Meta::ComposerList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::YearList)), SIGNAL(newResultReady(Meta::YearList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::TrackList)), SIGNAL(newResultReady(Meta::TrackList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(QStringList)), SIGNAL(newResultReady(QStringList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::LabelList)), SIGNAL(newResultReady(Meta::LabelList)), Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newAlbumsReady, this, &SqlQueryMaker::newAlbumsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newArtistsReady, this, &SqlQueryMaker::newArtistsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newGenresReady, this, &SqlQueryMaker::newGenresReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newComposersReady, this, &SqlQueryMaker::newComposersReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newYearsReady, this, &SqlQueryMaker::newYearsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newTracksReady, this, &SqlQueryMaker::newTracksReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newResultReady, this, &SqlQueryMaker::newResultReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newLabelsReady, this, &SqlQueryMaker::newLabelsReady, Qt::DirectConnection );
d->worker = new SqlWorkerThread( qmi );
- connect( d->worker, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(done(ThreadWeaver::JobPointer)) );
+ connect( d->worker, &SqlWorkerThread::done, this, &SqlQueryMaker::done );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(d->worker) );
}
else //use it blocking
{
- connect( qmi, SIGNAL(newResultReady(Meta::AlbumList)), SLOT(blockingNewResultReady(Meta::AlbumList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::ArtistList)), SLOT(blockingNewResultReady(Meta::ArtistList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::GenreList)), SLOT(blockingNewResultReady(Meta::GenreList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::ComposerList)), SLOT(blockingNewResultReady(Meta::ComposerList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::YearList)), SLOT(blockingNewResultReady(Meta::YearList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::TrackList)), SLOT(blockingNewResultReady(Meta::TrackList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(QStringList)), SLOT(blockingNewResultReady(QStringList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::LabelList)), SLOT(blockingNewResultReady(Meta::LabelList)), Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newAlbumsReady, this, &SqlQueryMaker::blockingNewAlbumsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newArtistsReady, this, &SqlQueryMaker::blockingNewArtistsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newGenresReady, this, &SqlQueryMaker::blockingNewGenresReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newComposersReady, this, &SqlQueryMaker::blockingNewComposersReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newYearsReady, this, &SqlQueryMaker::blockingNewYearsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newTracksReady, this, &SqlQueryMaker::blockingNewTracksReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newResultReady, this, &SqlQueryMaker::blockingNewResultReady, Qt::DirectConnection );
+ connect( qmi, &Collections::SqlQueryMakerInternal::newLabelsReady, this, &SqlQueryMaker::blockingNewLabelsReady, Qt::DirectConnection );
qmi->run();
delete qmi;
}
}
d->used = true;
}
void
SqlQueryMaker::done( ThreadWeaver::JobPointer job )
{
d->worker = 0; // d->worker *is* the job, prevent stale pointer
emit queryDone();
}
QueryMaker*
SqlQueryMaker::setQueryType( QueryType type )
{
// we need the unchanged m_queryType in the blocking result methods so prevent
// reseting queryType without reseting the QM
if ( d->blocking && d->used )
return this;
switch( type ) {
case QueryMaker::Track:
//make sure to keep this method in sync with handleTracks(QStringList) and the SqlTrack ctor
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Track;
d->linkedTables |= Private::URLS_TAB;
d->linkedTables |= Private::TAGS_TAB;
d->linkedTables |= Private::GENRE_TAB;
d->linkedTables |= Private::ARTIST_TAB;
d->linkedTables |= Private::ALBUM_TAB;
d->linkedTables |= Private::COMPOSER_TAB;
d->linkedTables |= Private::YEAR_TAB;
d->linkedTables |= Private::STATISTICS_TAB;
d->queryReturnValues = Meta::SqlTrack::getTrackReturnValues();
}
return this;
case QueryMaker::Artist:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Artist;
d->withoutDuplicates = true;
d->linkedTables |= Private::ARTIST_TAB;
//reading the ids from the database means we don't have to query for them later
d->queryReturnValues = "artists.name, artists.id";
}
return this;
case QueryMaker::Album:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Album;
d->withoutDuplicates = true;
d->linkedTables |= Private::ALBUM_TAB;
//add whatever is necessary to identify compilations
d->queryReturnValues = "albums.name, albums.id, albums.artist";
}
return this;
case QueryMaker::AlbumArtist:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::AlbumArtist;
d->withoutDuplicates = true;
d->linkedTables |= Private::ALBUMARTIST_TAB;
d->linkedTables |= Private::ALBUM_TAB;
d->queryReturnValues = "albumartists.name, albumartists.id";
}
return this;
case QueryMaker::Composer:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Composer;
d->withoutDuplicates = true;
d->linkedTables |= Private::COMPOSER_TAB;
d->queryReturnValues = "composers.name, composers.id";
}
return this;
case QueryMaker::Genre:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Genre;
d->withoutDuplicates = true;
d->linkedTables |= Private::GENRE_TAB;
d->queryReturnValues = "genres.name, genres.id";
}
return this;
case QueryMaker::Year:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Year;
d->withoutDuplicates = true;
d->linkedTables |= Private::YEAR_TAB;
d->queryReturnValues = "years.name, years.id";
}
return this;
case QueryMaker::Custom:
if( d->queryType == QueryMaker::None )
d->queryType = QueryMaker::Custom;
return this;
case QueryMaker::Label:
if( d->queryType == QueryMaker::None )
{
d->queryType = QueryMaker::Label;
d->withoutDuplicates = true;
d->queryReturnValues = "labels.label,labels.id";
d->linkedTables |= Private::LABELS_TAB;
}
case QueryMaker::None:
return this;
}
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::TrackPtr &track )
{
QString url = track->uidUrl();
if( !url.isEmpty() )
/*
QUrl kurl( url );
if( kurl.scheme() == "amarok-sqltrackuid" )
*/
{
d->queryMatch += QString( " AND urls.uniqueid = '%1' " ).arg( url /*kurl.url()*/ );
}
else
{
QString path;
/*
if( kurl.isLocalFile() )
{
path = kurl.path();
}
else
*/
{
path = track->playableUrl().path();
}
int deviceid = m_collection->mountPointManager()->getIdForUrl( QUrl::fromUserInput(path) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceid, path );
d->queryMatch += QString( " AND urls.deviceid = %1 AND urls.rpath = '%2'" )
.arg( QString::number( deviceid ), escape( rpath ) );
}
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour )
{
d->linkedTables |= Private::ARTIST_TAB;
if( behaviour == AlbumArtists || behaviour == AlbumOrTrackArtists )
d->linkedTables |= Private::ALBUMARTIST_TAB;
QString artistQuery;
QString albumArtistQuery;
if( artist && !artist->name().isEmpty() )
{
artistQuery = QString("artists.name = '%1'").arg( escape( artist->name() ) );
albumArtistQuery = QString("albumartists.name = '%1'").arg( escape( artist->name() ) );
}
else
{
artistQuery = "( artists.name IS NULL OR artists.name = '')";
albumArtistQuery = "( albumartists.name IS NULL OR albumartists.name = '')";
}
switch( behaviour )
{
case TrackArtists:
d->queryMatch += " AND " + artistQuery;
break;
case AlbumArtists:
d->queryMatch += " AND " + albumArtistQuery;
break;
case AlbumOrTrackArtists:
d->queryMatch += " AND ( (" + artistQuery + " ) OR ( " + albumArtistQuery + " ) )";
break;
}
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
d->linkedTables |= Private::ALBUM_TAB;
// handle singles
if( !album || album->name().isEmpty() )
d->queryMatch += QString( " AND ( albums.name IS NULL OR albums.name = '' )" );
else
d->queryMatch += QString( " AND albums.name = '%1'" ).arg( escape( album->name() ) );
if( album )
{
//handle compilations
Meta::ArtistPtr albumArtist = album->albumArtist();
if( albumArtist )
{
d->linkedTables |= Private::ALBUMARTIST_TAB;
d->queryMatch += QString( " AND albumartists.name = '%1'" ).arg( escape( albumArtist->name() ) );
}
else
{
d->queryMatch += " AND albums.artist IS NULL";
}
}
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
d->linkedTables |= Private::GENRE_TAB;
d->queryMatch += QString( " AND genres.name = '%1'" ).arg( escape( genre->name() ) );
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
d->linkedTables |= Private::COMPOSER_TAB;
d->queryMatch += QString( " AND composers.name = '%1'" ).arg( escape( composer->name() ) );
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::YearPtr &year )
{
// handle tracks without a year
if( !year )
{
d->queryMatch += " AND year IS NULL";
}
else
{
d->linkedTables |= Private::YEAR_TAB;
d->queryMatch += QString( " AND years.name = '%1'" ).arg( escape( year->name() ) );
}
return this;
}
QueryMaker*
SqlQueryMaker::addMatch( const Meta::LabelPtr &label )
{
KSharedPtr<Meta::SqlLabel> sqlLabel = KSharedPtr<Meta::SqlLabel>::dynamicCast( label );
QString labelSubQuery;
if( sqlLabel )
{
labelSubQuery = "SELECT url FROM urls_labels WHERE label = %1";
labelSubQuery = labelSubQuery.arg( sqlLabel->id() );
}
else
{
labelSubQuery = "SELECT a.url FROM urls_labels a INNER JOIN labels b ON a.label = b.id WHERE b.label = '%1'";
labelSubQuery = labelSubQuery.arg( escape( label->name() ) );
}
d->linkedTables |= Private::TAGS_TAB;
QString match = " AND tracks.url in (%1)";
d->queryMatch += match.arg( labelSubQuery );
return this;
}
QueryMaker*
SqlQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
// special case for albumartist...
if( value == Meta::valAlbumArtist && filter.isEmpty() )
{
d->linkedTables |= Private::ALBUMARTIST_TAB;
d->linkedTables |= Private::ALBUM_TAB;
d->queryFilter += QString( " %1 ( albums.artist IS NULL or albumartists.name = '') " ).arg( andOr() );
}
else if( value == Meta::valLabel )
{
d->linkedTables |= Private::TAGS_TAB;
QString like = likeCondition( filter, !matchBegin, !matchEnd );
QString filter = " %1 tracks.url IN (SELECT a.url FROM urls_labels a INNER JOIN labels b ON a.label = b.id WHERE b.label %2) ";
d->queryFilter += filter.arg( andOr(), like );
}
else
{
QString like = likeCondition( filter, !matchBegin, !matchEnd );
d->queryFilter += QString( " %1 %2 %3 " ).arg( andOr(), nameForValue( value ), like );
}
return this;
}
QueryMaker*
SqlQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
// special case for album...
if( value == Meta::valAlbumArtist && filter.isEmpty() )
{
d->linkedTables |= Private::ALBUMARTIST_TAB;
d->queryFilter += QString( " %1 NOT ( albums.artist IS NULL or albumartists.name = '') " ).arg( andOr() );
}
else if( value == Meta::valLabel )
{
d->linkedTables |= Private::TAGS_TAB;
QString like = likeCondition( filter, !matchBegin, !matchEnd );
QString filter = " %1 tracks.url NOT IN (SELECT a.url FROM urls_labels a INNER JOIN labels b ON a.label = b.id WHERE b.label %2) ";
d->queryFilter += filter.arg( andOr(), like );
}
else
{
QString like = likeCondition( filter, !matchBegin, !matchEnd );
d->queryFilter += QString( " %1 NOT %2 %3 " ).arg( andOr(), nameForValue( value ), like );
}
return this;
}
QueryMaker*
SqlQueryMaker::addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
QString comparison;
switch( compare )
{
case QueryMaker::Equals:
comparison = '=';
break;
case QueryMaker::GreaterThan:
comparison = '>';
break;
case QueryMaker::LessThan:
comparison = '<';
break;
}
// note: a NULL value in the database means undefined and not 0!
d->queryFilter += QString( " %1 %2 %3 %4 " ).arg( andOr(), nameForValue( value ), comparison, QString::number( filter ) );
return this;
}
QueryMaker*
SqlQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
QString comparison;
switch( compare )
{
case QueryMaker::Equals:
comparison = "!=";
break;
case QueryMaker::GreaterThan: //negating greater than is less or equal
comparison = "<=";
break;
case QueryMaker::LessThan: //negating less than is greater or equal
comparison = ">=";
break;
}
// note: a NULL value in the database means undefined and not 0!
// We can't exclude NULL values here because they are not defined!
d->queryFilter += QString( " %1 (%2 %3 %4 or %2 is null)" ).arg( andOr(), nameForValue( value ), comparison, QString::number( filter ) );
return this;
}
QueryMaker*
SqlQueryMaker::addReturnValue( qint64 value )
{
if( d->queryType == QueryMaker::Custom )
{
if ( !d->queryReturnValues.isEmpty() )
d->queryReturnValues += ',';
d->queryReturnValues += nameForValue( value );
d->returnValueType = value;
}
return this;
}
QueryMaker*
SqlQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
if( d->queryType == QueryMaker::Custom )
{
if( !d->queryReturnValues.isEmpty() )
d->queryReturnValues += ',';
QString sqlfunction;
switch( function )
{
case QueryMaker::Count:
sqlfunction = "COUNT";
break;
case QueryMaker::Sum:
sqlfunction = "SUM";
break;
case QueryMaker::Max:
sqlfunction = "MAX";
break;
case QueryMaker::Min:
sqlfunction = "MIN";
break;
default:
sqlfunction = "Unknown function in SqlQueryMaker::addReturnFunction, function was: " + QString::number( function );
}
d->queryReturnValues += QString( "%1(%2)" ).arg( sqlfunction, nameForValue( value ) );
d->returnValueType = value;
}
return this;
}
QueryMaker*
SqlQueryMaker::orderBy( qint64 value, bool descending )
{
if ( d->queryOrderBy.isEmpty() )
d->queryOrderBy = " ORDER BY ";
else
d->queryOrderBy += ',';
d->queryOrderBy += nameForValue( value );
d->queryOrderBy += QString( " %1 " ).arg( descending ? "DESC" : "ASC" );
return this;
}
QueryMaker*
SqlQueryMaker::limitMaxResultSize( int size )
{
d->maxResultSize = size;
return this;
}
QueryMaker*
SqlQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
if( mode != AllAlbums )
{
d->linkedTables |= Private::ALBUM_TAB;
}
d->albumMode = mode;
return this;
}
QueryMaker*
SqlQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
d->labelMode = mode;
return this;
}
QueryMaker*
SqlQueryMaker::beginAnd()
{
d->queryFilter += andOr();
d->queryFilter += " ( 1 ";
d->andStack.push( true );
return this;
}
QueryMaker*
SqlQueryMaker::beginOr()
{
d->queryFilter += andOr();
d->queryFilter += " ( 0 ";
d->andStack.push( false );
return this;
}
QueryMaker*
SqlQueryMaker::endAndOr()
{
d->queryFilter += ')';
d->andStack.pop();
return this;
}
void
SqlQueryMaker::linkTables()
{
switch( d->queryType )
{
case QueryMaker::Track:
{
d->queryFrom += " tracks";
if( d->linkedTables & Private::TAGS_TAB )
d->linkedTables ^= Private::TAGS_TAB;
break;
}
case QueryMaker::Artist:
{
d->queryFrom += " artists";
if( d->linkedTables != Private::ARTIST_TAB )
d->queryFrom += " JOIN tracks ON tracks.artist = artists.id";
if( d->linkedTables & Private::ARTIST_TAB )
d->linkedTables ^= Private::ARTIST_TAB;
break;
}
case QueryMaker::Album:
case QueryMaker::AlbumArtist:
{
d->queryFrom += " albums";
if( d->linkedTables != Private::ALBUM_TAB && d->linkedTables != ( Private::ALBUM_TAB | Private::ALBUMARTIST_TAB ) )
d->queryFrom += " JOIN tracks ON tracks.album = albums.id";
if( d->linkedTables & Private::ALBUM_TAB )
d->linkedTables ^= Private::ALBUM_TAB;
break;
}
case QueryMaker::Genre:
{
d->queryFrom += " genres";
if( d->linkedTables != Private::GENRE_TAB )
d->queryFrom += " INNER JOIN tracks ON tracks.genre = genres.id";
if( d->linkedTables & Private::GENRE_TAB )
d->linkedTables ^= Private::GENRE_TAB;
break;
}
case QueryMaker::Composer:
{
d->queryFrom += " composers";
if( d->linkedTables != Private::COMPOSER_TAB )
d->queryFrom += " JOIN tracks ON tracks.composer = composers.id";
if( d->linkedTables & Private::COMPOSER_TAB )
d->linkedTables ^= Private::COMPOSER_TAB;
break;
}
case QueryMaker::Year:
{
d->queryFrom += " years";
if( d->linkedTables != Private::YEAR_TAB )
d->queryFrom += " JOIN tracks on tracks.year = years.id";
if( d->linkedTables & Private::YEAR_TAB )
d->linkedTables ^= Private::YEAR_TAB;
break;
}
case QueryMaker::Label:
{
d->queryFrom += " labels";
if( d->linkedTables != Private::LABELS_TAB )
d->queryFrom += " INNER JOIN urls_labels ON labels.id = urls_labels.label"
" INNER JOIN tracks ON urls_labels.url = tracks.url";
if( d->linkedTables & Private::LABELS_TAB )
d->linkedTables ^= Private::LABELS_TAB;
break;
}
case QueryMaker::Custom:
{
switch( d->returnValueType )
{
default:
case Meta::valUrl:
{
d->queryFrom += " tracks";
if( d->linkedTables & Private::TAGS_TAB )
d->linkedTables ^= Private::TAGS_TAB;
break;
}
case Meta::valAlbum:
{
d->queryFrom += " albums";
if( d->linkedTables & Private::ALBUM_TAB )
d->linkedTables ^= Private::ALBUM_TAB;
if( d->linkedTables & Private::URLS_TAB )
d->linkedTables ^= Private::URLS_TAB;
break;
}
case Meta::valArtist:
{
d->queryFrom += " artists";
if( d->linkedTables & Private::ARTIST_TAB )
d->linkedTables ^= Private::ARTIST_TAB;
if( d->linkedTables & Private::URLS_TAB )
d->linkedTables ^= Private::URLS_TAB;
break;
}
case Meta::valGenre:
{
d->queryFrom += " genres";
if( d->linkedTables & Private::GENRE_TAB )
d->linkedTables ^= Private::GENRE_TAB;
if( d->linkedTables & Private::URLS_TAB )
d->linkedTables ^= Private::URLS_TAB;
break;
}
}
}
case QueryMaker::None:
{
//???
break;
}
}
if( !d->linkedTables )
return;
if( d->linkedTables & Private::URLS_TAB )
d->queryFrom += " INNER JOIN urls ON tracks.url = urls.id";
if( d->linkedTables & Private::ARTIST_TAB )
d->queryFrom += " LEFT JOIN artists ON tracks.artist = artists.id";
if( d->linkedTables & Private::ALBUM_TAB )
d->queryFrom += " LEFT JOIN albums ON tracks.album = albums.id";
if( d->linkedTables & Private::ALBUMARTIST_TAB )
d->queryFrom += " LEFT JOIN artists AS albumartists ON albums.artist = albumartists.id";
if( d->linkedTables & Private::GENRE_TAB )
d->queryFrom += " LEFT JOIN genres ON tracks.genre = genres.id";
if( d->linkedTables & Private::COMPOSER_TAB )
d->queryFrom += " LEFT JOIN composers ON tracks.composer = composers.id";
if( d->linkedTables & Private::YEAR_TAB )
d->queryFrom += " LEFT JOIN years ON tracks.year = years.id";
if( d->linkedTables & Private::STATISTICS_TAB )
{
if( d->linkedTables & Private::URLS_TAB )
{
d->queryFrom += " LEFT JOIN statistics ON urls.id = statistics.url";
}
else
{
d->queryFrom += " LEFT JOIN statistics ON tracks.url = statistics.url";
}
}
}
void
SqlQueryMaker::buildQuery()
{
//URLS is always required for dynamic collection
d->linkedTables |= Private::URLS_TAB;
linkTables();
QString query = "SELECT ";
if ( d->withoutDuplicates )
query += "DISTINCT ";
query += d->queryReturnValues;
query += " FROM ";
query += d->queryFrom;
// dynamic collection (only mounted file systems are considered)
if( (d->linkedTables & Private::URLS_TAB) && m_collection->mountPointManager() )
{
query += " WHERE 1 ";
IdList list = m_collection->mountPointManager()->getMountedDeviceIds();
if( !list.isEmpty() )
{
QString commaSeparatedIds;
foreach( int id, list )
{
if( !commaSeparatedIds.isEmpty() )
commaSeparatedIds += ',';
commaSeparatedIds += QString::number( id );
}
query += QString( " AND urls.deviceid in (%1)" ).arg( commaSeparatedIds );
}
}
switch( d->albumMode )
{
case OnlyNormalAlbums:
query += " AND albums.artist IS NOT NULL ";
break;
case OnlyCompilations:
query += " AND albums.artist IS NULL ";
break;
case AllAlbums:
//do nothing
break;
}
if( d->labelMode != QueryMaker::NoConstraint )
{
switch( d->labelMode )
{
case QueryMaker::OnlyWithLabels:
query += " AND tracks.url IN ";
break;
case QueryMaker::OnlyWithoutLabels:
query += " AND tracks.url NOT IN ";
break;
case QueryMaker::NoConstraint:
//do nothing, will never be called
break;
}
query += " (SELECT DISTINCT url FROM urls_labels) ";
}
query += d->queryMatch;
if ( !d->queryFilter.isEmpty() )
{
query += " AND ( 1 ";
query += d->queryFilter;
query += " ) ";
}
query += d->queryOrderBy;
if ( d->maxResultSize > -1 )
query += QString( " LIMIT %1 OFFSET 0 " ).arg( d->maxResultSize );
query += ';';
d->query = query;
}
QString
SqlQueryMaker::query()
{
if ( d->query.isEmpty() )
buildQuery();
return d->query;
}
QStringList
SqlQueryMaker::runQuery( const QString &query )
{
return m_collection->sqlStorage()->query( query );
}
void
SqlQueryMaker::setBlocking( bool enabled )
{
d->blocking = enabled;
}
QStringList
SqlQueryMaker::collectionIds() const
{
QStringList list;
list << m_collection->collectionId();
return list;
}
Meta::TrackList
SqlQueryMaker::tracks() const
{
return d->blockingTracks;
}
Meta::AlbumList
SqlQueryMaker::albums() const
{
return d->blockingAlbums;
}
Meta::ArtistList
SqlQueryMaker::artists() const
{
return d->blockingArtists;
}
Meta::GenreList
SqlQueryMaker::genres() const
{
return d->blockingGenres;
}
Meta::ComposerList
SqlQueryMaker::composers() const
{
return d->blockingComposers;
}
Meta::YearList
SqlQueryMaker::years() const
{
return d->blockingYears;
}
QStringList
SqlQueryMaker::customData() const
{
return d->blockingCustomData;
}
Meta::LabelList
SqlQueryMaker::labels() const
{
return d->blockingLabels;
}
QString
SqlQueryMaker::nameForValue( qint64 value )
{
switch( value )
{
case Meta::valUrl:
d->linkedTables |= Private::URLS_TAB;
return "urls.rpath"; //TODO figure out how to handle deviceid
case Meta::valTitle:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.title";
case Meta::valArtist:
d->linkedTables |= Private::ARTIST_TAB;
return "artists.name";
case Meta::valAlbum:
d->linkedTables |= Private::ALBUM_TAB;
return "albums.name";
case Meta::valGenre:
d->linkedTables |= Private::GENRE_TAB;
return "genres.name";
case Meta::valComposer:
d->linkedTables |= Private::COMPOSER_TAB;
return "composers.name";
case Meta::valYear:
d->linkedTables |= Private::YEAR_TAB;
return "years.name";
case Meta::valBpm:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.bpm";
case Meta::valComment:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.comment";
case Meta::valTrackNr:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.tracknumber";
case Meta::valDiscNr:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.discnumber";
case Meta::valLength:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.length";
case Meta::valBitrate:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.bitrate";
case Meta::valSamplerate:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.samplerate";
case Meta::valFilesize:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.filesize";
case Meta::valFormat:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.filetype";
case Meta::valCreateDate:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.createdate";
case Meta::valScore:
d->linkedTables |= Private::STATISTICS_TAB;
return "statistics.score";
case Meta::valRating:
d->linkedTables |= Private::STATISTICS_TAB;
return "statistics.rating";
case Meta::valFirstPlayed:
d->linkedTables |= Private::STATISTICS_TAB;
return "statistics.createdate";
case Meta::valLastPlayed:
d->linkedTables |= Private::STATISTICS_TAB;
return "statistics.accessdate";
case Meta::valPlaycount:
d->linkedTables |= Private::STATISTICS_TAB;
return "statistics.playcount";
case Meta::valUniqueId:
d->linkedTables |= Private::URLS_TAB;
return "urls.uniqueid";
case Meta::valAlbumArtist:
d->linkedTables |= Private::ALBUMARTIST_TAB;
//albumartist_tab means that the artist table is joined to the albums table
//so add albums as well
d->linkedTables |= Private::ALBUM_TAB;
return "albumartists.name";
case Meta::valModified:
d->linkedTables |= Private::TAGS_TAB;
return "tracks.modifydate";
default:
return "ERROR: unknown value in SqlQueryMaker::nameForValue(qint64): value=" + QString::number( value );
}
}
QString
SqlQueryMaker::andOr() const
{
return d->andStack.top() ? " AND " : " OR ";
}
QString
SqlQueryMaker::escape( QString text ) const //krazy:exclude=constref
{
return m_collection->sqlStorage()->escape( text );
}
QString
SqlQueryMaker::likeCondition( const QString &text, bool anyBegin, bool anyEnd ) const
{
if( anyBegin || anyEnd )
{
QString escaped = text;
//according to http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html
//the escape character (\ as we are using the default) is escaped twice when using like.
//mysql_real_escape will escape it once, so we have to escape it another time here
escaped = escaped.replace( '\\', "\\\\" ); // "////" will result in two backslahes
escaped = escape( escaped );
//as we are in pattern matching mode '_' and '%' have to be escaped
//mysql_real_excape_string does not do that for us
//see http://dev.mysql.com/doc/refman/5.0/en/string-syntax.html
//and http://dev.mysql.com/doc/refman/5.0/en/mysql-real-escape-string.html
//replace those characters after calling escape(), which calls the mysql
//function in turn, so that mysql does not escape the escape backslashes
escaped.replace( '%', "\\%" ).replace( '_', "\\_" );
QString ret = " LIKE ";
ret += '\'';
if ( anyBegin )
ret += '%';
ret += escaped;
if ( anyEnd )
ret += '%';
ret += '\'';
//Case insensitive collation for queries
ret += " COLLATE utf8_unicode_ci ";
//Use \ as the escape character
//ret += " ESCAPE '\\' ";
return ret;
}
else
{
return QString( " = '%1' COLLATE utf8_unicode_ci " ).arg( escape( text ) );
}
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::AlbumList &albums)
+SqlQueryMaker::blockingNewAlbumsReady(const Meta::AlbumList &albums)
{
d->blockingAlbums = albums;
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::ArtistList &artists)
+SqlQueryMaker::blockingNewArtistsReady(const Meta::ArtistList &artists)
{
d->blockingArtists = artists;
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::GenreList &genres)
+SqlQueryMaker::blockingNewGenresReady(const Meta::GenreList &genres)
{
d->blockingGenres = genres;
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::ComposerList &composers)
+SqlQueryMaker::blockingNewComposersReady(const Meta::ComposerList &composers)
{
d->blockingComposers = composers;
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::YearList &years)
+SqlQueryMaker::blockingNewYearsReady(const Meta::YearList &years)
{
d->blockingYears = years;
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::TrackList &tracks)
+SqlQueryMaker::blockingNewTracksReady(const Meta::TrackList &tracks)
{
d->blockingTracks = tracks;
}
void
SqlQueryMaker::blockingNewResultReady(const QStringList &customData)
{
d->blockingCustomData = customData;
}
void
-SqlQueryMaker::blockingNewResultReady(const Meta::LabelList &labels )
+SqlQueryMaker::blockingNewLabelsReady(const Meta::LabelList &labels )
{
d->blockingLabels = labels;
}
#include "SqlQueryMaker.moc"
diff --git a/src/core-impl/collections/db/sql/SqlQueryMaker.h b/src/core-impl/collections/db/sql/SqlQueryMaker.h
index 4e41cf0586..c1a85172ff 100644
--- a/src/core-impl/collections/db/sql/SqlQueryMaker.h
+++ b/src/core-impl/collections/db/sql/SqlQueryMaker.h
@@ -1,135 +1,135 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_COLLECTION_SQLQUERYMAKER_H
#define AMAROK_COLLECTION_SQLQUERYMAKER_H
#include "core/collections/QueryMaker.h"
#include "amarok_sqlcollection_export.h"
#include <ThreadWeaver/Job>
namespace Collections {
class SqlCollection;
class AMAROK_SQLCOLLECTION_EXPORT SqlQueryMaker : public QueryMaker
{
Q_OBJECT
public:
SqlQueryMaker( SqlCollection* collection );
virtual ~SqlQueryMaker();
virtual void abortQuery();
virtual void run();
virtual QueryMaker* setQueryType( QueryType type );
virtual QueryMaker* addMatch( const Meta::TrackPtr &track );
virtual QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists );
virtual QueryMaker* addMatch( const Meta::AlbumPtr &album );
virtual QueryMaker* addMatch( const Meta::ComposerPtr &composer );
virtual QueryMaker* addMatch( const Meta::GenrePtr &genre );
virtual QueryMaker* addMatch( const Meta::YearPtr &year );
virtual QueryMaker* addMatch( const Meta::LabelPtr &label );
virtual QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd );
virtual QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd );
virtual QueryMaker* addNumberFilter( qint64 value, qint64 filter, NumberComparison compare );
virtual QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare );
virtual QueryMaker* addReturnValue( qint64 value );
virtual QueryMaker* addReturnFunction( ReturnFunction function, qint64 value );
virtual QueryMaker* orderBy( qint64 value, bool descending = false );
virtual QueryMaker* limitMaxResultSize( int size );
virtual QueryMaker* setAlbumQueryMode( AlbumQueryMode mode );
virtual QueryMaker* setLabelQueryMode( LabelQueryMode mode );
virtual QueryMaker* beginAnd();
virtual QueryMaker* beginOr();
virtual QueryMaker* endAndOr();
QString query();
QStringList runQuery( const QString &query );
void handleResult( const QStringList &result );
// for using it blocking (only for collection internal use)
void setBlocking( bool enabled );
QStringList collectionIds() const;
Meta::TrackList tracks() const;
Meta::AlbumList albums() const;
Meta::ArtistList artists() const;
Meta::GenreList genres() const;
Meta::ComposerList composers() const;
Meta::YearList years() const;
QStringList customData() const;
Meta::LabelList labels() const;
protected:
virtual QString escape( QString text ) const;
/**
* returns a pattern for LIKE operator that will match given text with given options
* @param text the text to match (should not be escape()'d, function does it itself)
* @param anyBegin wildcard match the beginning of @p text (*text)
* @param anyEnd wildcard match the end of @p text (text*)
*/
virtual QString likeCondition( const QString &text, bool anyBegin, bool anyEnd ) const;
public Q_SLOTS:
void done( ThreadWeaver::JobPointer job );
- void blockingNewResultReady( const QStringList &customData );
- void blockingNewResultReady( const Meta::AlbumList &albums );
- void blockingNewResultReady( const Meta::ArtistList &artists );
- void blockingNewResultReady( const Meta::GenreList &genres );
- void blockingNewResultReady( const Meta::ComposerList &composers );
- void blockingNewResultReady( const Meta::YearList &years );
- void blockingNewResultReady( const Meta::TrackList &tracks );
- void blockingNewResultReady( const Meta::LabelList &labels );
+ void blockingNewTracksReady( const Meta::TrackList& );
+ void blockingNewArtistsReady( const Meta::ArtistList& );
+ void blockingNewAlbumsReady( const Meta::AlbumList& );
+ void blockingNewGenresReady( const Meta::GenreList& );
+ void blockingNewComposersReady( const Meta::ComposerList& );
+ void blockingNewYearsReady( const Meta::YearList& );
+ void blockingNewResultReady( const QStringList& );
+ void blockingNewLabelsReady( const Meta::LabelList& );
private:
void linkTables();
void buildQuery();
QString nameForValue( qint64 value );
QString andOr() const;
SqlCollection *m_collection;
struct Private;
Private * const d;
};
class SqlQueryMakerFactory
{
public:
virtual SqlQueryMaker* createQueryMaker() const = 0;
virtual ~SqlQueryMakerFactory() {};
};
} //namespace Collections
#endif /* AMAROK_COLLECTION_SQLQUERYMAKER_H */
diff --git a/src/core-impl/collections/db/sql/SqlQueryMakerInternal.cpp b/src/core-impl/collections/db/sql/SqlQueryMakerInternal.cpp
index 88a1f93265..307d23e2b3 100644
--- a/src/core-impl/collections/db/sql/SqlQueryMakerInternal.cpp
+++ b/src/core-impl/collections/db/sql/SqlQueryMakerInternal.cpp
@@ -1,244 +1,244 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SqlQueryMakerInternal.h"
#include <core/storage/SqlStorage.h>
#include "core/support/Debug.h"
#include "SqlCollection.h"
#include "SqlMeta.h"
#include "SqlRegistry.h"
#include <QStringList>
using namespace Collections;
SqlQueryMakerInternal::SqlQueryMakerInternal( SqlCollection *collection )
: QObject()
, m_collection( collection )
, m_queryType( QueryMaker::None )
{
}
SqlQueryMakerInternal::~ SqlQueryMakerInternal()
{
disconnect();
}
void
SqlQueryMakerInternal::run()
{
Q_ASSERT( !m_query.isEmpty() );
if( !m_collection.isNull() )
{
QStringList result = m_collection.data()->sqlStorage()->query( m_query );
handleResult( result );
}
else
{
deleteLater();
}
}
void
SqlQueryMakerInternal::setQuery( const QString &query )
{
//qDebug() << query;
m_query = query;
}
void
SqlQueryMakerInternal::setQueryType( QueryMaker::QueryType type )
{
m_queryType = type;
}
void
SqlQueryMakerInternal::handleResult( const QStringList &result )
{
if( !result.isEmpty() )
{
switch( m_queryType ) {
case QueryMaker::Custom:
emit newResultReady( result );
break;
case QueryMaker::Track:
handleTracks( result );
break;
case QueryMaker::Artist:
case QueryMaker::AlbumArtist:
handleArtists( result );
break;
case QueryMaker::Album:
handleAlbums( result );
break;
case QueryMaker::Genre:
handleGenres( result );
break;
case QueryMaker::Composer:
handleComposers( result );
break;
case QueryMaker::Year:
handleYears( result );
break;
case QueryMaker::Label:
handleLabels( result );
break;
case QueryMaker::None:
debug() << "Warning: queryResult with queryType == NONE";
}
}
else
{
switch( m_queryType ) {
case QueryMaker::Custom:
emit newResultReady( QStringList() );
break;
case QueryMaker::Track:
- emit newResultReady( Meta::TrackList() );
+ emit newTracksReady( Meta::TrackList() );
break;
case QueryMaker::Artist:
case QueryMaker::AlbumArtist:
- emit newResultReady( Meta::ArtistList() );
+ emit newArtistsReady( Meta::ArtistList() );
break;
case QueryMaker::Album:
- emit newResultReady( Meta::AlbumList() );
+ emit newAlbumsReady( Meta::AlbumList() );
break;
case QueryMaker::Genre:
- emit newResultReady( Meta::GenreList() );
+ emit newGenresReady( Meta::GenreList() );
break;
case QueryMaker::Composer:
- emit newResultReady( Meta::ComposerList() );
+ emit newComposersReady( Meta::ComposerList() );
break;
case QueryMaker::Year:
- emit newResultReady( Meta::YearList() );
+ emit newYearsReady( Meta::YearList() );
break;
case QueryMaker::Label:
- emit newResultReady( Meta::LabelList() );
+ emit newLabelsReady( Meta::LabelList() );
break;
case QueryMaker::None:
debug() << "Warning: queryResult with queryType == NONE";
}
}
//queryDone will be emitted in done(Job*)
}
void
SqlQueryMakerInternal::handleTracks( const QStringList &result )
{
Meta::TrackList tracks;
SqlRegistry* reg = m_collection.data()->registry();
int returnCount = Meta::SqlTrack::getTrackReturnValueCount();
int resultRows = result.size() / returnCount;
for( int i = 0; i < resultRows; i++ )
{
QStringList row = result.mid( i*returnCount, returnCount );
tracks.append( reg->getTrack( row[Meta::SqlTrack::returnIndex_trackId].toInt(), row ) );
}
- emit newResultReady( tracks );
+ emit newTracksReady( tracks );
}
void
SqlQueryMakerInternal::handleArtists( const QStringList &result )
{
Meta::ArtistList artists;
SqlRegistry* reg = m_collection.data()->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
if( id.toInt() > 0 )
artists.append( reg->getArtist( id.toInt(), name ) );
}
- emit newResultReady( artists );
+ emit newArtistsReady( artists );
}
void
SqlQueryMakerInternal::handleAlbums( const QStringList &result )
{
Meta::AlbumList albums;
SqlRegistry* reg = m_collection.data()->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
QString artist = iter.next();
albums.append( reg->getAlbum( id.toInt(), name, artist.toInt() ) );
}
- emit newResultReady( albums );
+ emit newAlbumsReady( albums );
}
void
SqlQueryMakerInternal::handleGenres( const QStringList &result )
{
Meta::GenreList genres;
SqlRegistry* reg = m_collection.data()->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
genres.append( reg->getGenre( id.toInt(), name ) );
}
- emit newResultReady( genres );
+ emit newGenresReady( genres );
}
void
SqlQueryMakerInternal::handleComposers( const QStringList &result )
{
Meta::ComposerList composers;
SqlRegistry* reg = m_collection.data()->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
composers.append( reg->getComposer( id.toInt(), name ) );
}
- emit newResultReady( composers );
+ emit newComposersReady( composers );
}
void
SqlQueryMakerInternal::handleYears( const QStringList &result )
{
Meta::YearList years;
SqlRegistry* reg = m_collection.data()->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
years.append( reg->getYear( id.toInt(), name.toInt() ) );
}
- emit newResultReady( years );
+ emit newYearsReady( years );
}
void
SqlQueryMakerInternal::handleLabels( const QStringList &result )
{
Meta::LabelList labels;
SqlRegistry *reg = m_collection.data()->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString label = iter.next();
QString id = iter.next();
labels.append( reg->getLabel( id.toInt(), label ) );
}
- emit newResultReady( labels );
+ emit newLabelsReady( labels );
}
diff --git a/src/core-impl/collections/db/sql/SqlQueryMakerInternal.h b/src/core-impl/collections/db/sql/SqlQueryMakerInternal.h
index 6c5cea7240..04e996850e 100644
--- a/src/core-impl/collections/db/sql/SqlQueryMakerInternal.h
+++ b/src/core-impl/collections/db/sql/SqlQueryMakerInternal.h
@@ -1,72 +1,72 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef SQLQUERYMAKERINTERNAL_H
#define SQLQUERYMAKERINTERNAL_H
#include "core/collections/QueryMaker.h"
#include "core/meta/forward_declarations.h"
#include <QObject>
#include <QWeakPointer>
#include <QString>
namespace Collections {
class SqlCollection;
class SqlQueryMakerInternal : public QObject
{
Q_OBJECT
public:
explicit SqlQueryMakerInternal( SqlCollection *collection );
virtual ~ SqlQueryMakerInternal();
void run();
void setQuery( const QString &query );
void setQueryType( QueryMaker::QueryType type );
void setResultAsDataPtrs( bool value );
Q_SIGNALS:
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
+ void newComposersReady( Meta::ComposerList );
+ void newYearsReady( Meta::YearList );
void newResultReady( QStringList );
- void newResultReady( Meta::LabelList );
+ void newLabelsReady( Meta::LabelList );
private:
void handleResult( const QStringList &result );
void handleTracks( const QStringList &result );
void handleArtists( const QStringList &result );
void handleAlbums( const QStringList &result );
void handleGenres( const QStringList &result );
void handleComposers( const QStringList &result );
void handleYears( const QStringList &result );
void handleLabels( const QStringList &result );
private:
QWeakPointer<SqlCollection> m_collection;
QueryMaker::QueryType m_queryType;
QString m_query;
};
} //namespace Collections
#endif // SQLQUERYMAKERINTERNAL_H
diff --git a/src/core-impl/collections/db/sql/SqlRegistry.cpp b/src/core-impl/collections/db/sql/SqlRegistry.cpp
index 0e4927dbf2..e9816c4a50 100644
--- a/src/core-impl/collections/db/sql/SqlRegistry.cpp
+++ b/src/core-impl/collections/db/sql/SqlRegistry.cpp
@@ -1,1003 +1,1003 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SqlRegistry"
#include "SqlRegistry.h"
#include "DatabaseUpdater.h"
#include "SqlRegistry_p.h"
#include "SqlCollection.h"
#include "core/support/Debug.h"
#include "core-impl/collections/db/MountPointManager.h"
#include "scanner/GenericScanManager.h"
#include <QMutableHashIterator>
#include <QMutexLocker>
SqlRegistry::SqlRegistry( Collections::SqlCollection* collection )
: QObject( 0 )
, m_collection( collection )
, m_blockDatabaseUpdateCount( 0 )
, m_collectionChanged( false )
{
DEBUG_BLOCK
setObjectName( "SqlRegistry" );
// -- remove unneeded entries from the database.
// we have to do this now before anyone can hold references
// to those objects.
DatabaseUpdater databaseUpdater( m_collection );
// url entries without associated directory just stick around and cannot be processed
// by SqlScanResultProcessor. Delete them before checking tracks
databaseUpdater.deleteOrphanedByDirectory( "urls" );
// tracks with no associated url entry are useless, just a bunch of medatada with
// nothing to associate them to; remove those first
databaseUpdater.deleteOrphanedByUrl( "tracks" );
databaseUpdater.deleteAllRedundant( "album" ); // what about cover images in database and disk cache?
databaseUpdater.deleteAllRedundant( "artist" );
databaseUpdater.deleteAllRedundant( "genre" );
databaseUpdater.deleteAllRedundant( "composer" );
databaseUpdater.deleteAllRedundant( "url" );
databaseUpdater.deleteAllRedundant( "year" );
databaseUpdater.deleteOrphanedByUrl( "lyrics" );
databaseUpdater.deleteOrphanedByUrl( "statistics" );
databaseUpdater.deleteOrphanedByUrl( "urls_labels" );
m_timer = new QTimer( this );
m_timer->setInterval( 30 * 1000 ); //try to clean up every 30 seconds, change if necessary
m_timer->setSingleShot( false );
- connect( m_timer, SIGNAL(timeout()), this, SLOT(emptyCache()) );
+ connect( m_timer, &QTimer::timeout, this, &SqlRegistry::emptyCache );
m_timer->start();
}
SqlRegistry::~SqlRegistry()
{
//don't delete m_collection
}
// ------ directory
int
SqlRegistry::getDirectory( const QString &path, uint mtime )
{
int dirId;
int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(path) );
QString rdir = m_collection->mountPointManager()->getRelativePath( deviceId, path );
SqlStorage *storage = m_collection->sqlStorage();
// - find existing entry
QString query = QString( "SELECT id, changedate FROM directories "
"WHERE deviceid = %1 AND dir = '%2';" )
.arg( QString::number( deviceId ), storage->escape( rdir ) );
QStringList res = storage->query( query );
// - create new entry
if( res.isEmpty() )
{
debug() << "SqlRegistry::getDirectory(): new directory" << path;
QString insert = QString( "INSERT INTO directories(deviceid,changedate,dir) "
"VALUES (%1,%2,'%3');" )
.arg( QString::number( deviceId ), QString::number( mtime ),
storage->escape( rdir ) );
dirId = storage->insert( insert, "directories" );
m_collectionChanged = true;
}
else
{
// update old one
dirId = res[0].toUInt();
uint oldMtime = res[1].toUInt();
if( oldMtime != mtime )
{
QString update = QString( "UPDATE directories SET changedate = %1 "
"WHERE id = %2;" )
.arg( QString::number( mtime ), res[0] );
debug() << "SqlRegistry::getDirectory(): update directory" << path << "(id" <<
res[0] << ") from" << oldMtime << "to" << mtime << "UNIX time";
storage->query( update );
}
}
return dirId;
}
// ------ track
Meta::TrackPtr
SqlRegistry::getTrack( int urlId )
{
QString query = "SELECT %1 FROM urls %2 "
"WHERE urls.id = %3";
query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
Meta::SqlTrack::getTrackJoinConditions(),
QString::number( urlId ) );
QStringList rowData = m_collection->sqlStorage()->query( query );
if( rowData.isEmpty() )
return Meta::TrackPtr();
TrackPath id( rowData[Meta::SqlTrack::returnIndex_urlDeviceId].toInt(),
rowData[Meta::SqlTrack::returnIndex_urlRPath] );
QString uid = rowData[Meta::SqlTrack::returnIndex_urlUid];
QMutexLocker locker( &m_trackMutex );
if( m_trackMap.contains( id ) )
{
Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( m_trackMap[ id ] );
// yes, it may happen that we get a different track in corner cases, see bug 323156
if( track->urlId() == urlId )
return Meta::TrackPtr::staticCast( track );
warning() << Q_FUNC_INFO << "track with (deviceId, rpath)" << id << "found in"
<< "m_trackMap, but it had different urlId (" << track->urlId() << ")"
<< "than requested (" << urlId << "). This may happen in corner-cases.";
}
if( m_uidMap.contains( uid ) )
{
Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( m_uidMap[ uid ] );
// yes, it may happen that we get a different track in corner cases, see bug 323156
if( track->urlId() == urlId )
return Meta::TrackPtr::staticCast( track );
warning() << Q_FUNC_INFO << "track with uid" << uid << "found in m_uidMap, but it"
<< "had different urlId (" << track->urlId() << ") than requested ("
<< urlId << "). This may happen in corner-cases.";
}
Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, rowData );
Meta::TrackPtr trackPtr( sqlTrack );
m_trackMap.insert( id, trackPtr );
m_uidMap.insert( sqlTrack->uidUrl(), trackPtr );
return trackPtr;
}
Meta::TrackPtr
SqlRegistry::getTrack( const QString &path )
{
int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(path) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, path );
TrackPath id( deviceId, rpath );
QMutexLocker locker( &m_trackMutex );
if( m_trackMap.contains( id ) )
return m_trackMap.value( id );
else
{
QString query;
QStringList result;
query = "SELECT %1 FROM urls %2 "
"WHERE urls.deviceid = %3 AND urls.rpath = '%4';";
query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
Meta::SqlTrack::getTrackJoinConditions(),
QString::number( deviceId ),
m_collection->sqlStorage()->escape( rpath ) );
result = m_collection->sqlStorage()->query( query );
if( result.isEmpty() )
return Meta::TrackPtr();
Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, result );
Meta::TrackPtr trackPtr( sqlTrack );
m_trackMap.insert( id, trackPtr );
m_uidMap.insert( sqlTrack->uidUrl(), trackPtr );
return trackPtr;
}
}
Meta::TrackPtr
SqlRegistry::getTrack( int deviceId, const QString &rpath, int directoryId, const QString &uidUrl )
{
TrackPath id( deviceId, rpath );
QMutexLocker locker( &m_trackMutex );
if( m_trackMap.contains( id ) )
return m_trackMap.value( id );
else
{
QString query;
QStringList result;
Meta::SqlTrack *sqlTrack = 0;
// -- get it from the database
query = "SELECT %1 FROM urls %2 "
"WHERE urls.deviceid = %3 AND urls.rpath = '%4';";
query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
Meta::SqlTrack::getTrackJoinConditions(),
QString::number( deviceId ),
m_collection->sqlStorage()->escape( rpath ) );
result = m_collection->sqlStorage()->query( query );
if( !result.isEmpty() )
sqlTrack = new Meta::SqlTrack( m_collection, result );
// -- we have to create a new track
if( !sqlTrack )
sqlTrack = new Meta::SqlTrack( m_collection, deviceId, rpath, directoryId, uidUrl );
Meta::TrackPtr trackPtr( sqlTrack );
m_trackMap.insert( id, trackPtr );
m_uidMap.insert( sqlTrack->uidUrl(), trackPtr );
return trackPtr;
}
}
Meta::TrackPtr
SqlRegistry::getTrack( int trackId, const QStringList &rowData )
{
Q_ASSERT( trackId == rowData[Meta::SqlTrack::returnIndex_trackId].toInt() );
Q_UNUSED( trackId );
TrackPath path( rowData[Meta::SqlTrack::returnIndex_urlDeviceId].toInt(),
rowData[Meta::SqlTrack::returnIndex_urlRPath] );
QString uid = rowData[Meta::SqlTrack::returnIndex_urlUid];
QMutexLocker locker( &m_trackMutex );
if( m_trackMap.contains( path ) )
return m_trackMap.value( path );
else if( m_uidMap.contains( uid ) )
return m_uidMap.value( uid );
else
{
Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, rowData );
Meta::TrackPtr track( sqlTrack );
m_trackMap.insert( path, track );
m_uidMap.insert( KSharedPtr<Meta::SqlTrack>::staticCast( track )->uidUrl(), track );
return track;
}
}
bool
SqlRegistry::updateCachedUrl( const QString &oldUrl, const QString &newUrl )
{
int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(oldUrl) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, oldUrl );
TrackPath oldId( deviceId, rpath );
int newdeviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(newUrl) );
QString newRpath = m_collection->mountPointManager()->getRelativePath( newdeviceId, newUrl );
TrackPath newId( newdeviceId, newRpath );
QMutexLocker locker( &m_trackMutex );
if( m_trackMap.contains( newId ) )
warning() << "updating path to an already existing path.";
else if( !m_trackMap.contains( oldId ) )
warning() << "updating path from a non existing path.";
else
{
Meta::TrackPtr track = m_trackMap.take( oldId );
m_trackMap.insert( newId, track );
return true;
}
return false;
}
bool
SqlRegistry::updateCachedUid( const QString &oldUid, const QString &newUid )
{
QMutexLocker locker( &m_trackMutex );
// TODO: improve uid handling
if( m_uidMap.contains( newUid ) )
warning() << "updating uid to an already existing uid.";
else if( !oldUid.isEmpty() && !m_uidMap.contains( oldUid ) )
warning() << "updating uid from a non existing uid.";
else
{
Meta::TrackPtr track = m_uidMap.take(oldUid);
m_uidMap.insert( newUid, track );
return true;
}
return false;
}
Meta::TrackPtr
SqlRegistry::getTrackFromUid( const QString &uid )
{
QMutexLocker locker( &m_trackMutex );
if( m_uidMap.contains( uid ) )
return m_uidMap.value( uid );
{
QString query;
QStringList result;
// -- get all the track info
query = "SELECT %1 FROM urls %2 "
"WHERE urls.uniqueid = '%3';";
query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
Meta::SqlTrack::getTrackJoinConditions(),
m_collection->sqlStorage()->escape( uid ) );
result = m_collection->sqlStorage()->query( query );
if( result.isEmpty() )
return Meta::TrackPtr();
Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, result );
Meta::TrackPtr trackPtr( sqlTrack );
int deviceid = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(trackPtr->playableUrl().path()) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceid, trackPtr->playableUrl().path() );
TrackPath id(deviceid, rpath);
m_trackMap.insert( id, trackPtr );
m_uidMap.insert( uid, trackPtr );
return trackPtr;
}
}
void
SqlRegistry::removeTrack( int urlId, const QString uid )
{
// delete all entries linked to the url, including track
QStringList tables = QStringList() << "tracks" << "lyrics" << "statistics" << "urls_labels";
foreach( const QString &table, tables )
{
QString query = QString( "DELETE FROM %1 WHERE url=%2" ).arg( table ).arg( urlId );
m_collection->sqlStorage()->query( query );
}
// delete url entry from database; we used to keep it and keep its statistics, but
// DatabaseUpdater::deleteAllRedundant( url ) removes the url entry on the next Amarok
// startup, plus we don't know how long we should keep the entry, so just delete
// everything. ScanResultProcessor should be witty enough not to delete tracks that
// have been moved to another directory and/or device, even if it is currently
// unavailable.
QString query = QString( "DELETE FROM urls WHERE id=%1" ).arg( urlId );
m_collection->sqlStorage()->query( query );
// --- delete the track from memory
QMutexLocker locker( &m_trackMutex );
if( m_uidMap.contains( uid ) )
{
// -- remove from hashes
Meta::TrackPtr track = m_uidMap.take( uid );
Meta::SqlTrack *sqlTrack = static_cast<Meta::SqlTrack*>( track.data() );
int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(sqlTrack->playableUrl().path()) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, sqlTrack->playableUrl().path() );
TrackPath id(deviceId, rpath);
m_trackMap.remove( id );
}
}
// -------- artist
Meta::ArtistPtr
SqlRegistry::getArtist( const QString &oName )
{
QMutexLocker locker( &m_artistMutex );
QString name = oName.left( DatabaseUpdater::textColumnLength() );
if( m_artistMap.contains( name ) )
return m_artistMap.value( name );
int id;
QString query = QString( "SELECT id FROM artists WHERE name = '%1';" ).arg( m_collection->sqlStorage()->escape( name ) );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
QString insert = QString( "INSERT INTO artists( name ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( name ) );
id = m_collection->sqlStorage()->insert( insert, "artists" );
m_collectionChanged = true;
}
else
{
id = res[0].toInt();
}
if( !id )
return Meta::ArtistPtr();
Meta::ArtistPtr artist( new Meta::SqlArtist( m_collection, id, name ) );
m_artistMap.insert( name, artist );
m_artistIdMap.insert( id, artist );
return artist;
}
Meta::ArtistPtr
SqlRegistry::getArtist( int id )
{
QMutexLocker locker( &m_artistMutex );
if( m_artistIdMap.contains( id ) )
return m_artistIdMap.value( id );
QString query = QString( "SELECT name FROM artists WHERE id = %1;" ).arg( id );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
return Meta::ArtistPtr();
QString name = res[0];
Meta::ArtistPtr artist( new Meta::SqlArtist( m_collection, id, name ) );
m_artistMap.insert( name, artist );
m_artistIdMap.insert( id, artist );
return artist;
}
Meta::ArtistPtr
SqlRegistry::getArtist( int id, const QString &name )
{
Q_ASSERT( id > 0 ); // must be a valid id
QMutexLocker locker( &m_artistMutex );
if( m_artistMap.contains( name ) )
return m_artistMap.value( name );
Meta::ArtistPtr artist( new Meta::SqlArtist( m_collection, id, name ) );
m_artistMap.insert( name, artist );
m_artistIdMap.insert( id, artist );
return artist;
}
// -------- genre
Meta::GenrePtr
SqlRegistry::getGenre( const QString &oName )
{
QMutexLocker locker( &m_genreMutex );
QString name = oName.left( DatabaseUpdater::textColumnLength() );
if( m_genreMap.contains( name ) )
return m_genreMap.value( name );
int id;
QString query = QString( "SELECT id FROM genres WHERE name = '%1';" ).arg( m_collection->sqlStorage()->escape( name ) );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
QString insert = QString( "INSERT INTO genres( name ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( name ) );
id = m_collection->sqlStorage()->insert( insert, "genres" );
m_collectionChanged = true;
}
else
{
id = res[0].toInt();
}
if( !id )
return Meta::GenrePtr();
Meta::GenrePtr genre( new Meta::SqlGenre( m_collection, id, name ) );
m_genreMap.insert( name, genre );
return genre;
}
Meta::GenrePtr
SqlRegistry::getGenre( int id )
{
QMutexLocker locker( &m_genreMutex );
QString query = QString( "SELECT name FROM genres WHERE id = '%1';" ).arg( id );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
return Meta::GenrePtr();
QString name = res[0];
Meta::GenrePtr genre( new Meta::SqlGenre( m_collection, id, name ) );
m_genreMap.insert( name, genre );
return genre;
}
Meta::GenrePtr
SqlRegistry::getGenre( int id, const QString &name )
{
Q_ASSERT( id > 0 ); // must be a valid id
QMutexLocker locker( &m_genreMutex );
if( m_genreMap.contains( name ) )
return m_genreMap.value( name );
Meta::GenrePtr genre( new Meta::SqlGenre( m_collection, id, name ) );
m_genreMap.insert( name, genre );
return genre;
}
// -------- composer
Meta::ComposerPtr
SqlRegistry::getComposer( const QString &oName )
{
QMutexLocker locker( &m_composerMutex );
QString name = oName.left( DatabaseUpdater::textColumnLength() );
if( m_composerMap.contains( name ) )
return m_composerMap.value( name );
int id;
QString query = QString( "SELECT id FROM composers WHERE name = '%1';" ).arg( m_collection->sqlStorage()->escape( name ) );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
QString insert = QString( "INSERT INTO composers( name ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( name ) );
id = m_collection->sqlStorage()->insert( insert, "composers" );
m_collectionChanged = true;
}
else
{
id = res[0].toInt();
}
if( !id )
return Meta::ComposerPtr();
Meta::ComposerPtr composer( new Meta::SqlComposer( m_collection, id, name ) );
m_composerMap.insert( name, composer );
return composer;
}
Meta::ComposerPtr
SqlRegistry::getComposer( int id )
{
if( id <= 0 )
return Meta::ComposerPtr();
QMutexLocker locker( &m_composerMutex );
QString query = QString( "SELECT name FROM composers WHERE id = '%1';" ).arg( id );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
return Meta::ComposerPtr();
QString name = res[0];
Meta::ComposerPtr composer( new Meta::SqlComposer( m_collection, id, name ) );
m_composerMap.insert( name, composer );
return composer;
}
Meta::ComposerPtr
SqlRegistry::getComposer( int id, const QString &name )
{
Q_ASSERT( id > 0 ); // must be a valid id
QMutexLocker locker( &m_composerMutex );
if( m_composerMap.contains( name ) )
return m_composerMap.value( name );
Meta::ComposerPtr composer( new Meta::SqlComposer( m_collection, id, name ) );
m_composerMap.insert( name, composer );
return composer;
}
// -------- year
Meta::YearPtr
SqlRegistry::getYear( int year, int yearId )
{
QMutexLocker locker( &m_yearMutex );
if( m_yearMap.contains( year ) )
return m_yearMap.value( year );
// don't know the id yet
if( yearId <= 0 )
{
QString query = QString( "SELECT id FROM years WHERE name = '%1';" ).arg( QString::number( year ) );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
QString insert = QString( "INSERT INTO years( name ) VALUES ('%1');" ).arg( QString::number( year ) );
yearId = m_collection->sqlStorage()->insert( insert, "years" );
m_collectionChanged = true;
}
else
{
yearId = res[0].toInt();
}
}
if( !yearId )
return Meta::YearPtr();
Meta::YearPtr yearPtr( new Meta::SqlYear( m_collection, yearId, year ) );
m_yearMap.insert( year, yearPtr );
return yearPtr;
}
// -------- album
Meta::AlbumPtr
SqlRegistry::getAlbum( const QString &oName, const QString &oArtist )
{
// we allow albums with empty name but nonempty artist, see bug 272471
QString name = oName.left( DatabaseUpdater::textColumnLength() );
QString albumArtist = oArtist.left( DatabaseUpdater::textColumnLength() );
AlbumKey key( name, albumArtist );
QMutexLocker locker( &m_albumMutex );
if( m_albumMap.contains( key ) )
return m_albumMap.value( key );
int albumId = -1;
int artistId = -1;
QString query = QString( "SELECT id FROM albums WHERE name = '%1' AND " ).arg( m_collection->sqlStorage()->escape( name ) );
if( albumArtist.isEmpty() )
{
query += QString( "artist IS NULL" );
}
else
{
Meta::ArtistPtr artistPtr = getArtist( albumArtist );
if( !artistPtr )
return Meta::AlbumPtr();
Meta::SqlArtist *sqlArtist = static_cast<Meta::SqlArtist*>(artistPtr.data());
artistId = sqlArtist->id();
query += QString( "artist=%1" ).arg( artistId );
}
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
// ok. have to create a new album
QString insert = QString( "INSERT INTO albums( name, artist ) VALUES ('%1',%2);" ).
arg( m_collection->sqlStorage()->escape( name ),
artistId > 0 ? QString::number( artistId ) : "NULL" );
albumId = m_collection->sqlStorage()->insert( insert, "albums" );
m_collectionChanged = true; // we just added a new album
}
else
{
albumId = res[0].toInt();
}
if( !albumId )
return Meta::AlbumPtr();
Meta::SqlAlbum *sqlAlbum = new Meta::SqlAlbum( m_collection, albumId, name, artistId );
Meta::AlbumPtr album( sqlAlbum );
m_albumMap.insert( key, album );
m_albumIdMap.insert( albumId, album );
locker.unlock(); // prevent deadlock
return album;
}
Meta::AlbumPtr
SqlRegistry::getAlbum( int albumId )
{
Q_ASSERT( albumId > 0 ); // must be a valid id
{
// we want locker only for this block because we call another getAlbum() below
QMutexLocker locker( &m_albumMutex );
if( m_albumIdMap.contains( albumId ) )
return m_albumIdMap.value( albumId );
}
QString query = QString( "SELECT name, artist FROM albums WHERE id = %1" ).arg( albumId );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
return Meta::AlbumPtr(); // someone messed up
QString name = res[0];
int artistId = res[1].toInt();
return getAlbum( albumId, name, artistId );
}
Meta::AlbumPtr
SqlRegistry::getAlbum( int albumId, const QString &name, int artistId )
{
Q_ASSERT( albumId > 0 ); // must be a valid id
QMutexLocker locker( &m_albumMutex );
if( m_albumIdMap.contains( albumId ) )
return m_albumIdMap.value( albumId );
Meta::ArtistPtr artist = getArtist( artistId );
AlbumKey key(name, artist ? artist->name() : QString() );
if( m_albumMap.contains( key ) )
return m_albumMap.value( key );
Meta::SqlAlbum *sqlAlbum = new Meta::SqlAlbum( m_collection, albumId, name, artistId );
Meta::AlbumPtr album( sqlAlbum );
m_albumMap.insert( key, album );
m_albumIdMap.insert( albumId, album );
return album;
}
// ------------ label
Meta::LabelPtr
SqlRegistry::getLabel( const QString &oLabel )
{
QMutexLocker locker( &m_labelMutex );
QString label = oLabel.left( DatabaseUpdater::textColumnLength() );
if( m_labelMap.contains( label ) )
return m_labelMap.value( label );
int id;
QString query = QString( "SELECT id FROM labels WHERE label = '%1';" ).arg( m_collection->sqlStorage()->escape( label ) );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
{
QString insert = QString( "INSERT INTO labels( label ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( label ) );
id = m_collection->sqlStorage()->insert( insert, "albums" );
}
else
{
id = res[0].toInt();
}
if( !id )
return Meta::LabelPtr();
Meta::LabelPtr labelPtr( new Meta::SqlLabel( m_collection, id, label ) );
m_labelMap.insert( label, labelPtr );
return labelPtr;
}
Meta::LabelPtr
SqlRegistry::getLabel( int id )
{
Q_ASSERT( id > 0 ); // must be a valid id
QMutexLocker locker( &m_labelMutex );
QString query = QString( "SELECT label FROM labels WHERE id = '%1';" ).arg( id );
QStringList res = m_collection->sqlStorage()->query( query );
if( res.isEmpty() )
return Meta::LabelPtr();
QString label = res[0];
Meta::LabelPtr labelPtr( new Meta::SqlLabel( m_collection, id, label ) );
m_labelMap.insert( label, labelPtr );
return labelPtr;
}
Meta::LabelPtr
SqlRegistry::getLabel( int id, const QString &label )
{
Q_ASSERT( id > 0 ); // must be a valid id
QMutexLocker locker( &m_labelMutex );
if( m_labelMap.contains( label ) )
return m_labelMap.value( label );
Meta::LabelPtr labelPtr( new Meta::SqlLabel( m_collection, id, label ) );
m_labelMap.insert( label, labelPtr );
return labelPtr;
}
// ---------------- generic database management --------------
void
SqlRegistry::blockDatabaseUpdate()
{
QMutexLocker locker( &m_blockMutex );
m_blockDatabaseUpdateCount++;
}
void
SqlRegistry::unblockDatabaseUpdate()
{
{
QMutexLocker locker( &m_blockMutex );
Q_ASSERT( m_blockDatabaseUpdateCount > 0 );
m_blockDatabaseUpdateCount--;
}
// update the database
commitDirtyTracks();
}
void
SqlRegistry::commitDirtyTracks()
{
QMutexLocker locker( &m_blockMutex );
if( m_blockDatabaseUpdateCount > 0 )
return;
QList< Meta::SqlYearPtr > dirtyYears = m_dirtyYears.toList();
QList< Meta::SqlGenrePtr > dirtyGenres = m_dirtyGenres.toList();
QList< Meta::SqlAlbumPtr > dirtyAlbums = m_dirtyAlbums.toList();
QList< Meta::SqlTrackPtr > dirtyTracks = m_dirtyTracks.toList();
QList< Meta::SqlArtistPtr > dirtyArtists = m_dirtyArtists.toList();
QList< Meta::SqlComposerPtr > dirtyComposers = m_dirtyComposers.toList();
m_dirtyYears.clear();
m_dirtyGenres.clear();
m_dirtyAlbums.clear();
m_dirtyTracks.clear();
m_dirtyArtists.clear();
m_dirtyComposers.clear();
locker.unlock(); // need to unlock before notifying the observers
// -- commit all the dirty tracks
TrackUrlsTableCommitter().commit( dirtyTracks );
TrackTracksTableCommitter().commit( dirtyTracks );
TrackStatisticsTableCommitter().commit( dirtyTracks );
// -- notify all observers
foreach( Meta::SqlYearPtr year, dirtyYears )
{
// this means that a new year was added to track or an old removed (or both),
// Collection docs says we need to emit updated() in this case. Ditto below.
m_collectionChanged = true;
year->invalidateCache();
year->notifyObservers();
}
foreach( Meta::SqlGenrePtr genre, dirtyGenres )
{
m_collectionChanged = true;
genre->invalidateCache();
genre->notifyObservers();
}
foreach( Meta::SqlAlbumPtr album, dirtyAlbums )
{
m_collectionChanged = true;
album->invalidateCache();
album->notifyObservers();
}
foreach( Meta::SqlTrackPtr track, dirtyTracks )
{
// if only track changes, no need to emit updated() from here
track->notifyObservers();
}
foreach( Meta::SqlArtistPtr artist, dirtyArtists )
{
m_collectionChanged = true;
artist->invalidateCache();
artist->notifyObservers();
}
foreach( Meta::SqlComposerPtr composer, dirtyComposers )
{
m_collectionChanged = true;
composer->invalidateCache();
composer->notifyObservers();
}
if( m_collectionChanged )
m_collection->collectionUpdated();
m_collectionChanged = false;
}
void
SqlRegistry::emptyCache()
{
if( m_collection->scanManager() && m_collection->scanManager()->isRunning() )
return; // don't clean the cache if a scan is done
bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer, hasLabel;
hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = hasLabel = false;
//try to avoid possible deadlocks by aborting when we can't get all locks
if ( ( hasTrack = m_trackMutex.tryLock() )
&& ( hasAlbum = m_albumMutex.tryLock() )
&& ( hasArtist = m_artistMutex.tryLock() )
&& ( hasYear = m_yearMutex.tryLock() )
&& ( hasGenre = m_genreMutex.tryLock() )
&& ( hasComposer = m_composerMutex.tryLock() )
&& ( hasLabel = m_labelMutex.tryLock() ) )
{
#define mapCached( Cache, Key, Map, Res ) \
Cache[Key] = qMakePair( Map.count(), Res.join(QLatin1String(" ")).toInt() );
QMap<QString, QPair<int, int> > cachedBefore;
QString query = QString( "SELECT COUNT(*) FROM albums;" );
QStringList res = m_collection->sqlStorage()->query( query );
mapCached( cachedBefore, "albums", m_albumMap, res );
query = QString( "SELECT COUNT(*) FROM tracks;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedBefore, "tracks", m_trackMap, res );
query = QString( "SELECT COUNT(*) FROM artists;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedBefore, "artists", m_artistMap, res );
query = QString( "SELECT COUNT(*) FROM genres;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedBefore, "genres", m_genreMap, res );
//this very simple garbage collector doesn't handle cyclic object graphs
//so care has to be taken to make sure that we are not dealing with a cyclic graph
//by invalidating the tracks cache on all objects
#define foreachInvalidateCache( Key, Type, RealType, x ) \
for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
RealType::staticCast( iter.next().value() )->invalidateCache()
foreachInvalidateCache( AlbumKey, Meta::AlbumPtr, KSharedPtr<Meta::SqlAlbum>, m_albumMap );
foreachInvalidateCache( QString, Meta::ArtistPtr, KSharedPtr<Meta::SqlArtist>, m_artistMap );
foreachInvalidateCache( QString, Meta::GenrePtr, KSharedPtr<Meta::SqlGenre>, m_genreMap );
foreachInvalidateCache( QString, Meta::ComposerPtr, KSharedPtr<Meta::SqlComposer>, m_composerMap );
foreachInvalidateCache( int, Meta::YearPtr, KSharedPtr<Meta::SqlYear>, m_yearMap );
foreachInvalidateCache( QString, Meta::LabelPtr, KSharedPtr<Meta::SqlLabel>, m_labelMap );
#undef foreachInvalidateCache
// elem.count() == 2 is correct because elem is one pointer to the object
// and the other is stored in the hash map (except for m_trackMap, m_albumMap
// and m_artistMap , where another refence is stored in m_uidMap, m_albumIdMap
// and m_artistIdMap
#define foreachCollectGarbage( Key, Type, RefCount, x ) \
for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
{ \
Type elem = iter.next().value(); \
if( elem.count() == RefCount ) \
iter.remove(); \
}
foreachCollectGarbage( TrackPath, Meta::TrackPtr, 3, m_trackMap );
foreachCollectGarbage( QString, Meta::TrackPtr, 2, m_uidMap );
// run before artist so that album artist pointers can be garbage collected
foreachCollectGarbage( AlbumKey, Meta::AlbumPtr, 3, m_albumMap );
foreachCollectGarbage( int, Meta::AlbumPtr, 2, m_albumIdMap );
foreachCollectGarbage( QString, Meta::ArtistPtr, 3, m_artistMap );
foreachCollectGarbage( int, Meta::ArtistPtr, 2, m_artistIdMap );
foreachCollectGarbage( QString, Meta::GenrePtr, 2, m_genreMap );
foreachCollectGarbage( QString, Meta::ComposerPtr, 2, m_composerMap );
foreachCollectGarbage( int, Meta::YearPtr, 2, m_yearMap );
foreachCollectGarbage( QString, Meta::LabelPtr, 2, m_labelMap );
#undef foreachCollectGarbage
QMap<QString, QPair<int, int> > cachedAfter;
query = QString( "SELECT COUNT(*) FROM albums;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedAfter, "albums", m_albumMap, res );
query = QString( "SELECT COUNT(*) FROM tracks;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedAfter, "tracks", m_trackMap, res );
query = QString( "SELECT COUNT(*) FROM artists;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedAfter, "artists", m_artistMap, res );
query = QString( "SELECT COUNT(*) FROM genres;" );
res = m_collection->sqlStorage()->query( query );
mapCached( cachedAfter, "genres", m_genreMap, res );
#undef mapCached
if( cachedBefore != cachedAfter )
{
QMapIterator<QString, QPair<int, int> > i(cachedAfter), iLast(cachedBefore);
while( i.hasNext() && iLast.hasNext() )
{
i.next();
iLast.next();
int count = i.value().first;
int total = i.value().second;
QString diff = QString::number( count - iLast.value().first );
QString text = QString( "%1 (%2) of %3 cached" ).arg( count ).arg( diff ).arg( total );
debug() << QString( "%1: %2" ).arg( i.key(), 8 ).arg( text ).toLocal8Bit().constData();
}
}
}
//make sure to unlock all necessary locks
//important: calling unlock() on an unlocked mutex gives an undefined result
//unlocking a mutex locked by another thread results in an error, so be careful
if( hasTrack ) m_trackMutex.unlock();
if( hasAlbum ) m_albumMutex.unlock();
if( hasArtist ) m_artistMutex.unlock();
if( hasYear ) m_yearMutex.unlock();
if( hasGenre ) m_genreMutex.unlock();
if( hasComposer ) m_composerMutex.unlock();
if( hasLabel ) m_labelMutex.unlock();
}
diff --git a/src/core-impl/collections/db/sql/SqlScanResultProcessor.cpp b/src/core-impl/collections/db/sql/SqlScanResultProcessor.cpp
index 7bfdd152c4..006dcbafa8 100644
--- a/src/core-impl/collections/db/sql/SqlScanResultProcessor.cpp
+++ b/src/core-impl/collections/db/sql/SqlScanResultProcessor.cpp
@@ -1,728 +1,728 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2009-2010 Jeff Mitchell <mitchell@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SqlScanResultProcessor"
#include "SqlScanResultProcessor.h"
#include "MainWindow.h"
#include "collectionscanner/Directory.h"
#include "collectionscanner/Album.h"
#include "collectionscanner/Track.h"
#include "collectionscanner/Playlist.h"
#include "core/support/Debug.h"
#include "core-impl/collections/db/MountPointManager.h"
#include "core-impl/collections/db/sql/SqlQueryMaker.h"
#include "playlistmanager/PlaylistManager.h"
#include <KMessageBox>
#include <QApplication>
SqlScanResultProcessor::SqlScanResultProcessor( GenericScanManager* manager,
Collections::SqlCollection* collection,
QObject *parent )
: AbstractScanResultProcessor( manager, parent )
, m_collection( collection )
{ }
SqlScanResultProcessor::~SqlScanResultProcessor()
{ }
void
SqlScanResultProcessor::scanStarted( GenericScanManager::ScanType type )
{
AbstractScanResultProcessor::scanStarted( type );
m_collection->sqlStorage()->clearLastErrors();
m_messages.clear();
}
void
SqlScanResultProcessor::scanSucceeded()
{
DEBUG_BLOCK;
// we are blocking the updated signal for maximum of one second.
m_blockedTime = QDateTime::currentDateTime();
blockUpdates();
urlsCacheInit();
// -- call the base implementation
AbstractScanResultProcessor::scanSucceeded();
// -- error reporting
m_messages.append( m_collection->sqlStorage()->getLastErrors() );
if( !m_messages.isEmpty() && qobject_cast<QGuiApplication*>(qApp) )
- QTimer::singleShot(0, this, SLOT(displayMessages())); // do in the UI thread
+ QTimer::singleShot(0, this, &SqlScanResultProcessor::displayMessages); // do in the UI thread
unblockUpdates();
}
void
SqlScanResultProcessor::displayMessages()
{
QString errorList = m_messages.join( "</li><li>" ).replace( '\n', "<br>" );
QString text = i18n( "<ul><li>%1</li></ul>"
"In most cases this means that not all of your tracks were imported.<br>"
"See <a href='http://userbase.kde.org/Amarok/Manual/Various/TroubleshootingAndCommonProblems#Duplicate_Tracks'>"
"Amarok Manual</a> for information about duplicate tracks.", errorList );
KMessageBox::error( The::mainWindow(), text, i18n( "Errors During Collection Scan" ),
KMessageBox::AllowLink );
m_messages.clear();
}
void
SqlScanResultProcessor::blockUpdates()
{
m_collection->blockUpdatedSignal();
m_collection->registry()->blockDatabaseUpdate();
}
void
SqlScanResultProcessor::unblockUpdates()
{
m_collection->registry()->unblockDatabaseUpdate();
m_collection->unblockUpdatedSignal();
}
void
SqlScanResultProcessor::message( const QString& message )
{
m_messages.append( message );
}
void
SqlScanResultProcessor::commitDirectory( QSharedPointer<CollectionScanner::Directory> directory )
{
QString path = directory->path();
// a bit of paranoia:
if( m_foundDirectories.contains( path ) )
warning() << "commitDirectory(): duplicate directory path" << path << "in"
<< "collectionscanner output. This shouldn't happen.";
// getDirectory() updates the directory entry mtime:
int dirId = m_collection->registry()->getDirectory( path, directory->mtime() );
// we never dereference key of m_directoryIds, it is safe to add it as a plain pointer
m_directoryIds.insert( directory.data(), dirId );
m_foundDirectories.insert( path, dirId );
AbstractScanResultProcessor::commitDirectory( directory );
// --- unblock every 5 second. Maybe not really needed, but still nice
if( m_blockedTime.secsTo( QDateTime::currentDateTime() ) >= 5 )
{
unblockUpdates();
m_blockedTime = QDateTime::currentDateTime();
blockUpdates();
}
}
void
SqlScanResultProcessor::commitAlbum( CollectionScanner::Album *album )
{
debug() << "commitAlbum on"<<album->name()<< "artist"<<album->artist();
// --- get or create the album
Meta::SqlAlbumPtr metaAlbum;
metaAlbum = Meta::SqlAlbumPtr::staticCast( m_collection->getAlbum( album->name(), album->artist() ) );
if( !metaAlbum )
return;
m_albumIds.insert( album, metaAlbum->id() );
// --- add all tracks
foreach( CollectionScanner::Track *track, album->tracks() )
commitTrack( track, album );
// --- set the cover if we have one
// we need to do this after the tracks are added in case of an embedded cover
bool suppressAutoFetch = metaAlbum->suppressImageAutoFetch();
metaAlbum->setSuppressImageAutoFetch( true );
if( m_type == GenericScanManager::FullScan )
{
if( !album->cover().isEmpty() )
{
metaAlbum->removeImage();
metaAlbum->setImage( album->cover() );
}
}
else
{
if( !metaAlbum->hasImage() && !album->cover().isEmpty() )
metaAlbum->setImage( album->cover() );
}
metaAlbum->setSuppressImageAutoFetch( suppressAutoFetch );
}
void
SqlScanResultProcessor::commitTrack( CollectionScanner::Track *track,
CollectionScanner::Album *srcAlbum )
{
// debug() << "commitTrack on"<<track->title()<< "album"<<srcAlbum->name() << "dir:" << track->directory()->path()<<track->directory();
Q_ASSERT( track );
Q_ASSERT( srcAlbum );
Q_ASSERT( m_directoryIds.contains( track->directory() ) );
int directoryId = m_directoryIds.value( track->directory() );
Q_ASSERT( m_albumIds.contains( srcAlbum ) );
int albumId = m_albumIds.value( srcAlbum );
QString uid = track->uniqueid();
if( uid.isEmpty() )
{
warning() << "commitTrack(): got track with empty unique id from the scanner,"
<< "not adding it";
m_messages.append( QString( "Not adding track %1 because it has no unique id." ).
arg(track->path()) );
return;
}
uid = m_collection->generateUidUrl( uid );
int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromUserInput(track->path()) );
QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, track->path() );
if( m_foundTracks.contains( uid ) )
{
const UrlEntry old = m_urlsCache.value( m_uidCache.value( uid ) );
const char *pattern = I18N_NOOP( "Duplicates found, the second file will be ignored:\n%1\n%2" );
// we want translated version for GUI and non-translated for debug log
warning() << "commitTrack():" << QString( pattern ).arg( old.path, track->path() );
m_messages.append( i18n( pattern, old.path, track->path() ) );
return;
}
Meta::SqlTrackPtr metaTrack;
UrlEntry entry;
// find an existing track by uid
if( m_uidCache.contains( uid ) )
{
// uid is sadly not unique. Try to find the best url id.
int urlId = findBestUrlId( uid, track->path() );
Q_ASSERT( urlId > 0 );
Q_ASSERT( m_urlsCache.contains( urlId ) );
entry = m_urlsCache.value( urlId );
entry.path = track->path();
entry.directoryId = directoryId;
metaTrack = Meta::SqlTrackPtr::staticCast( m_collection->registry()->getTrack( urlId ) );
Q_ASSERT( metaTrack->urlId() == entry.id );
}
// find an existing track by path
else if( m_pathCache.contains( track->path() ) )
{
int urlId = m_pathCache.value( track->path() );
Q_ASSERT( m_urlsCache.contains( urlId ) );
entry = m_urlsCache.value( urlId );
entry.uid = uid;
entry.directoryId = directoryId;
metaTrack = Meta::SqlTrackPtr::staticCast( m_collection->registry()->getTrack( urlId ) );
Q_ASSERT( metaTrack->urlId() == entry.id );
}
// create a new one
else
{
static int autoDecrementId = -1;
entry.id = autoDecrementId--;
entry.path = track->path();
entry.uid = uid;
entry.directoryId = directoryId;
metaTrack = Meta::SqlTrackPtr::staticCast( m_collection->getTrack( deviceId, rpath, directoryId, uid ) );
}
if( !metaTrack )
{
QString text = QString( "Something went wrong when importing track %1, metaTrack "
"is null while it shouldn't be." ).arg( track->path() );
warning() << "commitTrack():" << text.toLocal8Bit().data();
m_messages.append( text );
return;
}
urlsCacheInsert( entry ); // removes the previous entry (by id) first if necessary
m_foundTracks.insert( uid, entry.id );
// TODO: we need to check the modified date of the file agains the last updated of the file
// to figure out if the track information was updated from outside Amarok.
// In such a case we would fully reread all the information as if in a FullScan
// -- set the values
metaTrack->setWriteFile( false ); // no need to write the tags back
metaTrack->beginUpdate();
metaTrack->setUidUrl( uid );
metaTrack->setUrl( deviceId, rpath, directoryId );
if( m_type == GenericScanManager::FullScan ||
!track->title().isEmpty() )
metaTrack->setTitle( track->title() );
if( m_type == GenericScanManager::FullScan ||
albumId != -1 )
metaTrack->setAlbum( albumId );
if( m_type == GenericScanManager::FullScan ||
!track->artist().isEmpty() )
metaTrack->setArtist( track->artist() );
if( m_type == GenericScanManager::FullScan ||
!track->composer().isEmpty() )
metaTrack->setComposer( track->composer() );
if( m_type == GenericScanManager::FullScan ||
track->year() >= 0 )
metaTrack->setYear( (track->year() >= 0) ? track->year() : 0 );
if( m_type == GenericScanManager::FullScan ||
!track->genre().isEmpty() )
metaTrack->setGenre( track->genre() );
metaTrack->setType( track->filetype() );
if( m_type == GenericScanManager::FullScan ||
track->bpm() >= 0 )
metaTrack->setBpm( track->bpm() );
if( m_type == GenericScanManager::FullScan ||
!track->comment().isEmpty() )
metaTrack->setComment( track->comment() );
if( (m_type == GenericScanManager::FullScan || metaTrack->score() == 0) &&
track->score() >= 0 )
metaTrack->setScore( track->score() );
if( (m_type == GenericScanManager::FullScan || metaTrack->rating() == 0.0) &&
track->rating() >= 0 )
metaTrack->setRating( track->rating() );
if( (m_type == GenericScanManager::FullScan || metaTrack->length() == 0) &&
track->length() >= 0 )
metaTrack->setLength( track->length() );
// the filesize is updated every time after the
// file is changed. Doesn't make sense to set it.
if( (m_type == GenericScanManager::FullScan || !metaTrack->modifyDate().isValid()) &&
track->modified().isValid() )
metaTrack->setModifyDate( track->modified() );
if( (m_type == GenericScanManager::FullScan || metaTrack->sampleRate() == 0) &&
track->samplerate() >= 0 )
metaTrack->setSampleRate( track->samplerate() );
if( (m_type == GenericScanManager::FullScan || metaTrack->bitrate() == 0) &&
track->bitrate() >= 0 )
metaTrack->setBitrate( track->bitrate() );
if( (m_type == GenericScanManager::FullScan || metaTrack->trackNumber() == 0) &&
track->track() >= 0 )
metaTrack->setTrackNumber( track->track() );
if( (m_type == GenericScanManager::FullScan || metaTrack->discNumber() == 0) &&
track->disc() >= 0 )
metaTrack->setDiscNumber( track->disc() );
if( m_type == GenericScanManager::FullScan && track->playcount() >= metaTrack->playCount() )
metaTrack->setPlayCount( track->playcount() );
Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain,
Meta::ReplayGain_Track_Peak,
Meta::ReplayGain_Album_Gain,
Meta::ReplayGain_Album_Peak };
for( int i=0; i<4; i++ )
{
if( track->replayGain( modes[i] ) != 0.0 )
metaTrack->setReplayGain( modes[i], track->replayGain( modes[i] ) );
}
metaTrack->endUpdate();
metaTrack->setWriteFile( true );
}
void
SqlScanResultProcessor::deleteDeletedDirectories()
{
SqlStorage *storage = m_collection->sqlStorage();
QList<DirectoryEntry> toCheck;
switch( m_type )
{
case GenericScanManager::FullScan:
case GenericScanManager::UpdateScan:
toCheck = mountedDirectories();
break;
case GenericScanManager::PartialUpdateScan:
toCheck = deletedDirectories();
}
// -- check if the have been found during the scan
foreach( const DirectoryEntry &e, toCheck )
{
/* we need to match directories by their (absolute) path, otherwise following
* scenario triggers statistics loss (bug 298275):
*
* 1. user relocates collection to different filesystem, but clones path structure
* or toggles MassStorageDeviceHandler enabled in Config -> plugins.
* 2. collectionscanner knows nothings about directory ids, so it doesn't detect
* any track changes and emits a bunch of skipped (unchanged) dirs with no
* tracks.
* 3. SqlRegistry::getDirectory() called there from returns different directory id
* then in past.
* 4. deleteDeletedDirectories() is called, and if it operates on directory ids,
* it happily removes _all_ directories, taking tracks with it.
* 5. Tracks disappear from the UI until full rescan, stats, lyrics, labels are
* lost forever.
*/
QString path = m_collection->mountPointManager()->getAbsolutePath( e.deviceId, e.dir );
bool deleteThisDir = false;
if( !m_foundDirectories.contains( path ) )
deleteThisDir = true;
else if( m_foundDirectories.value( path ) != e.dirId )
{
int newDirId = m_foundDirectories.value( path );
// as a safety measure, we don't delete the old dir if relocation fails
deleteThisDir = relocateTracksToNewDirectory( e.dirId, newDirId );
}
if( deleteThisDir )
{
deleteDeletedTracks( e.dirId );
QString query = QString( "DELETE FROM directories WHERE id = %1;" ).arg( e.dirId );
storage->query( query );
}
}
}
void
SqlScanResultProcessor::deleteDeletedTracksAndSubdirs( QSharedPointer<CollectionScanner::Directory> directory )
{
Q_ASSERT( m_directoryIds.contains( directory.data() ) );
int directoryId = m_directoryIds.value( directory.data() );
// only deletes tracks directly in this dir
deleteDeletedTracks( directoryId );
// trigger deletion of deleted subdirectories in deleteDeletedDirectories():
m_scannedDirectoryIds.insert( directoryId );
}
void
SqlScanResultProcessor::deleteDeletedTracks( int directoryId )
{
// -- find all tracks
QList<int> urlIds = m_directoryCache.values( directoryId );
// -- check if the tracks have been found during the scan
foreach( int urlId, urlIds )
{
Q_ASSERT( m_urlsCache.contains( urlId ) );
const UrlEntry &entry = m_urlsCache[ urlId ];
Q_ASSERT( entry.directoryId == directoryId );
// we need to match both uid and url id, because uid is not unique
if( !m_foundTracks.contains( entry.uid, entry.id ) )
{
removeTrack( entry );
urlsCacheRemove( entry );
}
}
}
int
SqlScanResultProcessor::findBestUrlId( const QString &uid, const QString &path )
{
QList<int> urlIds = m_uidCache.values( uid );
if( urlIds.isEmpty() )
return -1;
if( urlIds.size() == 1 )
return urlIds.at( 0 ); // normal operation
foreach( int testedUrlId, urlIds )
{
Q_ASSERT( m_urlsCache.contains( testedUrlId ) );
if( m_urlsCache[ testedUrlId ].path == path )
return testedUrlId;
}
warning() << "multiple url entries with uid" << uid << "found in the database, but"
<< "none with current path" << path << "Choosing blindly the last one out"
<< "of url id candidates" << urlIds;
return urlIds.last();
}
bool
SqlScanResultProcessor::relocateTracksToNewDirectory( int oldDirId, int newDirId )
{
QList<int> urlIds = m_directoryCache.values( oldDirId );
if( urlIds.isEmpty() )
return true; // nothing to do
MountPointManager *manager = m_collection->mountPointManager();
SqlRegistry *reg = m_collection->registry();
SqlStorage *storage = m_collection->sqlStorage();
// sanity checking, not strictly needed, but imagine new device appearing in the
// middle of the scan, so rather prevent db corruption:
QStringList res = storage->query( QString( "SELECT deviceid FROM directories "
"WHERE id = %1" ).arg( newDirId ) );
if( res.count() != 1 )
{
warning() << "relocateTracksToNewDirectory(): no or multiple entries when"
<< "querying directory with id" << newDirId;
return false;
}
int newDirDeviceId = res.at( 0 ).toInt();
foreach( int urlId, urlIds )
{
Q_ASSERT( m_urlsCache.contains( urlId ) );
UrlEntry entry = m_urlsCache.value( urlId );
Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( reg->getTrack( urlId ) );
Q_ASSERT( track );
// not strictly needed, but we want to sanity check it to prevent corrupt db
int deviceId = manager->getIdForUrl( QUrl::fromUserInput(entry.path) );
if( newDirDeviceId != deviceId )
{
warning() << "relocateTracksToNewDirectory(): device id from newDirId ("
<< res.at( 0 ).toInt() << ") and device id from mountPointManager ("
<< deviceId << ") don't match!";
return false;
}
QString rpath = manager->getRelativePath( deviceId, entry.path );
track->setUrl( deviceId, rpath, newDirId );
entry.directoryId = newDirId;
urlsCacheInsert( entry ); // removes the previous entry (by id) first
}
return true;
}
void
SqlScanResultProcessor::removeTrack( const UrlEntry &entry )
{
debug() << "removeTrack(" << entry << ")";
// we used to skip track removal is m_messages wasn't empty, but that lead to to
// tracks left laying around. We now hope that the result processor got better and
// only removes tracks that should be removed.
SqlRegistry *reg = m_collection->registry();
// we must get the track by id, uid is not unique
Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( reg->getTrack( entry.id ) );
Q_ASSERT( track->urlId() == entry.id );
track->remove();
}
QList<SqlScanResultProcessor::DirectoryEntry>
SqlScanResultProcessor::mountedDirectories() const
{
SqlStorage *storage = m_collection->sqlStorage();
// -- get a list of all mounted device ids
QList<int> idList = m_collection->mountPointManager()->getMountedDeviceIds();
QString deviceIds;
foreach( int id, idList )
{
if ( !deviceIds.isEmpty() )
deviceIds += ',';
deviceIds += QString::number( id );
}
// -- get all (mounted) directories
QString query = QString( "SELECT id, deviceid, dir FROM directories "
"WHERE deviceid IN (%1)" ).arg( deviceIds );
QStringList res = storage->query( query );
QList<DirectoryEntry> result;
for( int i = 0; i < res.count(); )
{
DirectoryEntry e;
e.dirId = res.at( i++ ).toInt();
e.deviceId = res.at( i++ ).toInt();
e.dir = res.at( i++ );
result << e;
}
return result;
}
QList<SqlScanResultProcessor::DirectoryEntry>
SqlScanResultProcessor::deletedDirectories() const
{
SqlStorage *storage = m_collection->sqlStorage();
QHash<int, DirectoryEntry> idToDirEntryMap; // for faster processing during filtering
foreach( int directoryId, m_scannedDirectoryIds )
{
QString query = QString( "SELECT deviceid, dir FROM directories WHERE id = %1" )
.arg( directoryId );
QStringList res = storage->query( query );
if( res.count() != 2 )
{
warning() << "unexpected query result" << res << "in deletedDirectories()";
continue;
}
int deviceId = res.at( 0 ).toInt();
QString dir = res.at( 1 );
// select all child directories
query = QString( "SELECT id, deviceid, dir FROM directories WHERE deviceid = %1 "
"AND dir LIKE '%2_%'" ).arg( deviceId ).arg( storage->escape( dir ) );
res = storage->query( query );
for( int i = 0; i < res.count(); )
{
DirectoryEntry e;
e.dirId = res.at( i++ ).toInt();
e.deviceId = res.at( i++ ).toInt();
e.dir = res.at( i++ );
idToDirEntryMap.insert( e.dirId, e );
}
}
// now we must fileter-out all found directories *and their children*, because the
// children are *not* in m_foundDirectories and deleteDeletedDirectories() would
// remove them errorneously
foreach( int foundDirectoryId, m_foundDirectories )
{
if( idToDirEntryMap.contains( foundDirectoryId ) )
{
int existingDeviceId = idToDirEntryMap[ foundDirectoryId ].deviceId;
QString existingPath = idToDirEntryMap[ foundDirectoryId ].dir;
idToDirEntryMap.remove( foundDirectoryId );
// now remove all children of the existing directory
QMutableHashIterator<int, DirectoryEntry> it( idToDirEntryMap );
while( it.hasNext() )
{
const DirectoryEntry &e = it.next().value();
if( e.deviceId == existingDeviceId && e.dir.startsWith( existingPath ) )
it.remove();
}
}
}
return idToDirEntryMap.values();
}
void
SqlScanResultProcessor::urlsCacheInit()
{
SqlStorage *storage = m_collection->sqlStorage();
QString query = QString( "SELECT id, deviceid, rpath, directory, uniqueid FROM urls;");
QStringList res = storage->query( query );
for( int i = 0; i < res.count(); )
{
int id = res.at(i++).toInt();
int deviceId = res.at(i++).toInt();
QString rpath = res.at(i++);
int directoryId = res.at(i++).toInt();
QString uid = res.at(i++);
QString path;
if( deviceId )
path = m_collection->mountPointManager()->getAbsolutePath( deviceId, rpath );
else
path = rpath;
UrlEntry entry;
entry.id = id;
entry.path = path;
entry.directoryId = directoryId;
entry.uid = uid;
if( !directoryId )
{
warning() << "Found urls entry without directory. A phantom track. Removing" << path;
removeTrack( entry );
continue;
}
urlsCacheInsert( entry );
}
}
void
SqlScanResultProcessor::urlsCacheInsert( const UrlEntry &entry )
{
// this case is normal operation
if( m_urlsCache.contains( entry.id ) )
urlsCacheRemove( m_urlsCache[ entry.id ] );
// following shoudn't normally happen:
if( m_pathCache.contains( entry.path ) )
{
int oldId = m_pathCache.value( entry.path );
Q_ASSERT( m_urlsCache.contains( oldId ) );
const UrlEntry &old = m_urlsCache[ oldId ];
warning() << "urlsCacheInsert(): found duplicate in path. old" << old
<< "will be hidden by the new one in the cache:" << entry;
}
// this will signify error in this class:
Q_ASSERT( !m_uidCache.contains( entry.uid, entry.id ) );
Q_ASSERT( !m_directoryCache.contains( entry.directoryId, entry.id ) );
m_urlsCache.insert( entry.id, entry );
m_uidCache.insert( entry.uid, entry.id );
m_pathCache.insert( entry.path, entry.id );
m_directoryCache.insert( entry.directoryId, entry.id );
}
void
SqlScanResultProcessor::cleanupMembers()
{
m_foundDirectories.clear();
m_foundTracks.clear();
m_scannedDirectoryIds.clear();
m_directoryIds.clear();
m_albumIds.clear();
m_urlsCache.clear();
m_uidCache.clear();
m_pathCache.clear();
m_directoryCache.clear();
AbstractScanResultProcessor::cleanupMembers();
}
void
SqlScanResultProcessor::urlsCacheRemove( const UrlEntry &entry )
{
if( !m_urlsCache.contains( entry.id ) )
return;
m_uidCache.remove( entry.uid, entry.id );
m_pathCache.remove( entry.path );
m_directoryCache.remove( entry.directoryId, entry.id );
m_urlsCache.remove( entry.id );
}
QDebug
operator<<( QDebug dbg, const SqlScanResultProcessor::UrlEntry &entry )
{
dbg.nospace() << "Entry(id=" << entry.id << ", path=" << entry.path << ", dirId="
<< entry.directoryId << ", uid=" << entry.uid << ")";
return dbg.space();
}
diff --git a/src/core-impl/collections/db/sql/mysqlcollection/MySqlCollectionFactory.cpp b/src/core-impl/collections/db/sql/mysqlcollection/MySqlCollectionFactory.cpp
index e0e902ca43..df135a5b0f 100644
--- a/src/core-impl/collections/db/sql/mysqlcollection/MySqlCollectionFactory.cpp
+++ b/src/core-impl/collections/db/sql/mysqlcollection/MySqlCollectionFactory.cpp
@@ -1,44 +1,45 @@
/****************************************************************************************
* Copyright (c) 2008 Edward Toroshchin <edward.hades@gmail.com> *
* Copyright (c) 2009 Jeff Mitchell <mitchell@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MySqlCollectionFactory.h"
#include <core-impl/storage/StorageManager.h>
#include <core-impl/collections/db/sql/SqlCollection.h>
#include <core-impl/collections/db/sql/SqlCollectionFactory.h>
-#include <KLocale>
+#include <KLocalizedString>
+#include <KPluginFactory>
using namespace Collections;
AMAROK_EXPORT_COLLECTION( MySqlCollectionFactory, mysqlcollection )
void
MySqlCollectionFactory::init()
{
if( m_initialized )
return;
SqlCollectionFactory fac;
SqlStorage *storage = StorageManager::instance()->sqlStorage();
SqlCollection *collection = fac.createSqlCollection( storage );
m_initialized = true;
emit newCollection( collection );
}
#include "MySqlCollectionFactory.moc"
diff --git a/src/core-impl/collections/ipodcollection/IpodCollection.cpp b/src/core-impl/collections/ipodcollection/IpodCollection.cpp
index 689b6791ba..e29dea20cc 100644
--- a/src/core-impl/collections/ipodcollection/IpodCollection.cpp
+++ b/src/core-impl/collections/ipodcollection/IpodCollection.cpp
@@ -1,716 +1,722 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IpodCollection.h"
#include "IpodCollectionLocation.h"
#include "IpodMeta.h"
#include "IpodPlaylistProvider.h"
#include "jobs/IpodWriteDatabaseJob.h"
#include "jobs/IpodParseTracksJob.h"
#include "support/IphoneMountPoint.h"
#include "support/IpodDeviceHelper.h"
#include "support/IpodTranscodeCapability.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/MemoryCollection.h"
#include "core-impl/collections/support/MemoryMeta.h"
#include "core-impl/collections/support/MemoryQueryMaker.h"
#include "playlistmanager/PlaylistManager.h"
#include <KDiskFreeSpaceInfo>
#include <solid/device.h>
#include <solid/predicate.h>
#include <solid/storageaccess.h>
#include <ThreadWeaver/Queue>
#include <QTemporaryFile>
#include <QWeakPointer>
#include <gpod/itdb.h>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
const QString IpodCollection::s_uidUrlProtocol = QString( "amarok-ipodtrackuid" );
const QStringList IpodCollection::s_audioFileTypes = QStringList() << "mp3" << "aac"
<< "m4a" /* MPEG-4 AAC and also ALAC */ << "m4b" /* audiobook */ << "aiff" << "wav";
const QStringList IpodCollection::s_videoFileTypes = QStringList() << "m4v" << "mov";
const QStringList IpodCollection::s_audioVideoFileTypes = QStringList() << "mp4";
IpodCollection::IpodCollection( const QDir &mountPoint, const QString &uuid )
: Collections::Collection()
, m_configureDialog( 0 )
, m_mc( new Collections::MemoryCollection() )
, m_itdb( 0 )
, m_lastUpdated( 0 )
, m_preventUnmountTempFile( 0 )
, m_mountPoint( mountPoint.absolutePath() )
, m_uuid( uuid )
, m_iphoneAutoMountpoint( 0 )
, m_playlistProvider( 0 )
, m_configureAction( 0 )
, m_ejectAction( 0 )
, m_consolidateAction( 0 )
{
DEBUG_BLOCK
if( m_uuid.isEmpty() )
m_uuid = m_mountPoint;
}
IpodCollection::IpodCollection( const QString &uuid )
: Collections::Collection()
, m_configureDialog( 0 )
, m_mc( new Collections::MemoryCollection() )
, m_itdb( 0 )
, m_lastUpdated( 0 )
, m_preventUnmountTempFile( 0 )
, m_uuid( uuid )
, m_playlistProvider( 0 )
, m_configureAction( 0 )
, m_ejectAction( 0 )
, m_consolidateAction( 0 )
{
DEBUG_BLOCK
// following constructor displays sorry message if it cannot mount iPhone:
m_iphoneAutoMountpoint = new IphoneMountPoint( uuid );
m_mountPoint = m_iphoneAutoMountpoint->mountPoint();
if( m_uuid.isEmpty() )
m_uuid = m_mountPoint;
}
bool IpodCollection::init()
{
if( m_mountPoint.isEmpty() )
return false; // we have already displayed sorry message
m_updateTimer.setSingleShot( true );
- connect( this, SIGNAL(startUpdateTimer()), SLOT(slotStartUpdateTimer()) );
- connect( &m_updateTimer, SIGNAL(timeout()), SLOT(collectionUpdated()) );
+ connect( this, &IpodCollection::startUpdateTimer, this, &IpodCollection::slotStartUpdateTimer );
+ connect( &m_updateTimer, &QTimer::timeout, this, &IpodCollection::collectionUpdated );
m_writeDatabaseTimer.setSingleShot( true );
- connect( this, SIGNAL(startWriteDatabaseTimer()), SLOT(slotStartWriteDatabaseTimer()) );
- connect( &m_writeDatabaseTimer, SIGNAL(timeout()), SLOT(slotInitiateDatabaseWrite()) );
+ connect( this, &IpodCollection::startWriteDatabaseTimer, this, &IpodCollection::slotStartWriteDatabaseTimer );
+ connect( &m_writeDatabaseTimer, &QTimer::timeout, this, &IpodCollection::slotInitiateDatabaseWrite );
m_configureAction = new QAction( QIcon::fromTheme( "configure" ), i18n( "&Configure Device" ), this );
m_configureAction->setProperty( "popupdropper_svg_id", "configure" );
- connect( m_configureAction, SIGNAL(triggered()), SLOT(slotShowConfigureDialog()) );
+ connect( m_configureAction, &QAction::triggered, this, &IpodCollection::slotShowConfigureDialog );
m_ejectAction = new QAction( QIcon::fromTheme( "media-eject" ), i18n( "&Eject Device" ), this );
m_ejectAction->setProperty( "popupdropper_svg_id", "eject" );
- connect( m_ejectAction, SIGNAL(triggered()), SLOT(slotEject()) );
+ connect( m_ejectAction, &QAction::triggered, this, &IpodCollection::slotEject );
QString parseErrorMessage;
m_itdb = IpodDeviceHelper::parseItdb( m_mountPoint, parseErrorMessage );
m_prettyName = IpodDeviceHelper::collectionName( m_itdb ); // allows null m_itdb
// m_consolidateAction is used by the provider
m_consolidateAction = new QAction( QIcon::fromTheme( "dialog-ok-apply" ), i18n( "Re-add orphaned and forget stale tracks" ), this );
// provider needs to be up before IpodParseTracksJob is started
m_playlistProvider = new IpodPlaylistProvider( this );
- connect( m_playlistProvider, SIGNAL(startWriteDatabaseTimer()), SIGNAL(startWriteDatabaseTimer()) );
- connect( m_consolidateAction, SIGNAL(triggered()), m_playlistProvider, SLOT(slotConsolidateStaleOrphaned()) );
+ connect( m_playlistProvider, &IpodPlaylistProvider::startWriteDatabaseTimer, this, &IpodCollection::startWriteDatabaseTimer );
+ connect( m_consolidateAction, &QAction::triggered, m_playlistProvider, &IpodPlaylistProvider::slotConsolidateStaleOrphaned );
The::playlistManager()->addProvider( m_playlistProvider, m_playlistProvider->category() );
if( m_itdb )
{
// parse tracks in a thread in order not to block main thread
IpodParseTracksJob *job = new IpodParseTracksJob( this );
m_parseTracksJob = job;
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &IpodParseTracksJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
else
- slotShowConfigureDialog( parseErrorMessage ); // shows error message and allows initializing
+ slotShowConfigureDialogWithError( parseErrorMessage ); // shows error message and allows initializing
return true; // we have found iPod, even if it might not be initialised
}
IpodCollection::~IpodCollection()
{
DEBUG_BLOCK
The::playlistManager()->removeProvider( m_playlistProvider );
// this is not racy: destructor should be called in a main thread, the timer fires in the
// same thread
if( m_writeDatabaseTimer.isActive() )
{
m_writeDatabaseTimer.stop();
// call directly from main thread in destructor, we have no other chance:
writeDatabase();
}
delete m_preventUnmountTempFile; // this should have been certaily 0, but why not
m_preventUnmountTempFile = 0;
/* because m_itdb takes ownership of the tracks added to it, we need to remove the
* tracks from itdb before we delete it because in Amarok, IpodMeta::Track is the owner
* of the track */
IpodDeviceHelper::unlinkPlaylistsTracksFromItdb( m_itdb ); // does nothing if m_itdb is null
itdb_free( m_itdb ); // does nothing if m_itdb is null
m_itdb = 0;
delete m_configureDialog;
delete m_iphoneAutoMountpoint; // this can unmount iPhone and remove temporary dir
}
bool
IpodCollection::possiblyContainsTrack( const QUrl &url ) const
{
return url.toLocalFile().startsWith( m_mountPoint );
}
Meta::TrackPtr
IpodCollection::trackForUrl( const QUrl &url )
{
QString relativePath = url.toLocalFile().mid( m_mountPoint.size() + 1 );
QString uidUrl = QString( "%1/%2" ).arg( collectionId(), relativePath );
return trackForUidUrl( uidUrl );
}
bool
IpodCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
switch( type )
{
case Capabilities::Capability::Actions:
case Capabilities::Capability::Transcode:
return true;
default:
break;
}
return false;
}
Capabilities::Capability*
IpodCollection::createCapabilityInterface( Capabilities::Capability::Type type )
{
switch( type )
{
case Capabilities::Capability::Actions:
{
QList<QAction *> actions;
if( m_configureAction )
actions << m_configureAction;
if( m_ejectAction )
actions << m_ejectAction;
if( m_consolidateAction && m_playlistProvider && m_playlistProvider->hasStaleOrOrphaned() )
actions << m_consolidateAction;
return new Capabilities::ActionsCapability( actions );
}
case Capabilities::Capability::Transcode:
{
gchar *deviceDirChar = itdb_get_device_dir( QFile::encodeName( m_mountPoint ) );
QString deviceDir = QFile::decodeName( deviceDirChar );
g_free( deviceDirChar );
return new Capabilities::IpodTranscodeCapability( this, deviceDir );
}
default:
break;
}
return 0;
}
Collections::QueryMaker*
IpodCollection::queryMaker()
{
return new Collections::MemoryQueryMaker( m_mc.toWeakRef(), collectionId() );
}
QString
IpodCollection::uidUrlProtocol() const
{
return s_uidUrlProtocol;
}
QString
IpodCollection::collectionId() const
{
return QString( "%1://%2" ).arg( s_uidUrlProtocol, m_uuid );
}
QString
IpodCollection::prettyName() const
{
return m_prettyName;
}
QIcon
IpodCollection::icon() const
{
return QIcon::fromTheme("multimedia-player-apple-ipod");
}
bool
IpodCollection::hasCapacity() const
{
return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).isValid();
}
float
IpodCollection::usedCapacity() const
{
return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).used();
}
float
IpodCollection::totalCapacity() const
{
return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).size();
}
Collections::CollectionLocation*
IpodCollection::location()
{
return new IpodCollectionLocation( QWeakPointer<IpodCollection>( this ) );
}
bool
IpodCollection::isWritable() const
{
return IpodDeviceHelper::safeToWrite( m_mountPoint, m_itdb ); // returns false if m_itdb is null
}
bool
IpodCollection::isOrganizable() const
{
return false; // iPods are never organizable
}
void
IpodCollection::metadataChanged( Meta::TrackPtr track )
{
// reflect change to ouside world:
bool mapsChanged = MemoryMeta::MapChanger( m_mc.data() ).trackChanged( track );
if( mapsChanged )
// while docs say somehting different, collection browser doesn't update unless we emit updated()
emit startUpdateTimer();
emit startWriteDatabaseTimer();
}
QString
IpodCollection::mountPoint()
{
return m_mountPoint;
}
float
IpodCollection::capacityMargin() const
{
return 20*1024*1024; // 20 MiB
}
QStringList
IpodCollection::supportedFormats() const
{
QStringList ret( s_audioFileTypes );
if( m_itdb && itdb_device_supports_video( m_itdb->device ) )
ret << s_videoFileTypes << s_audioVideoFileTypes;
return ret;
}
Playlists::UserPlaylistProvider*
IpodCollection::playlistProvider() const
{
return m_playlistProvider;
}
Meta::TrackPtr
IpodCollection::trackForUidUrl( const QString &uidUrl )
{
m_mc->acquireReadLock();
Meta::TrackPtr ret = m_mc->trackMap().value( uidUrl, Meta::TrackPtr() );
m_mc->releaseLock();
return ret;
}
void
IpodCollection::slotDestroy()
{
// guard against user hitting the button twice or hitting it while there is another
// write database job alreaddy running
if( m_writeDatabaseJob )
{
IpodWriteDatabaseJob *job = m_writeDatabaseJob.data();
// don't create duplicate connections:
- disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotRemove()) );
- disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotPerformTeardownAndRemove()) );
- connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotRemove()) );
+ disconnect( job, &QObject::destroyed, this, &IpodCollection::slotRemove );
+ disconnect( job, &QObject::destroyed, this, &IpodCollection::slotPerformTeardownAndRemove );
+ connect( job, &QObject::destroyed, this, &IpodCollection::slotRemove );
}
// this is not racy: slotDestroy() is delivered to main thread, the timer fires in the
// same thread
else if( m_writeDatabaseTimer.isActive() )
{
// write database in a thread so that it need not be written in destructor
m_writeDatabaseTimer.stop();
IpodWriteDatabaseJob *job = new IpodWriteDatabaseJob( this );
m_writeDatabaseJob = job;
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
- connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotRemove()) );
+ connect( job, &IpodWriteDatabaseJob::done, job, &QObject::deleteLater );
+ connect( job, &QObject::destroyed, this, &IpodCollection::slotRemove );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
else
slotRemove();
}
void
IpodCollection::slotEject()
{
// guard against user hitting the button twice or hitting it while there is another
// write database job alreaddy running
if( m_writeDatabaseJob )
{
IpodWriteDatabaseJob *job = m_writeDatabaseJob.data();
// don't create duplicate connections:
- disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotRemove()) );
- disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotPerformTeardownAndRemove()) );
- connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotPerformTeardownAndRemove()) );
+ disconnect( job, &QObject::destroyed, this, &IpodCollection::slotRemove );
+ disconnect( job, &QObject::destroyed, this, &IpodCollection::slotPerformTeardownAndRemove );
+ connect( job, &QObject::destroyed, this, &IpodCollection::slotPerformTeardownAndRemove );
}
// this is not racy: slotEject() is delivered to main thread, the timer fires in the
// same thread
else if( m_writeDatabaseTimer.isActive() )
{
// write database now because iPod will be already unmounted in destructor
m_writeDatabaseTimer.stop();
IpodWriteDatabaseJob *job = new IpodWriteDatabaseJob( this );
m_writeDatabaseJob = job;
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
- connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotPerformTeardownAndRemove()) );
+ connect( job, &IpodWriteDatabaseJob::done, job, &QObject::deleteLater );
+ connect( job, &QObject::destroyed, this, &IpodCollection::slotPerformTeardownAndRemove );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
else
slotPerformTeardownAndRemove();
}
void
-IpodCollection::slotShowConfigureDialog( const QString &errorMessage )
+IpodCollection::slotShowConfigureDialog()
+{
+ slotShowConfigureDialogWithError( QString() );
+}
+
+void
+IpodCollection::slotShowConfigureDialogWithError( const QString &errorMessage )
{
if( !m_configureDialog )
{
// create the dialog
m_configureDialog = new QDialog();
QWidget *settingsWidget = new QWidget( m_configureDialog );
m_configureDialogUi.setupUi( settingsWidget );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout;
m_configureDialog->setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- m_configureDialog->connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- m_configureDialog->connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, m_configureDialog, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, m_configureDialog, &QDialog::reject);
mainLayout->addWidget(settingsWidget);
mainLayout->addWidget(buttonBox);
m_configureDialog->setWindowTitle( settingsWidget->windowTitle() ); // setupUi() sets this
if( m_itdb )
{
// we will never initialize this iPod this time, hide ui for it completely
m_configureDialogUi.modelComboLabel->hide();
m_configureDialogUi.modelComboBox->hide();
m_configureDialogUi.initializeLabel->hide();
m_configureDialogUi.initializeButton->hide();
}
- connect( m_configureDialogUi.initializeButton, SIGNAL(clicked(bool)), SLOT(slotInitialize()) );
- connect( m_configureDialog, SIGNAL(clicked()), SLOT(slotApplyConfiguration()) );
+ connect( m_configureDialogUi.initializeButton, &QPushButton::clicked, this, &IpodCollection::slotInitialize );
+ connect( m_configureDialog, &QDialog::accepted, this, &IpodCollection::slotApplyConfiguration );
}
QScopedPointer<Capabilities::TranscodeCapability> tc( create<Capabilities::TranscodeCapability>() );
IpodDeviceHelper::fillInConfigureDialog( m_configureDialog, &m_configureDialogUi,
m_mountPoint, m_itdb, tc->savedConfiguration(),
errorMessage );
// don't allow to resize the dialog too small:
m_configureDialog->setMinimumSize( m_configureDialog->sizeHint() );
m_configureDialog->show();
m_configureDialog->raise();
}
void IpodCollection::collectionUpdated()
{
m_lastUpdated = QDateTime::currentMSecsSinceEpoch();
emit updated();
}
void
IpodCollection::slotInitialize()
{
if( m_itdb )
return; // why the hell we were called?
m_configureDialogUi.initializeButton->setEnabled( false );
QString errorMessage;
bool success = IpodDeviceHelper::initializeIpod( m_mountPoint, &m_configureDialogUi, errorMessage );
if( !success )
{
- slotShowConfigureDialog( errorMessage );
+ slotShowConfigureDialogWithError( errorMessage );
return;
}
errorMessage.clear();
m_itdb = IpodDeviceHelper::parseItdb( m_mountPoint, errorMessage );
m_prettyName = IpodDeviceHelper::collectionName( m_itdb ); // allows null m_itdb
if( m_itdb )
{
QScopedPointer<Capabilities::TranscodeCapability> tc( create<Capabilities::TranscodeCapability>() );
errorMessage = i18nc( "iPod was successfully initialized", "Initialization successful." );
// so that the buttons are re-enabled, info filled etc:
IpodDeviceHelper::fillInConfigureDialog( m_configureDialog, &m_configureDialogUi,
m_mountPoint, m_itdb, tc->savedConfiguration(), errorMessage );
// there will be probably 0 tracks, but it may do more in future, for example stale
// & orphaned track search.
IpodParseTracksJob *job = new IpodParseTracksJob( this );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &IpodParseTracksJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
else
- slotShowConfigureDialog( errorMessage ); // shows error message and allows initializing
+ slotShowConfigureDialogWithError( errorMessage ); // shows error message and allows initializing
}
void
IpodCollection::slotApplyConfiguration()
{
if( !isWritable() )
return; // we can do nothing if we are not writeable
QString newName = m_configureDialogUi.nameLineEdit->text();
if( !newName.isEmpty() && newName != IpodDeviceHelper::ipodName( m_itdb ) )
{
IpodDeviceHelper::setIpodName( m_itdb, newName );
m_prettyName = IpodDeviceHelper::collectionName( m_itdb );
emit startWriteDatabaseTimer(); // the change should be written down to the database
emit startUpdateTimer();
}
QScopedPointer<Capabilities::TranscodeCapability> tc( create<Capabilities::TranscodeCapability>() );
tc->setSavedConfiguration( m_configureDialogUi.transcodeComboBox->currentChoice() );
}
void
IpodCollection::slotStartUpdateTimer()
{
// there are no concurrency problems, this method can only be called from the main
// thread and that's where the timer fires
if( m_updateTimer.isActive() )
return; // already running, nothing to do
// number of milliseconds to next desired update, may be negative
int timeout = m_lastUpdated + 1000 - QDateTime::currentMSecsSinceEpoch();
// give at least 50 msecs to catch multi-tracks edits nicely on the first frame
m_updateTimer.start( qBound( 50, timeout, 1000 ) );
}
void
IpodCollection::slotStartWriteDatabaseTimer()
{
m_writeDatabaseTimer.start( 30000 );
// ensure we have a file on iPod open that prevents unmounting it if db is dirty
if( !m_preventUnmountTempFile )
{
m_preventUnmountTempFile = new QTemporaryFile();
QString name( "/.itunes_database_dirty_in_amarok_prevent_unmounting" );
m_preventUnmountTempFile->setFileTemplate( m_mountPoint + name );
m_preventUnmountTempFile->open();
}
}
void IpodCollection::slotInitiateDatabaseWrite()
{
if( m_writeDatabaseJob )
{
warning() << __PRETTY_FUNCTION__ << "called while m_writeDatabaseJob still points"
<< "to an older job. Not doing anyhing.";
return;
}
IpodWriteDatabaseJob *job = new IpodWriteDatabaseJob( this );
m_writeDatabaseJob = job;
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &IpodWriteDatabaseJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
void IpodCollection::slotPerformTeardownAndRemove()
{
/* try to eject the device from system. Following technique potentially catches more
* cases than simply passing the udi from IpodCollectionFactory, think of fuse-based
* filesystems for mounting iPhones et caetera.. */
Solid::Predicate query( Solid::DeviceInterface::StorageAccess, QString( "filePath" ),
m_mountPoint );
QList<Solid::Device> devices = Solid::Device::listFromQuery( query );
if( devices.count() == 1 )
{
Solid::Device device = devices.at( 0 );
Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
if( ssa )
ssa->teardown();
}
slotRemove();
}
void IpodCollection::slotRemove()
{
// this is not racy, we are in the main thread and parseTracksJob can be deleted only
// in the main thread
if( m_parseTracksJob )
{
// we need to wait until parseTracksJob finishes, because it acceses IpodCollection
// and IpodPlaylistProvider in an asynchronous way that cannot safely cope with
// IpodCollection disappearing
- connect( m_parseTracksJob.data(), SIGNAL(destroyed(QObject*)), SIGNAL(remove()) );
- m_parseTracksJob.data()->abort();
+ connect( m_parseTracksJob.data(), &QObject::destroyed, this, &IpodCollection::remove );
+ m_parseTracksJob->abort();
}
else
emit remove();
}
Meta::TrackPtr
IpodCollection::addTrack( IpodMeta::Track *track )
{
if( !track || !m_itdb )
return Meta::TrackPtr();
Itdb_Track *itdbTrack = track->itdbTrack();
bool justAdded = false;
m_itdbMutex.lock();
Q_ASSERT( !itdbTrack->itdb || itdbTrack->itdb == m_itdb /* refuse to take track from another itdb */ );
if( !itdbTrack->itdb )
{
itdb_track_add( m_itdb, itdbTrack, -1 );
// if it wasn't in itdb, it couldn't have legally been in master playlist
// TODO: podcasts should not go into MPL
itdb_playlist_add_track( itdb_playlist_mpl( m_itdb ), itdbTrack, -1 );
justAdded = true;
emit startWriteDatabaseTimer();
}
track->setCollection( QWeakPointer<IpodCollection>( this ) );
Meta::TrackPtr trackPtr( track );
Meta::TrackPtr memTrack = MemoryMeta::MapChanger( m_mc.data() ).addTrack( trackPtr );
if( !memTrack && justAdded )
{
/* this new track was not added to MemoryCollection, it may vanish soon, prevent
* dangling pointer in m_itdb */
itdb_playlist_remove_track( 0 /* = MPL */, itdbTrack );
itdb_track_unlink( itdbTrack );
}
m_itdbMutex.unlock();
if( memTrack )
{
subscribeTo( trackPtr );
emit startUpdateTimer();
}
return memTrack;
}
void
IpodCollection::removeTrack( const Meta::TrackPtr &track )
{
if( !track )
return; // nothing to do
/* Following call ensures thread-safety even when this method is called multiple times
* from different threads with the same track: only one thread will get non-null
* deletedTrack from MapChanger. */
Meta::TrackPtr deletedTrack = MemoryMeta::MapChanger( m_mc.data() ).removeTrack( track );
if( !deletedTrack )
{
warning() << __PRETTY_FUNCTION__ << "attempt to delete a track that was not in"
<< "MemoryCollection or not added using MapChanger";
return;
}
IpodMeta::Track *ipodTrack = dynamic_cast<IpodMeta::Track *>( deletedTrack.data() );
if( !ipodTrack )
{
warning() << __PRETTY_FUNCTION__ << "attempt to delete a track that was not"
<< "internally iPod track";
return;
}
Itdb_Track *itdbTrack = ipodTrack->itdbTrack();
if( itdbTrack->itdb && m_itdb )
{
// remove from all playlists excluding the MPL:
m_playlistProvider->removeTrackFromPlaylists( track );
QMutexLocker locker( &m_itdbMutex );
// remove track from the master playlist:
itdb_playlist_remove_track( itdb_playlist_mpl( m_itdb ), itdbTrack );
// remove it from the db:
itdb_track_unlink( itdbTrack );
emit startWriteDatabaseTimer();
}
emit startUpdateTimer();
}
bool IpodCollection::writeDatabase()
{
if( !IpodDeviceHelper::safeToWrite( m_mountPoint, m_itdb ) ) // returns false if m_itdb is null
{
// we have to delete unmount-preventing file even in this case
delete m_preventUnmountTempFile;
m_preventUnmountTempFile = 0;
warning() << "Refusing to write iTunes database to iPod becauase device is not safe to write";
return false;
}
m_itdbMutex.lock();
GError *error = 0;
bool success = itdb_write( m_itdb, &error );
m_itdbMutex.unlock();
QString gpodError;
if( error )
{
gpodError = QString::fromUtf8( error->message );
g_error_free( error );
error = 0;
}
delete m_preventUnmountTempFile; // this deletes the file
m_preventUnmountTempFile = 0;
if( success )
{
QString message = i18nc( "%1: iPod collection name",
"iTunes database successfully written to %1", prettyName() );
Amarok::Components::logger()->shortMessage( message );
}
else
{
QString message;
if( gpodError.isEmpty() )
message = i18nc( "%1: iPod collection name",
"Writing iTunes database to %1 failed without an indication of error",
prettyName() );
else
message = i18nc( "%1: iPod collection name, %2: technical error from libgpod",
"Writing iTunes database to %1 failed: %2", prettyName(), gpodError );
Amarok::Components::logger()->longMessage( message );
}
return success;
}
diff --git a/src/core-impl/collections/ipodcollection/IpodCollection.h b/src/core-impl/collections/ipodcollection/IpodCollection.h
index a0ebac1fec..7615941169 100644
--- a/src/core-impl/collections/ipodcollection/IpodCollection.h
+++ b/src/core-impl/collections/ipodcollection/IpodCollection.h
@@ -1,282 +1,288 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef IPODCOLLECTION_H
#define IPODCOLLECTION_H
#include "ui_IpodConfiguration.h"
#include "core/collections/Collection.h"
#include "core/meta/Observer.h"
#include <QMutex>
#include <QSharedPointer>
#include <QTimer>
namespace Collections { class MemoryCollection; }
namespace IpodMeta { class Track; }
class IphoneMountPoint;
class IpodParseTracksJob;
class IpodWriteDatabaseJob;
class IpodPlaylistProvider;
class QDir;
class QTemporaryFile;
struct _Itdb_iTunesDB;
typedef _Itdb_iTunesDB Itdb_iTunesDB;
class IpodCollection : public Collections::Collection, public Meta::Observer
{
Q_OBJECT
public:
/**
* Creates an iPod collection on top of already-mounted filesystem.
*
* @param mountPoint actual iPod mount point to use, must be already mounted and
* accessible. When eject is requested, solid StorageAccess with this mount point
* is searched for to perform unmounting.
* @param uuid filesystem volume UUID or another unique identifier for this iPod
*/
explicit IpodCollection( const QDir &mountPoint, const QString &uuid );
/**
* Creates an iPod collection on top of not-mounted iPhone/iPad by accessing it
* using libimobiledevice by its 40-digit device UUID. UUID may be empty which
* means "any connected iPhone/iPad".
*/
explicit IpodCollection( const QString &uuid );
virtual ~IpodCollection();
// TrackProvider methods:
virtual bool possiblyContainsTrack( const QUrl &url ) const;
virtual Meta::TrackPtr trackForUrl( const QUrl &url );
// CollectionBase methods:
virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const;
virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type );
// Collection methods:
virtual Collections::QueryMaker *queryMaker();
virtual QString uidUrlProtocol() const;
virtual QString collectionId() const;
virtual QString prettyName() const;
virtual QIcon icon() const;
virtual bool hasCapacity() const;
virtual float usedCapacity() const;
virtual float totalCapacity() const;
virtual Collections::CollectionLocation *location();
virtual bool isWritable() const;
virtual bool isOrganizable() const;
// Observer methods:
virtual void metadataChanged( Meta::TrackPtr track );
// so that the compiler doesn't complain about hidden virtual functions:
using Meta::Observer::metadataChanged;
// IpodCollection methods:
/**
* In-fact second phase of the construcor. Called by CollectionFactory right after
* constructor. Should return true if the collection initialised itself successfully
* and should be shown to the user; return value of false means it should be
* destroyed and forgotten by the factory.
*/
bool init();
/**
* Get local mount point. Can return QString() in case no reasonamble mountpoint
* is available
*/
QString mountPoint();
/**
* Return number of bytes that should be kept free in iPod for database operations.
* CollectionLocation should try hard not to occupy this safety margin.
*/
float capacityMargin() const;
/**
* Return a list of file formats (compatible with Meta::Track::type()) current iPod
* is able to play.
*/
QStringList supportedFormats() const;
/**
* Return pointer to playlist provider associated with this iPod. May be null in
* special cases (iPod not yet initialised etc.)
*/
Playlists::UserPlaylistProvider *playlistProvider() const;
Meta::TrackPtr trackForUidUrl( const QString &uidUrl );
Q_SIGNALS:
/**
* Start a count-down that emits updated() signal after it expires.
* Resets the timer to original timeout if already running. This is to ensure
* that we emit update() max. once per <timeout> for batch updates.
*
* Timers can only be started from "their" thread so use signals & slots for that.
*/
void startUpdateTimer();
/**
* Start a count-down that initiates iTunes database wrtiging after it expires.
* Resets the timer to original timeout if already running. This is to ensure
* that we don't write the database all the time for batch updates.
*
* Timers can only be started from "their" thread so use signals & slots for that.
*/
void startWriteDatabaseTimer();
public Q_SLOTS:
/**
* Destroy the collection, try to write back iTunes database (if dirty)
*/
void slotDestroy();
/**
* Destroy the collection, write back iTunes db (if dirty) and try to eject the
* iPod from system
*/
void slotEject();
/**
* Shows the configuration dialog in a non-modal window. If m_itdb is null, shows
* some info and a button to try to initialize iPod.
*/
- void slotShowConfigureDialog( const QString &errorMessage = QString() );
+ void slotShowConfigureDialog();
+
+ /**
+ * Shows the configuration dialog in a non-modal window. If m_itdb is null, shows
+ * some info and a button to try to initialize iPod.
+ */
+ void slotShowConfigureDialogWithError( const QString &errorMessage );
private Q_SLOTS:
/**
* Update m_lastUpdated timestamp and emit updated()
*/
void collectionUpdated();
/**
* Tries to initialize iPod, read the database, add tracks. (Re)shows the
* configuration dialog with info about initialization.
*/
void slotInitialize();
/**
* Sets iPod name to the name in configure dialog.
*/
void slotApplyConfiguration();
/**
* Starts a timer that ensures we emit updated() signal sometime in future.
*/
void slotStartUpdateTimer();
/**
* Starts a timer that initiates iTunes database writing after 30 seconds.
*/
void slotStartWriteDatabaseTimer();
/**
* Enqueues a job in a thread that writes iTunes database back to iPod. Should
* only be called from m_writeDatabaseTimer's timeout() signal. (with exception
* when IpodCollection is about to destroy itself)
*/
void slotInitiateDatabaseWrite();
/**
* Tries to unmount underlying solid device. You must try to write database before
* calling this. Emits remove() before returning.
*/
void slotPerformTeardownAndRemove();
/**
* Do sanity checks and emit remove() so that this collection is destroyed by
* CollectionManager. No other method is allowed to emit remove()!
*/
void slotRemove();
private:
friend class IpodCopyTracksJob;
friend class IpodDeleteTracksJob;
friend class IpodParseTracksJob;
friend class IpodWriteDatabaseJob;
friend class IpodPlaylistProvider;
static const QString s_uidUrlProtocol;
static const QStringList s_audioFileTypes;
static const QStringList s_videoFileTypes;
static const QStringList s_audioVideoFileTypes;
// method for IpodParseTracksJob and IpodCopyTracksJob:
/**
* Add an iPod track to the collection.
*
* This method adds it to the collection, master playlist (if not already there)
* etc. The file must be already physically copied to iPod. (Re)Sets track's
* collection to this collection. Takes ownership of the track (passes it to
* KSharedPtr)
*
* This method is thread-safe.
*
* @return pointer to newly added track if successful, null pointer otherwise
*/
Meta::TrackPtr addTrack( IpodMeta::Track *track );
// method for IpodDeleteTracksJob:
/**
* Removes a track from iPod collection. Does not delete the file physically,
* caller must do it after calling this method.
*
* @param track a track from associated MemoryCollection to delete. Accepts also
* underlying IpodMeta::Track, this is treated as if MemoryMeta::Track track
* proxy it was passed.
*
* This method is thread-safe.
*/
void removeTrack( const Meta::TrackPtr &track );
// method for IpodWriteDatabaseJob and destructor:
/**
* Calls itdb_write() directly. Logs a message about success/failure in Amarok
* interface.
*/
bool writeDatabase();
KDialog *m_configureDialog;
Ui::IpodConfiguration m_configureDialogUi;
QSharedPointer<Collections::MemoryCollection> m_mc;
/**
* pointer to libgpod iTunes database. If null, this collection is invalid
* (not yet initialised). Can only be changed with m_itdbMutex hold.
*/
Itdb_iTunesDB *m_itdb;
QMutex m_itdbMutex;
QTimer m_updateTimer;
qint64 m_lastUpdated; /* msecs since epoch */
QTimer m_writeDatabaseTimer;
QTemporaryFile *m_preventUnmountTempFile;
QString m_mountPoint;
QString m_uuid;
IphoneMountPoint *m_iphoneAutoMountpoint;
QString m_prettyName;
IpodPlaylistProvider *m_playlistProvider;
QAction *m_configureAction;
QAction *m_ejectAction;
QAction *m_consolidateAction;
QWeakPointer<IpodParseTracksJob> m_parseTracksJob;
QWeakPointer<IpodWriteDatabaseJob> m_writeDatabaseJob;
};
#endif // IPODCOLLECTION_H
diff --git a/src/core-impl/collections/ipodcollection/IpodCollectionFactory.cpp b/src/core-impl/collections/ipodcollection/IpodCollectionFactory.cpp
index 25513bf26e..e8c882c9ab 100644
--- a/src/core-impl/collections/ipodcollection/IpodCollectionFactory.cpp
+++ b/src/core-impl/collections/ipodcollection/IpodCollectionFactory.cpp
@@ -1,273 +1,275 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IpodCollectionFactory.h"
#include "IpodCollection.h"
#include "core/support/Debug.h"
#include <solid/device.h>
#include <solid/devicenotifier.h>
#include <solid/portablemediaplayer.h>
#include <solid/storageaccess.h>
#include <solid/storagevolume.h>
#include <QDir>
-AMAROK_EXPORT_COLLECTION( IpodCollectionFactory, ipodcollection )
+#include <KPluginFactory>
+
+K_PLUGIN_FACTORY_WITH_JSON( ipodcollection, "amarok_collection-ipodcollection.json", registerPlugin<IpodCollectionFactory>(); )
IpodCollectionFactory::IpodCollectionFactory( QObject *parent, const QVariantList &args )
: CollectionFactory( parent, args )
{
m_info = KPluginInfo( "amarok_collection-ipodcollection.desktop", );
}
IpodCollectionFactory::~IpodCollectionFactory()
{
}
void
IpodCollectionFactory::init()
{
- connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
- SLOT(slotAddSolidDevice(QString)) );
- connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)),
- SLOT(slotRemoveSolidDevice(QString)) );
+ connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded,
+ this, &IpodCollectionFactory::slotAddSolidDevice );
+ connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved,
+ this, &IpodCollectionFactory::slotRemoveSolidDevice );
// detect iPods that were already connected on startup
QString query( "[IS StorageAccess OR IS PortableMediaPlayer]" );
QList<Solid::Device> ipodDevices = Solid::Device::listFromQuery( query );
foreach( const Solid::Device &device, ipodDevices )
{
if( identifySolidDevice( device.udi() ) )
createCollectionForSolidDevice( device.udi() );
}
m_initialized = true;
}
void
IpodCollectionFactory::slotAddSolidDevice( const QString &udi )
{
if( m_collectionMap.contains( udi ) )
return; // a device added twice (?)
if( identifySolidDevice( udi ) )
createCollectionForSolidDevice( udi );
}
void
IpodCollectionFactory::slotAccessibilityChanged( bool accessible, const QString &udi )
{
if( accessible )
slotAddSolidDevice( udi );
else
slotRemoveSolidDevice( udi );
}
void
IpodCollectionFactory::slotRemoveSolidDevice( const QString &udi )
{
IpodCollection *collection = m_collectionMap.take( udi );
if( collection )
collection->slotDestroy();
}
void
IpodCollectionFactory::slotCollectionDestroyed( QObject *collection )
{
// remove destroyed collection from m_collectionMap
QMutableMapIterator<QString, IpodCollection *> it( m_collectionMap );
while( it.hasNext() )
{
it.next();
if( (QObject *) it.value() == collection )
it.remove();
}
}
/**
* @brief Return true if device is identified as iPod-compatible using product and vendor.
*
* @param device Solid device to identify
* @return true if the device is iPod-like, false if it cannot be proved.
**/
static bool
deviceIsRootIpodDevice( const Solid::Device &device )
{
if( !device.vendor().contains( "Apple", Qt::CaseInsensitive ) )
return false;
return device.product().startsWith( "iPod" )
|| device.product().startsWith( "iPhone" )
|| device.product().startsWith( "iPad" );
}
/**
* @brief Returns true if device is identified as iPod-compatible using
* PortableMediaPlayer interface.
*
* @param device Solid device to identify
* @return true if the device is iPod-like, false if it cannot be proved.
**/
static bool
deviceIsPMPIpodDevice( const Solid::Device &device )
{
/* This should be the one and only way to identify iPod-likes, but as of KDE 4.9.4,
* solid attaches PortableMediaPlayer to a wrong device path like
* /org/kde/solid/udev/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1/1-1.1.4/1-1.1.4:1.0/host6/target6:0:0/6:0:0:0/block/sdc/sdc2,
* while it should attach is to UDIsks device /org/freedesktop/UDisks/devices/sdc2
*
* It works correctly for iPhones though.
*/
const Solid::PortableMediaPlayer *pmp = device.as<Solid::PortableMediaPlayer>();
if( !pmp )
return false;
debug() << "Device supported PMP protocols:" << pmp->supportedProtocols();
return pmp->supportedProtocols().contains( "ipod", Qt::CaseInsensitive );
}
bool
IpodCollectionFactory::identifySolidDevice( const QString &udi ) const
{
DEBUG_BLOCK
Solid::Device device( udi );
if( deviceIsPMPIpodDevice( device ) )
{
debug() << "Device" << device.udi() << "identified iPod-like using "
"PortableMediaPlayer interface";
return true;
}
if( !device.is<Solid::StorageAccess>() )
{
debug() << "Device" << device.udi() << "doesn't have PortableMediaPlayer ipod"
<< "interface or StorageAccess interface -> cannot be and iPod";
return false;
}
/* Start with device to identify, opportunistically try to identify it as
* iPod-compatible. If found not, try its parent. Repeat until parent device is
* valid.
*
* @see MediaDeviceCache::slotAddSolidDevice(), whose iPhone hack shouldn't be
* needed since iPod rewrite in Amarok 2.6.
*/
while ( device.isValid() )
{
if( deviceIsRootIpodDevice( device ) )
{
debug() << "Device" << device.udi() << "identified iPod-like using "
"vendor and product name";
return true;
}
debug() << "Device" << device.udi() << "not identified iPod-like, trying parent device";
device = device.parent();
}
debug() << "Device" << device.udi() << "is invalid, returning false. (i.e. was not iPod-like)";
return false;
}
void
IpodCollectionFactory::createCollectionForSolidDevice( const QString &udi )
{
DEBUG_BLOCK
DeviceType type;
QDir mountPoint;
QString uuid;
Solid::Device device( udi );
Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
if( ssa )
{
type = iPod;
if( ssa->isIgnored() )
{
debug() << "device" << udi << "ignored, ignoring :-)";
return;
}
// we are definitely interested in this device, listen for accessibility changes
- disconnect( ssa, SIGNAL(accessibilityChanged(bool,QString)), this, 0 );
- connect( ssa, SIGNAL(accessibilityChanged(bool,QString)),
- SLOT(slotAccessibilityChanged(bool,QString)) );
+ disconnect( ssa, &Solid::StorageAccess::accessibilityChanged, this, 0 );
+ connect( ssa, &Solid::StorageAccess::accessibilityChanged,
+ this, &IpodCollectionFactory::slotAccessibilityChanged );
if( !ssa->isAccessible() )
{
debug() << "device" << udi << "not accessible, ignoring for now";
return;
}
mountPoint = ssa->filePath();
Solid::StorageVolume *volume = device.as<Solid::StorageVolume>();
if( volume )
uuid = volume->uuid();
}
else // no ssa
{
do { // break inside this block means "continue with collection creation"
type = iOS;
debug() << "device" << udi << "has no StorageAccess interface, treating as iPhone/iPad";
Solid::PortableMediaPlayer *pmp = device.as<Solid::PortableMediaPlayer>();
if( !pmp )
{
debug() << "Ignoring above device as it doesn't have PortableMediaPlayer interface";
return;
}
if( pmp->supportedProtocols().contains( "ipod" ) &&
pmp->supportedDrivers().contains( "usbmux" ) )
{
uuid = pmp->driverHandle( "usbmux" ).toString();
debug() << "Above device supports ipod/usbmux protocol/driver combo, good";
break;
}
debug() << "Ignoring above device as it doesn't support ipod/usbmux"
<< "PortableMediaPlayer protocol/driver combo";
return;
} while( false );
}
debug() << "Creating iPod collection, mount-point (empty if iOS):" << mountPoint
<< "uuid:" << uuid;
IpodCollection *collection;
switch( type )
{
case iPod:
collection = new IpodCollection( mountPoint, uuid );
break;
case iOS:
collection = new IpodCollection( uuid );
break;
}
m_collectionMap.insert( udi, collection );
// when the collection is destroyed by someone else, remove it from m_collectionMap:
- connect( collection, SIGNAL(destroyed(QObject*)), SLOT(slotCollectionDestroyed(QObject*)) );
+ connect( collection, &QObject::destroyed, this, &IpodCollectionFactory::slotCollectionDestroyed );
if( ssa )
// try to gracefully destroy collection when unmounting is requested using
// external means: Device notifier plasmoid etc.. Because the original action
// could fail if we hold some files on the device open, we eject the collection,
// not just destroy it.
- connect( ssa, SIGNAL(teardownRequested(QString)), collection, SLOT(slotEject()) );
+ connect( ssa, &Solid::StorageAccess::teardownRequested, collection, &IpodCollection::slotEject );
if( collection->init() )
emit newCollection( collection );
else
collection->deleteLater();
}
diff --git a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp
index 012b682ce2..d3ca5cf88f 100644
--- a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp
+++ b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp
@@ -1,153 +1,153 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IpodCollectionLocation.h"
#include "jobs/IpodDeleteTracksJob.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include <ThreadWeaver/Queue>
#include <QDir>
#include <QFile>
#include <gpod/itdb.h>
IpodCollectionLocation::IpodCollectionLocation( QWeakPointer<IpodCollection> parentCollection )
: CollectionLocation() // we implement collection(), we need not pass parentCollection
, m_coll( parentCollection )
{
}
IpodCollectionLocation::~IpodCollectionLocation()
{
}
Collections::Collection*
IpodCollectionLocation::collection() const
{
// overridden to avoid dangling pointers
return m_coll.data();
}
QString
IpodCollectionLocation::prettyLocation() const
{
if( m_coll )
return m_coll.data()->prettyName();
// match string with IpodCopyTracksJob::slotDisplaySorryDialog()
return i18n( "Disconnected iPod/iPad/iPhone" );
}
bool
IpodCollectionLocation::isWritable() const
{
if( !m_coll )
return false;
return m_coll.data()->isWritable(); // no infinite loop, IpodCollection iplements this
}
void
IpodCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr,QUrl> &sources,
const Transcoding::Configuration &configuration )
{
if( !isWritable() )
return; // mostly unreachable, CollectionLocation already checks this and issues a warning
ensureDirectoriesExist();
IpodCopyTracksJob *job = new IpodCopyTracksJob( sources, m_coll, configuration, isGoingToRemoveSources() );
int trackCount = sources.size();
Amarok::Components::logger()->newProgressOperation( job,
operationInProgressText( configuration, trackCount ), trackCount, job, SLOT(abort()) );
qRegisterMetaType<IpodCopyTracksJob::CopiedStatus>( "IpodCopyTracksJob::CopiedStatus" );
- connect( job, SIGNAL(signalTrackProcessed(Meta::TrackPtr,Meta::TrackPtr,IpodCopyTracksJob::CopiedStatus)),
- this, SLOT(slotCopyTrackProcessed(Meta::TrackPtr,Meta::TrackPtr,IpodCopyTracksJob::CopiedStatus)) );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotCopyOperationFinished()) );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &IpodCopyTracksJob::signalTrackProcessed,
+ this, &IpodCollectionLocation::slotCopyTrackProcessed );
+ connect( job, &IpodCopyTracksJob::done, this, &IpodCollectionLocation::slotCopyOperationFinished );
+ connect( job, &IpodCopyTracksJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
void
IpodCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources )
{
if( !isWritable() )
return;
IpodDeleteTracksJob *job = new IpodDeleteTracksJob( sources, m_coll );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotRemoveOperationFinished()) );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &IpodDeleteTracksJob::done, this, &IpodCollectionLocation::slotRemoveOperationFinished );
+ connect( job, &IpodDeleteTracksJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
void
IpodCollectionLocation::setDestinationPlaylist( Playlists::PlaylistPtr destPlaylist, const QMap<Meta::TrackPtr, int> &trackPlaylistPositions )
{
m_destPlaylist = destPlaylist;
m_trackPlaylistPositions = trackPlaylistPositions;
}
void
IpodCollectionLocation::slotCopyTrackProcessed( Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack,
IpodCopyTracksJob::CopiedStatus status )
{
if( status == IpodCopyTracksJob::Success )
// we do not include track found by matching meta-data here for safety reasons
source()->transferSuccessful( srcTrack );
if( m_destPlaylist && ( status == IpodCopyTracksJob::Success || status == IpodCopyTracksJob::Duplicate )
&& destTrack && m_trackPlaylistPositions.contains( srcTrack ) )
// add this track to iPod playlist
{
m_destPlaylist->addTrack( destTrack, m_trackPlaylistPositions.value( srcTrack ) );
}
}
void IpodCollectionLocation::ensureDirectoriesExist()
{
QByteArray mountPoint = m_coll ? QFile::encodeName( m_coll.data()->mountPoint() ) : QByteArray();
if( mountPoint.isEmpty() )
return;
gchar *musicDirChar = itdb_get_music_dir( mountPoint.constData() );
QString musicDirPath = QFile::decodeName( musicDirChar );
g_free( musicDirChar );
if( musicDirPath.isEmpty() )
return;
QDir musicDir( musicDirPath );
if( !musicDir.exists() && !musicDir.mkpath( "." ) /* try to create it */ )
{
warning() << __PRETTY_FUNCTION__ << "failed to create" << musicDirPath << "directory.";
return;
}
QChar fillChar( '0' );
for( int i = 0; i < 20; i++ )
{
QString name = QString( "F%1" ).arg( i, /* min-width */ 2, /* base */ 10, fillChar );
if( musicDir.exists( name ) )
continue;
QString toCreatePath = QString( "%1/%2" ).arg( musicDirPath, name );
if( musicDir.mkdir( name ) )
debug() << __PRETTY_FUNCTION__ << "created" << toCreatePath << "directory.";
else
warning() << __PRETTY_FUNCTION__ << "failed to create" << toCreatePath << "directory.";
}
}
diff --git a/src/core-impl/collections/ipodcollection/IpodMeta.cpp b/src/core-impl/collections/ipodcollection/IpodMeta.cpp
index 304386efe6..5fff417168 100644
--- a/src/core-impl/collections/ipodcollection/IpodMeta.cpp
+++ b/src/core-impl/collections/ipodcollection/IpodMeta.cpp
@@ -1,888 +1,888 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IpodMeta.h"
#include "amarokconfig.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core-impl/collections/ipodcollection/IpodCollection.h"
#include "core-impl/collections/ipodcollection/config-ipodcollection.h"
#include "core-impl/collections/support/jobs/WriteTagsJob.h"
#include "core-impl/collections/support/ArtistHelper.h"
#include "covermanager/CoverCache.h"
#include "FileType.h"
#include <KTemporaryFile>
#include <ThreadWeaver/Queue>
#include <cmath>
#include <gpod/itdb.h>
#ifdef GDKPIXBUF_FOUND
#undef signals // gdbusintrospection.h uses a member named signals, prevent build fail
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <QBuffer>
#endif
using namespace IpodMeta;
gpointer AmarokItdbUserDataDuplicateFunc( gpointer userdata )
{
Q_UNUSED( userdata )
return 0; // we never copy our userdata
}
Track::Track( Itdb_Track *ipodTrack )
: m_track( ipodTrack )
, m_batch( 0 )
{
Q_ASSERT( m_track != 0 );
m_track->usertype = m_gpodTrackUserTypeAmarokTrackPtr;
m_track->userdata = this;
m_track->userdata_duplicate = AmarokItdbUserDataDuplicateFunc;
}
Track::Track( const Meta::TrackPtr &origTrack )
: m_track( itdb_track_new() )
, m_batch( 0 )
{
Q_ASSERT( m_track != 0 );
m_track->usertype = m_gpodTrackUserTypeAmarokTrackPtr;
m_track->userdata = this;
m_track->userdata_duplicate = AmarokItdbUserDataDuplicateFunc;
Meta::AlbumPtr origAlbum = origTrack->album();
Meta::ArtistPtr origArtist = origTrack->artist();
beginUpdate();
setTitle( origTrack->name() );
// url is set in setCollection()
setAlbum( origAlbum ? origAlbum->name() : QString() );
setArtist( origArtist ? origArtist->name() : QString() );
setComposer( origTrack->composer() ? origTrack->composer()->name() : QString() );
setGenre( origTrack->genre() ? origTrack->genre()->name() : QString() );
setYear( origTrack->year() ? origTrack->year()->year() : 0 );
QString albumArtist;
bool isCompilation = false;
if ( origAlbum )
{
isCompilation = origAlbum->isCompilation();
if( origAlbum->hasAlbumArtist() && origAlbum->albumArtist() )
albumArtist = origAlbum->albumArtist()->name();
if( origAlbum->hasImage() )
setImage( origAlbum->image() );
}
/* iPod doesn't handle empty album artist well for compilation albums (splits these
* albums). Ensure that we have something in albumArtist. We filter it for Amarok for
* compilation albums in IpodMeta::Album::albumArtist() */
if( albumArtist.isEmpty() && origArtist )
albumArtist = origArtist->name();
if( albumArtist.isEmpty() )
albumArtist = i18n( "Various Artists" );
Meta::ConstStatisticsPtr origStats = origTrack->statistics();
setAlbumArtist( albumArtist );
setIsCompilation( isCompilation );
setBpm( origTrack->bpm() );
setComment( origTrack->comment() );
setScore( origStats->score() );
setRating( origStats->rating() );
setLength( origTrack->length() );
// filesize is set in finalizeCopying(), which could be more accurate
setSampleRate( origTrack->sampleRate() );
setBitrate( origTrack->bitrate() );
setCreateDate( QDateTime::currentDateTime() ); // createDate == added to collection
setModifyDate( origTrack->modifyDate() );
setTrackNumber( origTrack->trackNumber() );
setDiscNumber( origTrack->discNumber() );
setLastPlayed( origStats->lastPlayed() );
setFirstPlayed( origStats->firstPlayed() );
setPlayCount( origStats->playCount() );
setReplayGain( Meta::ReplayGain_Track_Gain, origTrack->replayGain( Meta::ReplayGain_Track_Gain ) );
setReplayGain( Meta::ReplayGain_Track_Peak, origTrack->replayGain( Meta::ReplayGain_Track_Peak ) );
setReplayGain( Meta::ReplayGain_Album_Gain, origTrack->replayGain( Meta::ReplayGain_Album_Gain ) );
setReplayGain( Meta::ReplayGain_Album_Peak, origTrack->replayGain( Meta::ReplayGain_Album_Peak ) );
setType( origTrack->type() );
m_changedFields.clear(); // some of the set{Something} insert to m_changedFields, not
// desirable for constructor
endUpdate();
}
Track::~Track()
{
itdb_track_free( m_track );
if( !m_tempImageFilePath.isEmpty() )
QFile::remove( m_tempImageFilePath );
}
QString
Track::name() const
{
QReadLocker locker( &m_trackLock );
return QString::fromUtf8( m_track->title );
}
void
Track::setTitle( const QString &newTitle )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->title );
m_track->title = g_strdup( newTitle.toUtf8() );
commitIfInNonBatchUpdate( Meta::valTitle, newTitle );
}
QUrl
Track::playableUrl() const
{
if( m_mountPoint.isEmpty() || !m_track->ipod_path || m_track->ipod_path[0] == '\0' )
return QUrl();
QReadLocker locker( &m_trackLock );
gchar *relPathChar = g_strdup( m_track->ipod_path );
locker.unlock();
itdb_filename_ipod2fs( relPathChar ); // in-place
// relPath begins with a slash
QString relPath = QFile::decodeName( relPathChar );
g_free( relPathChar );
return QUrl( m_mountPoint + relPath );
}
QString
Track::prettyUrl() const
{
const QUrl &url = playableUrl();
if( url.isLocalFile() )
return url.toLocalFile();
QString collName = m_coll ? m_coll.data()->prettyName() : i18n( "Unknown Collection" );
QString artistName = artist() ? artist()->prettyName() : i18n( "Unknown Artist" );
QString trackName = !name().isEmpty() ? name() : i18n( "Unknown track" );
return QString( "%1: %2 - %3" ).arg( collName, artistName, trackName );
}
QString
Track::uidUrl() const
{
QReadLocker locker( &m_trackLock );
gchar *relPathChar = g_strdup( m_track->ipod_path );
locker.unlock();
itdb_filename_ipod2fs( relPathChar ); // in-place
// relPath begins with a slash
QString relPath = QFile::decodeName( relPathChar );
g_free( relPathChar );
if( m_coll )
return m_coll.data()->collectionId() + relPath;
else
return m_mountPoint + relPath;
}
QString
Track::notPlayableReason() const
{
return localFileNotPlayableReason( playableUrl().toLocalFile() );
}
Meta::AlbumPtr
Track::album() const
{
// we may not store KSharedPtr to Album because it would create circular reference
return Meta::AlbumPtr( new Album( const_cast<Track*>( this ) ) );
}
void
Track::setAlbum( const QString &newAlbum )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->album );
m_track->album = g_strdup( newAlbum.toUtf8() );
commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum );
}
void
Track::setAlbumArtist( const QString &newAlbumArtist )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->albumartist );
m_track->albumartist = g_strdup( newAlbumArtist.toUtf8() );
commitIfInNonBatchUpdate( Meta::valAlbumArtist, newAlbumArtist );
}
void
Track::setIsCompilation( bool newIsCompilation )
{
// libgpod says: m_track->combination: True if set to 0x1, false if set to 0x0.
if( m_track->compilation == newIsCompilation )
return; // nothing to do
QWriteLocker locker( &m_trackLock );
m_track->compilation = newIsCompilation ? 0x1 : 0x0;
commitIfInNonBatchUpdate( Meta::valCompilation, newIsCompilation );
}
void
Track::setImage( const QImage &newImage )
{
QWriteLocker locker( &m_trackLock );
if( !m_tempImageFilePath.isEmpty() )
QFile::remove( m_tempImageFilePath );
m_tempImageFilePath.clear();
if( newImage.isNull() )
itdb_track_remove_thumbnails( m_track );
else
{
// we set artwork even for devices that don't support it, everyone has new-enough iPod nowadays
const int maxSize = AmarokConfig::writeBackCoverDimensions();
QImage image;
if( newImage.width() > maxSize || newImage.height() > maxSize )
image = newImage.scaled( maxSize, maxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
else
image = newImage;
KTemporaryFile tempImageFile;
tempImageFile.setAutoRemove( false ); // file will be removed in ~Track()
tempImageFile.setSuffix( QString( ".png" ) );
// we save the file to disk rather than pass image data to save several megabytes of RAM
if( tempImageFile.open() )
m_tempImageFilePath = tempImageFile.fileName();
if( tempImageFile.isOpen() && image.save( &tempImageFile, "PNG" ) )
/* this function remembers image path, it also fogots previous images (if any)
* and sets artwork_size, artwork_count and has_artwork m_track fields */
itdb_track_set_thumbnails( m_track, QFile::encodeName( m_tempImageFilePath ) );
}
commitIfInNonBatchUpdate( Meta::valImage, newImage );
}
Meta::ArtistPtr
Track::artist() const
{
QReadLocker locker( &m_trackLock );
return Meta::ArtistPtr( new Artist( QString::fromUtf8( m_track->artist ) ) );
}
void
Track::setArtist( const QString &newArtist )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->artist );
m_track->artist = g_strdup( newArtist.toUtf8() );
commitIfInNonBatchUpdate( Meta::valArtist, newArtist );
}
Meta::ComposerPtr
Track::composer() const
{
QReadLocker locker( &m_trackLock );
return Meta::ComposerPtr( new Composer( QString::fromUtf8( m_track->composer ) ) );
}
void
Track::setComposer( const QString &newComposer )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->composer );
m_track->composer = g_strdup( newComposer.toUtf8() );
commitIfInNonBatchUpdate( Meta::valComposer, newComposer );
}
Meta::GenrePtr
Track::genre() const
{
QReadLocker locker( &m_trackLock );
return Meta::GenrePtr( new Genre( QString::fromUtf8( m_track->genre ) ) );
}
void
Track::setGenre( const QString &newGenre )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->genre );
m_track->genre = g_strdup( newGenre.toUtf8() );
commitIfInNonBatchUpdate( Meta::valGenre, newGenre );
}
Meta::YearPtr
Track::year() const
{
// no need for lock here, reading integer should be atomic
return Meta::YearPtr( new Year( QString::number( m_track->year ) ) );
}
void Track::setYear( int newYear )
{
QWriteLocker locker( &m_trackLock );
m_track->year = newYear;
commitIfInNonBatchUpdate( Meta::valYear, newYear );
}
qreal
Track::bpm() const
{
// no need for lock here, integer read
return m_track->BPM;
}
void Track::setBpm( const qreal newBpm )
{
QWriteLocker locker( &m_trackLock );
m_track->BPM = newBpm;
commitIfInNonBatchUpdate( Meta::valBpm, newBpm );
}
QString
Track::comment() const
{
QReadLocker locker( &m_trackLock );
return QString::fromUtf8( m_track->comment );
}
void Track::setComment( const QString &newComment )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->comment );
m_track->comment = g_strdup( newComment.toUtf8() );
commitIfInNonBatchUpdate( Meta::valComment, newComment );
}
int
Track::rating() const
{
/* (rating/RATING_STEP) is a number of stars, Amarok uses numer of half-stars.
* the order of multiply and divide operations is significant because of rounding */
return ( ( m_track->rating * 2 ) / ITDB_RATING_STEP );
}
void
Track::setRating( int newRating )
{
newRating = ( newRating * ITDB_RATING_STEP ) / 2;
if( newRating == (int) m_track->rating ) // casting prevents compiler waring about signedness
return; // nothing to do, do not notify observers
QWriteLocker locker( &m_trackLock );
m_track->rating = newRating;
commitIfInNonBatchUpdate( Meta::valRating, newRating );
}
qint64
Track::length() const
{
return m_track->tracklen;
}
void
Track::setLength( qint64 newLength )
{
QWriteLocker locker( &m_trackLock );
m_track->tracklen = newLength;
commitIfInNonBatchUpdate( Meta::valLength, newLength );
}
int
Track::filesize() const
{
return m_track->size;
}
int
Track::sampleRate() const
{
return m_track->samplerate;
}
void
Track::setSampleRate( int newSampleRate )
{
QWriteLocker locker( &m_trackLock );
m_track->samplerate = newSampleRate;
commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate );
}
int
Track::bitrate() const
{
return m_track->bitrate;
}
void
Track::setBitrate( int newBitrate )
{
QWriteLocker locker( &m_trackLock );
m_track->bitrate = newBitrate;
commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate );
}
QDateTime
Track::createDate() const
{
time_t time = m_track->time_added;
if( time == 0 )
return QDateTime(); // 0 means "no reasonable time", so return invalid QDateTime
return QDateTime::fromTime_t( time );
}
void
Track::setCreateDate( const QDateTime &newDate )
{
QWriteLocker locker( &m_trackLock );
m_track->time_added = newDate.isValid() ? newDate.toTime_t() : 0;
commitIfInNonBatchUpdate( Meta::valCreateDate, newDate );
}
QDateTime
Track::modifyDate() const
{
time_t time = m_track->time_modified;
if( time == 0 )
return QDateTime(); // 0 means "no reasonable time", so return invalid QDateTime
return QDateTime::fromTime_t( time );
}
void
Track::setModifyDate( const QDateTime &newDate )
{
// this method _cannot_ lock m_trackLock or deadlock will occur in commitChanges()
m_track->time_modified = newDate.isValid() ? newDate.toTime_t() : 0;
}
int
Track::trackNumber() const
{
// no need for lock here, integer read
return m_track->track_nr;
}
void
Track::setTrackNumber( int newTrackNumber )
{
QWriteLocker locker( &m_trackLock );
m_track->track_nr = newTrackNumber;
commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber );
}
int
Track::discNumber() const
{
// no need for lock here, integer read
return m_track->cd_nr;
}
void
Track::setDiscNumber( int newDiscNumber )
{
QWriteLocker locker( &m_trackLock );
m_track->cd_nr = newDiscNumber;
commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber );
}
QDateTime
Track::lastPlayed() const
{
return m_track->time_played ? QDateTime::fromTime_t( m_track->time_played ) : QDateTime();
}
void
Track::setLastPlayed( const QDateTime &time )
{
QWriteLocker locker( &m_trackLock );
m_track->time_played = time.isValid() ? time.toTime_t() : 0;
commitIfInNonBatchUpdate( Meta::valLastPlayed, time );
}
QDateTime
Track::firstPlayed() const
{
// we abuse time_released for this, which should be okay for non-podcast tracks
// TODO: return QDateTime for podcast tracks
return m_track->time_released ? QDateTime::fromTime_t( m_track->time_released ) : QDateTime();
}
void
Track::setFirstPlayed( const QDateTime &time )
{
QWriteLocker locker( &m_trackLock );
m_track->time_released = time.isValid() ? time.toTime_t() : 0;
commitIfInNonBatchUpdate( Meta::valFirstPlayed, time );
}
int
Track::playCount() const
{
return m_track->playcount;
}
int
Track::recentPlayCount() const
{
if( !m_coll || !m_coll.data()->isWritable() )
return 0; // we must be able to reset recent playcount if we return nonzero
return m_track->recent_playcount;
}
void
Track::setPlayCount( const int playcount )
{
QWriteLocker locker( &m_trackLock );
m_track->playcount = playcount;
m_track->recent_playcount = 0;
commitIfInNonBatchUpdate( Meta::valLastPlayed, playcount );
}
qreal
Track::replayGain( Meta::ReplayGainTag mode ) const
{
// iPods are not able to differentiante between different replay gain modes (track & album)
switch( mode )
{
case Meta::ReplayGain_Track_Gain:
case Meta::ReplayGain_Album_Gain:
break; // fall to the computation
case Meta::ReplayGain_Track_Peak:
case Meta::ReplayGain_Album_Peak:
return 0.0; // perhaps return -replayGain assuming there was enough headroom
}
if( m_track->soundcheck == 0 )
return 0.0; // libgpod: The value 0 is special, treated as "no Soundcheck"
// libgpod: X = 1000 * 10 ^ (-.1 * Y)
// where Y is the adjustment value in dB and X is the value that goes into the SoundCheck field
return 30.0 - 10.0 * std::log10( m_track->soundcheck );
}
void
Track::setReplayGain( Meta::ReplayGainTag mode, qreal newReplayGain )
{
guint32 soundcheck;
switch( mode )
{
case Meta::ReplayGain_Track_Gain:
if( newReplayGain == 0.0 )
// libgpod: The value 0 is special, treated as "no Soundcheck"
soundcheck = 0;
else
// libgpod: X = 1000 * 10 ^ (-.1 * Y)
// where Y is the adjustment value in dB and X is the value that goes into the SoundCheck field
soundcheck = 1000 * std::pow( 10.0, -0.1 * newReplayGain );
m_track->soundcheck = soundcheck;
break;
case Meta::ReplayGain_Album_Gain:
case Meta::ReplayGain_Track_Peak:
// we should somehow abuse Itdb_Track to store this, it is really needed
case Meta::ReplayGain_Album_Peak:
break;
}
}
QString
Track::type() const
{
QReadLocker locker( &m_trackLock );
return QString::fromUtf8( m_track->filetype );
}
void
Track::setType( const QString &newType )
{
QWriteLocker locker( &m_trackLock );
g_free( m_track->filetype );
m_track->filetype = g_strdup( newType.toUtf8() );
commitIfInNonBatchUpdate( Meta::valFormat, newType );
}
bool
Track::inCollection() const
{
return m_coll; // converts to bool nicely
}
Collections::Collection*
Track::collection() const
{
return m_coll.data();
}
Meta::TrackEditorPtr
Track::editor()
{
return Meta::TrackEditorPtr( isEditable() ? this : 0 );
}
Meta::StatisticsPtr
Track::statistics()
{
return Meta::StatisticsPtr( this );
}
Meta::TrackPtr
Track::fromIpodTrack( const Itdb_Track *ipodTrack )
{
if( !ipodTrack )
return Meta::TrackPtr();
if( ipodTrack->usertype != m_gpodTrackUserTypeAmarokTrackPtr )
return Meta::TrackPtr();
if( !ipodTrack->userdata )
return Meta::TrackPtr();
return Meta::TrackPtr( static_cast<Track *>( ipodTrack->userdata ) );
}
Itdb_Track*
Track::itdbTrack() const
{
return m_track;
}
bool
Track::finalizeCopying( const gchar *mountPoint, const gchar *filePath )
{
GError *error = 0;
// we cannot use m_mountPoint, we are not yet in collection
Itdb_Track *res = itdb_cp_finalize( m_track, mountPoint, filePath, &error );
if( error )
{
warning() << "Failed to finalize copying of iPod track:" << error->message;
g_error_free( error );
error = 0;
}
return res == m_track;
}
void
Track::setCollection( QWeakPointer<IpodCollection> collection )
{
m_coll = collection;
if( !collection )
return;
{ // scope for locker
QWriteLocker locker( &m_trackLock );
// paranoia: collection may become null while we were waiting for lock...
m_mountPoint = collection ? collection.data()->mountPoint() : QString();
}
// m_track->filetype field may have been set by someone else, rather check it (if set
// by us, it can be more accurate than file extension, so we prefer it)
if( !Amarok::FileTypeSupport::possibleFileTypes().contains( type() ) )
setType( Amarok::extension( playableUrl().path() ) );
// we don't make the datbase dirty, this can be recomputed every time
}
void Track::beginUpdate()
{
QWriteLocker locker( &m_trackLock );
m_batch++;
}
void Track::endUpdate()
{
QWriteLocker locker( &m_trackLock );
Q_ASSERT( m_batch > 0 );
m_batch--;
commitIfInNonBatchUpdate();
}
bool
Track::isEditable() const
{
if( !inCollection() )
return false;
return collection()->isWritable(); // IpodCollection implements this nicely
}
void
Track::commitIfInNonBatchUpdate( qint64 field, const QVariant &value )
{
m_changedFields.insert( field, value );
commitIfInNonBatchUpdate();
}
void
Track::commitIfInNonBatchUpdate()
{
static const QSet<qint64> statFields = ( QSet<qint64>() << Meta::valFirstPlayed <<
Meta::valLastPlayed << Meta::valPlaycount << Meta::valScore << Meta::valRating );
if( m_batch > 0 || m_changedFields.isEmpty() )
return;
// we block changing the track meta-data of read-only iPod Collections;
// it would only be cofusing to the user as the changes would get discarded.
if( !m_coll || !m_coll.data()->isWritable() )
return;
if( AmarokConfig::writeBackStatistics() ||
!(QSet<qint64>::fromList( m_changedFields.keys() ) - statFields).isEmpty() )
{
setModifyDate( QDateTime::currentDateTime() );
}
m_trackLock.unlock(); // playableUrl() locks it too, notifyObservers() better without lock
QString path = playableUrl().path(); // needs to be here because it locks m_trackLock too
// write tags to file in a thread in order not to block
WriteTagsJob *job = new WriteTagsJob( path, m_changedFields );
- job->connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ job->connect( job, &WriteTagsJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
notifyObservers();
m_trackLock.lockForWrite(); // reset to original state when this was called
m_changedFields.clear();
}
// IpodMeta:Album
Album::Album( Track *track )
: m_track( track )
{
}
QString Album::name() const
{
QReadLocker locker( &m_track->m_trackLock );
return QString::fromUtf8( m_track->m_track->album );
}
bool
Album::isCompilation() const
{
return m_track->m_track->compilation;
}
bool
Album::canUpdateCompilation() const
{
Collections::Collection *coll = m_track->collection();
return coll ? coll->isWritable() : false;
}
void
Album::setCompilation( bool isCompilation )
{
m_track->setIsCompilation( isCompilation );
}
bool
Album::hasAlbumArtist() const
{
return !isCompilation();
}
Meta::ArtistPtr
Album::albumArtist() const
{
if( isCompilation() )
return Meta::ArtistPtr();
QReadLocker locker( &m_track->m_trackLock );
QString albumArtistName = QString::fromUtf8( m_track->m_track->albumartist );
if( albumArtistName.isEmpty() )
albumArtistName = QString::fromUtf8( m_track->m_track->artist );
return Meta::ArtistPtr( new Artist( albumArtistName ) );
}
bool
Album::hasImage( int size ) const
{
Q_UNUSED(size)
if( m_track->m_track->has_artwork != 0x01 )
return false; // libgpod: has_artwork: True if set to 0x01, false if set to 0x02.
return itdb_track_has_thumbnails( m_track->m_track ); // should be false if GdbPixBuf is not available
}
QImage
Album::image( int size ) const
{
Q_UNUSED(size) // MemoryMeta does scaling for us
QImage albumImage;
#ifdef GDKPIXBUF_FOUND
do {
if( m_track->m_track->has_artwork != 0x01 )
break; // libgpod: has_artwork: True if set to 0x01, false if set to 0x02.
// it reads "thumbnail", but this is the correct function to call
GdkPixbuf *pixbuf = (GdkPixbuf*) itdb_track_get_thumbnail( m_track->m_track, -1, -1 );
if( !pixbuf )
break;
if( gdk_pixbuf_get_colorspace( pixbuf ) != GDK_COLORSPACE_RGB )
{
warning() << __PRETTY_FUNCTION__ << "Unsupported GTK colorspace.";
g_object_unref( pixbuf );
break;
}
if( gdk_pixbuf_get_bits_per_sample( pixbuf ) != 8 )
{
warning() << __PRETTY_FUNCTION__ << "Unsupported number of bits per sample.";
g_object_unref( pixbuf );
break;
}
int n_channels = gdk_pixbuf_get_n_channels( pixbuf );
bool has_alpha = gdk_pixbuf_get_has_alpha( pixbuf );
QImage::Format format;
if( n_channels == 4 && has_alpha )
format = QImage::Format_ARGB32;
else if( n_channels == 3 && !has_alpha )
format = QImage::Format_RGB888;
else
{
warning() << __PRETTY_FUNCTION__ << "Unsupported n_channels / has_alpha combination.";
g_object_unref( pixbuf );
break;
}
// cost cast needed to choose QImage constructor that takes read-only image data
albumImage = QImage( const_cast<const uchar *>( gdk_pixbuf_get_pixels( pixbuf ) ),
gdk_pixbuf_get_width( pixbuf ),
gdk_pixbuf_get_height( pixbuf ),
gdk_pixbuf_get_rowstride( pixbuf ),
format );
// force deep copy so that memory from gdk pixbuf can be unreferenced:
albumImage.setDotsPerMeterX( 2835 );
g_object_unref( pixbuf );
} while( false );
#endif
return albumImage;
}
bool Album::canUpdateImage() const
{
#ifdef GDKPIXBUF_FOUND
Collections::Collection *coll = m_track->collection();
return coll ? coll->isWritable() : false;
#else
return false;
#endif
}
void Album::setImage( const QImage &image )
{
m_track->setImage( image );
CoverCache::invalidateAlbum( this );
}
void Album::removeImage()
{
setImage( QImage() );
}
diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp
index 82f265859e..57584bbd60 100644
--- a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp
+++ b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp
@@ -1,431 +1,431 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IpodCopyTracksJob.h"
#include "IpodMeta.h"
#include "core/collections/QueryMaker.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/transcoding/TranscodingController.h"
#include "MetaTagLib.h"
#include "FileType.h"
#include "transcoding/TranscodingJob.h"
#include <KIO/CopyJob>
#include <KIO/Job>
#include <KMessageBox>
#include <QFile>
#include <gpod/itdb.h>
#include <unistd.h> // fsync()
IpodCopyTracksJob::IpodCopyTracksJob( const QMap<Meta::TrackPtr,QUrl> &sources,
const QWeakPointer<IpodCollection> &collection,
const Transcoding::Configuration &configuration,
bool goingToRemoveSources )
: Job()
, m_coll( collection )
, m_transcodingConfig( configuration )
, m_sources( sources )
, m_aborted( false )
, m_goingToRemoveSources( goingToRemoveSources )
{
- connect( this, SIGNAL(startDuplicateTrackSearch(Meta::TrackPtr)),
- SLOT(slotStartDuplicateTrackSearch(Meta::TrackPtr)) );
- connect( this, SIGNAL(startCopyOrTranscodeJob(QUrl,QUrl,bool)),
- SLOT(slotStartCopyOrTranscodeJob(QUrl,QUrl,bool)) );
- connect( this, SIGNAL(displaySorryDialog()), SLOT(slotDisplaySorryDialog()) );
+ connect( this, &IpodCopyTracksJob::startDuplicateTrackSearch,
+ this, &IpodCopyTracksJob::slotStartDuplicateTrackSearch );
+ connect( this, &IpodCopyTracksJob::startCopyOrTranscodeJob,
+ this, &IpodCopyTracksJob::slotStartCopyOrTranscodeJob );
+ connect( this, &IpodCopyTracksJob::displaySorryDialog, this, &IpodCopyTracksJob::slotDisplaySorryDialog );
}
void
IpodCopyTracksJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
if( !m_coll )
return; // destructed behind our back
float totalSafeCapacity = m_coll.data()->totalCapacity() - m_coll.data()->capacityMargin();
QByteArray mountPoint = QFile::encodeName( m_coll.data()->mountPoint() );
QString collectionPrettyName = m_coll.data()->prettyName();
itdb_start_sync( m_coll.data()->m_itdb );
QMapIterator<Meta::TrackPtr, QUrl> it( m_sources );
while( it.hasNext() )
{
if( m_aborted || !m_coll )
break;
it.next();
Meta::TrackPtr track = it.key();
QUrl sourceUrl = it.value();
emit startDuplicateTrackSearch( track );
// wait for searching to finish:
m_searchingForDuplicates.acquire( 1 );
if( m_duplicateTrack )
{
trackProcessed( Duplicate, track, m_duplicateTrack );
continue;
}
if( !m_coll )
break; // destructed behind our back
bool isJustCopy = m_transcodingConfig.isJustCopy( track, m_coll.data()->supportedFormats() );
if( isJustCopy // if not copying, we catch big files later
&& track->filesize() > totalSafeCapacity - m_coll.data()->usedCapacity() )
{
// this is a best effort check, we do one definite one after the file is copied
debug() << "Refusing to copy" << track->prettyUrl() << "to iPod: there are only"
<< totalSafeCapacity - m_coll.data()->usedCapacity() << "free bytes (not"
<< "counting a safety margin) on iPod and track has" << track->filesize()
<< "bytes.";
trackProcessed( ExceededingSafeCapacity, track );
continue;
}
QString fileExtension;
if( isJustCopy )
fileExtension = track->type();
else
fileExtension = Amarok::Components::transcodingController()->format(
m_transcodingConfig.encoder() )->fileExtension();
if( !m_coll.data()->supportedFormats().contains( fileExtension ) )
{
m_notPlayableFormats.insert( fileExtension );
trackProcessed( NotPlayable, track );
continue;
}
QByteArray fakeSrcName( "filename." ); // only for file extension
fakeSrcName.append( QFile::encodeName( fileExtension ) );
/* determine destination filename; we cannot use ipodTrack because as it has no itdb
* (and thus mountpoint) set */
GError *error = 0;
gchar *destFilename = itdb_cp_get_dest_filename( 0, mountPoint, fakeSrcName, &error );
if( error )
{
warning() << "Cannot construct iPod track filename:" << error->message;
g_error_free( error );
error = 0;
}
if( !destFilename )
{
trackProcessed( InternalError, track );
continue;
}
// start the physical copying
QUrl destUrl = QUrl( QFile::decodeName( destFilename ) );
emit startCopyOrTranscodeJob( sourceUrl, destUrl, isJustCopy );
// wait for copying to finish:
m_copying.acquire( 1 );
/* fsync so that progress bar gives correct info and user doesn't remove the iPod
* prematurely */
QFile destFile( QFile::decodeName( destFilename ) );
if( !destFile.exists() )
{
debug() << destFile.fileName() << "does not exist even though we thought we copied it to iPod";
trackProcessed( CopyingFailed, track );
continue;
}
if( !m_coll )
break; // destructed behind our back
if( m_coll.data()->usedCapacity() > totalSafeCapacity )
{
debug() << "We exceeded total safe-to-use capacity on iPod (safe-to-use:"
<< totalSafeCapacity << "B, used:" << m_coll.data()->usedCapacity()
<< "): removing copied track from iPod";
destFile.remove();
trackProcessed( ExceededingSafeCapacity, track );
continue;
}
// fsync needs a file opened for writing, and without Apped it truncates files (?)
if( !destFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
{
warning() << "Cannot open file copied to ipod (for writing):" << destFile.fileName()
<< ": removing it";
destFile.remove();
trackProcessed( InternalError, track );
continue;
}
if( destFile.size() )
fsync( destFile.handle() ); // should flush all kernel buffers to disk
destFile.close();
// create a new track object by copying meta-data from existing one:
IpodMeta::Track *ipodTrack = new IpodMeta::Track( track );
// tell the track it has been copied:
bool accepted = ipodTrack->finalizeCopying( mountPoint, destFilename );
g_free( destFilename );
destFilename = 0;
if( !accepted )
{
debug() << "ipodTrack->finalizeCopying( destFilename ) returned false!";
delete ipodTrack;
trackProcessed( InternalError, track );
continue;
}
if( !isJustCopy )
{
// we need to reread some metadata in case the file was transcoded
Meta::FieldHash fields = Meta::Tag::readTags( destFile.fileName() );
ipodTrack->setBitrate( fields.value( Meta::valBitrate, 0 ).toInt() );
ipodTrack->setLength( fields.value( Meta::valLength, 0 ).toLongLong() );
ipodTrack->setSampleRate( fields.value( Meta::valSamplerate, 0 ).toInt() );
Amarok::FileType type = Amarok::FileType( fields.value( Meta::valFormat, 0 ).toInt() );
ipodTrack->setType( Amarok::FileTypeSupport::toString( type ) );
// we retain ReplayGain, tags etc - these shouldn't change; size is read
// in finalizeCopying()
}
// add the track to collection
if( !m_coll )
{
delete ipodTrack;
break; // we were waiting for copying, m_coll may got destoryed
}
Meta::TrackPtr newTrack = m_coll.data()->addTrack( ipodTrack );
if( !newTrack )
{
destFile.remove();
trackProcessed( InternalError, track );
continue;
}
trackProcessed( Success, track, newTrack );
}
if( m_coll )
itdb_stop_sync( m_coll.data()->m_itdb );
emit endProgressOperation( this );
int sourceSize = m_sources.size();
int successCount = m_sourceTrackStatus.count( Success );
int duplicateCount = m_sourceTrackStatus.count( Duplicate );
QString transferredText;
if ( m_transcodingConfig.isJustCopy() )
transferredText = i18ncp( "%2 is collection name", "Transferred one track to %2.",
"Transferred %1 tracks to %2.", successCount, collectionPrettyName );
else
transferredText = i18ncp( "%2 is collection name", "Transcoded one track to %2.",
"Transcoded %1 tracks to %2.", successCount, collectionPrettyName );
if( successCount == sourceSize )
{
Amarok::Components::logger()->shortMessage( transferredText );
}
else if( m_aborted )
{
QString text = i18np( "Transfer aborted. Managed to transfer one track.",
"Transfer aborted. Managed to transfer %1 tracks.",
successCount );
Amarok::Components::logger()->longMessage( text );
}
else if( successCount + duplicateCount == sourceSize )
{
QString text = i18ncp( "%2 is the 'Transferred 123 tracks to Some collection.' message",
"%2 One track was already there.", "%2 %1 tracks were already there.",
duplicateCount, transferredText );
Amarok::Components::logger()->longMessage( text );
}
else
{
// somethig more severe failed, notify user using a dialog
emit displaySorryDialog();
}
}
void
IpodCopyTracksJob::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
IpodCopyTracksJob::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
IpodCopyTracksJob::abort()
{
m_aborted = true;
}
void
IpodCopyTracksJob::slotStartDuplicateTrackSearch( const Meta::TrackPtr &track )
{
Collections::QueryMaker *qm = m_coll.data()->queryMaker();
qm->setQueryType( Collections::QueryMaker::Track );
// we cannot qm->addMatch( track ) - it matches by uidUrl()
qm->addFilter( Meta::valTitle, track->name(), true, true );
qm->addMatch( track->album() );
qm->addMatch( track->artist(), Collections::QueryMaker::TrackArtists );
qm->addMatch( track->composer() );
qm->addMatch( track->year() );
qm->addNumberFilter( Meta::valTrackNr, track->trackNumber(), Collections::QueryMaker::Equals );
qm->addNumberFilter( Meta::valDiscNr, track->discNumber(), Collections::QueryMaker::Equals );
// we don't want to match by filesize, track length, filetype etc - these change during
// transcoding. We don't match album artist because handling of it is inconsistent
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)),
- SLOT(slotDuplicateTrackSearchNewResult(Meta::TrackList)) );
- connect( qm, SIGNAL(queryDone()), SLOT(slotDuplicateTrackSearchQueryDone()) );
+ connect( qm, &Collections::QueryMaker::newTracksReady,
+ this, &IpodCopyTracksJob::slotDuplicateTrackSearchNewResult );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &IpodCopyTracksJob::slotDuplicateTrackSearchQueryDone );
qm->setAutoDelete( true );
m_duplicateTrack = Meta::TrackPtr(); // reset duplicate track from previous query
qm->run();
}
void
IpodCopyTracksJob::slotDuplicateTrackSearchNewResult( const Meta::TrackList &tracks )
{
if( !tracks.isEmpty() )
// we don't really know which one, but be sure to allow multiple results
m_duplicateTrack = tracks.last();
}
void
IpodCopyTracksJob::slotDuplicateTrackSearchQueryDone()
{
m_searchingForDuplicates.release( 1 ); // wakeup run()
}
void
IpodCopyTracksJob::slotStartCopyOrTranscodeJob( const QUrl &sourceUrl, const QUrl &destUrl,
bool isJustCopy )
{
KJob *job = 0;
if( isJustCopy )
{
if( m_goingToRemoveSources && m_coll &&
sourceUrl.toLocalFile().startsWith( m_coll.data()->mountPoint() ) )
{
// special case for "add orphaned tracks" to either save space and significantly
// speed-up the process:
debug() << "Moving from" << sourceUrl << "to" << destUrl;
job = KIO::file_move( sourceUrl, destUrl, -1, KIO::HideProgressInfo | KIO::Overwrite );
}
else
{
debug() << "Copying from" << sourceUrl << "to" << destUrl;
job = KIO::file_copy( sourceUrl, destUrl, -1, KIO::HideProgressInfo | KIO::Overwrite );
}
}
else
{
debug() << "Transcoding from" << sourceUrl << "to" << destUrl;
job = new Transcoding::Job( sourceUrl, destUrl, m_transcodingConfig );
}
job->setUiDelegate( 0 ); // be non-interactive
- connect( job, SIGNAL(finished(KJob*)), // we must use this instead of result() to prevent deadlock
- SLOT(slotCopyOrTranscodeJobFinished(KJob*)) );
+ connect( job, &Transcoding::Job::finished, // we must use this instead of result() to prevent deadlock
+ this, &IpodCopyTracksJob::slotCopyOrTranscodeJobFinished );
job->start(); // no-op for KIO job, but matters for transcoding job
}
void
IpodCopyTracksJob::slotCopyOrTranscodeJobFinished( KJob *job )
{
if( job->error() != 0 && m_copyErrors.count() < 10 )
m_copyErrors.insert( job->errorString() );
m_copying.release( 1 ); // wakeup run()
}
void
IpodCopyTracksJob::slotDisplaySorryDialog()
{
int sourceSize = m_sources.size();
int successCount = m_sourceTrackStatus.count( Success );
// match string with IpodCollectionLocation::prettyLocation()
QString collName = m_coll ? m_coll.data()->prettyName() : i18n( "Disconnected iPod/iPad/iPhone" );
QString caption = i18nc( "%1 is collection pretty name, e.g. My Little iPod",
"Transferred Tracks to %1", collName );
QString text;
if( successCount )
text = i18np( "One track successfully transferred, but transfer of some other tracks failed.",
"%1 tracks successfully transferred, but transfer of some other tracks failed.",
successCount );
else
text = i18n( "Transfer of tracks failed." );
QString details;
int exceededingSafeCapacityCount = m_sourceTrackStatus.count( ExceededingSafeCapacity );
if( exceededingSafeCapacityCount )
{
details += i18np( "One track was not transferred because it would exceed iPod capacity.<br>",
"%1 tracks were not transferred because it would exceed iPod capacity.<br>",
exceededingSafeCapacityCount );
QString reservedSpace = m_coll ? KGlobal::locale()->formatByteSize(
m_coll.data()->capacityMargin(), 1 ) : QString( "???" ); // improbable, don't bother translators
details += i18nc( "Example of %1 would be: 20.0 MiB",
"<i>Amarok reserves %1 on iPod for iTunes database writing.</i><br>",
reservedSpace );
}
int notPlayableCount = m_sourceTrackStatus.count( NotPlayable );
if( notPlayableCount )
{
QString formats = QStringList( m_notPlayableFormats.toList() ).join( ", " );
details += i18np( "One track was not copied because it wouldn't be playable - its "
" %2 format is unsupported.<br>",
"%1 tracks were not copied because they wouldn't be playable - "
"they are in unsupported formats (%2).<br>",
notPlayableCount, formats );
}
int copyingFailedCount = m_sourceTrackStatus.count( CopyingFailed );
if( copyingFailedCount )
{
details += i18np( "Copy/move/transcode of one file failed.<br>",
"Copy/move/transcode of %1 files failed.<br>", copyingFailedCount );
}
int internalErrorCount = m_sourceTrackStatus.count( InternalError );
if( internalErrorCount )
{
details += i18np( "One track was not transferred due to an internal Amarok error.<br>",
"%1 tracks were not transferred due to an internal Amarok error.<br>",
internalErrorCount );
details += i18n( "<i>You can find details in Amarok debugging output.</i><br>" );
}
if( m_sourceTrackStatus.size() != sourceSize )
{
// aborted case was already caught in run()
details += i18n( "The rest was not transferred because iPod collection disappeared.<br>" );
}
if( !m_copyErrors.isEmpty() )
{
details += i18nc( "%1 is a list of errors that occurred during copying of tracks",
"Error causes: %1<br>", QStringList( m_copyErrors.toList() ).join( "<br>" ) );
}
KMessageBox::detailedSorry( 0, text, details, caption );
}
void
IpodCopyTracksJob::trackProcessed( CopiedStatus status, Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack )
{
m_sourceTrackStatus.insert( status, srcTrack );
emit incrementProgress();
emit signalTrackProcessed( srcTrack, destTrack, status );
}
diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.cpp b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.cpp
index b65488d643..3e69ae1135 100644
--- a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.cpp
+++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.cpp
@@ -1,250 +1,250 @@
/****************************************************************************************
* Copyright (c) 2006 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2006 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MediaDeviceCollection"
#include "MediaDeviceMonitor.h"
#include "core/capabilities/ActionsCapability.h"
#include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h"
#include "core-impl/collections/mediadevicecollection/support/MediaDeviceInfo.h"
#include "core-impl/collections/support/MemoryQueryMaker.h"
#include <KDiskFreeSpaceInfo>
using namespace Collections;
MediaDeviceCollectionFactoryBase::MediaDeviceCollectionFactoryBase( QObject *parent, const QVariantList &args,
ConnectionAssistant* assistant )
: Collections::CollectionFactory( parent, args )
, m_assistant( assistant )
{
}
MediaDeviceCollectionFactoryBase::~MediaDeviceCollectionFactoryBase()
{
}
void
MediaDeviceCollectionFactoryBase::init()
{
// When assistant identifies a device, Factory will attempt to build Collection
- connect( m_assistant, SIGNAL(identified(MediaDeviceInfo*)), SLOT(slotDeviceDetected(MediaDeviceInfo*)) );
+ connect( m_assistant, &ConnectionAssistant::identified, this, &MediaDeviceCollectionFactoryBase::slotDeviceDetected );
// When assistant told to disconnect, Factory will disconnect
// the device, and have the Collection destroyed
- connect( m_assistant, SIGNAL(disconnected(QString)), SLOT(slotDeviceDisconnected(QString)) );
+ connect( m_assistant, &ConnectionAssistant::disconnected, this, &MediaDeviceCollectionFactoryBase::slotDeviceDisconnected );
// Register the device type with the Monitor
MediaDeviceMonitor::instance()->registerDeviceType( m_assistant );
m_initialized = true;
}
void MediaDeviceCollectionFactoryBase::slotDeviceDetected(MediaDeviceInfo* info)
{
MediaDeviceCollection* coll = 0;
// If device not already connected to
if( !m_collectionMap.contains( info->udi() ) )
{
// create the collection using the info provided
coll = createCollection( info );
// if collection successfully created,
// aka device connected to, then...
if( coll )
{
// insert it into the map of known collections
m_collectionMap.insert( info->udi(), coll );
- connect( coll, SIGNAL(collectionReady(Collections::Collection*)),
- this, SIGNAL(newCollection(Collections::Collection*)) );
- connect( coll, SIGNAL(collectionDisconnected(QString)),
- this, SLOT(slotDeviceDisconnected(QString)) );
+ connect( coll, &Collections::MediaDeviceCollection::collectionReady,
+ this, &MediaDeviceCollectionFactoryBase::newCollection );
+ connect( coll, &Collections::MediaDeviceCollection::collectionDisconnected,
+ this, &MediaDeviceCollectionFactoryBase::slotDeviceDisconnected );
coll->init();
}
}
}
void
MediaDeviceCollectionFactoryBase::slotDeviceDisconnected( const QString &udi )
{
DEBUG_BLOCK
// If device is known about
if( m_collectionMap.contains( udi ) )
{
// Pull collection for the udi out of map
MediaDeviceCollection* coll = m_collectionMap[ udi ];
// If collection exists/found
if( coll )
{
// Remove collection from map
m_collectionMap.remove( udi );
// Have Collection disconnect device
// and destroy itself
coll->deleteCollection();
}
}
return;
}
//MediaDeviceCollection
MediaDeviceCollection::MediaDeviceCollection()
: Collection()
, m_ejectAction( 0 )
, m_mc( new MemoryCollection() )
{
- connect( this, SIGNAL(attemptConnectionDone(bool)),
- this, SLOT(slotAttemptConnectionDone(bool)) );
+ connect( this, &MediaDeviceCollection::attemptConnectionDone,
+ this, &MediaDeviceCollection::slotAttemptConnectionDone );
}
MediaDeviceCollection::~MediaDeviceCollection()
{
DEBUG_BLOCK
}
QueryMaker*
MediaDeviceCollection::queryMaker()
{
return new MemoryQueryMaker( m_mc.toWeakRef(), collectionId() );
}
QString MediaDeviceCollection::collectionId() const
{
return m_udi;
}
void
MediaDeviceCollection::startFullScanDevice()
{
DEBUG_BLOCK
// If handler successfully connected to device
m_handler->parseTracks();
//emit collectionReady( this );
}
Meta::MediaDeviceHandler*
MediaDeviceCollection::handler()
{
return m_handler;
}
void
MediaDeviceCollection::eject()
{
DEBUG_BLOCK
// Do nothing special here.
emit collectionDisconnected( m_udi );
}
void
MediaDeviceCollection::deleteCollection()
{
DEBUG_BLOCK
emit deletingCollection();
emit remove();
}
void
MediaDeviceCollection::slotAttemptConnectionDone( bool success )
{
DEBUG_BLOCK
if( success )
{
debug() << "starting full scan";
// TODO: thread the track parsing?
startFullScanDevice();
}
else
{
debug() << "connection failed, not scanning";
emit collectionDisconnected( m_udi );
}
}
/// CollectionCapability for Disconnect Action
bool
MediaDeviceCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
switch( type )
{
case Capabilities::Capability::Actions:
return true;
default:
return false;
}
}
Capabilities::Capability*
MediaDeviceCollection::createCapabilityInterface( Capabilities::Capability::Type type )
{
switch( type )
{
case Capabilities::Capability::Actions:
{
QList< QAction* > actions;
actions << m_handler->collectionActions();
actions << ejectAction();
return new Capabilities::ActionsCapability( actions );
}
default:
return 0;
}
}
bool
MediaDeviceCollection::hasCapacity() const
{
return totalCapacity() > 0;
}
float
MediaDeviceCollection::usedCapacity() const
{
return m_handler->usedcapacity();
}
float
MediaDeviceCollection::totalCapacity() const
{
return m_handler->totalcapacity();
}
void
MediaDeviceCollection::emitCollectionReady()
{
emit collectionReady( this );
}
QAction *
MediaDeviceCollection::ejectAction() const
{
if( !m_ejectAction )
{
m_ejectAction = new QAction( QIcon::fromTheme( "media-eject" ), i18n( "&Disconnect Device" ),
const_cast<MediaDeviceCollection*>(this) );
m_ejectAction->setProperty( "popupdropper_svg_id", "eject" );
- connect( m_ejectAction, SIGNAL(triggered()), SLOT(eject()) );
+ connect( m_ejectAction, &QAction::triggered, this, &MediaDeviceCollection::eject );
}
return m_ejectAction;
}
diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp
index 82a1621ad7..e1338ce051 100644
--- a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp
+++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp
@@ -1,139 +1,139 @@
/****************************************************************************************
* Copyright (c) 2009 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MediaDeviceCollectionLocation.h"
#include "MediaDeviceCache.h" // for collection refresh hack
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h"
#include <KJob>
#include <KLocale>
#include <kio/job.h>
#include <kio/jobclasses.h>
using namespace Collections;
MediaDeviceCollectionLocation::MediaDeviceCollectionLocation( MediaDeviceCollection *collection )
: CollectionLocation( collection )
, m_collection( collection )
, m_handler( m_collection->handler() )
{
//nothing to do
}
MediaDeviceCollectionLocation::~MediaDeviceCollectionLocation()
{
//nothing to do
}
QString
MediaDeviceCollectionLocation::prettyLocation() const
{
return collection()->prettyName();
}
bool
MediaDeviceCollectionLocation::isWritable() const
{
return m_handler->isWritable();
}
void
MediaDeviceCollectionLocation::getKIOCopyableUrls( const Meta::TrackList &tracks )
{
- connect( m_handler, SIGNAL(gotCopyableUrls(QMap<Meta::TrackPtr,QUrl>)),SLOT(slotGetKIOCopyableUrlsDone(QMap<Meta::TrackPtr,QUrl>)) );
+ connect( m_handler, &Meta::MediaDeviceHandler::gotCopyableUrls, this, &MediaDeviceCollectionLocation::slotGetKIOCopyableUrlsDone );
m_handler->getCopyableUrls( tracks );
}
void
MediaDeviceCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration )
{
DEBUG_BLOCK
Q_UNUSED( configuration )
- connect( m_handler, SIGNAL(copyTracksDone(bool)),
- this, SLOT(copyOperationFinished(bool)),
+ connect( m_handler, &Meta::MediaDeviceHandler::copyTracksDone,
+ this, &MediaDeviceCollectionLocation::copyOperationFinished,
Qt::QueuedConnection );
m_handler->copyTrackListToDevice( sources.keys() );
/*
connect( m_collection, SIGNAL(copyTracksCompleted(bool)),
SLOT(copyOperationFinished(bool)) );
// Copy list of tracks
m_collection->copyTrackListToDevice( sources.keys() );
*/
// TODO: call handler's method for copying a list of
// tracks to the device. At the end, if successful,
// write to database, and any unsuccessful track
// copies will generate warning/error messages
}
void
MediaDeviceCollectionLocation::copyOperationFinished( bool success )
{
// TODO: should connect database written signal to
// to this slot
if( success )
{
m_handler->writeDatabase();
}
// TODO: will be replaced with a more powerful method
// which deals with particular reasons for failed copies
/*
DEBUG_BLOCK
if( !success )
{
QMap<Meta::TrackPtr, QString> failedTracks = m_collection->handler()->tracksFailed();
debug() << "The following tracks failed to copy";
foreach( Meta::TrackPtr track, failedTracks.keys() )
{
// TODO: better error handling
debug() << track->artist()->name() << " - " << track->name() << " with error: " << failedTracks[ track ];
source()->transferError( track, failedTracks[ track ] );
}
}
*/
slotCopyOperationFinished();
}
void
MediaDeviceCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources )
{
DEBUG_BLOCK
- connect( m_handler, SIGNAL(removeTracksDone()),
- this, SLOT(removeOperationFinished()) );
+ connect( m_handler, &Meta::MediaDeviceHandler::removeTracksDone,
+ this, &MediaDeviceCollectionLocation::removeOperationFinished );
m_handler->removeTrackListFromDevice( sources );
}
void
MediaDeviceCollectionLocation::removeOperationFinished()
{
DEBUG_BLOCK
m_handler->writeDatabase();
slotRemoveOperationFinished();
}
diff --git a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp
index 465a98014a..df4133fbc6 100644
--- a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp
+++ b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp
@@ -1,1284 +1,1284 @@
/****************************************************************************************
* Copyright (c) 2009 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MediaDeviceHandler.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h"
#include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.h"
#include "core-impl/collections/support/ArtistHelper.h"
#include "playlistmanager/PlaylistManager.h"
#include <QSharedPointer>
#include <KMessageBox>
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/ThreadWeaver>
using namespace Meta;
bool
MetaHandlerCapability::hasCapabilityInterface( Handler::Capability::Type type ) const
{
Q_UNUSED( type );
return false;
}
Handler::Capability*
MetaHandlerCapability::createCapabilityInterface( Handler::Capability::Type type )
{
Q_UNUSED( type );
return 0;
}
MediaDeviceHandler::MediaDeviceHandler( QObject *parent )
: QObject( parent )
, m_memColl( qobject_cast<Collections::MediaDeviceCollection*>(parent) )
, m_provider( 0 )
, m_isCopying( false )
, m_isDeleting( false )
, m_pc( 0 )
, m_rc( 0 )
, m_wc( 0 )
{
DEBUG_BLOCK
- connect( m_memColl, SIGNAL(deletingCollection()),
- this, SLOT(slotDeletingHandler()), Qt::QueuedConnection );
+ connect( m_memColl, &Collections::MediaDeviceCollection::deletingCollection,
+ this, &MediaDeviceHandler::slotDeletingHandler, Qt::QueuedConnection );
- connect( this, SIGNAL(databaseWritten(bool)),
- this, SLOT(slotDatabaseWritten(bool)), Qt::QueuedConnection );
+ connect( this, &MediaDeviceHandler::databaseWritten,
+ this, &MediaDeviceHandler::slotDatabaseWritten, Qt::QueuedConnection );
}
MediaDeviceHandler::~MediaDeviceHandler()
{
DEBUG_BLOCK
delete m_provider;
}
void
MediaDeviceHandler::slotDeletingHandler()
{
DEBUG_BLOCK
if( m_provider )
The::playlistManager()->removeProvider( m_provider );
m_memColl = NULL;
}
void
MediaDeviceHandler::getBasicMediaDeviceTrackInfo( const Meta::MediaDeviceTrackPtr &srcTrack, Meta::MediaDeviceTrackPtr destTrack )
{
/* 1-liner info retrieval */
destTrack->setTitle( m_rc->libGetTitle( srcTrack ) );
destTrack->setLength( m_rc->libGetLength( srcTrack ) );
destTrack->setTrackNumber( m_rc->libGetTrackNumber( srcTrack ) );
destTrack->setComment( m_rc->libGetComment( srcTrack ) );
destTrack->setDiscNumber( m_rc->libGetDiscNumber( srcTrack ) );
destTrack->setBitrate( m_rc->libGetBitrate( srcTrack ) );
destTrack->setSamplerate( m_rc->libGetSamplerate( srcTrack ) );
destTrack->setBpm( m_rc->libGetBpm( srcTrack ) );
destTrack->setFileSize( m_rc->libGetFileSize( srcTrack ) );
destTrack->setPlayCount( m_rc->libGetPlayCount( srcTrack ) );
destTrack->setLastPlayed( m_rc->libGetLastPlayed( srcTrack ) );
destTrack->setRating( m_rc->libGetRating( srcTrack ) );
destTrack->setReplayGain( m_rc->libGetReplayGain( srcTrack ) );
destTrack->setPlayableUrl( m_rc->libGetPlayableUrl( srcTrack ) );
destTrack->setType( m_rc->libGetType( srcTrack ) );
}
void
MediaDeviceHandler::setBasicMediaDeviceTrackInfo( const Meta::TrackPtr& srcTrack, MediaDeviceTrackPtr destTrack )
{
DEBUG_BLOCK
if( !setupWriteCapability() )
return;
m_wc->libSetTitle( destTrack, srcTrack->name() );
QString albumArtist;
bool isCompilation = false;
if ( srcTrack->album() )
{
AlbumPtr album = srcTrack->album();
m_wc->libSetAlbum( destTrack, album->name() );
isCompilation = album->isCompilation();
m_wc->libSetIsCompilation( destTrack, isCompilation );
if( album->hasAlbumArtist() )
albumArtist = album->albumArtist()->name();
if( album->hasImage() )
m_wc->libSetCoverArt( destTrack, album->image() );
}
QString trackArtist;
if ( srcTrack->artist() )
{
trackArtist = srcTrack->artist()->name();
m_wc->libSetArtist( destTrack, trackArtist );
}
QString composer;
if ( srcTrack->composer() )
{
composer = srcTrack->composer()->name();
m_wc->libSetComposer( destTrack, composer );
}
QString genre;
if ( srcTrack->genre() )
{
genre = srcTrack->genre()->name();
m_wc->libSetGenre( destTrack, genre );
}
if( isCompilation && albumArtist.isEmpty() )
// iPod doesn't handle empy album artist well for compilation albums (splits these albums)
albumArtist = i18n( "Various Artists" );
else
albumArtist = ArtistHelper::bestGuessAlbumArtist( albumArtist, trackArtist, genre, composer );
m_wc->libSetAlbumArtist( destTrack, albumArtist );
if ( srcTrack->year() )
m_wc->libSetYear( destTrack, srcTrack->year()->name() );
m_wc->libSetLength( destTrack, srcTrack->length() );
m_wc->libSetTrackNumber( destTrack, srcTrack->trackNumber() );
m_wc->libSetComment( destTrack, srcTrack->comment() );
m_wc->libSetDiscNumber( destTrack, srcTrack->discNumber() );
m_wc->libSetBitrate( destTrack, srcTrack->bitrate() );
m_wc->libSetSamplerate( destTrack, srcTrack->sampleRate() );
m_wc->libSetBpm( destTrack, srcTrack->bpm() );
m_wc->libSetFileSize( destTrack, srcTrack->filesize() );
m_wc->libSetPlayCount( destTrack, srcTrack->statistics()->playCount() );
m_wc->libSetLastPlayed( destTrack, srcTrack->statistics()->lastPlayed() );
m_wc->libSetRating( destTrack, srcTrack->statistics()->rating() );
// MediaDeviceTrack stores only track gain:
m_wc->libSetReplayGain( destTrack, srcTrack->replayGain( Meta::ReplayGain_Track_Gain ) );
m_wc->libSetType( destTrack, srcTrack->type() );
//libSetPlayableUrl( destTrack, srcTrack );
}
void
MediaDeviceHandler::addMediaDeviceTrackToCollection( Meta::MediaDeviceTrackPtr& track )
{
if( !setupReadCapability() )
return;
TrackMap trackMap = m_memColl->memoryCollection()->trackMap();
ArtistMap artistMap = m_memColl->memoryCollection()->artistMap();
AlbumMap albumMap = m_memColl->memoryCollection()->albumMap();
GenreMap genreMap = m_memColl->memoryCollection()->genreMap();
ComposerMap composerMap = m_memColl->memoryCollection()->composerMap();
YearMap yearMap = m_memColl->memoryCollection()->yearMap();
/* 1-liner info retrieval */
//Meta::TrackPtr srcTrack = Meta::TrackPtr::staticCast( track );
//getBasicMediaDeviceTrackInfo( srcTrack, track );
/* map-related info retrieval */
// NB: setupArtistMap _MUST_ be called before setupAlbumMap
setupArtistMap( track, artistMap );
setupAlbumMap( track, albumMap, artistMap );
setupGenreMap( track, genreMap );
setupComposerMap( track, composerMap );
setupYearMap( track, yearMap );
/* trackmap also soon to be subordinated */
trackMap.insert( track->uidUrl(), TrackPtr::staticCast( track ) );
m_titlemap.insert( track->name(), TrackPtr::staticCast( track ) );
// Finally, assign the created maps to the collection
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setTrackMap( trackMap );
m_memColl->memoryCollection()->setArtistMap( artistMap );
m_memColl->memoryCollection()->setAlbumMap( albumMap );
m_memColl->memoryCollection()->setGenreMap( genreMap );
m_memColl->memoryCollection()->setComposerMap( composerMap );
m_memColl->memoryCollection()->setYearMap( yearMap );
m_memColl->memoryCollection()->releaseLock();
}
void
MediaDeviceHandler::removeMediaDeviceTrackFromCollection( Meta::MediaDeviceTrackPtr &track )
{
TrackMap trackMap = m_memColl->memoryCollection()->trackMap();
ArtistMap artistMap = m_memColl->memoryCollection()->artistMap();
AlbumMap albumMap = m_memColl->memoryCollection()->albumMap();
GenreMap genreMap = m_memColl->memoryCollection()->genreMap();
ComposerMap composerMap = m_memColl->memoryCollection()->composerMap();
YearMap yearMap = m_memColl->memoryCollection()->yearMap();
Meta::MediaDeviceArtistPtr artist = Meta::MediaDeviceArtistPtr::dynamicCast( track->artist() );
Meta::MediaDeviceAlbumPtr album = Meta::MediaDeviceAlbumPtr::dynamicCast( track->album() );
Meta::MediaDeviceGenrePtr genre = Meta::MediaDeviceGenrePtr::dynamicCast( track->genre() );
Meta::MediaDeviceComposerPtr composer = Meta::MediaDeviceComposerPtr::dynamicCast( track->composer() );
Meta::MediaDeviceYearPtr year = Meta::MediaDeviceYearPtr::dynamicCast( track->year() );
// remove track from metadata's tracklists
artist->remTrack( track );
album->remTrack( track );
genre->remTrack( track );
composer->remTrack( track );
year->remTrack( track );
// if empty, get rid of metadata in general
if( artist->tracks().isEmpty() )
{
artistMap.remove( artist->name() );
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setArtistMap( artistMap );
m_memColl->memoryCollection()->releaseLock();
}
if( album->tracks().isEmpty() )
{
albumMap.remove( AlbumPtr::staticCast( album ) );
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setAlbumMap( albumMap );
m_memColl->memoryCollection()->releaseLock();
}
if( genre->tracks().isEmpty() )
{
genreMap.remove( genre->name() );
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setGenreMap( genreMap );
m_memColl->memoryCollection()->releaseLock();
}
if( composer->tracks().isEmpty() )
{
composerMap.remove( composer->name() );
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setComposerMap( composerMap );
m_memColl->memoryCollection()->releaseLock();
}
if( year->tracks().isEmpty() )
{
yearMap.remove( year->year() );
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setYearMap( yearMap );
m_memColl->memoryCollection()->releaseLock();
}
// remove from trackmap
trackMap.remove( track->uidUrl() );
m_titlemap.remove( track->name(), TrackPtr::staticCast( track ) );
// Finally, assign the created maps to the collection
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setTrackMap( trackMap );
m_memColl->memoryCollection()->setArtistMap( artistMap );
m_memColl->memoryCollection()->setAlbumMap( albumMap );
m_memColl->memoryCollection()->setGenreMap( genreMap );
m_memColl->memoryCollection()->setComposerMap( composerMap );
m_memColl->memoryCollection()->setYearMap( yearMap );
m_memColl->memoryCollection()->releaseLock();
}
void
MediaDeviceHandler::getCopyableUrls(const Meta::TrackList &tracks)
{
QMap<Meta::TrackPtr, QUrl> urls;
foreach( Meta::TrackPtr track, tracks )
{
if( track->isPlayable() )
urls.insert( track, track->playableUrl() );
}
emit gotCopyableUrls( urls );
}
void
MediaDeviceHandler::copyTrackListToDevice(const Meta::TrackList tracklist)
{
DEBUG_BLOCK
const QString copyErrorCaption = i18n( "Copying Tracks Failed" );
if ( m_isCopying )
{
KMessageBox::error( 0, i18n( "Tracks not copied: the device is already being copied to" ), copyErrorCaption );
return;
}
setupReadCapability();
if( !setupWriteCapability() )
return;
m_isCopying = true;
bool isDupe = false;
bool hasError = false;
QString format;
TrackMap trackMap = m_memColl->memoryCollection()->trackMap();
Meta::TrackList tempTrackList;
m_copyFailed = false;
m_tracksFailed.clear();
// Clear Transfer queue
m_tracksToCopy.clear();
// Check for same tags, don't copy if same tags
// Also check for compatible format
foreach( Meta::TrackPtr track, tracklist )
{
// Check for compatible formats
format = track->type();
if( !m_wc->supportedFormats().contains( format ) )
{
const QString error = i18n("Unsupported format: %1", format);
m_tracksFailed.insert( track, error );
hasError = true;
continue;
}
tempTrackList = m_titlemap.values( track->name() );
/* If no song with same title, already not a dupe */
if( tempTrackList.isEmpty() )
{
debug() << "No tracks with same title, track not a dupe";
m_tracksToCopy.append( track );
continue;
}
/* Songs with same title present, check other tags */
isDupe = false;
foreach( Meta::TrackPtr tempTrack, tempTrackList )
{
if( ( tempTrack->artist()->name() != track->artist()->name() )
|| ( tempTrack->album()->name() != track->album()->name() )
|| ( tempTrack->genre()->name() != track->genre()->name() )
|| ( tempTrack->composer()->name() != track->composer()->name() )
|| ( tempTrack->year()->name() != track->year()->name() ) )
{
continue;
}
// Track is already on there, break
isDupe = true;
hasError = true;
break;
}
if( !isDupe )
m_tracksToCopy.append( track );
else
{
debug() << "Track " << track->name() << " is a dupe!";
const QString error = i18n("Already on device");
m_tracksFailed.insert( track, error );
}
}
// NOTE: see comment at top of copyTrackListToDevice
if( hasError )
m_copyFailed = true;
/* List ready, begin copying */
// Do not bother copying 0 tracks
// This could happen if all tracks to copy are dupes
if( m_tracksToCopy.size() == 0 )
{
KMessageBox::error( 0, i18n( "Tracks not copied: the device already has these tracks" ), copyErrorCaption );
m_isCopying = false;
emit copyTracksDone( false );
return;
}
// Check for available space, in bytes, after the copy
int transfersize = 0;
foreach( Meta::TrackPtr track, m_tracksToCopy )
transfersize += track->filesize();
// NOTE: if the device will not have more than 5 MB to spare, abort the copy
// This is important because on some devices if there is insufficient space to write the database, it will appear as
// though all music has been wiped off the device - since neither the device nor amarok will be able to read the
// (corrupted) database.
if( !( (freeSpace() - transfersize) > 1024*1024*5 ) )
{
debug() << "Free space: " << freeSpace();
debug() << "Space would've been after copy: " << (freeSpace() - transfersize);
KMessageBox::error( 0, i18n( "Tracks not copied: the device has insufficient space" ), copyErrorCaption );
m_isCopying = false;
emit copyTracksDone( false );
return;
}
debug() << "Copying " << m_tracksToCopy.size() << " tracks";
// Set up progress bar
Amarok::Components::logger()->newProgressOperation( this,
i18n( "Transferring Tracks to Device" ), m_tracksToCopy.size() );
// prepare to copy
m_wc->prepareToCopy();
m_numTracksToCopy = m_tracksToCopy.count();
m_tracksCopying.clear();
m_trackSrcDst.clear();
// begin copying tracks to device
if( !m_copyingthreadsafe )
copyNextTrackToDevice();
else
enqueueNextCopyThread();
}
void
MediaDeviceHandler::copyNextTrackToDevice()
{
DEBUG_BLOCK
Meta::TrackPtr track;
debug() << "Tracks left to copy after this one is now done: " << m_numTracksToCopy;
if ( !m_tracksToCopy.isEmpty() )
{
// Pop the track off the front of the list
track = m_tracksToCopy.takeFirst();
// Copy the track and check result
if ( !privateCopyTrackToDevice( track ) )
slotCopyTrackFailed( track );
}
else
{
if ( m_numTracksToCopy > 0 )
debug() << "Oops. \"Tracks to copy\" counter is not zero, but copy list is empty. Something missed?";
if ( m_copyFailed )
{
Amarok::Components::logger()->shortMessage(
i18np( "%1 track failed to copy to the device",
"%1 tracks failed to copy to the device", m_tracksFailed.size() ) );
}
// clear maps/hashes used
m_tracksCopying.clear();
m_trackSrcDst.clear();
m_tracksFailed.clear();
m_tracksToCopy.clear();
// copying done
m_isCopying = false;
emit copyTracksDone( true );
}
}
bool
MediaDeviceHandler::privateCopyTrackToDevice( const Meta::TrackPtr &track )
{
DEBUG_BLOCK
// Create new destTrack that will go into the device collection, based on source track
Meta::MediaDeviceTrackPtr destTrack ( new Meta::MediaDeviceTrack( m_memColl ) );
// find path to copy to
m_wc->findPathToCopy( track, destTrack );
// Create a track struct, associate it to destTrack
m_wc->libCreateTrack( destTrack );
// Fill the track struct of the destTrack with info from the track parameter as source
setBasicMediaDeviceTrackInfo( track, destTrack );
// set up the play url
m_wc->libSetPlayableUrl( destTrack, track );
getBasicMediaDeviceTrackInfo( destTrack, destTrack );
m_trackSrcDst[ track ] = destTrack; // associate source with destination, for finalizing copy
// Copy the file to the device
return m_wc->libCopyTrack( track, destTrack );
}
/// @param track is the source track from which we are copying
void
MediaDeviceHandler::slotFinalizeTrackCopy( const Meta::TrackPtr & track )
{
DEBUG_BLOCK
//m_tracksCopying.removeOne( track );
Meta::MediaDeviceTrackPtr destTrack = m_trackSrcDst[ track ];
// Add the track struct into the database, if the library needs to
m_wc->addTrackInDB( destTrack );
// Inform subclass that a track has been added to the db
m_wc->setDatabaseChanged();
// Add the new Meta::MediaDeviceTrackPtr into the device collection
// add track to collection
addMediaDeviceTrackToCollection( destTrack );
emit incrementProgress();
m_numTracksToCopy--;
}
void
MediaDeviceHandler::slotCopyTrackFailed( const Meta::TrackPtr & track )
{
DEBUG_BLOCK
emit incrementProgress();
m_numTracksToCopy--;
QString error = i18n( "The track failed to copy to the device" );
m_tracksFailed.insert( track, error );
}
void
MediaDeviceHandler::removeTrackListFromDevice( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
QString removeError = i18np( "Track not deleted:", "Tracks not deleted:", tracks.size() );
QString removeErrorCaption = i18np( "Deleting Track Failed", "Deleting Tracks Failed", tracks.size() );
if ( m_isDeleting )
{
KMessageBox::error( 0, i18n( "%1 tracks are already being deleted from the device.", removeError ), removeErrorCaption );
return;
}
if( !setupWriteCapability() )
return;
m_isDeleting = true;
// Init the list of tracks to be deleted
m_tracksToDelete = tracks;
// Set up statusbar for deletion operation
Amarok::Components::logger()->newProgressOperation( this,
i18np( "Removing Track from Device", "Removing Tracks from Device", tracks.size() ),
tracks.size() );
m_wc->prepareToDelete();
m_numTracksToRemove = m_tracksToDelete.count();
removeNextTrackFromDevice();
}
void
MediaDeviceHandler::removeNextTrackFromDevice()
{
DEBUG_BLOCK
Meta::TrackPtr track;
// If there are more tracks to remove, remove the next one
if( !m_tracksToDelete.isEmpty() )
{
// Pop the track off the front of the list
track = m_tracksToDelete.takeFirst();
// Remove the track
privateRemoveTrackFromDevice( track );
}
}
void
MediaDeviceHandler::privateRemoveTrackFromDevice( const Meta::TrackPtr &track )
{
DEBUG_BLOCK
Meta::MediaDeviceTrackPtr devicetrack = Meta::MediaDeviceTrackPtr::staticCast( track );
// Remove the physical file from the device, perhaps using a libcall, or KIO
m_wc->libDeleteTrackFile( devicetrack );
}
void
MediaDeviceHandler::slotFinalizeTrackRemove( const Meta::TrackPtr & track )
{
DEBUG_BLOCK
Meta::MediaDeviceTrackPtr devicetrack = Meta::MediaDeviceTrackPtr::staticCast( track );
// Remove the track struct from the db, references to it
m_wc->removeTrackFromDB( devicetrack );
// delete the struct associated with this track
m_wc->libDeleteTrack( devicetrack );
// Inform subclass that a track has been removed from
m_wc->setDatabaseChanged();
// remove from memory collection
removeMediaDeviceTrackFromCollection( devicetrack );
emit incrementProgress();
m_numTracksToRemove--;
if( m_numTracksToRemove == 0 )
{
/*
if( m_tracksFailed.size() > 0 )
{
Amarok::Components::logger()->shortMessage(
i18n( "%1 tracks failed to copy to the device", m_tracksFailed.size() ) );
}
*/
debug() << "Done removing tracks";
m_isDeleting = false;
emit removeTracksDone();
}
}
void
MediaDeviceHandler::slotDatabaseWritten( bool success )
{
DEBUG_BLOCK
Q_UNUSED( success )
emit endProgressOperation( this );
m_memColl->collectionUpdated();
}
void
MediaDeviceHandler::setupArtistMap( Meta::MediaDeviceTrackPtr track, ArtistMap &artistMap )
{
const QString artist( m_rc->libGetArtist( track ) );
MediaDeviceArtistPtr artistPtr;
if( artistMap.contains( artist ) )
artistPtr = MediaDeviceArtistPtr::staticCast( artistMap.value( artist ) );
else
{
artistPtr = MediaDeviceArtistPtr( new MediaDeviceArtist( artist ) );
artistMap.insert( artist, ArtistPtr::staticCast( artistPtr ) );
}
artistPtr->addTrack( track );
track->setArtist( artistPtr );
}
void
MediaDeviceHandler::setupAlbumMap( Meta::MediaDeviceTrackPtr track, AlbumMap& albumMap, ArtistMap &artistMap )
{
const QString album( m_rc->libGetAlbum( track ) );
QString albumArtist( m_rc->libGetAlbumArtist( track ) );
if( albumArtist.compare( "Various Artists", Qt::CaseInsensitive ) == 0 ||
albumArtist.compare( i18n( "Various Artists" ), Qt::CaseInsensitive ) == 0 )
{
albumArtist.clear();
}
MediaDeviceAlbumPtr albumPtr;
if ( albumMap.contains( album, albumArtist ) )
albumPtr = MediaDeviceAlbumPtr::staticCast( albumMap.value( album, albumArtist ) );
else
{
MediaDeviceArtistPtr albumArtistPtr;
if( artistMap.contains( albumArtist ) )
albumArtistPtr = MediaDeviceArtistPtr::staticCast( artistMap.value( albumArtist ) );
else if( !albumArtist.isEmpty() )
{
albumArtistPtr = MediaDeviceArtistPtr( new MediaDeviceArtist( albumArtist ) );
artistMap.insert( albumArtist, ArtistPtr::staticCast( albumArtistPtr ) );
}
albumPtr = MediaDeviceAlbumPtr( new MediaDeviceAlbum( m_memColl, album ) );
albumPtr->setAlbumArtist( albumArtistPtr ); // needs to be before albumMap.insert()
albumMap.insert( AlbumPtr::staticCast( albumPtr ) );
}
albumPtr->addTrack( track );
track->setAlbum( albumPtr );
bool isCompilation = albumPtr->isCompilation();
/* if at least one track from album identifies itself as a part of compilation, mark
* whole album as such: (we should be deterministic wrt track adding order) */
isCompilation |= m_rc->libIsCompilation( track );
albumPtr->setIsCompilation( isCompilation );
if( albumArtist.isEmpty() )
{
// set compilation flag, otherwise the album would be invisible in collection
// browser if "Album Artist / Album" view is selected.
albumPtr->setIsCompilation( true );
}
}
void
MediaDeviceHandler::setupGenreMap( Meta::MediaDeviceTrackPtr track, GenreMap& genreMap )
{
const QString genre = m_rc->libGetGenre( track );
MediaDeviceGenrePtr genrePtr;
if ( genreMap.contains( genre ) )
genrePtr = MediaDeviceGenrePtr::staticCast( genreMap.value( genre ) );
else
{
genrePtr = MediaDeviceGenrePtr( new MediaDeviceGenre( genre ) );
genreMap.insert( genre, GenrePtr::staticCast( genrePtr ) );
}
genrePtr->addTrack( track );
track->setGenre( genrePtr );
}
void
MediaDeviceHandler::setupComposerMap( Meta::MediaDeviceTrackPtr track, ComposerMap& composerMap )
{
QString composer ( m_rc->libGetComposer( track ) );
MediaDeviceComposerPtr composerPtr;
if ( composerMap.contains( composer ) )
composerPtr = MediaDeviceComposerPtr::staticCast( composerMap.value( composer ) );
else
{
composerPtr = MediaDeviceComposerPtr( new MediaDeviceComposer( composer ) );
composerMap.insert( composer, ComposerPtr::staticCast( composerPtr ) );
}
composerPtr->addTrack( track );
track->setComposer( composerPtr );
}
void
MediaDeviceHandler::setupYearMap( Meta::MediaDeviceTrackPtr track, YearMap& yearMap )
{
int year = m_rc->libGetYear( track );
MediaDeviceYearPtr yearPtr;
if ( yearMap.contains( year ) )
yearPtr = MediaDeviceYearPtr::staticCast( yearMap.value( year ) );
else
{
yearPtr = MediaDeviceYearPtr( new MediaDeviceYear( QString::number(year) ) );
yearMap.insert( year, YearPtr::staticCast( yearPtr ) );
}
yearPtr->addTrack( track );
track->setYear( yearPtr );
}
bool
MediaDeviceHandler::privateParseTracks()
{
DEBUG_BLOCK
if( !setupReadCapability() )
return false;
TrackMap trackMap;
ArtistMap artistMap;
AlbumMap albumMap;
GenreMap genreMap;
ComposerMap composerMap;
YearMap yearMap;
/* iterate through tracklist and add to appropriate map */
for( m_rc->prepareToParseTracks(); !m_rc->isEndOfParseTracksList(); m_rc->prepareToParseNextTrack() )
{
/// Fetch next track to parse
m_rc->nextTrackToParse();
// FIXME: should we return true or false?
if (!m_memColl)
return true;
MediaDeviceTrackPtr track( new MediaDeviceTrack( m_memColl ) );
m_rc->setAssociateTrack( track );
getBasicMediaDeviceTrackInfo( track, track );
/* map-related info retrieval */
setupArtistMap( track, artistMap );
setupAlbumMap( track, albumMap, artistMap );
setupGenreMap( track, genreMap );
setupComposerMap( track, composerMap );
setupYearMap( track, yearMap );
/* TrackMap stuff to be subordinated later */
trackMap.insert( track->uidUrl(), TrackPtr::staticCast( track ) );
// TODO: setup titlemap for copy/deleting
m_titlemap.insert( track->name(), TrackPtr::staticCast( track ) );
// TODO: abstract playlist parsing
// Subscribe to Track for metadata updates
subscribeTo( Meta::TrackPtr::staticCast( track ) );
}
if( !m_pc && this->hasCapabilityInterface( Handler::Capability::Playlist ) )
m_pc = this->create<Handler::PlaylistCapability>();
if( m_pc )
{
// Register the playlist provider with the playlistmanager
// register a playlist provider for this type of device
m_provider = new Playlists::MediaDeviceUserPlaylistProvider( m_memColl );
// Begin parsing the playlists
Playlists::MediaDevicePlaylistList playlists;
for( m_pc->prepareToParsePlaylists(); !m_pc->isEndOfParsePlaylistsList(); m_pc->prepareToParseNextPlaylist() )
{
m_pc->nextPlaylistToParse();
if( m_pc->shouldNotParseNextPlaylist() )
continue;
// Create a new track list
Meta::TrackList tracklist;
for ( m_pc->prepareToParsePlaylistTracks(); !(m_pc->isEndOfParsePlaylist()); m_pc->prepareToParseNextPlaylistTrack() )
{
m_pc->nextPlaylistTrackToParse();
// Grab the track associated with the next struct
Meta::TrackPtr track = Meta::TrackPtr::staticCast( m_pc->libGetTrackPtrForTrackStruct() );
// if successful, add it into the list at the end.
// it is assumed that the list has some presorted order
// and this is left to the library
if ( track )
tracklist << track;
}
// Make a playlist out of this tracklist
Playlists::MediaDevicePlaylistPtr playlist( new Playlists::MediaDevicePlaylist( m_pc->libGetPlaylistName(), tracklist ) );
m_pc->setAssociatePlaylist( playlist );
// Insert the new playlist into the list of playlists
//playlists << playlist;
// Inform the provider of the new playlist
m_provider->addMediaDevicePlaylist( playlist );
}
// When the provider saves a playlist, the handler should save it internally
- connect( m_provider, SIGNAL(playlistSaved(Playlists::MediaDevicePlaylistPtr,QString)),
- SLOT(savePlaylist(Playlists::MediaDevicePlaylistPtr,QString)) );
- connect( m_provider, SIGNAL(playlistRenamed(Playlists::MediaDevicePlaylistPtr)),
- SLOT(renamePlaylist(Playlists::MediaDevicePlaylistPtr)) );
- connect( m_provider, SIGNAL(playlistsDeleted(Playlists::MediaDevicePlaylistList)),
- SLOT(deletePlaylists(Playlists::MediaDevicePlaylistList)) );
+ connect( m_provider, &Playlists::MediaDeviceUserPlaylistProvider::playlistSaved,
+ this, &MediaDeviceHandler::savePlaylist );
+ connect( m_provider, &Playlists::MediaDeviceUserPlaylistProvider::playlistRenamed,
+ this, &MediaDeviceHandler::renamePlaylist );
+ connect( m_provider, &Playlists::MediaDeviceUserPlaylistProvider::playlistsDeleted,
+ this, &MediaDeviceHandler::deletePlaylists );
The::playlistManager()->addProvider( m_provider, m_provider->category() );
m_provider->sendUpdated();
}
if( !m_podcastCapability && hasCapabilityInterface( Handler::Capability::Podcast ) )
{
}
// Finally, assign the created maps to the collection
m_memColl->memoryCollection()->acquireWriteLock();
m_memColl->memoryCollection()->setTrackMap( trackMap );
m_memColl->memoryCollection()->setArtistMap( artistMap );
m_memColl->memoryCollection()->setAlbumMap( albumMap );
m_memColl->memoryCollection()->setGenreMap( genreMap );
m_memColl->memoryCollection()->setComposerMap( composerMap );
m_memColl->memoryCollection()->setYearMap( yearMap );
m_memColl->memoryCollection()->releaseLock();
m_memColl->collectionUpdated();
return true;
}
void
MediaDeviceHandler::slotCopyNextTrackFailed( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track )
{
Q_UNUSED( job );
enqueueNextCopyThread();
m_copyFailed = true;
slotCopyTrackFailed( track );
}
void
MediaDeviceHandler::slotCopyNextTrackDone( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track )
{
Q_UNUSED( track )
enqueueNextCopyThread();
if ( job->success() )
slotFinalizeTrackCopy( track );
else
{
m_copyFailed = true;
slotCopyTrackFailed( track );
}
}
void
MediaDeviceHandler::enqueueNextCopyThread()
{
Meta::TrackPtr track;
// If there are more tracks to copy, copy the next one
if( !m_tracksToCopy.isEmpty() )
{
// Pop the track off the front of the list
track = m_tracksToCopy.first();
m_tracksToCopy.removeFirst();
// Copy the track
ThreadWeaver::Queue::instance()->enqueue( (QSharedPointer<ThreadWeaver::Job>(new CopyWorkerThread( track, this )) ) );
}
else
{
// Finish the progress bar
emit incrementProgress();
emit endProgressOperation( this );
// Inform CollectionLocation that copying is done
m_isCopying = false;
emit copyTracksDone( true );
}
}
float
MediaDeviceHandler::freeSpace()
{
if ( setupReadCapability() )
return m_rc->totalCapacity() - m_rc->usedCapacity();
else
return 0.0;
}
float
MediaDeviceHandler::usedcapacity()
{
if ( setupReadCapability() )
return m_rc->usedCapacity();
else
return 0.0;
}
float
MediaDeviceHandler::totalcapacity()
{
if ( setupReadCapability() )
return m_rc->totalCapacity();
else
return 0.0;
}
Playlists::UserPlaylistProvider*
MediaDeviceHandler::provider()
{
DEBUG_BLOCK
return (qobject_cast<Playlists::UserPlaylistProvider *>( m_provider ) );
}
void
MediaDeviceHandler::savePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name )
{
DEBUG_BLOCK
if( !m_pc )
{
if( this->hasCapabilityInterface( Handler::Capability::Playlist ) )
{
m_pc = this->create<Handler::PlaylistCapability>();
if( !m_pc )
{
debug() << "Handler does not have MediaDeviceHandler::PlaylistCapability.";
}
}
}
if( m_pc )
{
m_pc->savePlaylist( playlist, name );
writeDatabase();
}
}
void
MediaDeviceHandler::renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist )
{
DEBUG_BLOCK
if( !m_pc )
{
if( this->hasCapabilityInterface( Handler::Capability::Playlist ) )
{
m_pc = this->create<Handler::PlaylistCapability>();
if( !m_pc )
{
debug() << "Handler does not have MediaDeviceHandler::PlaylistCapability.";
}
}
}
if( m_pc )
{
debug() << "Renaming playlist";
m_pc->renamePlaylist( playlist );
writeDatabase();
}
}
void
MediaDeviceHandler::deletePlaylists( const Playlists::MediaDevicePlaylistList &playlistlist )
{
DEBUG_BLOCK
if( !m_pc )
{
if( this->hasCapabilityInterface( Handler::Capability::Playlist ) )
{
m_pc = this->create<Handler::PlaylistCapability>();
if( !m_pc )
{
debug() << "Handler does not have MediaDeviceHandler::PlaylistCapability.";
}
}
}
if( m_pc )
{
debug() << "Deleting playlists";
foreach( Playlists::MediaDevicePlaylistPtr playlist, playlistlist )
{
m_pc->deletePlaylist( playlist );
}
writeDatabase();
}
}
bool
MediaDeviceHandler::setupReadCapability()
{
if( m_rc )
return true;
if( !hasCapabilityInterface( Handler::Capability::Readable ) )
return false;
m_rc = create<Handler::ReadCapability>();
return (bool) m_rc;
}
bool
MediaDeviceHandler::setupWriteCapability()
{
if( m_wc )
return true;
if( !hasCapabilityInterface( Handler::Capability::Writable ) )
return false;
m_wc = create<Handler::WriteCapability>();
return (bool) m_wc;
}
/** Observer Methods **/
void
MediaDeviceHandler::metadataChanged( TrackPtr track )
{
DEBUG_BLOCK
Meta::MediaDeviceTrackPtr trackPtr = Meta::MediaDeviceTrackPtr::staticCast( track );
if( !setupWriteCapability() )
return;
setBasicMediaDeviceTrackInfo( track, trackPtr );
m_wc->setDatabaseChanged();
m_wc->updateTrack( trackPtr );
}
void
MediaDeviceHandler::metadataChanged( ArtistPtr artist )
{
Q_UNUSED( artist );
}
void
MediaDeviceHandler::metadataChanged( AlbumPtr album )
{
Q_UNUSED( album );
}
void
MediaDeviceHandler::metadataChanged( GenrePtr genre )
{
Q_UNUSED( genre );
}
void
MediaDeviceHandler::metadataChanged( ComposerPtr composer )
{
Q_UNUSED( composer );
}
void
MediaDeviceHandler::metadataChanged( YearPtr year )
{
Q_UNUSED( year );
}
void
MediaDeviceHandler::parseTracks()
{
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(new ParseWorkerThread( this )) );
}
// ParseWorkerThread
ParseWorkerThread::ParseWorkerThread( MediaDeviceHandler* handler )
: QObject()
, ThreadWeaver::Job()
, m_success( false )
, m_handler( handler )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDoneSuccess(ThreadWeaver::JobPointer)) );
+ connect( this, &ParseWorkerThread::done, this, &ParseWorkerThread::slotDoneSuccess );
}
ParseWorkerThread::~ParseWorkerThread()
{
//nothing to do
}
bool
ParseWorkerThread::success() const
{
return m_success;
}
void
ParseWorkerThread::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
m_success = m_handler->privateParseTracks();
}
void
ParseWorkerThread::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
ParseWorkerThread::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
ParseWorkerThread::slotDoneSuccess( ThreadWeaver::JobPointer )
{
if (m_handler->m_memColl)
m_handler->m_memColl->emitCollectionReady();
}
// CopyWorkerThread
CopyWorkerThread::CopyWorkerThread( const Meta::TrackPtr &track, MediaDeviceHandler* handler )
: QObject()
, ThreadWeaver::Job()
, m_success( false )
, m_track( track )
, m_handler( handler )
{
- //connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), m_handler, SLOT(slotCopyNextTrackToDevice(ThreadWeaver::JobPointer)), Qt::QueuedConnection );
- connect( this, SIGNAL(failed(ThreadWeaver::JobPointer)), this, SLOT(slotDoneFailed(ThreadWeaver::JobPointer)), Qt::QueuedConnection );
- connect( this, SIGNAL(copyTrackFailed(ThreadWeaver::JobPointer,Meta::TrackPtr)), m_handler, SLOT(slotCopyNextTrackFailed(ThreadWeaver::JobPointer,Meta::TrackPtr)) );
- connect( this, SIGNAL(copyTrackDone(ThreadWeaver::JobPointer,Meta::TrackPtr)), m_handler, SLOT(slotCopyNextTrackDone(ThreadWeaver::JobPointer,Meta::TrackPtr)) );
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDoneSuccess(ThreadWeaver::JobPointer)) );
+ //connect( this, &CopyWorkerThread::done, m_handler, &Meta::MediaDeviceHandler::slotCopyNextTrackToDevice, Qt::QueuedConnection );
+ connect( this, &CopyWorkerThread::failed, this, &CopyWorkerThread::slotDoneFailed, Qt::QueuedConnection );
+ connect( this, &CopyWorkerThread::copyTrackFailed, m_handler, &Meta::MediaDeviceHandler::slotCopyNextTrackFailed );
+ connect( this, &CopyWorkerThread::copyTrackDone, m_handler, &Meta::MediaDeviceHandler::slotCopyNextTrackDone );
+ connect( this, &CopyWorkerThread::done, this, &CopyWorkerThread::slotDoneSuccess );
- //connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(deleteLater()) );
+ //connect( this, &CopyWorkerThread::done, this, &CopyWorkerThread::deleteLater );
}
CopyWorkerThread::~CopyWorkerThread()
{
//nothing to do
}
bool
CopyWorkerThread::success() const
{
return m_success;
}
void
CopyWorkerThread::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
m_success = m_handler->privateCopyTrackToDevice( m_track );
}
void
CopyWorkerThread::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
CopyWorkerThread::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
CopyWorkerThread::slotDoneSuccess( ThreadWeaver::JobPointer )
{
emit copyTrackDone( QSharedPointer<ThreadWeaver::Job>(this), m_track );
}
void
CopyWorkerThread::slotDoneFailed( ThreadWeaver::JobPointer )
{
emit copyTrackFailed( QSharedPointer<ThreadWeaver::Job>(this), m_track );
}
diff --git a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h
index 7894eaf937..f1e404dc68 100644
--- a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h
+++ b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h
@@ -1,508 +1,507 @@
/****************************************************************************************
* Copyright (c) 2009 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef MEDIADEVICEHANDLER_H
#define MEDIADEVICEHANDLER_H
#include "core/meta/Observer.h"
#include "core-impl/collections/mediadevicecollection/MediaDeviceMeta.h"
#include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.h"
#include "core-impl/collections/mediadevicecollection/handler/capabilities/PlaylistCapability.h"
#include "core-impl/collections/mediadevicecollection/handler/capabilities/PodcastCapability.h"
#include "core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h"
#include "core-impl/collections/mediadevicecollection/handler/capabilities/WriteCapability.h"
#include "core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h"
#include "core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.h"
#include "core-impl/collections/support/MemoryCollection.h"
#include "core-impl/playlists/providers/user/UserPlaylistProvider.h"
#include <ThreadWeaver/Job>
#include <QAction>
#include <QObject>
#include <QMap>
#include <QMultiMap>
class QString;
class QMutex;
namespace Collections {
class MediaDeviceCollection;
}
namespace Meta
{
typedef QMultiMap<QString, Meta::TrackPtr> TitleMap;
class MEDIADEVICECOLLECTION_EXPORT MetaHandlerCapability
{
public:
virtual ~MetaHandlerCapability() {}
virtual bool hasCapabilityInterface( Handler::Capability::Type type ) const;
virtual Handler::Capability* createCapabilityInterface( Handler::Capability::Type type );
/**
* Retrieves a specialized interface which represents a capability of this
* object.
*
* @returns a pointer to the capability interface if it exists, 0 otherwise
*/
template <class CapIface> CapIface *create()
{
Handler::Capability::Type type = CapIface::capabilityInterfaceType();
Handler::Capability *iface = createCapabilityInterface(type);
return qobject_cast<CapIface *>(iface);
}
/**
* Tests if an object provides a given capability interface.
*
* @returns true if the interface is available, false otherwise
*/
template <class CapIface> bool is() const
{
return hasCapabilityInterface( CapIface::capabilityInterfaceType() );
}
};
/**
The MediaDeviceHandler is the backend where all low-level library calls are made.
It exists to leave a generic API in the other classes, while allowing for low-level
calls to be isolated here.
*/
class MEDIADEVICECOLLECTION_EXPORT MediaDeviceHandler : public QObject, public Meta::MetaHandlerCapability, public Meta::Observer
{
Q_OBJECT
public:
/**
* Destructor
*/
virtual ~MediaDeviceHandler();
// Declare thread as friend class
friend class ParseWorkerThread;
/**
* Begins an attempt to connect to the device, and emits
* attemptConnectionDone when it finishes.
*/
virtual void init() = 0; // collection
/**
* Checks if the handler successfully connected
* to the device.
* @return
* TRUE if the device was successfully connected to
* FALSE if the device was not successfully connected to
*/
bool succeeded() const // collection
{
return m_success;
}
/// Methods provided for CollectionLocation
/**
* Checks if a device can be written to.
* @return
* TRUE if the device can be written to
* FALSE if the device can not be written to
*/
virtual bool isWritable() const = 0;
/** Given a list of tracks, get URLs for device tracks
* of this type of device. If the device needs to
* do some work to get URLs (e.g. copy tracks to a
* temporary location) the overridden method in
* the handler takes care of it, but must emit
* gotCopyableUrls when finished.
* @param tracks The list of tracks for which to fetch urls
*/
virtual void getCopyableUrls( const Meta::TrackList &tracks );
/**
* Fetches the human-readable name of the device.
* This is often called from the Collection since
* a library call is needed to get this name.
* @return A QString with the name
*/
virtual QString prettyName() const = 0;
/**
* Copies a list of tracks to the device.
* @param tracklist The list of tracks to copy.
*/
void copyTrackListToDevice( const Meta::TrackList tracklist );
/**
* Removes a list of tracks from the device.
* @param tracklist The list of tracks to remove.
*/
void removeTrackListFromDevice( const Meta::TrackList &tracks );
/** This function is called just before a track in the playlist is to be played, and gives
* a chance for e.g. MTP to copy the track to a temporary location, set a playable url,
* to emulate the track actually being played off the device
* @param track The track that needs to prepare to be played
*/
virtual void prepareToPlay( Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } // called by @param track
virtual float usedcapacity();
virtual float totalcapacity();
Playlists::UserPlaylistProvider* provider();
// HACK: Used for device-specific actions, such as initialize for iPod
virtual QList<QAction *> collectionActions() { return QList<QAction*> (); }
Q_SIGNALS:
void gotCopyableUrls( const QMap<Meta::TrackPtr, QUrl> &urls );
void databaseWritten( bool succeeded );
void deleteTracksDone();
void incrementProgress();
void endProgressOperation( QObject *owner );
void copyTracksDone( bool success );
void removeTracksDone();
/* File I/O Methods */
public Q_SLOTS:
/**
* Parses the media device's database and creates a Meta::MediaDeviceTrack
* for each track in the database. NOTE: only call once per device.
*/
void parseTracks(); // collection
/**
* Writes to the device's database if it has one, otherwise
* simply calls slotDatabaseWritten to continue the workflow.
*/
virtual void writeDatabase() { slotDatabaseWritten( true ); }
void savePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name );
void renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist );
void deletePlaylists( const Playlists::MediaDevicePlaylistList &playlistlist );
bool privateParseTracks();
void copyNextTrackToDevice();
bool privateCopyTrackToDevice( const Meta::TrackPtr& track );
void removeNextTrackFromDevice();
void privateRemoveTrackFromDevice( const Meta::TrackPtr &track );
+ void slotCopyNextTrackFailed( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track );
+ void slotCopyNextTrackDone( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track );
protected:
/**
* Constructor
* @param parent the Collection whose handler this is
*/
MediaDeviceHandler( QObject *parent );
/**
* Creates a MediaDeviceTrack based on the latest track struct created as a
* result of a copy to the device, and adds it into the collection to reflect
* that it has been copied.
* @param track The track to add to the collection
*/
void addMediaDeviceTrackToCollection( Meta::MediaDeviceTrackPtr &track );
/**
* Removes the @param track from all the collection's maps to reflect that
* it has been removed from the collection
* @param track The track to remove from the collection
*/
void removeMediaDeviceTrackFromCollection( Meta::MediaDeviceTrackPtr &track );
/**
* Uses wrapped libGet methods to fill a track with information from device
* @param track The track from whose associated struct to get the information
* @param destTrack The track that we want to fill with information
*/
void getBasicMediaDeviceTrackInfo( const Meta::MediaDeviceTrackPtr& track, Meta::MediaDeviceTrackPtr destTrack );
/**
* Uses wrapped libSet methods to fill a track struct of the particular library
* with information from a Meta::Track
* @param srcTrack The track that has the source information
* @param destTrack The track whose associated struct we want to fill with information
*/
void setBasicMediaDeviceTrackInfo( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr destTrack );
Collections::MediaDeviceCollection *m_memColl; ///< Associated collection
bool m_success;
bool m_copyingthreadsafe; ///< whether or not the handler's method of copying is threadsafe
TitleMap m_titlemap; ///< Map of track titles to tracks, used to detect duplicates
protected Q_SLOTS:
- void slotCopyNextTrackFailed( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track );
- void slotCopyNextTrackDone( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track );
-
void slotFinalizeTrackCopy( const Meta::TrackPtr & track );
void slotCopyTrackFailed( const Meta::TrackPtr & track );
void slotFinalizeTrackRemove( const Meta::TrackPtr & track );
void slotDatabaseWritten( bool success );
void enqueueNextCopyThread();
void slotDeletingHandler();
private:
/**
* Pulls out meta information (e.g. artist string)
* from track struct, inserts into appropriate map
* (e.g. ArtistMap). Sets track's meta info
* (e.g. artist string) to that extracted from
* track struct's.
* @param track - track being written to
* @param Map - map where meta information is
* associated to appropriate meta pointer
* (e.g. QString artist, ArtistPtr )
*/
void setupArtistMap( Meta::MediaDeviceTrackPtr track, ArtistMap &artistMap );
void setupAlbumMap( Meta::MediaDeviceTrackPtr track, AlbumMap &albumMap, ArtistMap &artistMap );
void setupGenreMap( Meta::MediaDeviceTrackPtr track, GenreMap &genreMap );
void setupComposerMap( Meta::MediaDeviceTrackPtr track, ComposerMap &composerMap );
void setupYearMap( Meta::MediaDeviceTrackPtr track, YearMap &yearMap );
// Misc. Helper Methods
/**
* Tries to create read capability in m_rc
* @return true if m_rc is valid read capability, false otherwise
*/
bool setupReadCapability();
/**
* Tries to create write capability in m_rc
* @return true if m_wc is valid write capability, false otherwise
*/
bool setupWriteCapability();
/**
* @return free space on the device
*/
float freeSpace();
// Observer Methods
/** These methods are called when the metadata of a track has changed. They invoke an MediaDevice DB update */
virtual void metadataChanged( Meta::TrackPtr track );
virtual void metadataChanged( Meta::ArtistPtr artist );
virtual void metadataChanged( Meta::AlbumPtr album );
virtual void metadataChanged( Meta::GenrePtr genre );
virtual void metadataChanged( Meta::ComposerPtr composer );
virtual void metadataChanged( Meta::YearPtr year );
/**
* Handler Variables
*/
Playlists::MediaDeviceUserPlaylistProvider *m_provider; ///< Associated playlist provider
bool m_copyFailed; ///< Indicates whether a copy failed or not
bool m_isCopying;
bool m_isDeleting;
Meta::TrackList m_tracksToCopy; ///< List of tracks left to copy
Meta::TrackList m_tracksCopying; ///< List of tracks currrently copying
Meta::TrackList m_tracksToDelete; ///< List of tracks left to delete
int m_numTracksToCopy; ///< The number of tracks left to copy
int m_numTracksToRemove; ///< The number of tracks left to remove
QMap<Meta::TrackPtr, QString> m_tracksFailed; ///< tracks that failed to copy
QHash<Meta::TrackPtr, Meta::MediaDeviceTrackPtr> m_trackSrcDst; ///< points source to destTracks, for completion of addition to collection
QMutex m_mutex; ///< A make certain operations atomic when threads are at play
// Capability-related variables
Handler::PlaylistCapability *m_pc;
Handler::PodcastCapability *m_podcastCapability;
Handler::ReadCapability *m_rc;
Handler::WriteCapability *m_wc;
};
/**
* The ParseWorkerThread is used to run a full parse of the device's database in
* a separate thread. Once done, it informs the Collection it is done
*/
class ParseWorkerThread : public QObject , public ThreadWeaver::Job
{
Q_OBJECT
public:
/**
* The constructor.
* @param handler The handler
*/
ParseWorkerThread( MediaDeviceHandler* handler);
/**
* The destructor.
*/
virtual ~ParseWorkerThread();
/**
* Sees the success variable, which says whether or not the copy completed
successfully.
* @return Whether or not the copy was successful, i.e. m_success
*/
virtual bool success() const;
Q_SIGNALS:
/** This signal is emitted when this job is being processed by a thread. */
void started(ThreadWeaver::JobPointer);
/** This signal is emitted when the job has been finished (no matter if it succeeded or not). */
void done(ThreadWeaver::JobPointer);
/** This job has failed.
* This signal is emitted when success() returns false after the job is executed. */
void failed(ThreadWeaver::JobPointer);
private Q_SLOTS:
/**
* Is called when the job is done successfully, and simply
* calls Collection's emitCollectionReady()
* @param job The job that was done
*/
void slotDoneSuccess( ThreadWeaver::JobPointer );
protected:
/**
* Reimplemented, simply runs the parse method.
*/
virtual void run(ThreadWeaver::JobPointer self = QSharedPointer<ThreadWeaver::Job>(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE;
void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE;
void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE;
private:
bool m_success; ///< Whether or not the parse was successful
MediaDeviceHandler *m_handler; ///< The handler
};
/**
* The CopyWorkerThread is used to run a copy operation on a single track in a separate thread.
* Copying is generally done one thread at a time so as to not hurt performance, and because
* many copying mechanisms like that of libmtp can only copy one file at a time. Copying
* methods that are not threadsafe should not use CopyWorkerThread, and should set the
* Handler's m_copyingthreadsafe variable to false in the Handler's constructor.
*/
class CopyWorkerThread : public QObject, public ThreadWeaver::Job
{
Q_OBJECT
public:
/**
* The constructor.
* @param track The source track to copy from
* @param handler The handler
*/
CopyWorkerThread( const Meta::TrackPtr &track, MediaDeviceHandler* handler );
/**
* The destructor.
*/
virtual ~CopyWorkerThread();
/**
* Sets the success variable, which says whether or not the copy completed successfully.
* @return Whether or not the copy was successful, i.e. m_success
*/
virtual bool success() const;
Q_SIGNALS:
/**
* Is emitted when the job is done successfully
* @param job The job that was done
* @param track The source track used for the copy
*/
void copyTrackDone( ThreadWeaver::JobPointer, const Meta::TrackPtr& track );
/**
* Is emitted when the job is done and has failed
* @param job The job that was done
* @param track The source track used for the copy
*/
void copyTrackFailed( ThreadWeaver::JobPointer, const Meta::TrackPtr& track );
/** This signal is emitted when this job is being processed by a thread. */
void started(ThreadWeaver::JobPointer);
/** This signal is emitted when the job has been finished (no matter if it succeeded or not). */
void done(ThreadWeaver::JobPointer);
/** This job has failed.
* This signal is emitted when success() returns false after the job is executed. */
void failed(ThreadWeaver::JobPointer);
private Q_SLOTS:
/**
* Is called when the job is done successfully, and simply
* emits copyTrackDone
* @param job The job that was done
*/
void slotDoneSuccess( ThreadWeaver::JobPointer );
/**
* Is called when the job is done and failed, and simply
* emits copyTrackFailed
* @param job The job that was done
*/
void slotDoneFailed( ThreadWeaver::JobPointer );
protected:
/**
* Reimplemented, simply runs the copy track method.
*/
virtual void run(ThreadWeaver::JobPointer self = QSharedPointer<ThreadWeaver::Job>(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE;
void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE;
void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE;
private:
bool m_success; ///< Whether or not the copy was successful
Meta::TrackPtr m_track; ///< The source track to copy from
MediaDeviceHandler *m_handler; ///< The handler
};
}
#endif
diff --git a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.cpp b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.cpp
index 0026b346ce..293a4dce4f 100644
--- a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.cpp
+++ b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.cpp
@@ -1,43 +1,43 @@
/****************************************************************************************
* Copyright (c) 2009 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MediaDeviceHandlerCapability.h"
Handler::Capability::Capability( QObject *handler )
: QObject()
{
if( thread() != handler->thread() ) {
/* we need to be in handler's thread so that we can become children */
moveToThread( handler->thread() );
}
/* moveToThread( hander->thread() ); setParent( handler ); fails on assert in debug
* Qt builds. This is a workaround that is safe as long as this object is only deleted
* using deleteLater() or form the handler's thread */
- connect( this, SIGNAL(signalSetParent(QObject*)), this, SLOT(slotSetParent(QObject*)) );
+ connect( this, &Handler::Capability::signalSetParent, this, &Handler::Capability::slotSetParent );
emit signalSetParent( handler );
}
Handler::Capability::~Capability()
{
// nothing to do
}
void Handler::Capability::slotSetParent( QObject *parent )
{
setParent( parent );
}
diff --git a/src/core-impl/collections/mtpcollection/MtpCollection.cpp b/src/core-impl/collections/mtpcollection/MtpCollection.cpp
index 2b6807388f..636daea9a1 100644
--- a/src/core-impl/collections/mtpcollection/MtpCollection.cpp
+++ b/src/core-impl/collections/mtpcollection/MtpCollection.cpp
@@ -1,86 +1,86 @@
/****************************************************************************************
* Copyright (c) 2008 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MtpCollection"
#include "MtpCollection.h"
#include "MtpConnectionAssistant.h"
#include "MtpDeviceInfo.h"
#include "MediaDeviceInfo.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include <QUrl>
using namespace Collections;
-AMAROK_EXPORT_COLLECTION( MtpCollectionFactory, mtpcollection )
+Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE "amarok_collection-mtpcollection.json" )
MtpCollectionFactory::MtpCollectionFactory( QObject *parent, const QVariantList &args )
: MediaDeviceCollectionFactory<MtpCollection>( parent, args, new MtpConnectionAssistant() )
{
m_info = KPluginInfo( "amarok_collection-mtpcollection.desktop" );
}
MtpCollectionFactory::~MtpCollectionFactory()
{
DEBUG_BLOCK
// nothing to do
}
//MtpCollection
MtpCollection::MtpCollection( MediaDeviceInfo* info )
: MediaDeviceCollection()
{
DEBUG_BLOCK
/** Fetch Info needed to construct MtpCollection */
debug() << "Getting mtp info";
MtpDeviceInfo *mtpinfo = qobject_cast<MtpDeviceInfo *>( info );
debug() << "Getting udi";
m_udi = mtpinfo->udi();
debug() << "constructing handler";
m_handler = new Meta::MtpHandler( this );
//startFullScan();// parse tracks
}
MtpCollection::~MtpCollection()
{
DEBUG_BLOCK
//if( m_handler )
// qobject_cast<Meta::MtpHandler*> ( m_handler )->terminate();
}
QString
MtpCollection::collectionId() const
{
return m_udi;
}
QString
MtpCollection::prettyName() const
{
return m_handler->prettyName();
}
diff --git a/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp b/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp
index e61d0f2b1a..6d3e60ca49 100644
--- a/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp
+++ b/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp
@@ -1,1531 +1,1531 @@
/****************************************************************************************
* Copyright (c) 2006 Andy Kelk <andy@mopoke.co.uk> *
* Copyright (c) 2008 Alejandro Wainzinger <aikawarazuni@gmail.com> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MtpHandler"
#include "MtpHandler.h"
#include "MtpCollection.h"
#include "core/support/Debug.h"
#include "core-impl/meta/file/File.h" // for KIO file handling
#include "core/interfaces/Logger.h"
#include <KIO/Job>
#include <KIO/DeleteJob>
#include "kjob.h"
#include <ThreadWeaver/ThreadWeaver>
#include <QUrl>
#include <QFileInfo>
#include <QString>
#include <QStringList>
#include <QTextStream>
using namespace Meta;
MtpHandler::MtpHandler( Collections::MtpCollection *mc )
: MediaDeviceHandler( mc )
, m_device( 0 )
, m_capacity( 0.0 )
, m_default_parent_folder( 0 )
, m_folders( 0 )
, m_folderStructure()
, m_format()
, m_name()
, m_supportedFiles()
, m_isCanceled( false )
, m_wait( false )
, m_dbChanged( false )
, m_trackcounter( 0 )
, m_copyParentId( 0 )
, m_tempDir( new KTempDir() )
{
DEBUG_BLOCK
m_copyingthreadsafe = true;
m_tempDir->setAutoRemove( true );
// init();
}
MtpHandler::~MtpHandler()
{
// TODO: free used memory
DEBUG_BLOCK
// clear folder structure
if ( m_folders != 0 )
{
LIBMTP_destroy_folder_t( m_folders );
m_folders = 0;
debug() << "Folders destroyed";
}
// Delete temporary files
//delete m_tempDir;
// release device
if ( m_device != 0 )
{
LIBMTP_Release_Device( m_device );
/* possible race condition with statusbar destructor,
will uncomment when fixed */
//Amarok::Components::logger()->longMessage(
// i18n( "The MTP device %1 has been disconnected", prettyName() ), StatusBar::Information );
debug() << "Device released";
}
}
bool
MtpHandler::isWritable() const
{
// TODO: check if read-only
return true;
}
void
MtpHandler::init()
{
mtpFileTypes[LIBMTP_FILETYPE_WAV] = "wav";
mtpFileTypes[LIBMTP_FILETYPE_MP3] = "mp3";
mtpFileTypes[LIBMTP_FILETYPE_WMA] = "wma";
mtpFileTypes[LIBMTP_FILETYPE_OGG] = "ogg";
mtpFileTypes[LIBMTP_FILETYPE_AUDIBLE] = "aa";
mtpFileTypes[LIBMTP_FILETYPE_MP4] = "mp4";
mtpFileTypes[LIBMTP_FILETYPE_UNDEF_AUDIO] = "undef-audio";
mtpFileTypes[LIBMTP_FILETYPE_WMV] = "wmv";
mtpFileTypes[LIBMTP_FILETYPE_AVI] = "avi";
mtpFileTypes[LIBMTP_FILETYPE_MPEG] = "mpg";
mtpFileTypes[LIBMTP_FILETYPE_ASF] = "asf";
mtpFileTypes[LIBMTP_FILETYPE_QT] = "mov";
mtpFileTypes[LIBMTP_FILETYPE_UNDEF_VIDEO] = "undef-video";
mtpFileTypes[LIBMTP_FILETYPE_JPEG] = "jpg";
mtpFileTypes[LIBMTP_FILETYPE_JFIF] = "jfif";
mtpFileTypes[LIBMTP_FILETYPE_TIFF] = "tiff";
mtpFileTypes[LIBMTP_FILETYPE_BMP] = "bmp";
mtpFileTypes[LIBMTP_FILETYPE_GIF] = "gif";
mtpFileTypes[LIBMTP_FILETYPE_PICT] = "pict";
mtpFileTypes[LIBMTP_FILETYPE_PNG] = "png";
mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR1] = "vcs";
mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR2] = "vcs";
mtpFileTypes[LIBMTP_FILETYPE_VCARD2] = "vcf";
mtpFileTypes[LIBMTP_FILETYPE_VCARD3] = "vcf";
mtpFileTypes[LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT] = "wim";
mtpFileTypes[LIBMTP_FILETYPE_WINEXEC] = "exe";
mtpFileTypes[LIBMTP_FILETYPE_TEXT] = "txt";
mtpFileTypes[LIBMTP_FILETYPE_HTML] = "html";
mtpFileTypes[LIBMTP_FILETYPE_AAC] = "aac";
mtpFileTypes[LIBMTP_FILETYPE_FLAC] = "flac";
mtpFileTypes[LIBMTP_FILETYPE_MP2] = "mp3";
mtpFileTypes[LIBMTP_FILETYPE_M4A] = "m4a";
mtpFileTypes[LIBMTP_FILETYPE_DOC] = "doc";
mtpFileTypes[LIBMTP_FILETYPE_XML] = "xml";
mtpFileTypes[LIBMTP_FILETYPE_XLS] = "xls";
mtpFileTypes[LIBMTP_FILETYPE_PPT] = "ppt";
mtpFileTypes[LIBMTP_FILETYPE_MHT] = "mht";
mtpFileTypes[LIBMTP_FILETYPE_JP2] = "jpg";
mtpFileTypes[LIBMTP_FILETYPE_JPX] = "jpx";
mtpFileTypes[LIBMTP_FILETYPE_UNKNOWN] = "unknown";
QString genericError = i18n( "Could not connect to MTP Device" );
m_success = false;
// begin checking connected devices
LIBMTP_raw_device_t * rawdevices;
int numrawdevices;
LIBMTP_error_number_t err;
debug() << "Initializing MTP stuff";
LIBMTP_Init();
// get list of raw devices
debug() << "Getting list of raw devices";
err = LIBMTP_Detect_Raw_Devices( &rawdevices, &numrawdevices );
debug() << "Error is: " << err;
switch ( err )
{
case LIBMTP_ERROR_NO_DEVICE_ATTACHED:
debug() << "No raw devices found.";
m_success = false;
break;
case LIBMTP_ERROR_CONNECTING:
debug() << "Detect: There has been an error connecting.";
m_success = false;
break;
case LIBMTP_ERROR_MEMORY_ALLOCATION:
debug() << "Detect: Encountered a Memory Allocation Error. Exiting";
m_success = false;
break;
case LIBMTP_ERROR_NONE:
m_success = true;
break;
default:
debug() << "Unhandled mtp error";
m_success = false;
break;
}
if ( m_success )
{
debug() << "Got mtp list, connecting to device using thread";
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(new WorkerThread( numrawdevices, rawdevices, this )) );
}
else
{
free( rawdevices );
// emit failed();
}
}
// this function is threaded
bool
MtpHandler::iterateRawDevices( int numrawdevices, LIBMTP_raw_device_t* rawdevices )
{
DEBUG_BLOCK
bool success = false;
LIBMTP_mtpdevice_t *device = 0;
// test raw device for connectability
for ( int i = 0; i < numrawdevices; i++ )
{
debug() << "Opening raw device number: " << ( i + 1 );
device = LIBMTP_Open_Raw_Device( &rawdevices[i] );
if ( device == NULL )
{
debug() << "Unable to open raw device: " << ( i + 1 );
success = false;
continue;
}
// HACK: not checking serial to confirm the right device is in place
// this is not incorrect, and long-term goal is to remove serial number from use altogether
/*
QString mtpSerial = QString::fromUtf8( LIBMTP_Get_Serialnumber( device ) );
if( !mtpSerial.contains(serial) )
{
debug() << "Wrong device, going to next";
debug() << "Expected: " << serial << " but got: " << mtpSerial;
success = false;
LIBMTP_Release_Device( device );
continue;
}
*/
debug() << "Correct device found";
success = true;
break;
}
m_device = device;
if ( m_device == 0 )
{
// TODO: error protection
success = false;
free( rawdevices );
}
//QString serial = QString::fromUtf8( LIBMTP_Get_Serialnumber( m_device ) );
// debug() << "Serial is: " << serial;
return success;
}
void
MtpHandler::getDeviceInfo()
{
DEBUG_BLOCK
// Get information for device
// Get Battery level and print to debug
unsigned char max;
unsigned char curr;
int failed;
failed = LIBMTP_Get_Batterylevel( m_device, &max, &curr );
if ( !failed )
debug() << "Battery at: " << curr << "/" << max;
else
debug() << "Unknown battery level";
if( LIBMTP_Get_Storage( m_device, LIBMTP_STORAGE_SORTBY_NOTSORTED ) != 0 )
{
debug() << "Failed to get storage properties, cannot get capacity";
m_capacity = 0.0;
}
else
{
m_capacity = m_device->storage->MaxCapacity;
}
QString modelname = QString( LIBMTP_Get_Modelname( m_device ) );
// NOTE: on next libmtp bump, may reintroduce owner name
// for now it doesn't work as expected
/*
QString ownername = QString( LIBMTP_Get_Friendlyname( m_device ) );
m_name = modelname;
if(! ownername.isEmpty() )
if( modelname != ownername )
m_name += " (" + ownername + ')';
else
m_name += " (No Owner Name)";
*/
m_name = modelname;
m_default_parent_folder = m_device->default_music_folder;
debug() << "setting default parent : " << m_default_parent_folder;
m_folders = LIBMTP_Get_Folder_List( m_device );
uint16_t *filetypes;
uint16_t filetypes_len;
int ret = LIBMTP_Get_Supported_Filetypes( m_device, &filetypes, &filetypes_len );
if ( ret == 0 )
{
uint16_t i;
for ( i = 0; i < filetypes_len; ++i )
{
debug() << "Device supports: " << mtpFileTypes.value( filetypes[ i ] );
m_supportedFiles << mtpFileTypes.value( filetypes[ i ] );
}
}
// find supported image types (for album art).
if ( m_supportedFiles.indexOf( "jpg" ) )
m_format = "JPEG";
else if ( m_supportedFiles.indexOf( "png" ) )
m_format = "PNG";
else if ( m_supportedFiles.indexOf( "gif" ) )
m_format = "GIF";
free( filetypes );
}
void
MtpHandler::terminate()
{
DEBUG_BLOCK
// clear folder structure
if ( m_folders != 0 )
{
LIBMTP_destroy_folder_t( m_folders );
m_folders = 0;
}
// release device
if ( m_device != 0 )
{
LIBMTP_Release_Device( m_device );
/* possible race condition with statusbar destructor,
will uncomment when fixed
Amarok::Components::logger()->longMessage(
i18n( "The MTP device %1 has been disconnected", prettyName() ),
Amarok::Logger::Information
); */
debug() << "Device released";
}
}
void
MtpHandler::getCopyableUrls( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
QMap<Meta::TrackPtr, QUrl> urls;
QString genericError = i18n( "Could not copy track from device." );
foreach( Meta::TrackPtr trackptr, tracks )
{
Meta::MediaDeviceTrackPtr track = Meta::MediaDeviceTrackPtr::dynamicCast( trackptr );
if( !track )
break;
QString trackFileName = QString::fromUtf8( m_mtpTrackHash.value( track )->filename );
QString filename = m_tempDir->name() + trackFileName;
debug() << "Temp Filename: " << filename;
int ret = getTrackToFile( m_mtpTrackHash.value( track )->item_id, filename );
if ( ret != 0 )
{
debug() << "Get Track failed: " << ret;
/*Amarok::Components::logger()->shortLongMessage(
genericError,
i18n( "Could not copy track from device." ),
StatusBar::Error
);*/
}
else
{
urls.insert( trackptr, QUrl(filename) );
}
}
emit gotCopyableUrls( urls );
}
/**
* Check (and optionally create) the folder structure to put a
* track into. Return the (possibly new) parent folder ID
*/
uint32_t
MtpHandler::checkFolderStructure( const Meta::TrackPtr track, bool create )
{
QString artistName;
Meta::ArtistPtr artist = track->artist();
if ( !artist || artist->prettyName().isEmpty() )
artistName = i18n( "Unknown Artist" );
else
artistName = artist->prettyName();
//FIXME: Port
// if( bundle.compilation() == MetaBundle::CompilationYes )
// artist = i18n( "Various Artists" );
QString albumName;
Meta::AlbumPtr album = track->album();
if ( !album || album->prettyName().isEmpty() )
albumName = i18n( "Unknown Album" );
else
albumName = album->prettyName();
QString genreName;
Meta::GenrePtr genre = track->genre();
if ( !genre || genre->prettyName().isEmpty() )
genreName = i18n( "Unknown Genre" );
else
genreName = genre->prettyName();
uint32_t parent_id = getDefaultParentId();
QStringList folders = m_folderStructure.split( '/' ); // use slash as a dir separator
QString completePath;
for ( QStringList::Iterator it = folders.begin(); it != folders.end(); ++it )
{
if (( *it ).isEmpty() )
continue;
// substitute %a , %b , %g
( *it ).replace( QRegExp( "%a" ), artistName )
.replace( QRegExp( "%b" ), albumName )
.replace( QRegExp( "%g" ), genreName );
// check if it exists
uint32_t check_folder = subfolderNameToID(( *it ).toUtf8(), m_folders, parent_id );
// create if not exists (if requested)
if ( check_folder == 0 )
{
if ( create )
{
check_folder = createFolder(( *it ).toUtf8() , parent_id );
if ( check_folder == 0 )
{
return 0;
}
}
else
{
return 0;
}
}
completePath += ( *it ).toUtf8() + '/';
// set new parent
parent_id = check_folder;
}
debug() << "Folder path : " << completePath;
return parent_id;
}
uint32_t
MtpHandler::getDefaultParentId( void )
{
// Decide which folder to send it to:
// If the device gave us a parent_folder setting, we use it
uint32_t parent_id = 0;
if ( m_default_parent_folder )
{
parent_id = m_default_parent_folder;
}
// Otherwise look for a folder called "Music"
else if ( m_folders != 0 )
{
parent_id = folderNameToID( qstrdup( QString( "Music" ).toUtf8() ), m_folders );
if ( !parent_id )
{
debug() << "Parent folder could not be found. Going to use top level.";
}
}
// Give up and don't set a parent folder, let the device deal with it
else
{
debug() << "No folders found. Going to use top level.";
}
return parent_id;
}
/**
* Recursively search the folder list for a matching one
* and return its ID
*/
uint32_t
MtpHandler::folderNameToID( char *name, LIBMTP_folder_t *folderlist )
{
uint32_t i;
if ( folderlist == 0 )
return 0;
if ( !strcasecmp( name, folderlist->name ) )
return folderlist->folder_id;
if (( i = ( folderNameToID( name, folderlist->child ) ) ) )
return i;
if (( i = ( folderNameToID( name, folderlist->sibling ) ) ) )
return i;
return 0;
}
/**
* Recursively search the folder list for a matching one under the specified
* parent ID and return the child's ID
*/
uint32_t
MtpHandler::subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id )
{
uint32_t i;
if ( folderlist == 0 )
return 0;
if ( !strcasecmp( name, folderlist->name ) && folderlist->parent_id == parent_id )
return folderlist->folder_id;
if (( i = ( subfolderNameToID( name, folderlist->child, parent_id ) ) ) )
return i;
if (( i = ( subfolderNameToID( name, folderlist->sibling, parent_id ) ) ) )
return i;
return 0;
}
/**
* Create a new mtp folder
*/
uint32_t
MtpHandler::createFolder( const char *name, uint32_t parent_id )
{
debug() << "Creating new folder '" << name << "' as a child of " << parent_id;
char *name_copy = qstrdup( name );
// NOTE: api change, 0 refers to default storage_id
uint32_t new_folder_id = LIBMTP_Create_Folder( m_device, name_copy, parent_id, 0 );
delete( name_copy );
debug() << "New folder ID: " << new_folder_id;
if ( new_folder_id == 0 )
{
debug() << "Attempt to create folder '" << name << "' failed.";
return 0;
}
updateFolders();
return new_folder_id;
}
/**
* Update local cache of mtp folders
*/
void
MtpHandler::updateFolders( void )
{
LIBMTP_destroy_folder_t( m_folders );
m_folders = 0;
m_folders = LIBMTP_Get_Folder_List( m_device );
}
#if 0
void
MtpHandler::privateDeleteTrackFromDevice( const Meta::MtpTrackPtr &track )
{
DEBUG_BLOCK
//If nothing is left in a folder, delete the folder
u_int32_t object_id = track->id();
QString genericError = i18n( "Could not delete item" );
debug() << "delete this id : " << object_id;
int status = LIBMTP_Delete_Object( m_device, object_id );
if ( status != 0 )
{
debug() << "delete object failed";
Amarok::Components::logger()->longMessage( i18n( "Delete failed" ),
Amarok::Logger::Error
);
// return false;
}
debug() << "object deleted";
// return true;
m_titlemap.remove( track->name(), Meta::TrackPtr::staticCast( track ) );
}
#endif
int
MtpHandler::getTrackToFile( const uint32_t id, const QString & filename )
{
return LIBMTP_Get_Track_To_File( m_device, id, filename.toUtf8(), 0, 0 );
}
int
MtpHandler::progressCallback( uint64_t const sent, uint64_t const total, void const * const data )
{
DEBUG_BLOCK
Q_UNUSED( sent );
MtpHandler *handler = ( MtpHandler* )( data );
// NOTE: setting max many times wastes cycles,
// but how else to get total outside of callback?
debug() << "Setting max to: " << (( int ) total );
debug() << "Device: " << handler->prettyName();
/*
handler->setBarMaximum(( int ) total );
handler->setBarProgress(( int ) sent );
if ( sent == total )
handler->endBarProgressOperation();
*/
return 0;
}
QString
MtpHandler::prettyName() const
{
return m_name;
}
/// Begin MediaDeviceHandler overrides
void
MtpHandler::findPathToCopy( const Meta::TrackPtr &srcTrack, const Meta::MediaDeviceTrackPtr &destTrack )
{
Q_UNUSED( destTrack );
uint32_t parent_id = 0;
if ( !m_folderStructure.isEmpty() )
{
parent_id = checkFolderStructure( srcTrack, true ); // true means create
if ( parent_id == 0 )
{
debug() << "Could not create new parent (" << m_folderStructure << ")";
/*Amarok::Components::logger()->shortLongMessage(
genericError,
i18n( "Cannot create parent folder. Check your structure." ),
Amarok::Logger::Error
);*/
return;
}
}
else
{
parent_id = getDefaultParentId();
}
debug() << "Parent id : " << parent_id;
m_copyParentId = parent_id;
}
bool
MtpHandler::libCopyTrack( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr &destTrack )
{
DEBUG_BLOCK
findPathToCopy( srcTrack, destTrack );
debug() << "sending...";
debug() << "Playable Url is: " << srcTrack->playableUrl();
debug() << "Sending file with path: " << srcTrack->playableUrl().path().toUtf8();
int ret = LIBMTP_Send_Track_From_File( m_device, qstrdup( srcTrack->playableUrl().path().toUtf8() ), m_mtpTrackHash.value( destTrack ),
0, 0 );
debug() << "sent";
// emit canCopyMoreTracks();
// emit libCopyTrackDone( srcTrack );
return ( ret == 0 );
}
bool
MtpHandler::libDeleteTrackFile( const Meta::MediaDeviceTrackPtr &track )
{
slotFinalizeTrackRemove( Meta::TrackPtr::staticCast( track ) );
return true;
}
void
MtpHandler::libDeleteTrack( const Meta::MediaDeviceTrackPtr &track )
{
DEBUG_BLOCK
LIBMTP_track_struct *mtptrack = m_mtpTrackHash.value( track );
m_mtpTrackHash.remove( track );
quint32 object_id = mtptrack->item_id;
const QString genericError = i18n( "Could not delete item" );
int status = LIBMTP_Delete_Object( m_device, object_id );
removeNextTrackFromDevice();
if( status != 0 )
debug() << "delete object failed";
else
debug() << "object deleted";
}
void
MtpHandler::setDatabaseChanged()
{
m_dbChanged = true;
}
void
MtpHandler::prepareToParseTracks()
{
DEBUG_BLOCK
m_currentTrackList = LIBMTP_Get_Tracklisting_With_Callback( m_device, 0, this );
}
bool
MtpHandler::isEndOfParseTracksList()
{
return m_currentTrackList ? false : true;
}
void
MtpHandler::prepareToParseNextTrack()
{
m_currentTrackList = m_currentTrackList->next;
}
void
MtpHandler::nextTrackToParse()
{
m_currentTrack = m_currentTrackList;
}
/// Playlist Parsing
void
MtpHandler::prepareToParsePlaylists()
{
m_currentPlaylistList = LIBMTP_Get_Playlist_List( m_device );
}
bool
MtpHandler::isEndOfParsePlaylistsList()
{
return (m_currentPlaylistList == 0);
}
void
MtpHandler::prepareToParseNextPlaylist()
{
m_currentPlaylistList = m_currentPlaylistList->next;
}
void
MtpHandler::nextPlaylistToParse()
{
m_currentPlaylist = m_currentPlaylistList;
}
bool
MtpHandler::shouldNotParseNextPlaylist()
{
// NOTE: parse all
return false;
}
void
MtpHandler::prepareToParsePlaylistTracks()
{
m_trackcounter = 0;
}
bool
MtpHandler::isEndOfParsePlaylist()
{
return (m_trackcounter >= m_currentPlaylist->no_tracks);
}
void
MtpHandler::prepareToParseNextPlaylistTrack()
{
m_trackcounter++;
}
void
MtpHandler::nextPlaylistTrackToParse()
{
m_currentTrack = m_idTrackHash.value( m_currentPlaylist->tracks[ m_trackcounter ] );
}
Meta::MediaDeviceTrackPtr
MtpHandler::libGetTrackPtrForTrackStruct()
{
return m_mtpTrackHash.key( m_currentTrack );
}
QString
MtpHandler::libGetPlaylistName()
{
return QString::fromUtf8( m_currentPlaylist->name );
}
void
MtpHandler::setAssociatePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist )
{
m_mtpPlaylisthash[ playlist ] = m_currentPlaylist;
}
void
MtpHandler::libSavePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name )
{
DEBUG_BLOCK
Meta::TrackList tracklist = const_cast<Playlists::MediaDevicePlaylistPtr&> ( playlist )->tracks();
// Make new playlist
LIBMTP_playlist_t *metadata = LIBMTP_new_playlist_t();
metadata->name = qstrdup( name.toUtf8() );
const int trackCount = tracklist.count();
if( trackCount > 0 )
{
uint32_t *tracks = ( uint32_t* )malloc( sizeof( uint32_t ) * trackCount );
uint32_t i = 0;
foreach( Meta::TrackPtr trk, tracklist )
{
if( !trk ) // playlists might contain invalid tracks. see BUG: 297816
continue;
Meta::MediaDeviceTrackPtr track = Meta::MediaDeviceTrackPtr::staticCast( trk );
tracks[i] = m_mtpTrackHash.value( track )->item_id;
}
metadata->tracks = tracks;
metadata->no_tracks = trackCount;
}
else
{
debug() << "no tracks available for playlist " << metadata->name;
metadata->no_tracks = 0;
}
QString genericError = i18n( "Could not save playlist." );
debug() << "creating new playlist : " << metadata->name << endl;
int ret = LIBMTP_Create_New_Playlist( m_device, metadata );
if( ret == 0 )
{
m_mtpPlaylisthash[ playlist ] = metadata;
debug() << "playlist saved : " << metadata->playlist_id << endl;
}
else
debug () << "Could not create new playlist on device.";
}
void
MtpHandler::deletePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist )
{
DEBUG_BLOCK
LIBMTP_playlist_t *pl = m_mtpPlaylisthash.value( playlist );
if( pl )
{
m_mtpPlaylisthash.remove( playlist );
quint32 object_id = pl->playlist_id;
QString genericError = i18n( "Could not delete item" );
debug() << "delete this id : " << object_id;
int status = LIBMTP_Delete_Object( m_device, object_id );
if ( status != 0 )
{
debug() << "delete object failed";
}
else
debug() << "object deleted";
}
}
void
MtpHandler::renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist )
{
DEBUG_BLOCK
LIBMTP_playlist_t *pl = m_mtpPlaylisthash.value( playlist );
if( pl )
{
debug() << "updating playlist : " << pl->name << endl;
int ret = LIBMTP_Update_Playlist( m_device, pl );
if( ret != 0 )
{
debug() << "Could not rename playlist";
}
else
debug() << "Playlist renamed";
}
}
void
MtpHandler::setAssociateTrack( const Meta::MediaDeviceTrackPtr track )
{
m_mtpTrackHash[ track ] = m_currentTrack;
m_idTrackHash[ m_currentTrack->item_id ] = m_currentTrack;
}
QStringList
MtpHandler::supportedFormats()
{
return m_supportedFiles;
}
QString
MtpHandler::libGetTitle( const Meta::MediaDeviceTrackPtr &track )
{
return QString::fromUtf8( m_mtpTrackHash.value( track )->title );
}
QString
MtpHandler::libGetAlbum( const Meta::MediaDeviceTrackPtr &track )
{
return QString::fromUtf8( m_mtpTrackHash.value( track )->album );
}
QString
MtpHandler::libGetArtist( const Meta::MediaDeviceTrackPtr &track )
{
return QString::fromUtf8( m_mtpTrackHash.value( track )->artist );
}
QString
MtpHandler::libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track )
{
//Album artist isn't supported by libmtp ATM.
Q_UNUSED( track )
return QString();
}
QString
MtpHandler::libGetComposer( const Meta::MediaDeviceTrackPtr &track )
{
return QString::fromUtf8( m_mtpTrackHash.value( track )->composer );
}
QString
MtpHandler::libGetGenre( const Meta::MediaDeviceTrackPtr &track )
{
return QString::fromUtf8( m_mtpTrackHash.value( track )->genre );
}
int
MtpHandler::libGetYear( const Meta::MediaDeviceTrackPtr &track )
{
return QString::fromUtf8( m_mtpTrackHash.value( track )->date ).mid( 0, 4 ).toUInt();
}
qint64
MtpHandler::libGetLength( const Meta::MediaDeviceTrackPtr &track )
{
if ( m_mtpTrackHash.value( track )->duration > 0 )
return ( ( m_mtpTrackHash.value( track )->duration ) );
return 0;
}
int
MtpHandler::libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track )
{
return m_mtpTrackHash.value( track )->tracknumber;
}
QString
MtpHandler::libGetComment( const Meta::MediaDeviceTrackPtr &track )
{
// NOTE: defaulting, since not provided
Q_UNUSED( track );
return QString();
}
int
MtpHandler::libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track )
{
Q_UNUSED( track );
// NOTE: defaulting, since not provided
return 1;
}
int
MtpHandler::libGetBitrate( const Meta::MediaDeviceTrackPtr &track )
{
return m_mtpTrackHash.value( track )->bitrate;
}
int
MtpHandler::libGetSamplerate( const Meta::MediaDeviceTrackPtr &track )
{
return m_mtpTrackHash.value( track )->samplerate;
}
qreal
MtpHandler::libGetBpm( const Meta::MediaDeviceTrackPtr &track )
{
Q_UNUSED( track );
// NOTE: defaulting, since not provided
return 0.0;
}
int
MtpHandler::libGetFileSize( const Meta::MediaDeviceTrackPtr &track )
{
return m_mtpTrackHash.value( track )->filesize;
}
int
MtpHandler::libGetPlayCount( const Meta::MediaDeviceTrackPtr &track )
{
return m_mtpTrackHash.value( track )->usecount;
}
QDateTime
MtpHandler::libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track )
{
Q_UNUSED( track );
// NOTE: defaulting, since not provided
return QDateTime();
}
// TODO: implement rating
int
MtpHandler::libGetRating( const Meta::MediaDeviceTrackPtr &track )
{
return ( m_mtpTrackHash.value( track )->rating / 10 );
}
QString
MtpHandler::libGetType( const Meta::MediaDeviceTrackPtr &track )
{
return mtpFileTypes.value( m_mtpTrackHash.value( track )->filetype );
}
QUrl
MtpHandler::libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track )
{
Q_UNUSED( track )
// NOTE: not a real url, using for unique key for qm
return QUrl( QString::number( m_mtpTrackHash.value( track )->item_id, 10 ) );
}
float
MtpHandler::totalCapacity() const
{
DEBUG_BLOCK
return m_capacity;
}
float
MtpHandler::usedCapacity() const
{
DEBUG_BLOCK
if( LIBMTP_Get_Storage( m_device, LIBMTP_STORAGE_SORTBY_NOTSORTED ) != 0 )
{
debug() << "Failed to get storage properties, cannot get capacity";
return 0.0;
}
return ( totalCapacity() - m_device->storage->FreeSpaceInBytes );
}
/// Sets
void
MtpHandler::libSetTitle( Meta::MediaDeviceTrackPtr& track, const QString& title )
{
m_mtpTrackHash.value( track )->title = ( title.isEmpty() ? qstrdup( "" ) : qstrdup( title.toUtf8() ) );
debug() << "Set to: " << m_mtpTrackHash.value( track )->title;
}
void
MtpHandler::libSetAlbum( Meta::MediaDeviceTrackPtr &track, const QString& album )
{
m_mtpTrackHash.value( track )->album = ( album.isEmpty() ? qstrdup( "" ) : qstrdup( album.toUtf8() ) );
debug() << "Set to: " << m_mtpTrackHash.value( track )->album;
}
void
MtpHandler::libSetArtist( Meta::MediaDeviceTrackPtr &track, const QString& artist )
{
m_mtpTrackHash.value( track )->artist = ( artist.isEmpty() ? qstrdup( "" ) : qstrdup( artist.toUtf8() ) );
debug() << "Set to: " << m_mtpTrackHash.value( track )->artist;
}
void
MtpHandler::libSetAlbumArtist( MediaDeviceTrackPtr &track, const QString &albumArtist )
{
//Album artist isn't supported by libmtp ATM.
Q_UNUSED( track )
Q_UNUSED( albumArtist )
}
void
MtpHandler::libSetComposer( Meta::MediaDeviceTrackPtr &track, const QString& composer )
{
m_mtpTrackHash.value( track )->composer = ( composer.isEmpty() ? qstrdup( "" ) : qstrdup( composer.toUtf8() ) );
debug() << "Set to: " << m_mtpTrackHash.value( track )->composer;
}
void
MtpHandler::libSetGenre( Meta::MediaDeviceTrackPtr &track, const QString& genre )
{
m_mtpTrackHash.value( track )->genre = ( genre.isEmpty() ? qstrdup( "" ) : qstrdup( genre.toUtf8() ) );
debug() << "Set to: " << m_mtpTrackHash.value( track )->genre;
}
void
MtpHandler::libSetYear( Meta::MediaDeviceTrackPtr &track, const QString& year )
{
if( year.toInt() > 0 )
{
QString date;
QTextStream( &date ) << year.toInt() << "0101T0000.0";
m_mtpTrackHash.value( track )->date = qstrdup( date.toUtf8() );
}
else
m_mtpTrackHash.value( track )->date = qstrdup( "00010101T0000.0" );
}
void
MtpHandler::libSetLength( Meta::MediaDeviceTrackPtr &track, int length )
{
m_mtpTrackHash.value( track )->duration = ( length > 0 ? length : 0 );
}
void
MtpHandler::libSetTrackNumber( Meta::MediaDeviceTrackPtr &track, int tracknum )
{
m_mtpTrackHash.value( track )->tracknumber = tracknum;
}
void
MtpHandler::libSetComment( Meta::MediaDeviceTrackPtr &track, const QString& comment )
{
// NOTE: defaulting, since not provided
Q_UNUSED( track )
Q_UNUSED( comment )
}
void
MtpHandler::libSetDiscNumber( Meta::MediaDeviceTrackPtr &track, int discnum )
{
// NOTE: defaulting, since not provided
Q_UNUSED( track )
Q_UNUSED( discnum )
}
void
MtpHandler::libSetBitrate( Meta::MediaDeviceTrackPtr &track, int bitrate )
{
m_mtpTrackHash.value( track )->bitrate = bitrate;
}
void
MtpHandler::libSetSamplerate( Meta::MediaDeviceTrackPtr &track, int samplerate )
{
m_mtpTrackHash.value( track )->samplerate = samplerate;
}
void
MtpHandler::libSetBpm( Meta::MediaDeviceTrackPtr &track, qreal bpm )
{
// NOTE: defaulting, since not provided
Q_UNUSED( track )
Q_UNUSED( bpm )
}
void
MtpHandler::libSetFileSize( Meta::MediaDeviceTrackPtr &track, int filesize )
{
m_mtpTrackHash.value( track )->filesize = filesize;
}
void
MtpHandler::libSetPlayCount( Meta::MediaDeviceTrackPtr &track, int playcount )
{
m_mtpTrackHash.value( track )->usecount = playcount;
}
void
MtpHandler::libSetLastPlayed( Meta::MediaDeviceTrackPtr &track, const QDateTime &lastplayed)
{
Q_UNUSED( track )
Q_UNUSED( lastplayed )
}
void
MtpHandler::libSetRating( Meta::MediaDeviceTrackPtr &track, int rating )
{
m_mtpTrackHash.value( track )->rating = ( rating * 10 );
}
void
MtpHandler::libSetType( Meta::MediaDeviceTrackPtr &track, const QString& type )
{
debug() << "filetype : " << type;
if ( type == "mp3" )
{
m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_MP3;
}
else if ( type == "ogg" )
{
m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_OGG;
}
else if ( type == "wma" )
{
m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_WMA;
}
else if ( type == "mp4" )
{
m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_MP4;
}
else
{
// Couldn't recognise an Amarok filetype.
// fallback to checking the extension (e.g. .wma, .ogg, etc)
debug() << "No filetype found by Amarok filetype";
const QString extension = type.toLower();
int libmtp_type = m_supportedFiles.indexOf( extension );
if ( libmtp_type >= 0 )
{
int keyIndex = mtpFileTypes.values().indexOf( extension );
libmtp_type = mtpFileTypes.keys()[keyIndex];
m_mtpTrackHash.value( track )->filetype = ( LIBMTP_filetype_t ) libmtp_type;
debug() << "set filetype to " << libmtp_type << " based on extension of ." << extension;
}
else
{
debug() << "We do not support the extension ." << extension;
/* Amarok::Components::logger()->shortLongMessage(
genericError,
i18n( "Cannot determine a valid file type" ),
Amarok::Logger::Error
);*/
}
}
debug() << "Filetype set to: " << mtpFileTypes.value( m_mtpTrackHash.value( track )->filetype );
}
void
MtpHandler::libSetPlayableUrl( Meta::MediaDeviceTrackPtr &destTrack, const Meta::TrackPtr &srcTrack )
{
if( !srcTrack->playableUrl().fileName().isEmpty() )
m_mtpTrackHash.value( destTrack )->filename = qstrdup( srcTrack->playableUrl().fileName().toUtf8() );
}
void
MtpHandler::libCreateTrack( const Meta::MediaDeviceTrackPtr& track )
{
m_mtpTrackHash[ track ] = LIBMTP_new_track_t();
m_mtpTrackHash.value( track )->item_id = 0;
m_mtpTrackHash.value( track )->parent_id = m_copyParentId;
m_mtpTrackHash.value( track )->storage_id = 0; // default storage id
}
void
MtpHandler::prepareToPlay( Meta::MediaDeviceTrackPtr &track )
{
DEBUG_BLOCK
QUrl url;
if( m_cachedTracks.contains( track ) )
{
debug() << "File is already copied, simply return";
//m_playableUrl = QUrl::fromLocalFile( m_playableUrl );
}
else
{
QString tempPath = setTempFile( track, libGetType( track ) );
track->setPlayableUrl( QUrl(tempPath) );
debug() << "Beginning temporary file copy";
// m_tempfile.open();
bool success = !(getTrackToFile( m_mtpTrackHash.value( track )->item_id , track->playableUrl().path() ) );
debug() << "File transfer complete";
if( success )
{
debug() << "File transfer successful!";
//m_playableUrl = QUrl::fromLocalFile( m_playableUrl );
}
else
{
debug() << "File transfer failed!";
//m_playableUrl = QUrl::fromLocalFile( "" );
m_cachedTracks.remove( track );
}
}
}
QString
MtpHandler::setTempFile( Meta::MediaDeviceTrackPtr &track, const QString &format )
{
m_cachedTracks[ track ] = new KTemporaryFile();
m_cachedTracks.value( track )->setSuffix( ('.' + format) ); // set suffix based on info from libmtp
if (!m_cachedTracks.value( track )->open())
return QString();
QFileInfo tempFileInfo( *(m_cachedTracks.value( track ) ) ); // get info for path
QString tempPath = tempFileInfo.absoluteFilePath(); // path
m_cachedTracks.value( track )->setAutoRemove( true );
return tempPath;
}
void
MtpHandler::slotDeviceMatchSucceeded( ThreadWeaver::JobPointer job )
{
DEBUG_BLOCK
if( !m_memColl ) // try to fix BUG:279966
return;
if ( job->success() )
{
getDeviceInfo();
// debug() << "Device matches serial, emitting succeeded()";
m_memColl->slotAttemptConnectionDone( true );
}
else
m_memColl->slotAttemptConnectionDone( false );
}
void
MtpHandler::slotDeviceMatchFailed( ThreadWeaver::JobPointer job )
{
DEBUG_BLOCK
if( !m_memColl ) // try to fix BUG:279966
return;
debug() << "Running slot device match failed";
disconnect( job, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDeviceMatchSucceeded(ThreadWeaver::JobPointer)) );
m_memColl->slotAttemptConnectionDone( false );
}
void
MtpHandler::updateTrack( Meta::MediaDeviceTrackPtr &track )
{
DEBUG_BLOCK
// pull out track struct to prepare for update
LIBMTP_track_t *mtptrack = m_mtpTrackHash.value( track );
// commence update on device
int failed = LIBMTP_Update_Track_Metadata( m_device, mtptrack );
if ( !failed )
debug() << "Metadata update succeeded!";
else
debug() << "Failed to update metadata";
}
/// Capability-related functions
bool
MtpHandler::hasCapabilityInterface( Handler::Capability::Type type ) const
{
switch( type )
{
case Handler::Capability::Readable:
return true;
case Handler::Capability::Playlist:
return true;
case Handler::Capability::Writable:
return true;
default:
return false;
}
}
Handler::Capability*
MtpHandler::createCapabilityInterface( Handler::Capability::Type type )
{
switch( type )
{
case Handler::Capability::Readable:
return new Handler::MtpReadCapability( this );
case Handler::Capability::Playlist:
return new Handler::MtpPlaylistCapability( this );
case Handler::Capability::Writable:
return new Handler::MtpWriteCapability( this );
default:
return 0;
}
}
WorkerThread::WorkerThread( int numrawdevices, LIBMTP_raw_device_t* rawdevices, MtpHandler* handler )
: QObject()
, ThreadWeaver::Job()
, m_success( false )
, m_numrawdevices( numrawdevices )
, m_rawdevices( rawdevices )
, m_handler( handler )
{
connect( this, SIGNAL(failed(ThreadWeaver::JobPointer)), m_handler, SLOT(slotDeviceMatchFailed(ThreadWeaver::JobPointer)) );
connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), m_handler, SLOT(slotDeviceMatchSucceeded(ThreadWeaver::JobPointer)) );
connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(deleteLater()) );
}
WorkerThread::~WorkerThread()
{
//nothing to do
}
bool
WorkerThread::success() const
{
return m_success;
}
void
WorkerThread::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
m_success = m_handler->iterateRawDevices( m_numrawdevices, m_rawdevices );
}
void
WorkerThread::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
WorkerThread::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
/*
void
MtpHandler::slotCopyNextTrackFailed( ThreadWeaver::Job* job )
{
Q_UNUSED( job );
m_copyFailed = true;
QString error = "Job Failed";
m_tracksFailed.insert( m_lastTrackCopied, error );
copyNextTrackToDevice();
}
void
MtpHandler::slotCopyNextTrackToDevice( ThreadWeaver::Job* job )
{
if ( job->success() )
{
emit incrementProgress();
}
else
{
m_copyFailed = true;
QString error = "MTP copy error";
m_tracksFailed.insert( m_lastTrackCopied, error );
}
copyNextTrackToDevice();
}
CopyWorkerThread::CopyWorkerThread( const Meta::TrackPtr &track, MtpHandler* handler )
: ThreadWeaver::Job()
, m_success( false )
, m_track( track )
, m_handler( handler )
{
connect( this, SIGNAL(failed(ThreadWeaver::Job*)), m_handler, SLOT(slotCopyNextTrackFailed(ThreadWeaver::Job*)) );
connect( this, SIGNAL(done(ThreadWeaver::Job*)), m_handler, SLOT(slotCopyNextTrackToDevice(ThreadWeaver::Job*)) );
- connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(deleteLater()) );
+ connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, &QObject::deleteLater );
}
CopyWorkerThread::~CopyWorkerThread()
{
//nothing to do
}
bool
CopyWorkerThread::success() const
{
return m_success;
}
void
CopyWorkerThread::run()
{
m_success = m_handler->privateCopyTrackToDevice( m_track );
}
*/
diff --git a/src/core-impl/collections/nepomukcollection/NepomukParser.h b/src/core-impl/collections/nepomukcollection/NepomukParser.h
index 038e60eaa9..8d68e441ce 100644
--- a/src/core-impl/collections/nepomukcollection/NepomukParser.h
+++ b/src/core-impl/collections/nepomukcollection/NepomukParser.h
@@ -1,127 +1,120 @@
/****************************************************************************************
* Copyright (c) 2013 Edward Toroshchin <amarok@hades.name>
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_COLLECTION_NEPOMUKPARSER_H
#define AMAROK_COLLECTION_NEPOMUKPARSER_H
#include "core/meta/Meta.h"
#include <Soprano/QueryResultIterator>
#include <QObject>
#include <QSet>
#include <QStringList>
namespace Collections {
class NepomukCollection;
/**
* This is an interface for classes that parse the results of the
* NepomukQueryMaker's Soprano queries.
*
* The parse() method is called by NepomukInquirer with the query result
* iterator.
*
* The implementations of this interface should return the results by emitting
* the appropriate newResultReady() signals.
*
* The instances of this class will be constructed in a non-main thread by the
* NepomukInquirer, so don't do anything nasty in constructors and methods (no
* GUI, no non-thread-safe methods of non-local objects, etc.)
*/
class NepomukParser: public QObject
{
Q_OBJECT
public:
/**
* Construct a NepomukParser.
*
* @param coll must be a valid NepomukCollection
*/
NepomukParser( NepomukCollection *coll );
/**
* Parse all the query results in the given QueryResultIterator and emit
* newResultReady with the constructed objects.
*/
virtual void parse( Soprano::QueryResultIterator& ) = 0;
Q_SIGNALS:
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
- void newResultReady( QStringList );
- void newResultReady( Meta::LabelList );
+ z
protected:
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::TrackList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::ArtistList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::AlbumList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::GenreList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::ComposerList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::YearList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
QStringList &objectList );
bool parseOne( Soprano::QueryResultIterator &queryResult,
Meta::LabelList &objectList );
private:
NepomukCollection *m_collection;
QSet<QUrl> m_seen_uri;
};
template< class MetaObjectList >
class NepomukObjectParser: public NepomukParser
{
public:
NepomukObjectParser( NepomukCollection *coll )
: NepomukParser( coll )
{}
virtual void parse( Soprano::QueryResultIterator &queryResult )
{
MetaObjectList result;
while( queryResult.next() )
parseOne( queryResult, result );
emit newResultReady( result );
}
};
typedef NepomukObjectParser< Meta::TrackList > NepomukTrackParser;
typedef NepomukObjectParser< Meta::ArtistList > NepomukArtistParser;
typedef NepomukObjectParser< Meta::AlbumList > NepomukAlbumParser;
typedef NepomukObjectParser< Meta::GenreList > NepomukGenreParser;
typedef NepomukObjectParser< Meta::ComposerList > NepomukComposerParser;
typedef NepomukObjectParser< Meta::YearList > NepomukYearParser;
typedef NepomukObjectParser< QStringList > NepomukCustomParser;
typedef NepomukObjectParser< Meta::LabelList > NepomukLabelParser;
}
#endif // AMAROK_COLLECTION_NEPOMUKPARSER_H
diff --git a/src/core-impl/collections/nepomukcollection/NepomukQueryMaker.cpp b/src/core-impl/collections/nepomukcollection/NepomukQueryMaker.cpp
index 7566a4a803..54a2f2f9b8 100644
--- a/src/core-impl/collections/nepomukcollection/NepomukQueryMaker.cpp
+++ b/src/core-impl/collections/nepomukcollection/NepomukQueryMaker.cpp
@@ -1,736 +1,736 @@
/****************************************************************************************
* Copyright (c) 2013 Edward Toroshchin <amarok@hades.name>
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "NepomukQueryMaker.h"
#include "NepomukInquirer.h"
#include "NepomukParser.h"
#include "NepomukSelectors.h"
#include "meta/NepomukAlbum.h"
#include "meta/NepomukArtist.h"
#include "meta/NepomukComposer.h"
#include "meta/NepomukTrack.h"
#include "core/support/Debug.h"
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/Job>
#include <QStack>
namespace Collections {
/**
* This class holds private data for a NepomukQueryMaker instance and provides
* some helper routines.
*/
class NepomukQueryMakerPrivate
{
public:
QString info; ///< a text description of the query (for debug purposes)
QueryMaker::QueryType type;
QStringList customSelectors; ///< SPARQL selectors for custom queries @see constructQuery
QString filters; ///< SPARQL filters
bool filterNeedsConjunction; ///< true, if a logical operator (AND or OR) must be added to filters
QString extra; ///< extra SPARQL clauses (ORDER BY, LIMIT, etc.)
QStack<QString> logic; ///< stack of logic operators (AND or OR) @see popLogic and pushLogic
bool distinct; ///< if a "DISTINCT" operator must be present in the SPARQL query
/**
* @return a text representation of SPARQL selectors for the current query
* type
*/
QString constructSelector();
/**
* @return the final SPARQL query
*/
QString constructQuery();
/**
* Convert a single Meta::val* value to a SPARQL selector.
*
* Won't (and shouldn't) work for a mask of Meta::val* values.
*/
QString valueToSelector(qint64 value);
/**
* Convert a ReturnFunction type to a SPARQL function.
*/
QString returnFunctionSelector(QueryMaker::ReturnFunction function, qint64 value);
/**
* Add a SPARQL filter term to the current filter expression along with
* correct logic operator if needed.
*/
void addFilter(QString);
/**
* Add a filter term that does not match anything
*/
void matchNothing();
/**
* Escape a string literal to be included in a SPARQL query
*/
QString escape(QString);
/**
* Convert a matchBegin/matchEnd flag combination to a SPARQL logical string
* function, like this:
*
* - !matchBegin && !matchEnd: CONTAINS( haystack, needle )
* - !matchBegin && matchEnd: STRENDS( haystack, needle )
* - matchBegin && !matchEnd: STRSTARTS( haystack, needle )
* - matchBegin && matchEnd: haystack = needle
*
* The result is returned as a string with "%1" and "%2" placeholders for
* the haystack and the needle respectively.
*/
QString stringOperation(bool matchBegin, bool matchEnd);
/**
* Convert a NumberComparison operator to a SPARQL operator
*/
QString numberOperator(QueryMaker::NumberComparison);
/**
* Begin a new filtering subexpression with the given logical operator.
*
* All filtration terms added by subsequent calls to addFilter() will be
* enclosed in braces and separated by the given operator.
*
* Example:
*
* pushLogic("OR")
* addFilter("a")
* pushLogic("AND")
* addFilter("b")
* addFilter("c")
* popLogic()
* popLogic()
*
* will yield the following filter expression:
*
* (a OR (b AND c))
*/
void pushLogic(QString oper);
/**
* End a filtering subexpression.
*
* @see pushLogic
*/
void popLogic();
NepomukInquirer *inquirer;
};
QString
NepomukQueryMakerPrivate::constructSelector()
{
// These are different SPARQL selectors for different sets of data
// Q macro is a question mark ("track" is a name, "?track" is a selector)
static const QString trackSelector(Q NS_track " " Q NS_trackTitle " " Q NS_trackUrl " "
Q NS_trackType " (concat(str(" Q NS_trackLengthSeconds "), \"000\") AS " Q NS_trackLength ") " // don't ask
"(" Q NS_trackBitrateBPS " / 1000 AS " Q NS_trackBitrate ") "
Q NS_trackNumber " " Q NS_trackBPM " " Q NS_trackComment " "
Q NS_trackSampleRate " " Q NS_trackFileSize " "
Q NS_trackGain " " Q NS_trackPeakGain " "
Q NS_trackModifyDate " " Q NS_trackCreateDate " ");
static const QString artistSelector(Q NS_artist " " Q NS_artistName " ");
static const QString albumSelector(Q NS_album " " Q NS_albumTitle " " Q NS_albumGain " " Q NS_albumPeakGain " ");
static const QString genreSelector(Q NS_genre " ");
static const QString composerSelector(Q NS_composer " " Q NS_composerName " ");
static const QString yearSelector(Q NS_date " (IF( bound(" Q NS_date "), year(" Q NS_date "), 0 ) AS " Q NS_year ") ");
static const QString allSelector( trackSelector
+ artistSelector
+ albumSelector
+ genreSelector
+ composerSelector
+ yearSelector );
switch(type)
{
case QueryMaker::None:
return QString();
case QueryMaker::Track:
return allSelector;
case QueryMaker::Artist:
return artistSelector;
case QueryMaker::Album:
return albumSelector;
case QueryMaker::AlbumArtist:
return artistSelector;
case QueryMaker::Genre:
return genreSelector;
case QueryMaker::Composer:
return composerSelector;
case QueryMaker::Year:
return yearSelector;
case QueryMaker::Custom:
return customSelectors.join(" ");
case QueryMaker::Label:
return QString();
}
warning() << "unknown QueryMaker type " << type;
return QString();
}
QString
NepomukQueryMakerPrivate::constructQuery()
{
// This is the big SPARQL relationship constraint clause that defines all
// the relationships between objects we want to know about. Basically this
// defines the semantics of the SPARQL selectors we're using everywhere
// Q macro is a question mark ("track" is a name, "?track" is a selector)
static const QString queryTemplate(
"SELECT %1 {"
" " Q NS_track " a nfo:Audio ;"
" nie:title " Q NS_trackTitle " ;"
" nie:url " Q NS_trackUrl " ."
" OPTIONAL { " Q NS_track " nmm:performer " Q NS_artist " ."
" " Q NS_artist " nco:fullname " Q NS_artistName " . }"
" OPTIONAL { " Q NS_track " nmm:musicAlbum " Q NS_album " ."
" " Q NS_album " nie:title " Q NS_albumTitle " ."
" OPTIONAL { " Q NS_album " nmm:albumGain " Q NS_albumGain " . }"
" OPTIONAL { " Q NS_album " nmm:albumPeakGain " Q NS_albumPeakGain " . } }"
" OPTIONAL { " Q NS_track " nmm:genre " Q NS_genre " . }"
" OPTIONAL { " Q NS_track " nmm:composer " Q NS_composer " ."
" " Q NS_composer " nco:fullname " Q NS_composerName " . }"
" OPTIONAL { " Q NS_track " nmm:releaseDate " Q NS_year " . }"
" OPTIONAL { " Q NS_track " nfo:codec " Q NS_trackType " . }"
" OPTIONAL { " Q NS_track " nfo:duration " Q NS_trackLengthSeconds " . }"
" OPTIONAL { " Q NS_track " nfo:averageBitrate " Q NS_trackBitrateBPS " . }"
" OPTIONAL { " Q NS_track " nmm:trackNumber " Q NS_trackNumber " . }"
" OPTIONAL { " Q NS_track " nmm:beatsPerMinute " Q NS_trackBPM " . }"
" OPTIONAL { " Q NS_track " nie:comment " Q NS_trackComment " . }"
" OPTIONAL { " Q NS_track " nfo:sampleRate " Q NS_trackSampleRate " . }"
" OPTIONAL { " Q NS_track " nfo:fileSize " Q NS_trackFileSize " . }"
" OPTIONAL { " Q NS_track " nie:contentSize " Q NS_trackFileSize " . }"
" OPTIONAL { " Q NS_track " nmm:trackGain " Q NS_trackGain " . }"
" OPTIONAL { " Q NS_track " nmm:trackPeakGain " Q NS_trackPeakGain " . }"
" OPTIONAL { " Q NS_track " nie:modified " Q NS_trackModifyDate " . }"
" OPTIONAL { " Q NS_track " nie:created " Q NS_trackCreateDate " . }"
" %2 " // a placeholder for filter expression
" }" );
// This is a special query for labels
static const QString labelQueryTemplate(
"SELECT DISTINCT " Q NS_tag " " Q NS_tagLabel " {"
" " Q NS_track " a nfo:Audio ."
" " Q NS_track " nao:hasTag " Q NS_tag " ."
" " Q NS_tag " nao:prefLabel " Q NS_tagLabel " ."
" }" );
if( type == QueryMaker::None )
{
error() << "requested to perform a none-query";
return QString();
}
else if( type == QueryMaker::Label )
{
return labelQueryTemplate;
}
QString filter;
if( !filters.isEmpty() ) filter = QString( "FILTER( %1 )" ).arg( filters );
QString selector( constructSelector() );
if( distinct )
selector = QString("DISTINCT ") + selector;
return queryTemplate.arg(selector).arg(filter) + extra;
}
/**
* Helper class to construct the map in valueToSelector
*/
template< class K, class V >
class ConstMap: public QHash< K, V >
{
public:
inline ConstMap &add( const K &key, const V &val )
{
this->insert( key, val );
return *this;
}
};
QString
NepomukQueryMakerPrivate::valueToSelector(qint64 bitValue)
{
typedef ConstMap< qint64, QString > ValueMap;
static const ValueMap map = ValueMap()
.add( Meta::valUrl, Q NS_trackUrl )
.add( Meta::valTitle, Q NS_trackTitle )
.add( Meta::valArtist, Q NS_artistName )
.add( Meta::valAlbum, Q NS_albumTitle )
.add( Meta::valGenre, Q NS_genre )
.add( Meta::valComposer, Q NS_composerName )
.add( Meta::valYear, "IF( bound(" Q NS_date "), year(" Q NS_date "), 0 )" )
.add( Meta::valComment, Q NS_trackComment )
.add( Meta::valTrackNr, Q NS_trackNumber )
.add( Meta::valDiscNr, Q NS_trackDiscNumber )
.add( Meta::valBpm, Q NS_trackBPM )
.add( Meta::valLength, "(concat(str(" Q NS_trackLengthSeconds "), \"000\"))" ) // I said, don't ask
.add( Meta::valBitrate, "(" Q NS_trackBitrateBPS " / 1000)" )
.add( Meta::valSamplerate, Q NS_trackSampleRate )
.add( Meta::valFilesize, Q NS_trackFileSize )
.add( Meta::valFormat, Q NS_trackType )
.add( Meta::valCreateDate, Q NS_trackCreateDate )
.add( Meta::valScore, Q NS_trackScore )
.add( Meta::valRating, Q NS_trackRating )
.add( Meta::valFirstPlayed, Q NS_trackFirstPlayed )
.add( Meta::valLastPlayed, Q NS_trackLastPlayed )
.add( Meta::valPlaycount, Q NS_trackPlaycount )
.add( Meta::valUniqueId, "STR(" Q NS_track ")" )
.add( Meta::valTrackGain, Q NS_trackGain )
.add( Meta::valTrackGainPeak, Q NS_trackPeakGain )
.add( Meta::valAlbumGain, Q NS_albumGain )
.add( Meta::valAlbumGainPeak, Q NS_albumPeakGain )
.add( Meta::valAlbumArtist, Q NS_albumArtist )
.add( Meta::valLabel, Q NS_label )
.add( Meta::valModified, Q NS_trackModifyDate );
return map.value( bitValue, Q NS__unknownValue ); // this will be just an unset SPARQL selector (e.g. NULL values)
}
QString
NepomukQueryMakerPrivate::returnFunctionSelector(QueryMaker::ReturnFunction function, qint64 value)
{
QString valSelector(valueToSelector(value));
if( valSelector != Q NS__unknownValue )
{
switch(function)
{
case QueryMaker::Count:
return QString("COUNT(DISTINCT %1)").arg(valSelector);
case QueryMaker::Sum:
return QString("SUM(%1)").arg(valSelector);
case QueryMaker::Max:
return QString("MAX(%1)").arg(valSelector);
case QueryMaker::Min:
return QString("MIN(%1)").arg(valSelector);
}
}
return Q NS__unknownFunction;
}
void
NepomukQueryMakerPrivate::addFilter( QString expression )
{
if( filterNeedsConjunction )
filters += logic.top();
filters += expression;
filterNeedsConjunction = true;
}
void
NepomukQueryMakerPrivate::matchNothing()
{
filters = '0';
filterNeedsConjunction = true;
}
QString
NepomukQueryMakerPrivate::escape( QString string )
{
return QString("\"\"\"%1\"\"\"").arg(string); // TODO: use some actual function from Nepomuk API
}
QString
NepomukQueryMakerPrivate::stringOperation(bool matchBegin, bool matchEnd)
{
// We'll convert matchBegin and matchEnd to an index in the following array:
// <result> <matchBegin> <matchEnd>
static const QString map[] = { "CONTAINS( str(%1), %2 )", // 0 0
"STRENDS( str(%1), %2 )", // 0 1
"STRSTARTS( str(%1), %2 )", // 1 0
"str(%1) = %2" }; // 1 1
return map[ !!matchBegin * 2 + !!matchEnd ];
}
QString
NepomukQueryMakerPrivate::numberOperator(QueryMaker::NumberComparison oper)
{
switch( oper )
{
case QueryMaker::GreaterThan:
return ">";
case QueryMaker::LessThan:
return "<";
case QueryMaker::Equals:
return "=";
}
warning() << "unknown number comparison" << oper;
return "="; // couldn't be worse
}
void
NepomukQueryMakerPrivate::pushLogic( QString oper )
{
if( filterNeedsConjunction )
filters += logic.top();
filters += '(';
logic.push( QString(" %1 ").arg( oper ) );
filterNeedsConjunction = false;
}
void
NepomukQueryMakerPrivate::popLogic()
{
filters += ')';
logic.pop();
}
NepomukQueryMaker::NepomukQueryMaker( NepomukCollection *collection )
: d(new NepomukQueryMakerPrivate)
, myCollection( collection )
{
Q_ASSERT( collection );
d->type = QueryType(0);
d->filterNeedsConjunction = false;
d->inquirer = 0;
d->logic.push(" && ");
d->distinct = true;
}
NepomukQueryMaker::~NepomukQueryMaker()
{
delete d;
d = 0;
}
void
NepomukQueryMaker::abortQuery()
{
// TODO
}
void
NepomukQueryMaker::run()
{
DEBUG_BLOCK
debug() << "running the following query" << d->info;
QString query(d->constructQuery());
debug() << "translated into" << query;
std::auto_ptr<NepomukParser> parser;
switch(d->type)
{
case None:
debug() << "QueryMaker requested to run a None-query";
break;
case Track:
parser.reset( new NepomukTrackParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::TrackList)),
SIGNAL(newResultReady(Meta::TrackList)));
break;
case Artist:
parser.reset( new NepomukArtistParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::ArtistList)),
SIGNAL(newResultReady(Meta::ArtistList)));
break;
case Album:
parser.reset( new NepomukAlbumParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::AlbumList)),
SIGNAL(newResultReady(Meta::AlbumList)));
break;
case AlbumArtist:
parser.reset( new NepomukArtistParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::ArtistList)),
SIGNAL(newResultReady(Meta::ArtistList)));
break;
case Genre:
parser.reset( new NepomukGenreParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::GenreList)),
SIGNAL(newResultReady(Meta::GenreList)));
break;
case Composer:
parser.reset( new NepomukComposerParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::ComposerList)),
SIGNAL(newResultReady(Meta::ComposerList)));
break;
case Year:
parser.reset( new NepomukYearParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::YearList)),
SIGNAL(newResultReady(Meta::YearList)));
break;
case Custom:
parser.reset( new NepomukCustomParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(QStringList)),
SIGNAL(newResultReady(QStringList)));
break;
case Label:
parser.reset( new NepomukLabelParser( myCollection ) );
connect(parser.get(), SIGNAL(newResultReady(Meta::LabelList)),
SIGNAL(newResultReady(Meta::LabelList)));
break;
}
if( !parser.get() )
{
emit queryDone();
return;
}
d->inquirer = new NepomukInquirer(query, parser);
- connect(d->inquirer, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(inquirerDone()));
+ connect(d->inquirer, &ThreadWeaver::Job::done, SLOT(inquirerDone()));
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(d->inquirer) );
}
QueryMaker*
NepomukQueryMaker::setQueryType( QueryType type )
{
d->type = type;
d->info += QString("[type %1] ").arg(type);
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::TrackPtr &track )
{
d->info += QString("[match track %1] ").arg(track->prettyName());
if( track )
d->addFilter( QString(Q NS_track " = <%1>").arg(track->uidUrl()) );
else
d->matchNothing();
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour )
{
d->info += QString("[match %1 artist %2] ").arg(behaviour).arg(artist->prettyName());
if( behaviour == TrackArtists || behaviour == AlbumOrTrackArtists )
{
if( artist )
{
const Meta::NepomukArtist *nartist = dynamic_cast<const Meta::NepomukArtist*>( artist.data() );
if( nartist )
d->addFilter( QString("bound(" Q NS_artist ") && " Q NS_artist " = <%1>").arg( nartist->resourceUri().toString() ) );
else
d->addFilter( QString("bound(" Q NS_artistName ") && str(" Q NS_artistName ") = %1").arg( d->escape( artist->name() ) ) );
return this;
}
else
d->addFilter( "!bound(" Q NS_artist ")" );
}
else
{
if( artist )
d->matchNothing(); // TODO add album artist matching behavior
}
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
d->info += QString("[match album %1] ").arg(album? album->prettyName() : "0");
if( album )
{
const Meta::NepomukAlbum *nalbum = dynamic_cast<const Meta::NepomukAlbum*>( album.data() );
if( nalbum )
d->addFilter( QString("bound(" Q NS_album ") && " Q NS_album " = <%1>").arg( nalbum->resourceUri().toString() ) );
else
// TODO: fix album matching once album artists are supported by Nepomuk
d->addFilter( QString("bound(" Q NS_albumTitle ") && str(" Q NS_albumTitle ") = %1").arg( d->escape( album->name() ) ) );
return this;
}
else
d->addFilter( "!bound(" Q NS_album ")" );
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
d->info += QString("[match composer %1] ").arg(composer->prettyName());
if( composer )
{
const Meta::NepomukComposer *ncomposer = dynamic_cast<const Meta::NepomukComposer*>( composer.data() );
if( ncomposer )
d->addFilter( QString("bound(" Q NS_composer ") && " Q NS_composer " = <%1>").arg( ncomposer->resourceUri().toString() ) );
else
d->addFilter( QString("bound(" Q NS_composerName ") && str(" Q NS_composerName ") = %1").arg( d->escape( composer->name() ) ) );
return this;
}
else
d->addFilter( "!bound(" Q NS_composer ")" );
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
d->info += QString("[match genre %1] ").arg(genre->prettyName());
if( genre )
d->addFilter( QString( "bound(" Q NS_genre ") && str(" Q NS_genre ") = %1" ).arg( d->escape( genre->name() ) ) );
else
d->addFilter( "!bound(" Q NS_genre ")" );
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::YearPtr &year )
{
d->info += QString("[match year %1] ").arg(year->prettyName());
if( year->year() )
d->addFilter( QString( "bound(" Q NS_date ") && year(" Q NS_date ") = %1" ).arg( year->year() ) );
else
d->addFilter( "!bound(" Q NS_date ")" );
return this;
}
QueryMaker*
NepomukQueryMaker::addMatch( const Meta::LabelPtr &label )
{
d->info += QString("[match label %1] ").arg(label->prettyName());
if( label )
d->addFilter( QString( "EXISTS { " Q NS_track " nao:hasTag " Q NS_tag " . " Q NS_tag " nao:prefLabel %1 . }" ).arg( label->name() ) );
else
d->addFilter( "NOT EXISTS { " Q NS_track " nao:hasTag " Q NS_tag " . }" );
return this;
}
QueryMaker*
NepomukQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
d->info += QString("[filter %1 %2 begin(%3) end(%4)] ").arg(value).arg(filter).arg(matchBegin).arg(matchEnd);
d->addFilter( d->stringOperation( matchBegin, matchEnd ).arg( d->valueToSelector( value ),
d->escape( filter ) ) );
return this;
}
QueryMaker*
NepomukQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
d->info += QString("[exclude %1 %2 begin(%3) end(%4)] ").arg(value).arg(filter).arg(matchBegin).arg(matchEnd);
d->addFilter( QString("!(%1)").arg( d->stringOperation( matchBegin, matchEnd ).arg( d->valueToSelector( value ),
d->escape( filter ) ) ) );
return this;
}
QueryMaker*
NepomukQueryMaker::addNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
d->info += QString("[filter %1 %2 (%3)] ").arg(value).arg(filter).arg(compare);
d->addFilter( QString("%1 %2 %3").arg( d->valueToSelector( value ) )
.arg( d->numberOperator( compare ) )
.arg( filter ) );
return this;
}
QueryMaker*
NepomukQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
d->info += QString("[exclude %1 %2 (%3)] ").arg(value).arg(filter).arg(compare);
d->addFilter( QString("!( %1 %2 %3 )").arg( d->valueToSelector( value ) )
.arg( d->numberOperator( compare ) )
.arg( filter ) );
return this;
}
QueryMaker*
NepomukQueryMaker::addReturnValue( qint64 value )
{
d->info += QString("[return %1] ").arg(value);
d->customSelectors << d->valueToSelector(value);
return this;
}
QueryMaker*
NepomukQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
d->info += QString("[return %1(%2)] ").arg(function).arg(value);
d->customSelectors << d->returnFunctionSelector(function, value);
d->distinct = false;
return this;
}
QueryMaker*
NepomukQueryMaker::orderBy( qint64 value, bool descending )
{
d->info += QString("[order %1(%2)] ").arg(value).arg(descending);
d->extra += QString(" ORDER BY %1").arg( d->valueToSelector( value ) );
return this;
}
QueryMaker*
NepomukQueryMaker::limitMaxResultSize( int size )
{
d->info += QString("[limit %1] ").arg(size);
d->extra += QString(" LIMIT %1").arg( size );
return this;
}
QueryMaker*
NepomukQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
// TODO
d->info += QString("[album mode %1] ").arg(mode);
return this;
}
QueryMaker*
NepomukQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
// TODO
d->info += QString("[label mode %1] ").arg(mode);
return this;
}
QueryMaker*
NepomukQueryMaker::beginAnd()
{
d->info += QString("(AND ");
d->pushLogic("&&");
return this;
}
QueryMaker*
NepomukQueryMaker::beginOr()
{
d->info += QString("(OR ");
d->pushLogic("||");
return this;
}
QueryMaker*
NepomukQueryMaker::endAndOr()
{
d->info += QString(") ");
d->popLogic();
return this;
}
void
NepomukQueryMaker::inquirerDone()
{
d->inquirer->deleteLater();
emit queryDone();
}
} //namespace Collections
diff --git a/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp b/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp
index a7be0de503..ebc7cf8832 100644
--- a/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp
+++ b/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp
@@ -1,376 +1,376 @@
/****************************************************************************************
* Copyright (c) 2010 Andrew Coder <andrew.coder@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PlaydarCollection"
#include "PlaydarCollection.h"
#include "core/collections/Collection.h"
#include "core-impl/collections/support/MemoryCollection.h"
#include "core-impl/collections/support/MemoryQueryMaker.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/meta/proxy/MetaProxy.h"
#include "PlaydarQueryMaker.h"
#include "support/Controller.h"
#include "support/ProxyResolver.h"
#include "core/support/Debug.h"
#include <KLocalizedString>
+#include <KPluginFactory>
#include <QIcon>
#include <QObject>
#include <QString>
#include <QTimer>
namespace Collections
{
-
- AMAROK_EXPORT_COLLECTION( PlaydarCollectionFactory, playdarcollection )
+ K_PLUGIN_FACTORY_WITH_JSON( playdarcollection, "amarok_collection-playdarcollection.json", registerPlugin<PlaydarCollectionFactory>(); )
PlaydarCollectionFactory::PlaydarCollectionFactory( QObject* parent, const QVariantList &args )
: CollectionFactory( parent, args )
, m_controller( 0 )
, m_collectionIsManaged( false )
{
m_info = KPluginInfo( "amarok_collection-playdarcollection.desktop" );
DEBUG_BLOCK
}
PlaydarCollectionFactory::~PlaydarCollectionFactory()
{
DEBUG_BLOCK
CollectionManager::instance()->removeTrackProvider( m_collection.data() );
delete m_collection.data();
delete m_controller;
}
void
PlaydarCollectionFactory::init()
{
DEBUG_BLOCK
m_controller = new Playdar::Controller( this );
- connect( m_controller, SIGNAL(playdarReady()),
- this, SLOT(playdarReady()) );
- connect( m_controller, SIGNAL(playdarError(Playdar::Controller::ErrorState)),
- this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) );
+ connect( m_controller, &Playdar::Controller::playdarReady,
+ this, &PlaydarCollectionFactory::playdarReady );
+ connect( m_controller, &Playdar::Controller::playdarError,
+ this, &PlaydarCollectionFactory::slotPlaydarError );
checkStatus();
m_collection = new PlaydarCollection;
- connect( m_collection.data(), SIGNAL(remove()), this, SLOT(collectionRemoved()) );
- CollectionManager::instance()->addTrackProvider( m_collection.data() );
+ connect( m_collection, &PlaydarCollection::remove, this, &PlaydarCollectionFactory::collectionRemoved );
+ CollectionManager::instance()->addTrackProvider( m_collection );
m_initialized = true;
}
void
PlaydarCollectionFactory::checkStatus()
{
m_controller->status();
}
void
PlaydarCollectionFactory::playdarReady()
{
DEBUG_BLOCK
if( !m_collection )
{
m_collection = new PlaydarCollection();
- connect( m_collection.data(), SIGNAL(remove()), this, SLOT(collectionRemoved()) );
+ connect( m_collection, &PlaydarCollection::remove, this, &PlaydarCollectionFactory::collectionRemoved );
}
if( !m_collectionIsManaged )
{
m_collectionIsManaged = true;
emit newCollection( m_collection.data() );
}
}
void
PlaydarCollectionFactory::slotPlaydarError( Playdar::Controller::ErrorState error )
{
// DEBUG_BLOCK
if( error == Playdar::Controller::ErrorState( 1 ) )
{
if( m_collection && !m_collectionIsManaged )
CollectionManager::instance()->removeTrackProvider( m_collection.data() );
- QTimer::singleShot( 10 * 60 * 1000, this, SLOT(checkStatus()) );
+ QTimer::singleShot( 10 * 60 * 1000, this, &PlaydarCollectionFactory::checkStatus );
}
}
void
PlaydarCollectionFactory::collectionRemoved()
{
DEBUG_BLOCK
m_collectionIsManaged = false;
- QTimer::singleShot( 10000, this, SLOT(checkStatus()) );
+ QTimer::singleShot( 10000, this, &PlaydarCollectionFactory::checkStatus );
}
PlaydarCollection::PlaydarCollection()
: m_collectionId( i18n( "Playdar Collection" ) )
, m_memoryCollection( new MemoryCollection )
{
DEBUG_BLOCK
}
PlaydarCollection::~PlaydarCollection()
{
DEBUG_BLOCK
}
QueryMaker*
PlaydarCollection::queryMaker()
{
DEBUG_BLOCK
PlaydarQueryMaker *freshQueryMaker = new PlaydarQueryMaker( this );
- connect( freshQueryMaker, SIGNAL(playdarError(Playdar::Controller::ErrorState)),
- this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) );
+ connect( freshQueryMaker, &PlaydarQueryMaker::playdarError,
+ this, &PlaydarCollection::slotPlaydarError );
return freshQueryMaker;
}
Playlists::UserPlaylistProvider*
PlaydarCollection::userPlaylistProvider()
{
DEBUG_BLOCK
return 0;
}
QString
PlaydarCollection::uidUrlProtocol() const
{
return QString( "playdar" );
}
QString
PlaydarCollection::collectionId() const
{
return m_collectionId;
}
QString
PlaydarCollection::prettyName() const
{
return collectionId();
}
QIcon
PlaydarCollection::icon() const
{
return QIcon::fromTheme( "network-server" );
}
bool
PlaydarCollection::isWritable() const
{
DEBUG_BLOCK
return false;
}
bool
PlaydarCollection::isOrganizable() const
{
DEBUG_BLOCK
return false;
}
bool
PlaydarCollection::possiblyContainsTrack( const QUrl &url ) const
{
DEBUG_BLOCK
if( url.scheme() == uidUrlProtocol() &&
url.hasQueryItem( QString( "artist" ) ) &&
url.hasQueryItem( QString( "album" ) ) &&
url.hasQueryItem( QString( "title" ) ) )
return true;
else
return false;
}
Meta::TrackPtr
PlaydarCollection::trackForUrl( const QUrl &url )
{
DEBUG_BLOCK
m_memoryCollection->acquireReadLock();
if( m_memoryCollection->trackMap().contains( url.url() ) )
{
Meta::TrackPtr track = m_memoryCollection->trackMap().value( url.url() );
m_memoryCollection->releaseLock();
return track;
}
else
{
m_memoryCollection->releaseLock();
MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( url ) );
proxyTrack->setArtist( QUrlQuery(url).queryItemValue( "artist" ) );
proxyTrack->setAlbum( QUrlQuery(url).queryItemValue( "album" ) );
proxyTrack->setTitle( QUrlQuery(url).queryItemValue( "title" ) );
Playdar::ProxyResolver *proxyResolver = new Playdar::ProxyResolver( this, url, proxyTrack );
- connect( proxyResolver, SIGNAL(playdarError(Playdar::Controller::ErrorState)),
- this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) );
+ connect( proxyResolver, &Playdar::ProxyResolver::playdarError,
+ this, &PlaydarCollection::slotPlaydarError );
return Meta::TrackPtr::staticCast( proxyTrack );
}
}
bool
PlaydarCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
//TODO: Make this work once capabilities are set.
Q_UNUSED( type );
return false;
}
Capabilities::Capability*
PlaydarCollection::createCapabilityInterface( Capabilities::Capability::Type type )
{
//TODO: Make this work once capabilities are set.
Q_UNUSED( type );
return 0;
}
void
PlaydarCollection::addNewTrack( Meta::PlaydarTrackPtr track )
{
DEBUG_BLOCK
m_memoryCollection->acquireReadLock();
if( !m_memoryCollection->trackMap().contains( track->uidUrl() ) )
{
m_memoryCollection->releaseLock();
m_memoryCollection->acquireWriteLock();
Meta::PlaydarArtistPtr artistPtr;
if( m_memoryCollection->artistMap().contains( track->artist()->name() ) )
{
Meta::ArtistPtr artist = m_memoryCollection->artistMap().value( track->artist()->name() );
artistPtr = Meta::PlaydarArtistPtr::staticCast( artist );
}
else
{
artistPtr = track->playdarArtist();
Meta::ArtistPtr artist = Meta::ArtistPtr::staticCast( artistPtr );
m_memoryCollection->addArtist( artist );
}
artistPtr->addTrack( track );
track->setArtist( artistPtr );
Meta::PlaydarAlbumPtr albumPtr;
if( m_memoryCollection->albumMap().contains( track->album()->name(),
artistPtr->name() ) )
{
Meta::AlbumPtr album = m_memoryCollection->albumMap().value( track->album()->name(),
artistPtr->name() );
albumPtr = Meta::PlaydarAlbumPtr::staticCast( album );
}
else
{
albumPtr = track->playdarAlbum();
albumPtr->setAlbumArtist( artistPtr );
artistPtr->addAlbum( albumPtr );
Meta::AlbumPtr album = Meta::AlbumPtr::staticCast( albumPtr );
m_memoryCollection->addAlbum( album );
}
albumPtr->addTrack( track );
track->setAlbum( albumPtr );
Meta::PlaydarGenrePtr genrePtr;
if( m_memoryCollection->genreMap().contains( track->genre()->name() ) )
{
Meta::GenrePtr genre = m_memoryCollection->genreMap().value( track->genre()->name() );
genrePtr = Meta::PlaydarGenrePtr::staticCast( genre );
}
else
{
genrePtr = track->playdarGenre();
Meta::GenrePtr genre = Meta::GenrePtr::staticCast( genrePtr );
m_memoryCollection->addGenre( genre );
}
genrePtr->addTrack( track );
track->setGenre( genrePtr );
Meta::PlaydarComposerPtr composerPtr;
if( m_memoryCollection->composerMap().contains( track->composer()->name() ) )
{
Meta::ComposerPtr composer = m_memoryCollection->composerMap().value( track->composer()->name() );
composerPtr = Meta::PlaydarComposerPtr::staticCast( composer );
}
else
{
composerPtr = track->playdarComposer();
Meta::ComposerPtr composer = Meta::ComposerPtr::staticCast( composerPtr );
m_memoryCollection->addComposer( composer );
}
composerPtr->addTrack( track );
track->setComposer( composerPtr );
Meta::PlaydarYearPtr yearPtr;
if( m_memoryCollection->yearMap().contains( track->year()->year() ) )
{
Meta::YearPtr year = m_memoryCollection->yearMap().value( track->year()->year() );
yearPtr = Meta::PlaydarYearPtr::staticCast( year );
}
else
{
yearPtr = track->playdarYear();
Meta::YearPtr year = Meta::YearPtr::staticCast( yearPtr );
m_memoryCollection->addYear( year );
}
yearPtr->addTrack( track );
track->setYear( yearPtr );
m_memoryCollection->addTrack( Meta::TrackPtr::staticCast( track ) );
foreach( Meta::PlaydarLabelPtr label, track->playdarLabels() )
{
m_memoryCollection->addLabelToTrack( Meta::LabelPtr::staticCast( label ),
Meta::TrackPtr::staticCast( track ) );
}
m_memoryCollection->releaseLock();
emit updated();
}
else
m_memoryCollection->releaseLock();
}
QSharedPointer< MemoryCollection >
PlaydarCollection::memoryCollection()
{
return m_memoryCollection;
}
void
PlaydarCollection::slotPlaydarError( Playdar::Controller::ErrorState error )
{
if( error == Playdar::Controller::ErrorState( 1 ) )
emit remove();
}
}
diff --git a/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.cpp b/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.cpp
index 8854bf968c..423cadab82 100644
--- a/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.cpp
+++ b/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.cpp
@@ -1,593 +1,593 @@
/****************************************************************************************
* Copyright (c) 2010 Andrew Coder <andrew.coder@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaydarQueryMaker.h"
#include "PlaydarMeta.h"
#include "PlaydarCollection.h"
#include "support/Controller.h"
#include "support/Query.h"
#include "support/QMFunctionTypes.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/MemoryQueryMaker.h"
#include <QObject>
#include <QStack>
namespace Collections
{
PlaydarQueryMaker::PlaydarQueryMaker( PlaydarCollection *collection )
: m_queryType( QueryMaker::QueryType( None ) )
, m_autoDelete( false )
, m_activeQueryCount( 0 )
, m_memoryQueryIsRunning( false )
, m_collectionUpdated( false )
, m_filterMap( )
, m_collection( collection )
, m_controller( new Playdar::Controller() )
{
DEBUG_BLOCK
- m_memoryQueryMaker = new MemoryQueryMaker( m_collection.data()->memoryCollection().toWeakRef(),
- m_collection.data()->collectionId() );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::TrackList)),
- this, SIGNAL(newResultReady(Meta::TrackList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::ArtistList)),
- this, SIGNAL(newResultReady(Meta::ArtistList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::AlbumList)),
- this, SIGNAL(newResultReady(Meta::AlbumList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::GenreList)),
- this, SIGNAL(newResultReady(Meta::GenreList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::ComposerList)),
- this, SIGNAL(newResultReady(Meta::ComposerList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::YearList)),
- this, SIGNAL(newResultReady(Meta::YearList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::DataList)),
- this, SIGNAL(newResultReady(Meta::DataList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(QStringList)),
- this, SIGNAL(newResultReady(QStringList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::LabelList)),
- this, SIGNAL(newResultReady(Meta::LabelList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(queryDone()),
- this, SLOT(memoryQueryDone()) );
- m_memoryQueryMaker.data()->setAutoDelete( true );
+ m_memoryQueryMaker = new MemoryQueryMaker( m_collection->memoryCollection().toWeakRef(),
+ m_collection->collectionId() );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newTracksReady,
+ this, &PlaydarQueryMaker::newTracksReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newArtistsReady,
+ this, &PlaydarQueryMaker::newArtistsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newAlbumsReady,
+ this, &PlaydarQueryMaker::newAlbumsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newGenresReady,
+ this, &PlaydarQueryMaker::newGenresReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newComposersReady,
+ this, &PlaydarQueryMaker::newComposersReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newYearsReady,
+ this, &PlaydarQueryMaker::newYearsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newDataReady,
+ this, &PlaydarQueryMaker::newDataReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newLabelsReady,
+ this, &PlaydarQueryMaker::newLabelsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newResultReady,
+ this, &PlaydarQueryMaker::newResultReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::queryDone,
+ this, &PlaydarQueryMaker::memoryQueryDone );
+ m_memoryQueryMaker->setAutoDelete( true );
}
PlaydarQueryMaker::~PlaydarQueryMaker()
{
DEBUG_BLOCK
if( !m_queryMakerFunctions.isEmpty() )
{
qDeleteAll( m_queryMakerFunctions.begin(), m_queryMakerFunctions.end() );
m_queryMakerFunctions.clear();
}
delete m_memoryQueryMaker.data();
}
void
PlaydarQueryMaker::run()
{
DEBUG_BLOCK
if( !m_filterMap.isEmpty() )
{
- connect( m_controller.data(), SIGNAL(playdarError(Playdar::Controller::ErrorState)),
- this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) );
- connect( m_controller.data(), SIGNAL(queryReady(Playdar::Query*)),
- this, SLOT(collectQuery(Playdar::Query*)) );
+ connect( m_controller, &Playdar::Controller::playdarError,
+ this, &PlaydarQueryMaker::slotPlaydarError );
+ connect( m_controller, &Playdar::Controller::queryReady,
+ this, &PlaydarQueryMaker::collectQuery );
QString artist( "" );
QString album( "" );
QString title( "" );
if( m_filterMap.contains( Meta::valArtist ) )
artist.append( m_filterMap.value( Meta::valArtist ) );
if( m_filterMap.contains( Meta::valAlbum ) )
album.append( m_filterMap.value( Meta::valAlbum ) );
if( m_filterMap.contains( Meta::valTitle ) )
title.append( m_filterMap.value( Meta::valTitle ) );
if( !artist.isEmpty() && !title.isEmpty() )
{
m_activeQueryCount++;
m_controller.data()->resolve( artist, album, title );
}
}
m_activeQueryCount++;
m_memoryQueryIsRunning = true;
m_memoryQueryMaker.data()->run();
}
void
PlaydarQueryMaker::abortQuery()
{
DEBUG_BLOCK
m_memoryQueryMaker.data()->abortQuery();
m_controller.data()->disconnect( this );
}
QueryMaker*
PlaydarQueryMaker::setQueryType( QueryType type )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< QueryType >::FunPtr funPtr = &QueryMaker::setQueryType;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< QueryType >( funPtr, type );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
m_queryType = type;
return this;
}
QueryMaker*
PlaydarQueryMaker::addReturnValue( qint64 value )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< qint64 >::FunPtr funPtr = &QueryMaker::addReturnValue;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< qint64 >( funPtr, value );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
DEBUG_BLOCK
CurriedBinaryQMFunction< ReturnFunction, qint64 >::FunPtr funPtr = &QueryMaker::addReturnFunction;
CurriedQMFunction *curriedFun = new CurriedBinaryQMFunction< ReturnFunction, qint64 >( funPtr, function, value );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::orderBy( qint64 value, bool descending )
{
DEBUG_BLOCK
CurriedBinaryQMFunction< qint64, bool >::FunPtr funPtr = &QueryMaker::orderBy;
CurriedQMFunction *curriedFun = new CurriedBinaryQMFunction< qint64, bool >( funPtr, value, descending );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::TrackPtr &track )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< const Meta::TrackPtr& >::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< const Meta::TrackPtr& >( funPtr, track );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour )
{
DEBUG_BLOCK
CurriedBinaryQMFunction<const Meta::ArtistPtr &, ArtistMatchBehaviour>::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedBinaryQMFunction<const Meta::ArtistPtr &, ArtistMatchBehaviour>( funPtr, artist, behaviour );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
if( artist )
m_filterMap.insert( Meta::valArtist, artist->name() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< const Meta::AlbumPtr& >::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< const Meta::AlbumPtr& >( funPtr, album );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
if( album )
m_filterMap.insert( Meta::valAlbum, album->name() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< const Meta::ComposerPtr& >::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< const Meta::ComposerPtr& >( funPtr, composer );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< const Meta::GenrePtr& >::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< const Meta::GenrePtr& >( funPtr, genre );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::YearPtr &year )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< const Meta::YearPtr& >::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< const Meta::YearPtr& >( funPtr, year );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addMatch( const Meta::LabelPtr &label )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< const Meta::LabelPtr& >::FunPtr funPtr = &QueryMaker::addMatch;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< const Meta::LabelPtr& >( funPtr, label );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
DEBUG_BLOCK
CurriedQMStringFilterFunction::FunPtr funPtr = &QueryMaker::addFilter;
CurriedQMFunction *curriedFun =
new CurriedQMStringFilterFunction( funPtr, value, filter, matchBegin, matchEnd );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
if( !m_filterMap.isEmpty() && m_filterMap.contains( value ) )
{
QString newFilter = m_filterMap.value( value );
newFilter.append( QString( " " ) ).append( filter );
m_filterMap.insert( value, newFilter );
}
else
m_filterMap.insert( value, filter );
return this;
}
QueryMaker*
PlaydarQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
DEBUG_BLOCK
CurriedQMStringFilterFunction::FunPtr funPtr = &QueryMaker::excludeFilter;
CurriedQMFunction *curriedFun =
new CurriedQMStringFilterFunction( funPtr, value, filter, matchBegin, matchEnd );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
if( m_filterMap.contains( value ) && m_filterMap.value( value ).contains( filter ) )
{
QString localFilter = m_filterMap.value( value );
localFilter.remove( filter );
m_filterMap.insert( value, localFilter );
}
return this;
}
QueryMaker*
PlaydarQueryMaker::addNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
DEBUG_BLOCK
CurriedTrinaryQMFunction< qint64, qint64, NumberComparison >::FunPtr funPtr = &QueryMaker::addNumberFilter;
CurriedQMFunction *curriedFun =
new CurriedTrinaryQMFunction< qint64, qint64, NumberComparison >
(
funPtr, value, filter, compare
);
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
DEBUG_BLOCK
CurriedTrinaryQMFunction< qint64, qint64, NumberComparison >::FunPtr funPtr = &QueryMaker::excludeNumberFilter;
CurriedQMFunction *curriedFun =
new CurriedTrinaryQMFunction< qint64, qint64, NumberComparison >
(
funPtr, value, filter, compare
);
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::limitMaxResultSize( int size )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< int >::FunPtr funPtr = &QueryMaker::limitMaxResultSize;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< int >( funPtr, size );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< AlbumQueryMode >::FunPtr funPtr = &QueryMaker::setAlbumQueryMode;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< AlbumQueryMode >( funPtr, mode );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
DEBUG_BLOCK
CurriedUnaryQMFunction< LabelQueryMode >::FunPtr funPtr = &QueryMaker::setLabelQueryMode;
CurriedQMFunction *curriedFun = new CurriedUnaryQMFunction< LabelQueryMode >( funPtr, mode );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::beginAnd()
{
DEBUG_BLOCK
CurriedZeroArityQMFunction::FunPtr funPtr = &QueryMaker::beginAnd;
CurriedQMFunction *curriedFun = new CurriedZeroArityQMFunction( funPtr );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::beginOr()
{
DEBUG_BLOCK
CurriedZeroArityQMFunction::FunPtr funPtr = &QueryMaker::beginOr;
CurriedQMFunction *curriedFun = new CurriedZeroArityQMFunction( funPtr );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::endAndOr()
{
DEBUG_BLOCK
CurriedZeroArityQMFunction::FunPtr funPtr = &QueryMaker::endAndOr;
CurriedQMFunction *curriedFun = new CurriedZeroArityQMFunction( funPtr );
m_queryMakerFunctions.append( curriedFun );
(*curriedFun)( m_memoryQueryMaker.data() );
return this;
}
QueryMaker*
PlaydarQueryMaker::setAutoDelete( bool autoDelete )
{
DEBUG_BLOCK
m_autoDelete = autoDelete;
return this;
}
int
PlaydarQueryMaker::validFilterMask()
{
DEBUG_BLOCK
return QueryMaker::ValidFilters( ArtistFilter ) |
QueryMaker::ValidFilters( AlbumFilter ) |
QueryMaker::ValidFilters( TitleFilter ) |
m_memoryQueryMaker.data()->validFilterMask();
}
void
PlaydarQueryMaker::slotPlaydarError( Playdar::Controller::ErrorState error )
{
DEBUG_BLOCK
emit playdarError( error );
}
void
PlaydarQueryMaker::collectQuery( Playdar::Query* query )
{
DEBUG_BLOCK
- connect( query, SIGNAL(newTrackAdded(Meta::PlaydarTrackPtr)),
- this, SLOT(collectResult(Meta::PlaydarTrackPtr)) );
- connect( query, SIGNAL(queryDone(Playdar::Query*,Meta::PlaydarTrackList)),
- this, SLOT(aQueryEnded(Playdar::Query*,Meta::PlaydarTrackList)) );
+ connect( query, &Playdar::Query::newTrackAdded,
+ this, &PlaydarQueryMaker::collectResult );
+ connect( query, &Playdar::Query::queryDone,
+ this, &PlaydarQueryMaker::aQueryEnded );
}
void
PlaydarQueryMaker::collectResult( Meta::PlaydarTrackPtr track )
{
DEBUG_BLOCK
track->addToCollection( m_collection.data() );
if( m_collection.data()->trackForUrl( QUrl(track->uidUrl()) ) == Meta::TrackPtr::staticCast( track ) )
m_collectionUpdated = true;
}
void
PlaydarQueryMaker::aQueryEnded( Playdar::Query *query, const Meta::PlaydarTrackList &trackList )
{
DEBUG_BLOCK
Q_UNUSED( query );
Q_UNUSED( trackList );
m_activeQueryCount--;
if( m_activeQueryCount <= 0 )
{
if( m_collectionUpdated && !m_memoryQueryIsRunning )
{
m_collectionUpdated = false;
runMemoryQueryAgain();
}
else
{
emit queryDone();
if( m_autoDelete )
deleteLater();
}
}
}
void
PlaydarQueryMaker::memoryQueryDone()
{
DEBUG_BLOCK
m_memoryQueryIsRunning = false;
m_activeQueryCount--;
if( m_activeQueryCount <= 0 )
{
emit queryDone();
if( m_autoDelete )
deleteLater();
}
}
void
PlaydarQueryMaker::runMemoryQueryAgain()
{
DEBUG_BLOCK
if( m_memoryQueryMaker.data() )
return;
- m_memoryQueryMaker = new MemoryQueryMaker( m_collection.data()->memoryCollection().toWeakRef(),
- m_collection.data()->collectionId() );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::TrackList)),
- this, SIGNAL(newResultReady(Meta::TrackList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::ArtistList)),
- this, SIGNAL(newResultReady(Meta::ArtistList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::AlbumList)),
- this, SIGNAL(newResultReady(Meta::AlbumList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::GenreList)),
- this, SIGNAL(newResultReady(Meta::GenreList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::ComposerList)),
- this, SIGNAL(newResultReady(Meta::ComposerList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::YearList)),
- this, SIGNAL(newResultReady(Meta::YearList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::DataList)),
- this, SIGNAL(newResultReady(Meta::DataList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(QStringList)),
- this, SIGNAL(newResultReady(QStringList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(newResultReady(Meta::LabelList)),
- this, SIGNAL(newResultReady(Meta::LabelList)) );
- connect( m_memoryQueryMaker.data(), SIGNAL(queryDone()),
- this, SLOT(memoryQueryDone()) );
- m_memoryQueryMaker.data()->setAutoDelete( true );
+ m_memoryQueryMaker = new MemoryQueryMaker( m_collection->memoryCollection().toWeakRef(),
+ m_collection->collectionId() );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newTracksReady,
+ this, &PlaydarQueryMaker::newTracksReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newArtistsReady,
+ this, &PlaydarQueryMaker::newArtistsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newAlbumsReady,
+ this, &PlaydarQueryMaker::newAlbumsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newGenresReady,
+ this, &PlaydarQueryMaker::newGenresReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newComposersReady,
+ this, &PlaydarQueryMaker::newComposersReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newYearsReady,
+ this, &PlaydarQueryMaker::newYearsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newDataReady,
+ this, &PlaydarQueryMaker::newDataReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newResultReady,
+ this, &PlaydarQueryMaker::newResultReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::newLabelsReady,
+ this, &PlaydarQueryMaker::newLabelsReady );
+ connect( m_memoryQueryMaker, &MemoryQueryMaker::queryDone,
+ this, &PlaydarQueryMaker::memoryQueryDone );
+ m_memoryQueryMaker->setAutoDelete( true );
foreach( CurriedQMFunction *funPtr, m_queryMakerFunctions )
(*funPtr)( m_memoryQueryMaker.data() );
m_activeQueryCount++;
m_memoryQueryIsRunning = true;
m_memoryQueryMaker.data()->run();
}
}
diff --git a/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.h b/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.h
index 5a9f561004..8e328c03a6 100644
--- a/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.h
+++ b/src/core-impl/collections/playdarcollection/PlaydarQueryMaker.h
@@ -1,126 +1,126 @@
/****************************************************************************************
* Copyright (c) 2010 Andrew Coder <andrew.coder@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef PLAYDAR_QUERYMAKER_H
#define PLAYDAR_QUERYMAKER_H
#include "PlaydarMeta.h"
#include "PlaydarCollection.h"
#include "support/Controller.h"
#include "support/Query.h"
#include "support/QMFunctionTypes.h"
#include "core/meta/forward_declarations.h"
#include "core/meta/support/MetaConstants.h"
#include "core/collections/QueryMaker.h"
#include <QMap>
#include <QStack>
namespace Collections
{
class QueryMakerFunction;
class PlaydarQueryMaker : public QueryMaker
{
Q_OBJECT
public:
PlaydarQueryMaker( PlaydarCollection *collection );
~PlaydarQueryMaker();
void run();
void abortQuery();
QueryMaker* setQueryType( QueryType type );
QueryMaker* addReturnValue( qint64 value );
QueryMaker* addReturnFunction( ReturnFunction function, qint64 value );
QueryMaker* orderBy( qint64 value, bool descending = false );
QueryMaker* addMatch( const Meta::TrackPtr &track );
QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists );
QueryMaker* addMatch( const Meta::AlbumPtr &album );
QueryMaker* addMatch( const Meta::ComposerPtr &composer );
QueryMaker* addMatch( const Meta::GenrePtr &genre );
QueryMaker* addMatch( const Meta::YearPtr &year );
QueryMaker* addMatch( const Meta::LabelPtr &label );
QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin = false, bool matchEnd = false );
QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin = false, bool matchEnd = false );
QueryMaker* addNumberFilter( qint64 value, qint64 filter, NumberComparison compare );
QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare );
QueryMaker* limitMaxResultSize( int size );
QueryMaker* setAlbumQueryMode( AlbumQueryMode mode );
QueryMaker* setLabelQueryMode( LabelQueryMode mode );
QueryMaker* beginAnd();
QueryMaker* beginOr();
QueryMaker* endAndOr();
QueryMaker* setAutoDelete( bool autoDelete );
int validFilterMask();
Q_SIGNALS:
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
+ void newComposersReady( Meta::ComposerList );
+ void newYearsReady( Meta::YearList );
void newResultReady( QStringList );
- void newResultReady( Meta::LabelList );
+ void newLabelsReady( Meta::LabelList );
void queryDone();
void playdarError( Playdar::Controller::ErrorState );
private Q_SLOTS:
void slotPlaydarError( Playdar::Controller::ErrorState error );
void collectQuery( Playdar::Query *query );
void collectResult( Meta::PlaydarTrackPtr track );
void aQueryEnded( Playdar::Query *query, const Meta::PlaydarTrackList &trackList );
void memoryQueryDone();
private:
QueryType m_queryType;
bool m_autoDelete;
int m_activeQueryCount;
bool m_memoryQueryIsRunning;
bool m_collectionUpdated;
QList< CurriedQMFunction* > m_queryMakerFunctions;
typedef QMap< qint64, QString > FilterMap;
FilterMap m_filterMap;
QWeakPointer< PlaydarCollection > m_collection;
QWeakPointer< QueryMaker > m_memoryQueryMaker;
QWeakPointer< Playdar::Controller > m_controller;
void runMemoryQueryAgain();
};
}
#endif
diff --git a/src/core-impl/collections/playdarcollection/support/Controller.cpp b/src/core-impl/collections/playdarcollection/support/Controller.cpp
index d584ac168a..d2ad48567b 100644
--- a/src/core-impl/collections/playdarcollection/support/Controller.cpp
+++ b/src/core-impl/collections/playdarcollection/support/Controller.cpp
@@ -1,205 +1,204 @@
/****************************************************************************************
* Copyright (c) 2010 Andrew Coder <andrew.coder@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playdar::Controller"
#include "Controller.h"
#include "Query.h"
#include "core/support/Debug.h"
#include <qjson/parser.h>
#include <QUrl>
#include <KIO/Job>
#include <QString>
#include <QVariant>
#include <QMap>
#include <QVariantMap>
namespace Playdar {
Controller::Controller( bool queriesShouldWaitForSolutions )
: m_errorState( ErrorState( NoError ) )
, m_queriesShouldWaitForSolutions( queriesShouldWaitForSolutions )
{
DEBUG_BLOCK
}
Controller::~Controller()
{
DEBUG_BLOCK
}
void
Controller::resolve( const QString &artist, const QString &album, const QString &title )
{
DEBUG_BLOCK
debug() << "Querying playdar for artist name = " << artist
<< ", album name = " << album << ", and track title = " << title;
const QString baseUrl( "http://localhost:60210/api/?method=resolve" );
QUrl resolveUrl( baseUrl );
resolveUrl.addQueryItem( QString( "artist" ), artist );
resolveUrl.addQueryItem( QString( "album" ), album );
resolveUrl.addQueryItem( QString( "track" ), title );
debug() << "Starting storedGetJob for " << resolveUrl.url();
KJob* resolveJob = KIO::storedGet( resolveUrl, KIO::Reload, KIO::HideProgressInfo );
- connect( resolveJob, SIGNAL(result(KJob*)), this, SLOT(processQuery(KJob*)) );
+ connect( resolveJob, &KJob::result, this, &Controller::processQuery );
}
void
Controller::getResults( Query* query )
{
DEBUG_BLOCK
const QString baseUrl( "http://localhost:60210/api/?method=get_results" );
QUrl getResultsUrl( baseUrl );
getResultsUrl.addQueryItem( QString( "qid" ), query->qid() );
KJob* getResultsJob = KIO::storedGet( getResultsUrl, KIO::Reload, KIO::HideProgressInfo );
- connect( getResultsJob, SIGNAL(result(KJob*)), query, SLOT(receiveResults(KJob*)) );
+ connect( getResultsJob, &KJob::result, query, &Query::receiveResults );
}
void
Controller::getResultsLongPoll( Query* query )
{
DEBUG_BLOCK
const QString baseUrl( "http://localhost:60210/api/?method=get_results_long" );
QUrl getResultsUrl( baseUrl );
getResultsUrl.addQueryItem( QString( "qid" ), query->qid() );
KJob* getResultsJob = KIO::storedGet( getResultsUrl, KIO::Reload, KIO::HideProgressInfo );
- connect( getResultsJob, SIGNAL(result(KJob*)), query, SLOT(receiveResults(KJob*)) );
+ connect( getResultsJob, &KJob::result, query, &Query::receiveResults );
}
QUrl
Controller::urlForSid( const QString &sid ) const
{
DEBUG_BLOCK
const QString baseUrl( "http://localhost:60210/sid/" );
QUrl playableUrl( baseUrl );
playableUrl = playableUrl.adjusted(QUrl::StripTrailingSlash);
playableUrl.setPath(playableUrl.path() + '/' + ( sid ));
return playableUrl;
}
void
Controller::status()
{
// DEBUG_BLOCK
const QString baseUrl( "http://localhost:60210/api/?method=stat" );
QUrl statusUrl( baseUrl );
KJob* statusJob = KIO::storedGet( statusUrl, KIO::Reload, KIO::HideProgressInfo );
- connect( statusJob, SIGNAL(result(KJob*)), this, SLOT(processStatus(KJob*)) );
+ connect( statusJob, &KJob::result, this, &Controller::processStatus );
}
void
Controller::processStatus( KJob *statusJob )
{
if( statusJob->error() != 0 ) {
// debug() << "Error getting status from Playdar";
emit playdarError( Playdar::Controller::ErrorState( ExternalError ) );
return;
}
debug() << "Processing received JSON data...";
KIO::StoredTransferJob* storedStatusJob = static_cast<KIO::StoredTransferJob*>( statusJob );
QVariant parsedStatusVariant;
QJson::Parser parser;
bool ok;
parsedStatusVariant = parser.parse( storedStatusJob->data(),&ok );
if ( !ok )
{
debug() << "Error parsing JSON Data";
}
QVariantMap parsedStatus = parsedStatusVariant.toMap();
if( !parsedStatus.contains("name") )
{
debug() << "Expected a service name from Playdar, received none";
emit playdarError( Playdar::Controller::ErrorState( MissingServiceName ) );
return;
}
if( parsedStatus.value("name") != QString( "playdar" ) )
{
debug() << "Expected Playdar, got response from some other service";
emit playdarError( Playdar::Controller::ErrorState( WrongServiceName ) );
return;
}
debug() << "All good! Emitting playdarReady()";
emit playdarReady();
}
void
Controller::processQuery( KJob *queryJob )
{
DEBUG_BLOCK
if( queryJob->error() != 0 )
{
debug() << "Error getting qid from Playdar";
emit playdarError( Playdar::Controller::ErrorState( ExternalError ) );
return;
}
debug() << "Processing received JSON data...";
KIO::StoredTransferJob* storedQueryJob =
static_cast<KIO::StoredTransferJob*>( queryJob );
QVariant parsedQueryVariant;
QJson::Parser parser;
bool ok;
parsedQueryVariant = parser.parse( storedQueryJob->data(),&ok );
if ( !ok )
{
debug() << "Error parsing JSON Data";
}
QVariantMap parsedQuery = parsedQueryVariant.toMap();
if( !parsedQuery.contains( "qid" ) )
{
debug() << "Expected qid in Playdar's response, but didn't get it";
emit playdarError( Playdar::Controller::ErrorState( MissingQid ) );
return;
}
Query* query = new Query( parsedQuery.value( "qid" ).toString(), this, m_queriesShouldWaitForSolutions );
debug() << "All good! Emitting queryReady( Playdar::Query* )...";
emit queryReady( query );
- connect( query, SIGNAL(playdarError(Playdar::Controller::ErrorState)),
- this, SIGNAL(playdarError(Playdar::Controller::ErrorState)) );
+ connect( query, &Query::playdarError, this, &Controller::playdarError );
}
}
diff --git a/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp b/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp
index dd7392bdbf..0300079d6c 100644
--- a/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp
+++ b/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp
@@ -1,93 +1,93 @@
/****************************************************************************************
* Copyright (c) 2010 Andrew Coder <andrew.coder@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ProxyResolver.h"
#include "Controller.h"
#include "../PlaydarCollection.h"
#include "../PlaydarMeta.h"
#include "core-impl/meta/proxy/MetaProxy.h"
#include <QUrl>
#include <QObject>
Playdar::ProxyResolver::ProxyResolver( Collections::PlaydarCollection *collection,
const QUrl &url, MetaProxy::TrackPtr track )
: m_collection( collection )
, m_proxyTrack( track )
, m_controller( new Playdar::Controller( true ) )
, m_query()
{
- connect( m_controller, SIGNAL(playdarError(Playdar::Controller::ErrorState)),
- this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) );
- connect( m_controller, SIGNAL(queryReady(Playdar::Query*)),
- this, SLOT(collectQuery(Playdar::Query*)) );
+ connect( m_controller, &Playdar::Controller::playdarError,
+ this, &Playdar::ProxyResolver::slotPlaydarError );
+ connect( m_controller, &Playdar::Controller::queryReady,
+ this, &Playdar::ProxyResolver::collectQuery );
m_controller->resolve( QUrlQuery(url).queryItemValue( "artist" ),
QUrlQuery(url).queryItemValue( "album" ),
QUrlQuery(url).queryItemValue( "title" ) );
}
Playdar::ProxyResolver::~ProxyResolver()
{
delete m_query;
delete m_controller;
}
void
Playdar::ProxyResolver::slotPlaydarError( Playdar::Controller::ErrorState error )
{
emit playdarError( error );
this->deleteLater();
}
void
Playdar::ProxyResolver::collectQuery( Playdar::Query *query )
{
m_query = query;
- connect( m_query, SIGNAL(querySolved(Meta::PlaydarTrackPtr)),
- this, SLOT(collectSolution(Meta::PlaydarTrackPtr)) );
- connect( m_query, SIGNAL(queryDone(Playdar::Query*,Meta::PlaydarTrackList)),
- this, SLOT(slotQueryDone(Playdar::Query*,Meta::PlaydarTrackList)) );
+ connect( m_query, &Playdar::Query::querySolved,
+ this, &Playdar::ProxyResolver::collectSolution );
+ connect( m_query, &Playdar::Query::queryDone,
+ this, &Playdar::ProxyResolver::slotQueryDone );
}
void
Playdar::ProxyResolver::collectSolution( Meta::PlaydarTrackPtr track )
{
if( !m_proxyTrack->isPlayable() )
{
Meta::TrackPtr realTrack;
if( !m_collection.isNull() )
{
track->addToCollection( m_collection );
realTrack = m_collection->trackForUrl( QUrl(track->uidUrl()) );
}
else
realTrack = Meta::TrackPtr::staticCast( track );
m_proxyTrack->updateTrack( realTrack );
}
}
void
Playdar::ProxyResolver::slotQueryDone( Playdar::Query* query, const Meta::PlaydarTrackList& tracks )
{
Q_UNUSED( query );
Q_UNUSED( tracks );
this->deleteLater();
}
diff --git a/src/core-impl/collections/support/CollectionManager.cpp b/src/core-impl/collections/support/CollectionManager.cpp
index 3b6d0218aa..bc81d5e340 100644
--- a/src/core-impl/collections/support/CollectionManager.cpp
+++ b/src/core-impl/collections/support/CollectionManager.cpp
@@ -1,428 +1,426 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CollectionManager"
#include "CollectionManager.h"
#include "core/capabilities/CollectionScanCapability.h"
#include "core/collections/Collection.h"
#include "core/collections/MetaQueryMaker.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/support/SmartPointerList.h"
#include "core-impl/meta/file/FileTrackProvider.h"
#include "core-impl/meta/stream/Stream.h"
#include "core-impl/meta/timecode/TimecodeTrackProvider.h"
#include <QMetaEnum>
#include <QMetaObject>
#include <QCoreApplication>
#include <QList>
#include <QPair>
#include <QTimer>
#include <QReadWriteLock>
#include <KGlobal>
typedef QPair<Collections::Collection*, CollectionManager::CollectionStatus> CollectionPair;
/** Private structure of the collection manager */
struct CollectionManager::Private
{
QList<CollectionPair> collections;
QList<Plugins::PluginFactory*> factories; // factories belong to PluginManager
QList<Collections::TrackProvider*> trackProviders;
TimecodeTrackProvider *timecodeTrackProvider;
Collections::TrackProvider *fileTrackProvider; // special case
Collections::Collection *primaryCollection;
QReadWriteLock lock; ///< protects all other variables against threading issues
};
CollectionManager *CollectionManager::s_instance = 0;
CollectionManager *
CollectionManager::instance()
{
if( !s_instance ) {
s_instance = new CollectionManager();
s_instance->init();
}
return s_instance;
}
void
CollectionManager::destroy()
{
if( s_instance ) {
delete s_instance;
s_instance = 0;
}
}
CollectionManager::CollectionManager()
: QObject()
, d( new Private )
{
DEBUG_BLOCK
// ensure this object is created in a main thread
Q_ASSERT( thread() == QCoreApplication::instance()->thread() );
setObjectName( "CollectionManager" );
d->primaryCollection = 0;
d->timecodeTrackProvider = 0;
d->fileTrackProvider = 0;
}
CollectionManager::~CollectionManager()
{
DEBUG_BLOCK
{
QWriteLocker locker( &d->lock );
d->collections.clear();
d->trackProviders.clear();
delete d->timecodeTrackProvider;
delete d->fileTrackProvider;
// Hmm, qDeleteAll from Qt 4.8 crashes with our SmartPointerList, do it manually. Bug 285951
while (!d->factories.isEmpty() )
delete d->factories.takeFirst();
}
delete d;
}
void
CollectionManager::init()
{
// register the timecode track provider now, as it needs to get added before loading
// the stored playlist... Since it can have playable urls that might also match other providers, it needs to get added first.
d->timecodeTrackProvider = new TimecodeTrackProvider();
addTrackProvider( d->timecodeTrackProvider );
// addint fileTrackProvider second since local tracks should be preferred even if the url matchs two tracks
d->fileTrackProvider = new FileTrackProvider();
addTrackProvider( d->fileTrackProvider );
}
void
CollectionManager::setFactories( const QList<Plugins::PluginFactory*> &factories )
{
using Collections::CollectionFactory;
QSet<Plugins::PluginFactory*> newFactories = factories.toSet();
QSet<Plugins::PluginFactory*> oldFactories;
{
QReadLocker locker( &d->lock );
oldFactories = d->factories.toSet();
}
// remove old factories
foreach( Plugins::PluginFactory* pFactory, oldFactories - newFactories )
{
CollectionFactory *factory = qobject_cast<CollectionFactory*>( pFactory );
if( !factory )
continue;
- disconnect( factory, SIGNAL(newCollection(Collections::Collection*)),
- this, SLOT(slotNewCollection(Collections::Collection*)) );
+ disconnect( factory, &CollectionFactory::newCollection,
+ this, &CollectionManager::slotNewCollection );
{
QWriteLocker locker( &d->lock );
d->factories.removeAll( factory );
}
}
// create new factories
foreach( Plugins::PluginFactory* pFactory, newFactories - oldFactories )
{
CollectionFactory *factory = qobject_cast<CollectionFactory*>( pFactory );
if( !factory )
continue;
- connect( factory, SIGNAL(newCollection(Collections::Collection*)),
- this, SLOT(slotNewCollection(Collections::Collection*)) );
+ connect( factory, &CollectionFactory::newCollection,
+ this, &CollectionManager::slotNewCollection );
{
QWriteLocker locker( &d->lock );
d->factories.append( factory );
}
}
-
- d->factories = factories;
}
void
CollectionManager::startFullScan()
{
QReadLocker locker( &d->lock );
foreach( const CollectionPair &pair, d->collections )
{
QScopedPointer<Capabilities::CollectionScanCapability> csc( pair.first->create<Capabilities::CollectionScanCapability>());
if( csc )
csc->startFullScan();
}
}
void
CollectionManager::startIncrementalScan( const QString &directory )
{
QReadLocker locker( &d->lock );
foreach( const CollectionPair &pair, d->collections )
{
QScopedPointer<Capabilities::CollectionScanCapability> csc( pair.first->create<Capabilities::CollectionScanCapability>());
if( csc )
csc->startIncrementalScan( directory );
}
}
void
CollectionManager::stopScan()
{
QReadLocker locker( &d->lock );
foreach( const CollectionPair &pair, d->collections )
{
QScopedPointer<Capabilities::CollectionScanCapability> csc( pair.first->create<Capabilities::CollectionScanCapability>());
if( csc )
csc->stopScan();
}
}
void
CollectionManager::checkCollectionChanges()
{
startIncrementalScan( QString() );
}
Collections::QueryMaker*
CollectionManager::queryMaker() const
{
QReadLocker locker( &d->lock );
QList<Collections::Collection*> colls;
foreach( const CollectionPair &pair, d->collections )
{
if( pair.second & CollectionQueryable )
{
colls << pair.first;
}
}
return new Collections::MetaQueryMaker( colls );
}
void
CollectionManager::slotNewCollection( Collections::Collection* newCollection )
{
DEBUG_BLOCK
if( !newCollection )
{
error() << "newCollection in slotNewCollection is 0";
return;
}
{
QWriteLocker locker( &d->lock );
foreach( const CollectionPair &p, d->collections )
{
if( p.first == newCollection )
{
error() << "newCollection " << newCollection->collectionId() << " is already being managed";
return;
}
}
}
const QMetaObject *mo = metaObject();
const QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "CollectionStatus" ) );
const QString &value = KGlobal::config()->group( "CollectionManager" ).readEntry( newCollection->collectionId() );
int enumValue = me.keyToValue( value.toLocal8Bit().constData() );
CollectionStatus status;
enumValue == -1 ? status = CollectionEnabled : status = (CollectionStatus) enumValue;
CollectionPair pair( newCollection, status );
{
QWriteLocker locker( &d->lock );
if( newCollection->collectionId() == QLatin1String("localCollection") )
{
d->primaryCollection = newCollection;
d->collections.insert( 0, pair ); // the primary collection should be the first collection to be searched
d->trackProviders.insert( 1, newCollection ); // the primary collection should be between the timecode track provider and the local file track provider
}
else
{
d->collections.append( pair );
d->trackProviders.append( newCollection );
}
- connect( newCollection, SIGNAL(remove()), SLOT(slotRemoveCollection()), Qt::QueuedConnection );
- connect( newCollection, SIGNAL(updated()), SLOT(slotCollectionChanged()), Qt::QueuedConnection );
+ connect( newCollection, &Collections::Collection::remove, this, &CollectionManager::slotRemoveCollection, Qt::QueuedConnection );
+ connect( newCollection, &Collections::Collection::updated, this, &CollectionManager::slotCollectionChanged, Qt::QueuedConnection );
debug() << "new Collection " << newCollection->collectionId();
}
if( status & CollectionViewable )
{
- emit collectionAdded( newCollection );
+// emit collectionAdded( newCollection );
emit collectionAdded( newCollection, status );
}
}
void
CollectionManager::slotRemoveCollection()
{
Collections::Collection* collection = qobject_cast<Collections::Collection*>( sender() );
if( collection )
{
CollectionStatus status = collectionStatus( collection->collectionId() );
CollectionPair pair( collection, status );
{
QWriteLocker locker( &d->lock );
d->collections.removeAll( pair );
d->trackProviders.removeAll( collection );
}
emit collectionRemoved( collection->collectionId() );
- QTimer::singleShot( 500, collection, SLOT(deleteLater()) ); // give the tree some time to update itself until we really delete the collection pointers.
+ QTimer::singleShot( 500, collection, &QObject::deleteLater ); // give the tree some time to update itself until we really delete the collection pointers.
}
}
void
CollectionManager::slotCollectionChanged()
{
Collections::Collection *collection = dynamic_cast<Collections::Collection*>( sender() );
if( collection )
{
CollectionStatus status = collectionStatus( collection->collectionId() );
if( status & CollectionViewable )
{
emit collectionDataChanged( collection );
}
}
}
QList<Collections::Collection*>
CollectionManager::viewableCollections() const
{
QReadLocker locker( &d->lock );
QList<Collections::Collection*> result;
foreach( const CollectionPair &pair, d->collections )
{
if( pair.second & CollectionViewable )
{
result << pair.first;
}
}
return result;
}
Collections::Collection*
CollectionManager::primaryCollection() const
{
QReadLocker locker( &d->lock );
return d->primaryCollection;
}
Meta::TrackPtr
CollectionManager::trackForUrl( const QUrl &url )
{
QReadLocker locker( &d->lock );
// TODO:
// might be a podcast, in that case we'll have additional meta information
// might be a lastfm track, another stream
if( !url.isValid() )
return Meta::TrackPtr( 0 );
foreach( Collections::TrackProvider *provider, d->trackProviders )
{
if( provider->possiblyContainsTrack( url ) )
{
Meta::TrackPtr track = provider->trackForUrl( url );
if( track )
return track;
}
}
// TODO: create specific TrackProviders for these:
static const QSet<QString> remoteProtocols = QSet<QString>()
<< "http" << "https" << "mms" << "smb"; // consider unifying with TrackLoader::tracksLoaded()
if( remoteProtocols.contains( url.scheme() ) )
return Meta::TrackPtr( new MetaStream::Track( url ) );
return Meta::TrackPtr( 0 );
}
CollectionManager::CollectionStatus
CollectionManager::collectionStatus( const QString &collectionId ) const
{
QReadLocker locker( &d->lock );
foreach( const CollectionPair &pair, d->collections )
{
if( pair.first->collectionId() == collectionId )
{
return pair.second;
}
}
return CollectionDisabled;
}
QHash<Collections::Collection*, CollectionManager::CollectionStatus>
CollectionManager::collections() const
{
QReadLocker locker( &d->lock );
QHash<Collections::Collection*, CollectionManager::CollectionStatus> result;
foreach( const CollectionPair &pair, d->collections )
{
result.insert( pair.first, pair.second );
}
return result;
}
void
CollectionManager::addTrackProvider( Collections::TrackProvider *provider )
{
{
QWriteLocker locker( &d->lock );
d->trackProviders.append( provider );
}
emit trackProviderAdded( provider );
}
void
CollectionManager::removeTrackProvider( Collections::TrackProvider *provider )
{
QWriteLocker locker( &d->lock );
d->trackProviders.removeAll( provider );
}
diff --git a/src/core-impl/collections/support/CollectionManager.h b/src/core-impl/collections/support/CollectionManager.h
index f078008c62..04096178b6 100644
--- a/src/core-impl/collections/support/CollectionManager.h
+++ b/src/core-impl/collections/support/CollectionManager.h
@@ -1,178 +1,178 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_COLLECTIONMANAGER_H
#define AMAROK_COLLECTIONMANAGER_H
#include "amarok_export.h"
#include "core/meta/forward_declarations.h"
#include <QUrl>
#include <QList>
#include <QObject>
class CollectionManagerSingleton;
namespace Plugins {
class PluginFactory;
}
namespace Collections {
class Collection;
class CollectionFactory;
class TrackProvider;
class QueryMaker;
}
/** Class managing the different collections.
*
* This singleton class is the main repository for all current collections.
* The most usefull functions are probably queryMaker and
* viewableCollections
*/
class AMAROK_EXPORT CollectionManager : public QObject
{
Q_OBJECT
Q_ENUMS( CollectionStatus )
public:
/**
* defines the status of a collection in respect to global queries (i.e. queries that query all known collections)
* or the collection browser.
*/
enum CollectionStatus { CollectionDisabled = 1, ///< Collection neither viewable nor queryable (but might produce tracks that can be played)
CollectionViewable = 2, ///< Collection will not be queried by CollectionManager::queryMaker
CollectionQueryable= 4, ///< Collection wil not show up in the browser, but is queryable by global queries
CollectionEnabled = CollectionViewable | CollectionQueryable ///< Collection viewable in the browser and queryable
};
static CollectionManager *instance();
/** Destroys the instance of the CollectionManager.
*/
static void destroy();
/**
* Returns a query maker that queries all queryable the collections
*/
Collections::QueryMaker *queryMaker() const;
/**
* returns all viewable collections.
*/
QList<Collections::Collection*> viewableCollections() const;
//TODO: Remove
/**
* Allows access to one of Amarok's collection.
*
* @deprecated DO NOT USE this method. This is legacy code from the early days of Amarok
* when SqlCollection was the only working collection. If you are writing new code, make
* sure that it is able to handle multiple collections,
* e.g. multiple connected media devices. Using this method means you are lazy. An easy way
* is to use CollectionManager::queryMaker(). Alternatively, access the available collections
* using CollectionManager::queryableCollections() or CollectionManager::viewableCollections()
* and do whatever you want to.
*
*/
Collections::Collection* primaryCollection() const;
/**
This method will try to get a Track object for the given url. This method will return 0 if no Track object
could be created for the url.
*/
Meta::TrackPtr trackForUrl( const QUrl &url );
CollectionStatus collectionStatus( const QString &collectionId ) const;
QHash<Collections::Collection*, CollectionStatus> collections() const;
/**
* adds a TrackProvider to the list of TrackProviders,
* which allows CollectionManager to create tracks in trackForUrl.
* CollectionManager does not take ownership of the TrackProvider pointer
*
* Note: collections that CollectionManager knows about are automatically
* added to the list of TrackProviders.
*
* @param provider the new TrackProvider
*/
void addTrackProvider( Collections::TrackProvider *provider );
/**
* removes a TrackProvider. Does not do anything if
* CollectionManager does not know the given TrackProvider.
*
* Note: collections will be automatically removed from
* the list of available TrackProviders.
*
* @param provider the provider to be removed
*/
void removeTrackProvider( Collections::TrackProvider *provider );
/**
* Set the list of current factories
*
* For every factory that is a CollectionFactory uses it to create new
* collections and register with this manager.
*/
void setFactories( const QList<Plugins::PluginFactory*> &factories );
public Q_SLOTS:
/** Starts the full scan for each collection with CollectionScanCapability */
void startFullScan();
/** Starts the incremetal scan for each collection with CollectionScanCapability */
void startIncrementalScan( const QString &directory = QString() );
void stopScan();
void checkCollectionChanges();
Q_SIGNALS:
//deprecated, use collectionAdded( Collections::Collection*, CollectionStatus ) instead
- void collectionAdded( Collections::Collection *newCollection );
+// void collectionAdded( Collections::Collection *newCollection );
void collectionAdded( Collections::Collection *newCollection, CollectionManager::CollectionStatus status );
void collectionRemoved( QString collectionId );
void trackProviderAdded( Collections::TrackProvider *provider );
//this signal will be emitted after major changes to the collection
//e.g. new songs where added, or an album changed
//from compilation to non-compilation (and vice versa)
//it will not be emitted on minor changes (e.g. the tags of a song were changed)
void collectionDataChanged( Collections::Collection *changedCollection );
private Q_SLOTS:
/** Will be called whenever a registered collection factory creates a new collection */
void slotNewCollection( Collections::Collection *newCollection );
/** Will remove the collection that emitted the signal */
void slotRemoveCollection();
void slotCollectionChanged();
private:
static CollectionManager* s_instance;
CollectionManager();
~CollectionManager();
void init();
Q_DISABLE_COPY( CollectionManager )
struct Private;
Private * const d;
};
#endif /* AMAROK_COLLECTIONMANAGER_H */
diff --git a/src/core-impl/collections/support/FileCollectionLocation.cpp b/src/core-impl/collections/support/FileCollectionLocation.cpp
index ed62370973..dde9979bb4 100644
--- a/src/core-impl/collections/support/FileCollectionLocation.cpp
+++ b/src/core-impl/collections/support/FileCollectionLocation.cpp
@@ -1,134 +1,134 @@
/****************************************************************************************
* Copyright (c) 2008 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "FileCollectionLocation.h"
#include "core/collections/CollectionLocationDelegate.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include <kio/job.h>
#include <kio/jobclasses.h>
#include <kio/deletejob.h>
#include <KLocale>
#include <QDir>
#include <QFile>
#include <QFileInfo>
using namespace Collections;
FileCollectionLocation::FileCollectionLocation()
: CollectionLocation()
{
// nothing to do
}
FileCollectionLocation::~FileCollectionLocation()
{
// nothing to do
}
QString
FileCollectionLocation::prettyLocation() const
{
return "File Browser Location";
}
bool
FileCollectionLocation::isWritable() const
{
return true;
}
bool
FileCollectionLocation::isOrganizable() const
{
return false;
}
void FileCollectionLocation::startRemoveJobs()
{
DEBUG_BLOCK
while ( !m_removetracks.isEmpty() )
{
Meta::TrackPtr track = m_removetracks.takeFirst();
QUrl src = track->playableUrl();
KIO::DeleteJob *job = 0;
src.setPath( QDir::cleanPath(src.path()) );
debug() << "deleting " << src;
KIO::JobFlags flags = KIO::HideProgressInfo;
job = KIO::del( src, flags );
- connect( job, SIGNAL(result(KJob*)), SLOT(slotRemoveJobFinished(KJob*)) );
+ connect( job, &KIO::Job::result, this, &FileCollectionLocation::slotRemoveJobFinished );
QString name = track->prettyName();
if( track->artist() )
name = QString( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() );
Amarok::Components::logger()->newProgressOperation( job, i18n( "Removing: %1", name ) );
m_removejobs.insert( job, track );
}
}
void FileCollectionLocation::slotRemoveJobFinished( KJob *job )
{
// ignore and error that the file did not exist in the first place. Perhaps destination
// collection too eager? :-)
if( job->error() && job->error() != KIO::ERR_DOES_NOT_EXIST )
transferError( m_removejobs.value( job ), KIO::buildErrorString( job->error(), job->errorString() ) );
else
transferSuccessful( m_removejobs.value( job ) );
m_removejobs.remove( job );
job->deleteLater();
if(m_removejobs.isEmpty()) {
slotRemoveOperationFinished();
}
}
void FileCollectionLocation::removeUrlsFromCollection(const Meta::TrackList& sources)
{
DEBUG_BLOCK
m_removetracks = sources;
debug() << "removing " << m_removetracks.size() << "tracks";
startRemoveJobs();
}
void FileCollectionLocation::showRemoveDialog( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
if( !isHidingRemoveConfirm() )
{
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
const bool del = delegate->reallyDelete( this, tracks );
if( !del )
abort();
else
slotShowRemoveDialogDone();
} else
slotShowRemoveDialogDone();
}
diff --git a/src/core-impl/collections/support/MemoryQueryMaker.cpp b/src/core-impl/collections/support/MemoryQueryMaker.cpp
index 2232969db2..4693e144f1 100644
--- a/src/core-impl/collections/support/MemoryQueryMaker.cpp
+++ b/src/core-impl/collections/support/MemoryQueryMaker.cpp
@@ -1,520 +1,520 @@
/****************************************************************************************
* Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007-2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MemoryQueryMaker.h"
#include "MemoryCustomValue.h"
#include "MemoryFilter.h"
#include "MemoryMatcher.h"
#include "MemoryQueryMakerHelper.h"
#include "MemoryQueryMakerInternal.h"
#include "core/support/Debug.h"
#include <ThreadWeaver/QObjectDecorator>
#include <ThreadWeaver/Queue>
#include <QList>
#include <QSet>
#include <QStack>
#include <QtAlgorithms>
#include <KRandomSequence>
#include <KSortableList>
using namespace Collections;
//QueryJob
class QueryJob : public QObject, public ThreadWeaver::Job
{
Q_OBJECT
public:
QueryJob( MemoryQueryMakerInternal *qmInternal )
: QObject()
, ThreadWeaver::Job()
, queryMakerInternal( qmInternal )
{
//nothing to do
}
~QueryJob()
{
delete queryMakerInternal;
}
protected:
void defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void run(ThreadWeaver::JobPointer self = QSharedPointer<ThreadWeaver::Job>(), ThreadWeaver::Thread *thread=0)
{
Q_UNUSED(self);
Q_UNUSED(thread);
queryMakerInternal->runQuery();
// setFinished( true );
setStatus(Status_Success);
}
public:
MemoryQueryMakerInternal *queryMakerInternal;
Q_SIGNALS:
/** This signal is emitted when this job is being processed by a thread. */
void started(ThreadWeaver::JobPointer);
/** This signal is emitted when the job has been finished (no matter if it succeeded or not). */
void done(ThreadWeaver::JobPointer);
/** This job has failed.
* This signal is emitted when success() returns false after the job is executed. */
void failed(ThreadWeaver::JobPointer);
};
struct MemoryQueryMaker::Private {
QueryMaker::QueryType type;
bool returnDataPtrs;
MemoryMatcher* matcher;
QueryJob *job;
int maxsize;
QStack<ContainerMemoryFilter*> containerFilters;
QList<CustomReturnFunction*> returnFunctions;
QList<CustomReturnValue*> returnValues;
bool usingFilters;
KRandomSequence sequence; //do not reset
qint64 orderByField;
bool orderDescending;
bool orderByNumberField;
AlbumQueryMode albumQueryMode;
LabelQueryMode labelQueryMode;
QString collectionId;
};
MemoryQueryMaker::MemoryQueryMaker( QWeakPointer<MemoryCollection> mc, const QString &collectionId )
: QueryMaker()
, m_collection( mc )
, d( new Private )
{
d->collectionId = collectionId;
d->matcher = 0;
d->job = 0;
d->type = QueryMaker::None;
d->returnDataPtrs = false;
d->job = 0;
d->job = 0;
d->maxsize = -1;
d->containerFilters.push( new AndContainerMemoryFilter() );
d->usingFilters = false;
d->orderByField = 0;
d->orderDescending = false;
d->orderByNumberField = false;
d->albumQueryMode = AllAlbums;
d->labelQueryMode = QueryMaker::NoConstraint;
}
MemoryQueryMaker::~MemoryQueryMaker()
{
disconnect();
abortQuery();
if( !d->containerFilters.isEmpty() )
delete d->containerFilters.first();
delete d;
}
void
MemoryQueryMaker::run()
{
if ( d->type == QueryMaker::None )
//TODO error handling
return;
else if( d->job && !d->job->isFinished() )
{
//the worker thread seems to be running
//TODO: wait or job to complete
}
else
{
MemoryQueryMakerInternal *qmi = new MemoryQueryMakerInternal( m_collection );
if( d->usingFilters )
{
qmi->setFilters( d->containerFilters.first() );
d->containerFilters.clear(); //will be deleted by MemoryQueryMakerInternal
}
qmi->setMatchers( d->matcher );
d->matcher = 0; //will be deleted by MemoryQueryMakerInternal
qmi->setMaxSize( d->maxsize );
qmi->setType( d->type );
qmi->setCustomReturnFunctions( d->returnFunctions );
d->returnFunctions.clear(); //will be deleted by MemoryQueryMakerInternal
qmi->setCustomReturnValues( d->returnValues );
d->returnValues.clear(); //will be deleted by MemoryQueryMakerInternal
qmi->setAlbumQueryMode( d->albumQueryMode );
qmi->setLabelQueryMode( d->labelQueryMode );
qmi->setOrderDescending( d->orderDescending );
qmi->setOrderByNumberField( d->orderByNumberField );
qmi->setOrderByField( d->orderByField );
qmi->setCollectionId( d->collectionId );
- connect( qmi, SIGNAL(newResultReady(Meta::AlbumList)), SIGNAL(newResultReady(Meta::AlbumList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::ArtistList)), SIGNAL(newResultReady(Meta::ArtistList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::GenreList)), SIGNAL(newResultReady(Meta::GenreList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::ComposerList)), SIGNAL(newResultReady(Meta::ComposerList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::YearList)), SIGNAL(newResultReady(Meta::YearList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::TrackList)), SIGNAL(newResultReady(Meta::TrackList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(QStringList)), SIGNAL(newResultReady(QStringList)), Qt::DirectConnection );
- connect( qmi, SIGNAL(newResultReady(Meta::LabelList)), SIGNAL(newResultReady(Meta::LabelList)), Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newAlbumsReady, this, &MemoryQueryMaker::newAlbumsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newArtistsReady, this, &MemoryQueryMaker::newArtistsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newGenresReady, this, &MemoryQueryMaker::newGenresReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newComposersReady, this, &MemoryQueryMaker::newComposersReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newYearsReady, this, &MemoryQueryMaker::newYearsReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newTracksReady, this, &MemoryQueryMaker::newTracksReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newResultReady, this, &MemoryQueryMaker::newResultReady, Qt::DirectConnection );
+ connect( qmi, &Collections::MemoryQueryMakerInternal::newLabelsReady, this, &MemoryQueryMaker::newLabelsReady, Qt::DirectConnection );
d->job = new QueryJob( qmi );
- connect( d->job, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(done(ThreadWeaver::JobPointer)) );
+ connect( d->job, &QueryJob::done, this, &MemoryQueryMaker::done );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(d->job) );
}
}
void
MemoryQueryMaker::abortQuery()
{
if( d->job )
{
d->job->requestAbort();
d->job->disconnect( this );
if( d->job->queryMakerInternal )
d->job->queryMakerInternal->disconnect( this );
}
}
QueryMaker*
MemoryQueryMaker::setQueryType( QueryType type )
{
switch( type ) {
case QueryMaker::Track:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Track;
return this;
case QueryMaker::Artist:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Artist;
return this;
case QueryMaker::Album:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Album;
return this;
case QueryMaker::AlbumArtist:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::AlbumArtist;
return this;
case QueryMaker::Composer:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Composer;
return this;
case QueryMaker::Genre:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Genre;
return this;
case QueryMaker::Year:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Year;
return this;
case QueryMaker::Custom:
if ( d->type == QueryMaker::None )
d->type = QueryMaker::Custom;
return this;
case QueryMaker::Label:
if( d->type == QueryMaker::None )
d->type = QueryMaker::Label;
return this;
case QueryMaker::None:
return this;
}
return this;
}
QueryMaker*
MemoryQueryMaker::addReturnValue( qint64 value )
{
//MQM can not deliver sensible results if both a custom return value and a return function is selected
if( d->returnFunctions.empty() )
{
CustomReturnValue *returnValue = CustomValueFactory::returnValue( value );
if( returnValue )
d->returnValues.append( returnValue );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
//MQM can not deliver sensible results if both a custom return value and a return function is selected
if( d->returnValues.empty() )
{
CustomReturnFunction *returnFunction = CustomValueFactory::returnFunction( function, value );
if( returnFunction )
d->returnFunctions.append( returnFunction );
}
return this;
}
QueryMaker*
MemoryQueryMaker::orderBy( qint64 value, bool descending )
{
d->orderByField = value;
d->orderDescending = descending;
switch( value )
{
case Meta::valYear:
case Meta::valDiscNr:
case Meta::valTrackNr:
case Meta::valScore:
case Meta::valRating:
case Meta::valPlaycount:
case Meta::valFilesize:
case Meta::valSamplerate:
case Meta::valBitrate:
case Meta::valLength:
{
d->orderByNumberField = true;
break;
}
//TODO: what about Meta::valFirstPlayed, Meta::valCreateDate or Meta::valLastPlayed??
default:
d->orderByNumberField = false;
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::TrackPtr &track )
{
MemoryMatcher *trackMatcher = new TrackMatcher( track );
if ( d->matcher == 0 )
d->matcher = trackMatcher;
else
{
MemoryMatcher *tmp = d->matcher;
while ( !tmp->isLast() )
tmp = tmp->next();
tmp->setNext( trackMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour )
{
MemoryMatcher *artistMatcher = new ArtistMatcher( artist, behaviour );
if ( d->matcher == 0 )
d->matcher = artistMatcher;
else
{
MemoryMatcher *tmp = d->matcher;
while ( !tmp->isLast() )
tmp = tmp->next();
tmp->setNext( artistMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
MemoryMatcher *albumMatcher = new AlbumMatcher( album );
if ( d->matcher == 0 )
d->matcher = albumMatcher;
else
{
MemoryMatcher *tmp = d->matcher;
while ( !tmp->isLast() )
tmp = tmp->next();
tmp->setNext( albumMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
MemoryMatcher *genreMatcher = new GenreMatcher( genre );
if ( d->matcher == 0 )
d->matcher = genreMatcher;
else
{
MemoryMatcher *tmp = d->matcher;
while ( !tmp->isLast() )
tmp = tmp->next();
tmp->setNext( genreMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
MemoryMatcher *composerMatcher = new ComposerMatcher( composer );
if ( d->matcher == 0 )
d->matcher = composerMatcher;
else
{
MemoryMatcher *tmp = d->matcher;
while ( !tmp->isLast() )
tmp = tmp->next();
tmp->setNext( composerMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::YearPtr &year )
{
MemoryMatcher *yearMatcher = new YearMatcher( year );
if ( d->matcher == 0 )
d->matcher = yearMatcher;
else
{
MemoryMatcher *tmp = d->matcher;
while ( !tmp->isLast() )
tmp = tmp->next();
tmp->setNext( yearMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addMatch( const Meta::LabelPtr &label )
{
MemoryMatcher *labelMatcher = new LabelMatcher( label );
if( d->matcher == 0 )
{
d->matcher = labelMatcher;
}
else
{
MemoryMatcher *tmp = d->matcher;
while( !tmp->isLast() )
{
tmp = tmp->next();
}
tmp->setNext( labelMatcher );
}
return this;
}
QueryMaker*
MemoryQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
d->containerFilters.top()->addFilter( FilterFactory::filter( value, filter, matchBegin, matchEnd ) );
d->usingFilters = true;
return this;
}
QueryMaker*
MemoryQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
MemoryFilter *tmp = FilterFactory::filter( value, filter, matchBegin, matchEnd );
d->containerFilters.top()->addFilter( new NegateMemoryFilter( tmp ) );
d->usingFilters = true;
return this;
}
QueryMaker*
MemoryQueryMaker::addNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
d->containerFilters.top()->addFilter( FilterFactory::numberFilter( value, filter, compare ) );
d->usingFilters = true;
return this;
}
QueryMaker*
MemoryQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
MemoryFilter *tmp = FilterFactory::numberFilter( value, filter, compare );
d->containerFilters.top()->addFilter( new NegateMemoryFilter( tmp ) );
d->usingFilters = true;
return this;
}
QueryMaker*
MemoryQueryMaker::limitMaxResultSize( int size )
{
d->maxsize = size;
return this;
}
QueryMaker*
MemoryQueryMaker::beginAnd()
{
ContainerMemoryFilter *filter = new AndContainerMemoryFilter();
d->containerFilters.top()->addFilter( filter );
d->containerFilters.push( filter );
return this;
}
QueryMaker*
MemoryQueryMaker::beginOr()
{
ContainerMemoryFilter *filter = new OrContainerMemoryFilter();
d->containerFilters.top()->addFilter( filter );
d->containerFilters.push( filter );
return this;
}
QueryMaker*
MemoryQueryMaker::endAndOr()
{
d->containerFilters.pop();
return this;
}
void
MemoryQueryMaker::done( ThreadWeaver::JobPointer job )
{
ThreadWeaver::Queue::instance()->dequeue( job );
d->job = 0;
emit queryDone();
}
QueryMaker * MemoryQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
d->albumQueryMode = mode;
return this;
}
QueryMaker*
MemoryQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
d->labelQueryMode = mode;
return this;
}
#include "MemoryQueryMaker.moc"
diff --git a/src/core-impl/collections/support/MemoryQueryMakerInternal.cpp b/src/core-impl/collections/support/MemoryQueryMakerInternal.cpp
index 06bd26bf29..5987f0a160 100644
--- a/src/core-impl/collections/support/MemoryQueryMakerInternal.cpp
+++ b/src/core-impl/collections/support/MemoryQueryMakerInternal.cpp
@@ -1,613 +1,602 @@
/****************************************************************************************
* Copyright (c) 2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MemoryQueryMakerInternal.h"
#include "MemoryCollection.h"
#include "MemoryCustomValue.h"
#include "MemoryFilter.h"
#include "MemoryMatcher.h"
#include "MemoryQueryMakerHelper.h"
#include "core/meta/Meta.h"
#include <QSharedPointer>
#include <KSortableList>
namespace Collections {
MemoryQueryMakerInternal::MemoryQueryMakerInternal( const QWeakPointer<MemoryCollection> &collection )
: QObject()
, m_collection( collection )
, m_matchers( 0 )
, m_filters( 0 )
, m_maxSize( 0 )
, m_type( QueryMaker::None )
, m_albumQueryMode( QueryMaker::AllAlbums )
, m_orderDescending( false )
, m_orderByNumberField( false )
, m_orderByField( 0 )
{
}
MemoryQueryMakerInternal::~MemoryQueryMakerInternal()
{
delete m_filters;
delete m_matchers;
qDeleteAll( m_returnFunctions );
qDeleteAll( m_returnValues );
}
void
MemoryQueryMakerInternal::runQuery()
{
QSharedPointer<MemoryCollection> coll = m_collection.toStrongRef();
if( coll )
coll->acquireReadLock();
//naive implementation, fix this
if ( m_matchers )
{
Meta::TrackList result = coll ? m_matchers->match( coll.data() ) : Meta::TrackList();
if ( m_filters )
{
Meta::TrackList filtered;
foreach( Meta::TrackPtr track, result )
{
if( m_filters->filterMatches( track ) )
filtered.append( track );
}
handleResult( filtered );
}
else
handleResult( result );
}
else if ( m_filters )
{
Meta::TrackList tracks = coll ? coll->trackMap().values() : Meta::TrackList();
Meta::TrackList filtered;
foreach( const Meta::TrackPtr &track, tracks )
{
if ( m_filters->filterMatches( track ) )
filtered.append( track );
}
handleResult( filtered );
}
else
handleResult();
if( coll )
coll->releaseLock();
}
-template <class PointerType>
-void MemoryQueryMakerInternal::emitProperResult( const QList<PointerType>& list )
-{
- QList<PointerType> resultList = list;
-
- if ( m_maxSize >= 0 && resultList.count() > m_maxSize )
- resultList = resultList.mid( 0, m_maxSize );
-
- emit newResultReady( list );
-}
-
template<typename T>
static inline QList<T> reverse(const QList<T> &l)
{
QList<T> ret;
for (int i=l.size() - 1; i>=0; --i)
ret.append(l.at(i));
return ret;
}
void
MemoryQueryMakerInternal::handleResult()
{
QSharedPointer<MemoryCollection> coll = m_collection.toStrongRef();
//this gets called when we want to return all values for the given query type
switch( m_type )
{
case QueryMaker::Custom :
{
QStringList result;
Meta::TrackList tmpTracks = coll ? coll->trackMap().values() : Meta::TrackList();
Meta::TrackList tracks;
foreach( const Meta::TrackPtr &track, tmpTracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
tracks.append( track );
}
}
if( !m_returnFunctions.empty() )
{
//no sorting necessary
foreach( CustomReturnFunction *function, m_returnFunctions )
{
result.append( function->value( tracks ) );
}
}
else if( !m_returnValues.empty() )
{
if( m_orderByField )
{
if( m_orderByNumberField )
tracks = MemoryQueryMakerHelper::orderListByNumber( tracks, m_orderByField, m_orderDescending );
else
tracks = MemoryQueryMakerHelper::orderListByString( tracks, m_orderByField, m_orderDescending );
}
int count = 0;
foreach( const Meta::TrackPtr &track, tracks )
{
if ( m_maxSize >= 0 && count == m_maxSize )
break;
foreach( CustomReturnValue *value, m_returnValues )
{
result.append( value->value( track ) );
}
count++;
}
}
emit newResultReady( result );
break;
}
case QueryMaker::Track :
{
Meta::TrackList tracks;
Meta::TrackList tmpTracks = coll ? coll->trackMap().values() : Meta::TrackList();
foreach( Meta::TrackPtr track, tmpTracks )
{
Meta::AlbumPtr album = track->album();
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && (!album || album->isCompilation()) )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && (album && !album->isCompilation()) ) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
tracks.append( track );
}
}
if( m_orderByField )
{
if( m_orderByNumberField )
tracks = MemoryQueryMakerHelper::orderListByNumber( tracks, m_orderByField, m_orderDescending );
else
tracks = MemoryQueryMakerHelper::orderListByString( tracks, m_orderByField, m_orderDescending );
}
- emitProperResult<Meta::TrackPtr>( tracks );
+ emit newTracksReady( tracks );
break;
}
case QueryMaker::Album :
{
Meta::AlbumList albums;
Meta::AlbumList tmp = coll ? coll->albumMap().values() : Meta::AlbumList();
foreach( Meta::AlbumPtr album, tmp )
{
Meta::TrackList tracks = album->tracks();
foreach( Meta::TrackPtr track, tracks )
{
Meta::AlbumPtr album = track->album();
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && (!album || album->isCompilation()) )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && (album && !album->isCompilation()) ) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
albums.append( album );
break;
}
}
}
albums = MemoryQueryMakerHelper::orderListByName<Meta::AlbumPtr>( albums, m_orderDescending );
- emitProperResult<Meta::AlbumPtr>( albums );
+ emit newAlbumsReady( albums );
break;
}
case QueryMaker::Artist :
{
Meta::ArtistList artists;
Meta::ArtistList tmp = coll ? coll->artistMap().values() : Meta::ArtistList();
foreach( Meta::ArtistPtr artist, tmp )
{
Meta::TrackList tracks = artist->tracks();
foreach( Meta::TrackPtr track, tracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
artists.append( artist );
break;
}
}
}
artists = MemoryQueryMakerHelper::orderListByName<Meta::ArtistPtr>( artists, m_orderDescending );
- emitProperResult<Meta::ArtistPtr>( artists );
+ emit newArtistsReady( artists );
break;
}
case QueryMaker::AlbumArtist :
{
Meta::ArtistList artists;
Meta::AlbumList tmp = coll ? coll->albumMap().values() : Meta::AlbumList();
foreach( Meta::AlbumPtr album, tmp )
{
if( !album->hasAlbumArtist() )
continue;
Meta::TrackList tracks = album->tracks();
foreach( Meta::TrackPtr track, tracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && album->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !album->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
artists.append( album->albumArtist() );
break;
}
}
}
artists = MemoryQueryMakerHelper::orderListByName<Meta::ArtistPtr>( artists, m_orderDescending );
- emitProperResult<Meta::ArtistPtr>( artists );
+ emit newArtistsReady( artists );
break;
}
case QueryMaker::Composer :
{
Meta::ComposerList composers;
Meta::ComposerList tmp = coll ? coll->composerMap().values() : Meta::ComposerList();
foreach( Meta::ComposerPtr composer, tmp )
{
Meta::TrackList tracks = composer->tracks();
foreach( Meta::TrackPtr track, tracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
composers.append( composer );
break;
}
}
}
composers = MemoryQueryMakerHelper::orderListByName<Meta::ComposerPtr>( composers, m_orderDescending );
- emitProperResult<Meta::ComposerPtr>( composers );
+ emit newComposersReady( composers );
break;
}
case QueryMaker::Genre :
{
Meta::GenreList genres;
Meta::GenreList tmp = coll ? coll->genreMap().values() : Meta::GenreList();
foreach( Meta::GenrePtr genre, tmp )
{
Meta::TrackList tracks = genre->tracks();
foreach( Meta::TrackPtr track, tracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
genres.append( genre );
break;
}
}
}
genres = MemoryQueryMakerHelper::orderListByName<Meta::GenrePtr>( genres, m_orderDescending );
- emitProperResult<Meta::GenrePtr>( genres );
+ emit newGenresReady( genres );
break;
}
case QueryMaker::Year :
{
Meta::YearList years;
Meta::YearList tmp = coll ? coll->yearMap().values() : Meta::YearList();
foreach( Meta::YearPtr year, tmp )
{
Meta::TrackList tracks = year->tracks();
foreach( Meta::TrackPtr track, tracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
years.append( year );
break;
}
}
}
//this a special case which requires a bit of code duplication
//years have to be ordered as numbers, but orderListByNumber does not work for Meta::YearPtrs
if( m_orderByField == Meta::valYear )
{
years = MemoryQueryMakerHelper::orderListByYear( years, m_orderDescending );
}
- emitProperResult<Meta::YearPtr>( years );
+ emit newYearsReady( years );
break;
}
case QueryMaker::Label:
{
Meta::LabelList labels;
Meta::LabelList tmp = coll ? coll->labelMap().values() : Meta::LabelList();
foreach( const Meta::LabelPtr &label, tmp )
{
Meta::TrackList tracks = coll ? coll->labelToTrackMap().value( label ) : Meta::TrackList();
foreach( const Meta::TrackPtr &track, tracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
labels.append( label );
break;
}
}
}
labels = MemoryQueryMakerHelper::orderListByName<Meta::LabelPtr>( labels, m_orderDescending );
- emitProperResult<Meta::LabelPtr>( labels );
+ emit newLabelsReady( labels );
break;
}
case QueryMaker::None :
//nothing to do
break;
}
}
void
MemoryQueryMakerInternal::handleResult( const Meta::TrackList &tmpTracks )
{
Meta::TrackList tracks;
foreach( const Meta::TrackPtr &track, tmpTracks )
{
if( ( m_albumQueryMode == QueryMaker::AllAlbums
|| ( m_albumQueryMode == QueryMaker::OnlyCompilations && track->album()->isCompilation() )
|| ( m_albumQueryMode == QueryMaker::OnlyNormalAlbums && !track->album()->isCompilation()) ) &&
( m_labelQueryMode == QueryMaker::NoConstraint
|| ( m_labelQueryMode == QueryMaker::OnlyWithLabels && track->labels().count() > 0 )
|| ( m_labelQueryMode == QueryMaker::OnlyWithoutLabels && track->labels().count() == 0) ) )
{
tracks.append( track );
}
}
switch( m_type )
{
case QueryMaker::Custom :
{
QStringList result;
if( !m_returnFunctions.empty() )
{
//no sorting necessary
foreach( CustomReturnFunction *function, m_returnFunctions )
{
result.append( function->value( tracks ) );
}
}
else if( !m_returnValues.empty() )
{
Meta::TrackList resultTracks = tracks;
if( m_orderByField )
{
if( m_orderByNumberField )
resultTracks = MemoryQueryMakerHelper::orderListByNumber( resultTracks, m_orderByField, m_orderDescending );
else
resultTracks = MemoryQueryMakerHelper::orderListByString( resultTracks, m_orderByField, m_orderDescending );
}
int count = 0;
foreach( const Meta::TrackPtr &track, resultTracks )
{
if ( m_maxSize >= 0 && count == m_maxSize )
break;
foreach( CustomReturnValue *value, m_returnValues )
{
result.append( value->value( track ) );
}
count++;
}
}
emit newResultReady( result );
break;
}
case QueryMaker::Track :
{
Meta::TrackList newResult;
if( m_orderByField )
{
if( m_orderByNumberField )
newResult = MemoryQueryMakerHelper::orderListByNumber( tracks, m_orderByField, m_orderDescending );
else
newResult = MemoryQueryMakerHelper::orderListByString( tracks, m_orderByField, m_orderDescending );
}
else
newResult = tracks;
- emitProperResult<Meta::TrackPtr>( newResult );
+ emit newTracksReady( newResult );
break;
}
case QueryMaker::Album :
{
QSet<Meta::AlbumPtr> albumSet;
foreach( Meta::TrackPtr track, tracks )
{
albumSet.insert( track->album() );
}
Meta::AlbumList albumList = albumSet.toList();
albumList = MemoryQueryMakerHelper::orderListByName<Meta::AlbumPtr>( albumList, m_orderDescending );
- emitProperResult<Meta::AlbumPtr>( albumList );
+ emit newAlbumsReady( albumList );
break;
}
case QueryMaker::Artist :
{
QSet<Meta::ArtistPtr> artistSet;
foreach( Meta::TrackPtr track, tracks )
{
artistSet.insert( track->artist() );
}
Meta::ArtistList list = artistSet.toList();
list = MemoryQueryMakerHelper::orderListByName<Meta::ArtistPtr>( list, m_orderDescending );
- emitProperResult<Meta::ArtistPtr>( list );
+ emit newArtistsReady( list );
break;
}
case QueryMaker::AlbumArtist :
{
QSet<Meta::ArtistPtr> artistSet;
foreach( Meta::TrackPtr track, tracks )
{
if( !track->album().isNull() && track->album()->hasAlbumArtist() )
artistSet.insert( track->album()->albumArtist() );
}
Meta::ArtistList list = artistSet.toList();
list = MemoryQueryMakerHelper::orderListByName<Meta::ArtistPtr>( list, m_orderDescending );
- emitProperResult<Meta::ArtistPtr>( list );
+ emit newArtistsReady( list );
break;
}
case QueryMaker::Genre :
{
QSet<Meta::GenrePtr> genreSet;
foreach( Meta::TrackPtr track, tracks )
{
genreSet.insert( track->genre() );
}
Meta::GenreList list = genreSet.toList();
list = MemoryQueryMakerHelper::orderListByName<Meta::GenrePtr>( list, m_orderDescending );
- emitProperResult<Meta::GenrePtr>( list );
+ emit newGenresReady( list );
break;
}
case QueryMaker::Composer :
{
QSet<Meta::ComposerPtr> composerSet;
foreach( Meta::TrackPtr track, tracks )
{
composerSet.insert( track->composer() );
}
Meta::ComposerList list = composerSet.toList();
list = MemoryQueryMakerHelper::orderListByName<Meta::ComposerPtr>( list, m_orderDescending );
- emitProperResult<Meta::ComposerPtr>( list );
+ emit newComposersReady( list );
break;
}
case QueryMaker::Year :
{
QSet<Meta::YearPtr> yearSet;
foreach( Meta::TrackPtr track, tracks )
{
yearSet.insert( track->year() );
}
Meta::YearList years = yearSet.toList();
if( m_orderByField == Meta::valYear )
{
years = MemoryQueryMakerHelper::orderListByYear( years, m_orderDescending );
}
- emitProperResult<Meta::YearPtr>( years );
+ emit newYearsReady( years );
break;
}
case QueryMaker::Label:
{
QSet<Meta::LabelPtr> labelSet;
foreach( const Meta::TrackPtr &track, tracks )
{
labelSet.unite( track->labels().toSet() );
}
Meta::LabelList labels = labelSet.toList();
if( m_orderByField == Meta::valLabel )
{
labels = MemoryQueryMakerHelper::orderListByName<Meta::LabelPtr>( labels, m_orderDescending );
}
- emitProperResult<Meta::LabelPtr>( labels );
+ emit newLabelsReady( labels );
break;
}
case QueryMaker::None:
//should never happen, but handle error anyway
break;
}
}
void
MemoryQueryMakerInternal::setMatchers( MemoryMatcher *matchers )
{
m_matchers = matchers;
}
void
MemoryQueryMakerInternal::setFilters( MemoryFilter *filters )
{
m_filters = filters;
}
void
MemoryQueryMakerInternal::setMaxSize( int maxSize )
{
m_maxSize = maxSize;
}
void
MemoryQueryMakerInternal::setType( QueryMaker::QueryType type )
{
m_type = type;
}
void
MemoryQueryMakerInternal::setCustomReturnFunctions( const QList<CustomReturnFunction *> &functions )
{
m_returnFunctions = functions;
}
void
MemoryQueryMakerInternal::setCustomReturnValues( const QList<CustomReturnValue *> &values )
{
m_returnValues = values;
}
} //namespace Collections
diff --git a/src/core-impl/collections/support/MemoryQueryMakerInternal.h b/src/core-impl/collections/support/MemoryQueryMakerInternal.h
index f1561a02c9..5f76b2d922 100644
--- a/src/core-impl/collections/support/MemoryQueryMakerInternal.h
+++ b/src/core-impl/collections/support/MemoryQueryMakerInternal.h
@@ -1,112 +1,108 @@
/****************************************************************************************
* Copyright (c) 2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef MEMORYQUERYMAKERINTERNAL_H
#define MEMORYQUERYMAKERINTERNAL_H
#include "core/collections/QueryMaker.h"
#include "core/meta/forward_declarations.h"
#include <QObject>
#include <QWeakPointer>
class CustomReturnFunction;
class CustomReturnValue;
class MemoryFilter;
class MemoryMatcher;
namespace Collections {
class MemoryCollection;
/**
* A helper class for MemoryQueryMaker
* This class will run in a dedicated thread. It exists so the actual MemoryQueryMaker
* can be safely deleted in the original thread while the query is still running.
* All relevant information is passed from MemoryQueryMaker to MemoryQueryMakerInternal
* using the provided setter methods.
*/
class MemoryQueryMakerInternal : public QObject
{
Q_OBJECT
public:
/**
* Creates a new MemoryQueryMakerInternal that will query collection.
* This class will run in a dedicated thread. It exists so the actual MemoryQueryMaker
* can be safely deleted in the original thread while the query is still running.
* @param guard a class that will be deleted before collection. It is used to
* ensure that MemoryQueryMakerInternal does not access a dangling MemoryCollection
* pointer.
* @param collection the MemoryCollection instance that the query should be run on.
*/
MemoryQueryMakerInternal( const QWeakPointer<Collections::MemoryCollection> &collection );
~MemoryQueryMakerInternal();
void runQuery();
void handleResult();
void handleResult( const Meta::TrackList &tracks );
void setMatchers( MemoryMatcher *matchers );
void setFilters( MemoryFilter *filters );
void setMaxSize( int maxSize );
void setReturnAsDataPtrs( bool returnAsDataPtrs );
void setType( QueryMaker::QueryType );
void setCustomReturnFunctions( const QList<CustomReturnFunction*> &functions );
void setCustomReturnValues( const QList<CustomReturnValue*> &values );
void setAlbumQueryMode( Collections::QueryMaker::AlbumQueryMode mode ) { m_albumQueryMode = mode; }
void setOrderDescending( bool orderDescending ) { m_orderDescending = orderDescending; }
void setOrderByNumberField( bool orderByNumberField ) { m_orderByNumberField = orderByNumberField; }
void setOrderByField( qint64 orderByField ) { m_orderByField = orderByField; }
void setCollectionId( const QString &collectionId ) { m_collectionId = collectionId; }
void setLabelQueryMode( Collections::QueryMaker::LabelQueryMode mode ) { m_labelQueryMode = mode; }
Q_SIGNALS:
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
+ void newComposersReady( Meta::ComposerList );
+ void newYearsReady( Meta::YearList );
void newResultReady( QStringList );
- void newResultReady( Meta::LabelList );
- void newResultReady( Meta::DataList );
-
-private:
- template <class PointerType>
- void emitProperResult( const QList<PointerType > &list );
+ void newLabelsReady( Meta::LabelList );
+ void newDataReady( Meta::DataList );
private:
QWeakPointer<Collections::MemoryCollection> m_collection;
QWeakPointer<QObject> m_guard;
MemoryMatcher *m_matchers;
MemoryFilter *m_filters;
int m_maxSize;
bool m_returnAsDataPtrs;
Collections::QueryMaker::QueryType m_type;
Collections::QueryMaker::AlbumQueryMode m_albumQueryMode;
Collections::QueryMaker::LabelQueryMode m_labelQueryMode;
bool m_orderDescending;
bool m_orderByNumberField;
qint64 m_orderByField;
QString m_collectionId;
QList<CustomReturnFunction*> m_returnFunctions;
QList<CustomReturnValue*> m_returnValues;
};
} //namespace Collections
#endif
diff --git a/src/core-impl/collections/support/TrashCollectionLocation.cpp b/src/core-impl/collections/support/TrashCollectionLocation.cpp
index 6780e086b9..91e3539dd8 100644
--- a/src/core-impl/collections/support/TrashCollectionLocation.cpp
+++ b/src/core-impl/collections/support/TrashCollectionLocation.cpp
@@ -1,139 +1,139 @@
/****************************************************************************************
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "TrashCollectionLocation"
#include "TrashCollectionLocation.h"
#include "core/collections/CollectionLocationDelegate.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include <KIO/CopyJob>
#include <KLocale>
#include <QFile>
namespace Collections {
TrashCollectionLocation::TrashCollectionLocation()
: CollectionLocation()
, m_trashConfirmed( false )
{
}
TrashCollectionLocation::~TrashCollectionLocation()
{
}
QString
TrashCollectionLocation::prettyLocation() const
{
return i18n( "Trash" );
}
bool
TrashCollectionLocation::isWritable() const
{
return true;
}
void
TrashCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration )
{
DEBUG_BLOCK
Q_UNUSED( configuration );
if( sources.isEmpty() )
{
debug() << "Error: sources is empty";
abort();
return;
}
if( m_trashConfirmed )
{
QList<QUrl> files = sources.values();
foreach( const QUrl &file, files )
{
if( !QFile::exists( file.toLocalFile() ) )
{
debug() << "Error: file does not exist!" << file.toLocalFile();
abort();
return;
}
}
KIO::CopyJob *job = KIO::trash( files, KIO::HideProgressInfo );
- connect( job, SIGNAL(result(KJob*)), SLOT(slotTrashJobFinished(KJob*)) );
+ connect( job, &KJob::result, this, &TrashCollectionLocation::slotTrashJobFinished );
Meta::TrackList tracks = sources.keys();
m_trashJobs.insert( job, tracks );
QString name = tracks.takeFirst()->prettyName();
if( !tracks.isEmpty() )
{
int max = 3;
while( !tracks.isEmpty() && (max > 0) )
{
name += QString( ", %1" ).arg( tracks.takeFirst()->prettyName() );
--max;
}
if( max == 0 && !tracks.isEmpty() )
name += " ...";
}
Amarok::Components::logger()->newProgressOperation( job, i18n( "Moving to trash: %1", name ) );
}
}
void
TrashCollectionLocation::showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration )
{
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
m_trashConfirmed = delegate->reallyTrash( source(), tracks );
if( !m_trashConfirmed )
abort();
else
CollectionLocation::showDestinationDialog( tracks, removeSources, configuration );
}
void
TrashCollectionLocation::slotTrashJobFinished( KJob *job )
{
DEBUG_BLOCK
if( job->error() )
{
warning() << "An error occurred when moving a file to trash: " << job->errorString();
foreach( Meta::TrackPtr track, m_trashJobs.value( job ) )
source()->transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) );
}
else
{
foreach( Meta::TrackPtr track, m_trashJobs.value( job ) )
source()->transferSuccessful( track );
}
m_trashJobs.remove( job );
job->deleteLater();
if( m_trashJobs.isEmpty() )
slotCopyOperationFinished();
}
} //namespace Collections
diff --git a/src/core-impl/collections/umscollection/UmsCollection.cpp b/src/core-impl/collections/umscollection/UmsCollection.cpp
index fd7481a8ad..3e8fafb2be 100644
--- a/src/core-impl/collections/umscollection/UmsCollection.cpp
+++ b/src/core-impl/collections/umscollection/UmsCollection.cpp
@@ -1,745 +1,747 @@
/****************************************************************************************
* Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "UmsCollection"
#include "UmsCollection.h"
#include "amarokconfig.h"
#include "ui_UmsConfiguration.h"
#include "collectionscanner/Track.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/MemoryQueryMaker.h"
#include "core-impl/collections/support/MemoryMeta.h"
#include "core-impl/collections/umscollection/UmsCollectionLocation.h"
#include "core-impl/collections/umscollection/UmsTranscodeCapability.h"
#include "core-impl/meta/file/File.h"
#include "dialogs/OrganizeCollectionDialog.h"
#include "dialogs/TrackOrganizer.h" //TODO: move to core/utils
#include "scanner/GenericScanManager.h"
#include <solid/deviceinterface.h>
#include <solid/devicenotifier.h>
#include <solid/genericinterface.h>
#include <solid/opticaldisc.h>
#include <solid/portablemediaplayer.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/storagevolume.h>
-#include <KDiskFreeSpaceInfo>
#include <kmimetype.h>
#include <QUrl>
#include <QThread>
#include <QTimer>
#include <KConfigGroup>
+#include <KDiskFreeSpaceInfo>
+#include <KPluginFactory>
+
AMAROK_EXPORT_COLLECTION( UmsCollectionFactory, umscollection )
UmsCollectionFactory::UmsCollectionFactory( QObject *parent, const QVariantList &args )
: CollectionFactory( parent, args )
{
m_info = KPluginInfo( "amarok_collection-umscollection.desktop" );
}
UmsCollectionFactory::~UmsCollectionFactory()
{
}
void
UmsCollectionFactory::init()
{
- connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
- SLOT(slotAddSolidDevice(QString)) );
- connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)),
- SLOT(slotRemoveSolidDevice(QString)) );
+ connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded,
+ this, &UmsCollectionFactory::slotAddSolidDevice );
+ connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved,
+ this, &UmsCollectionFactory::slotRemoveSolidDevice );
// detect UMS devices that were already connected on startup
QString query( "IS StorageAccess" );
QList<Solid::Device> devices = Solid::Device::listFromQuery( query );
foreach( const Solid::Device &device, devices )
{
if( identifySolidDevice( device.udi() ) )
createCollectionForSolidDevice( device.udi() );
}
m_initialized = true;
}
void
UmsCollectionFactory::slotAddSolidDevice( const QString &udi )
{
if( m_collectionMap.contains( udi ) )
return; // a device added twice (?)
if( identifySolidDevice( udi ) )
createCollectionForSolidDevice( udi );
}
void
UmsCollectionFactory::slotAccessibilityChanged( bool accessible, const QString &udi )
{
if( accessible )
slotAddSolidDevice( udi );
else
slotRemoveSolidDevice( udi );
}
void
UmsCollectionFactory::slotRemoveSolidDevice( const QString &udi )
{
UmsCollection *collection = m_collectionMap.take( udi );
if( collection )
collection->slotDestroy();
}
void
UmsCollectionFactory::slotRemoveAndTeardownSolidDevice( const QString &udi )
{
UmsCollection *collection = m_collectionMap.take( udi );
if( collection )
collection->slotEject();
}
void
UmsCollectionFactory::slotCollectionDestroyed( QObject *collection )
{
// remove destroyed collection from m_collectionMap
QMutableMapIterator<QString, UmsCollection *> it( m_collectionMap );
while( it.hasNext() )
{
it.next();
if( (QObject *) it.value() == collection )
it.remove();
}
}
bool
UmsCollectionFactory::identifySolidDevice( const QString &udi ) const
{
Solid::Device device( udi );
if( !device.is<Solid::StorageAccess>() )
return false;
// HACK to exlude iPods until UMS and iPod have common collection factory
if( device.vendor().contains( "Apple", Qt::CaseInsensitive ) )
return false;
// everything okay, check whether the device is a data CD
if( device.is<Solid::OpticalDisc>() )
{
const Solid::OpticalDisc *disc = device.as<Solid::OpticalDisc>();
if( disc && ( disc->availableContent() & Solid::OpticalDisc::Data ) )
return true;
return false;
}
// check whether there is parent USB StorageDrive device
while( device.isValid() )
{
if( device.is<Solid::StorageDrive>() )
{
Solid::StorageDrive *sd = device.as<Solid::StorageDrive>();
if( sd->driveType() == Solid::StorageDrive::CdromDrive )
return false;
// USB Flash discs are usually hotpluggable, SD/MMC card slots are usually removable
return sd->isHotpluggable() || sd->isRemovable();
}
device = device.parent();
}
return false; // no valid parent USB StorageDrive
}
void
UmsCollectionFactory::createCollectionForSolidDevice( const QString &udi )
{
DEBUG_BLOCK
Solid::Device device( udi );
Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
if( !ssa )
{
warning() << __PRETTY_FUNCTION__ << "called for non-StorageAccess device!?!";
return;
}
if( ssa->isIgnored() )
{
debug() << "device" << udi << "ignored, ignoring :-)";
return;
}
// we are definitely interested in this device, listen for accessibility changes
- disconnect( ssa, SIGNAL(accessibilityChanged(bool,QString)), this, 0 );
- connect( ssa, SIGNAL(accessibilityChanged(bool,QString)),
- SLOT(slotAccessibilityChanged(bool,QString)) );
+ disconnect( ssa, &Solid::StorageAccess::accessibilityChanged, this, 0 );
+ connect( ssa, &Solid::StorageAccess::accessibilityChanged,
+ this, &UmsCollectionFactory::slotAccessibilityChanged );
if( !ssa->isAccessible() )
{
debug() << "device" << udi << "not accessible, ignoring for now";
return;
}
UmsCollection *collection = new UmsCollection( device );
m_collectionMap.insert( udi, collection );
// when the collection is destroyed by someone else, remove it from m_collectionMap:
- connect( collection, SIGNAL(destroyed(QObject*)), SLOT(slotCollectionDestroyed(QObject*)) );
+ connect( collection, &QObject::destroyed, this, &UmsCollectionFactory::slotCollectionDestroyed );
// try to gracefully destroy collection when unmounting is requested using
// external means: (Device notifier plasmoid etc.). Because the original action could
// fail if we hold some files on the device open, we try to tearDown the device too.
- connect( ssa, SIGNAL(teardownRequested(QString)), SLOT(slotRemoveAndTeardownSolidDevice(QString)) );
+ connect( ssa, &Solid::StorageAccess::teardownRequested, this, &UmsCollectionFactory::slotRemoveAndTeardownSolidDevice );
emit newCollection( collection );
}
//UmsCollection
QString UmsCollection::s_settingsFileName( ".is_audio_player" );
QString UmsCollection::s_musicFolderKey( "audio_folder" );
QString UmsCollection::s_musicFilenameSchemeKey( "music_filenamescheme" );
QString UmsCollection::s_vfatSafeKey( "vfat_safe" );
QString UmsCollection::s_asciiOnlyKey( "ascii_only" );
QString UmsCollection::s_postfixTheKey( "ignore_the" );
QString UmsCollection::s_replaceSpacesKey( "replace_spaces" );
QString UmsCollection::s_regexTextKey( "regex_text" );
QString UmsCollection::s_replaceTextKey( "replace_text" );
QString UmsCollection::s_podcastFolderKey( "podcast_folder" );
QString UmsCollection::s_autoConnectKey( "use_automatically" );
QString UmsCollection::s_collectionName( "collection_name" );
QString UmsCollection::s_transcodingGroup( "transcoding" );
UmsCollection::UmsCollection( Solid::Device device )
: Collection()
, m_device( device )
, m_mc( 0 )
, m_tracksParsed( false )
, m_autoConnect( false )
, m_musicFilenameScheme( "%artist%/%album%/%track% %title%" )
, m_vfatSafe( true )
, m_asciiOnly( false )
, m_postfixThe( false )
, m_replaceSpaces( false )
, m_regexText( QString() )
, m_replaceText( QString() )
, m_collectionName( QString() )
, m_scanManager( 0 )
, m_lastUpdated( 0 )
{
debug() << "Creating UmsCollection for device with udi: " << m_device.udi();
m_updateTimer.setSingleShot( true );
- connect( this, SIGNAL(startUpdateTimer()), SLOT(slotStartUpdateTimer()) );
- connect( &m_updateTimer, SIGNAL(timeout()), SLOT(collectionUpdated()) );
+ connect( this, &UmsCollection::startUpdateTimer, this, &UmsCollection::slotStartUpdateTimer );
+ connect( &m_updateTimer, &QTimer::timeout, this, &UmsCollection::collectionUpdated );
m_configureAction = new QAction( QIcon::fromTheme( "configure" ), i18n( "&Configure Device" ), this );
m_configureAction->setProperty( "popupdropper_svg_id", "configure" );
- connect( m_configureAction, SIGNAL(triggered()), SLOT(slotConfigure()) );
+ connect( m_configureAction, &QAction::triggered, this, &UmsCollection::slotConfigure );
m_parseAction = new QAction( QIcon::fromTheme( "checkbox" ), i18n( "&Activate This Collection" ), this );
m_parseAction->setProperty( "popupdropper_svg_id", "edit" );
- connect( m_parseAction, SIGNAL(triggered()), this, SLOT(slotParseActionTriggered()) );
+ connect( m_parseAction, &QAction::triggered, this, &UmsCollection::slotParseActionTriggered );
m_ejectAction = new QAction( QIcon::fromTheme( "media-eject" ), i18n( "&Eject Device" ),
const_cast<UmsCollection*>( this ) );
m_ejectAction->setProperty( "popupdropper_svg_id", "eject" );
- connect( m_ejectAction, SIGNAL(triggered()), SLOT(slotEject()) );
+ connect( m_ejectAction, &QAction::triggered, this, &UmsCollection::slotEject );
init();
}
UmsCollection::~UmsCollection()
{
DEBUG_BLOCK
}
void
UmsCollection::init()
{
Solid::StorageAccess *storageAccess = m_device.as<Solid::StorageAccess>();
m_mountPoint = storageAccess->filePath();
Solid::StorageVolume *ssv = m_device.as<Solid::StorageVolume>();
m_collectionId = ssv ? ssv->uuid() : m_device.udi();
debug() << "Mounted at: " << m_mountPoint << "collection id:" << m_collectionId;
// read .is_audio_player from filesystem
KConfig config( m_mountPoint + '/' + s_settingsFileName, KConfig::SimpleConfig );
KConfigGroup entries = config.group( QString() ); // default group
if( entries.hasKey( s_musicFolderKey ) )
{
m_musicPath = QUrl( m_mountPoint );
m_musicPath = m_musicPath.adjusted(QUrl::StripTrailingSlash);
m_musicPath.setPath(m_musicPath.path() + '/' + ( entries.readPathEntry( s_musicFolderKey, QString() ) ));
m_musicPath.setPath( QDir::cleanPath(m_musicPath.path()) );
if( !QDir( m_musicPath.toLocalFile() ).exists() )
{
QString message = i18n( "File <i>%1</i> suggests that we should use <i>%2</i> "
"as music folder on the device, but it doesn't exist. Falling back to "
"<i>%3</i> instead", m_mountPoint + '/' + s_settingsFileName,
m_musicPath.toLocalFile(), m_mountPoint );
Amarok::Components::logger()->longMessage( message, Amarok::Logger::Warning );
m_musicPath = QUrl(m_mountPoint);
}
}
else if( !entries.keyList().isEmpty() )
// config file exists, but has no s_musicFolderKey -> music should be disabled
m_musicPath = QUrl();
else
m_musicPath = QUrl(m_mountPoint); // related BR 259849
QString scheme = entries.readEntry( s_musicFilenameSchemeKey );
m_musicFilenameScheme = !scheme.isEmpty() ? scheme : m_musicFilenameScheme;
m_vfatSafe = entries.readEntry( s_vfatSafeKey, m_vfatSafe );
m_asciiOnly = entries.readEntry( s_asciiOnlyKey, m_asciiOnly );
m_postfixThe = entries.readEntry( s_postfixTheKey, m_postfixThe );
m_replaceSpaces = entries.readEntry( s_replaceSpacesKey, m_replaceSpaces );
m_regexText = entries.readEntry( s_regexTextKey, m_regexText );
m_replaceText = entries.readEntry( s_replaceTextKey, m_replaceText );
if( entries.hasKey( s_podcastFolderKey ) )
{
m_podcastPath = QUrl( m_mountPoint );
m_podcastPath = m_podcastPath.adjusted(QUrl::StripTrailingSlash);
m_podcastPath.setPath(m_podcastPath.path() + '/' + ( entries.readPathEntry( s_podcastFolderKey, QString() ) ));
m_podcastPath.setPath( QDir::cleanPath(m_podcastPath.path()) );
}
m_autoConnect = entries.readEntry( s_autoConnectKey, m_autoConnect );
m_collectionName = entries.readEntry( s_collectionName, m_collectionName );
m_mc = QSharedPointer<MemoryCollection>(new MemoryCollection());
if( m_autoConnect )
QTimer::singleShot( 0, this, SLOT(slotParseTracks()) );
}
bool
UmsCollection::possiblyContainsTrack( const QUrl &url ) const
{
//not initialized yet.
if( m_mc.isNull() )
return false;
QString u = QUrl::fromPercentEncoding( url.url().toUtf8() );
return u.startsWith( m_mountPoint ) || u.startsWith( "file://" + m_mountPoint );
}
Meta::TrackPtr
UmsCollection::trackForUrl( const QUrl &url )
{
//not initialized yet.
if( m_mc.isNull() )
return Meta::TrackPtr();
QString uid = QUrl::fromPercentEncoding( url.url().toUtf8() );
if( uid.startsWith("file://") )
uid = uid.remove( 0, 7 );
return m_mc->trackMap().value( uid, Meta::TrackPtr() );
}
QueryMaker *
UmsCollection::queryMaker()
{
return new MemoryQueryMaker( m_mc.toWeakRef(), collectionId() );
}
QString
UmsCollection::uidUrlProtocol() const
{
return QString( "file://" );
}
QString
UmsCollection::collectionId() const
{
return m_collectionId;
}
QString
UmsCollection::prettyName() const
{
QString actualName;
if( !m_collectionName.isEmpty() )
actualName = m_collectionName;
else if( !m_device.description().isEmpty() )
actualName = m_device.description();
else
{
actualName = m_device.vendor().simplified();
if( !actualName.isEmpty() )
actualName += ' ';
actualName += m_device.product().simplified();
}
if( m_tracksParsed )
return actualName;
else
return i18nc( "Name of the USB Mass Storage collection that has not yet been "
"activated. See also the 'Activate This Collection' action; %1 is "
"actual collection name", "%1 (not activated)", actualName );
}
QIcon
UmsCollection::icon() const
{
if( m_device.icon().isEmpty() )
return QIcon::fromTheme( "drive-removable-media-usb-pendrive" );
else
return QIcon::fromTheme( m_device.icon() );
}
bool
UmsCollection::hasCapacity() const
{
if( m_device.isValid() && m_device.is<Solid::StorageAccess>() )
return m_device.as<Solid::StorageAccess>()->isAccessible();
return false;
}
float
UmsCollection::usedCapacity() const
{
return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).used();
}
float
UmsCollection::totalCapacity() const
{
return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).size();
}
CollectionLocation *
UmsCollection::location()
{
return new UmsCollectionLocation( this );
}
bool
UmsCollection::isOrganizable() const
{
return isWritable();
}
bool
UmsCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
switch( type )
{
case Capabilities::Capability::Actions:
case Capabilities::Capability::Transcode:
return true;
default:
return false;
}
}
Capabilities::Capability *
UmsCollection::createCapabilityInterface( Capabilities::Capability::Type type )
{
switch( type )
{
case Capabilities::Capability::Actions:
{
QList<QAction *> actions;
if( m_tracksParsed )
{
actions << m_configureAction;
actions << m_ejectAction;
}
else
{
actions << m_parseAction;
}
return new Capabilities::ActionsCapability( actions );
}
case Capabilities::Capability::Transcode:
return new UmsTranscodeCapability( m_mountPoint + '/' + s_settingsFileName,
s_transcodingGroup );
default:
return 0;
}
}
void
UmsCollection::metadataChanged( Meta::TrackPtr track )
{
if( MemoryMeta::MapChanger( m_mc.data() ).trackChanged( track ) )
// big-enough change:
emit startUpdateTimer();
}
QUrl
UmsCollection::organizedUrl( Meta::TrackPtr track, const QString &fileExtension ) const
{
TrackOrganizer trackOrganizer( Meta::TrackList() << track );
//%folder% prefix required to get absolute url.
trackOrganizer.setFormatString( "%collectionroot%/" + m_musicFilenameScheme + ".%filetype%" );
trackOrganizer.setVfatSafe( m_vfatSafe );
trackOrganizer.setAsciiOnly( m_asciiOnly );
trackOrganizer.setFolderPrefix( m_musicPath.path() );
trackOrganizer.setPostfixThe( m_postfixThe );
trackOrganizer.setReplaceSpaces( m_replaceSpaces );
trackOrganizer.setReplace( m_regexText, m_replaceText );
if( !fileExtension.isEmpty() )
trackOrganizer.setTargetFileExtension( fileExtension );
return QUrl( trackOrganizer.getDestinations().value( track ) );
}
void
UmsCollection::slotDestroy()
{
//TODO: stop scanner if running
//unregister PlaylistProvider
//CollectionManager will call destructor.
emit remove();
}
void
UmsCollection::slotEject()
{
slotDestroy();
Solid::StorageAccess *storageAccess = m_device.as<Solid::StorageAccess>();
storageAccess->teardown();
}
void
UmsCollection::slotTrackAdded( QUrl location )
{
Q_ASSERT( m_musicPath.isParentOf( location ) || m_musicPath.matches( location , QUrl::StripTrailingSlash) );
MetaFile::Track *fileTrack = new MetaFile::Track( location );
fileTrack->setCollection( this );
Meta::TrackPtr fileTrackPtr = Meta::TrackPtr( fileTrack );
Meta::TrackPtr proxyTrack = MemoryMeta::MapChanger( m_mc.data() ).addTrack( fileTrackPtr );
if( proxyTrack )
{
subscribeTo( fileTrackPtr );
emit startUpdateTimer();
}
else
warning() << __PRETTY_FUNCTION__ << "Failed to add" << fileTrackPtr->playableUrl()
<< "to MemoryCollection. Perhaps already there?!?";
}
void
UmsCollection::slotTrackRemoved( const Meta::TrackPtr &track )
{
Meta::TrackPtr removedTrack = MemoryMeta::MapChanger( m_mc.data() ).removeTrack( track );
if( removedTrack )
{
unsubscribeFrom( removedTrack );
// we only added MetaFile::Tracks, following static cast is safe
static_cast<MetaFile::Track*>( removedTrack.data() )->setCollection( 0 );
emit startUpdateTimer();
}
else
warning() << __PRETTY_FUNCTION__ << "Failed to remove" << track->playableUrl()
<< "from MemoryCollection. Perhaps it was never there?";
}
void
UmsCollection::collectionUpdated()
{
m_lastUpdated = QDateTime::currentMSecsSinceEpoch();
emit updated();
}
void
UmsCollection::slotParseTracks()
{
if( !m_scanManager )
{
m_scanManager = new GenericScanManager( this );
- connect( m_scanManager, SIGNAL(directoryScanned(QSharedPointer<CollectionScanner::Directory>)),
- SLOT(slotDirectoryScanned(QSharedPointer<CollectionScanner::Directory>)) );
+ connect( m_scanManager, &GenericScanManager::directoryScanned,
+ this, &UmsCollection::slotDirectoryScanned );
}
m_tracksParsed = true;
m_scanManager->requestScan( QList<QUrl>() << m_musicPath, GenericScanManager::FullScan );
}
void
UmsCollection::slotParseActionTriggered()
{
if( m_mc->trackMap().isEmpty() )
QTimer::singleShot( 0, this, SLOT(slotParseTracks()) );
}
void
UmsCollection::slotConfigure()
{
KDialog umsSettingsDialog;
QWidget *settingsWidget = new QWidget( &umsSettingsDialog );
QScopedPointer<Capabilities::TranscodeCapability> tc( create<Capabilities::TranscodeCapability>() );
Ui::UmsConfiguration *settings = new Ui::UmsConfiguration();
settings->setupUi( settingsWidget );
settings->m_autoConnect->setChecked( m_autoConnect );
settings->m_musicFolder->setMode( KFile::Directory );
settings->m_musicCheckBox->setChecked( !m_musicPath.isEmpty() );
settings->m_musicWidget->setEnabled( settings->m_musicCheckBox->isChecked() );
settings->m_musicFolder->setUrl( m_musicPath.isEmpty() ? QUrl( m_mountPoint ) : m_musicPath );
settings->m_transcodeConfig->fillInChoices( tc->savedConfiguration() );
settings->m_podcastFolder->setMode( KFile::Directory );
settings->m_podcastCheckBox->setChecked( !m_podcastPath.isEmpty() );
settings->m_podcastWidget->setEnabled( settings->m_podcastCheckBox->isChecked() );
settings->m_podcastFolder->setUrl( m_podcastPath.isEmpty() ? QUrl( m_mountPoint )
: m_podcastPath );
settings->m_collectionName->setText( prettyName() );
OrganizeCollectionWidget layoutWidget( &umsSettingsDialog );
//TODO: save the setting that are normally written in onAccept()
// connect( this, SIGNAL(accepted()), &layoutWidget, SLOT(onAccept()) );
QVBoxLayout layout( &umsSettingsDialog );
layout.addWidget( &layoutWidget );
settings->m_filenameSchemeBox->setLayout( &layout );
//hide the unuse preset selector.
//TODO: change the presets to concurrent presets for regular albums v.s. compilations
// layoutWidget.setformatPresetVisible( false );
layoutWidget.setScheme( m_musicFilenameScheme );
OrganizeCollectionOptionWidget optionsWidget;
optionsWidget.setVfatCompatible( m_vfatSafe );
optionsWidget.setAsciiOnly( m_asciiOnly );
optionsWidget.setPostfixThe( m_postfixThe );
optionsWidget.setReplaceSpaces( m_replaceSpaces );
optionsWidget.setRegexpText( m_regexText );
optionsWidget.setReplaceText( m_replaceText );
layout.addWidget( &optionsWidget );
umsSettingsDialog.setButtons( KDialog::Ok | KDialog::Cancel );
umsSettingsDialog.setMainWidget( settingsWidget );
umsSettingsDialog.setWindowTitle( i18n( "Configure USB Mass Storage Device" ) );
if( umsSettingsDialog.exec() == QDialog::Accepted )
{
debug() << "accepted";
if( settings->m_musicCheckBox->isChecked() )
{
if( settings->m_musicFolder->url() != m_musicPath )
{
debug() << "music location changed from " << m_musicPath.toLocalFile() << " to ";
debug() << settings->m_musicFolder->url().toLocalFile();
m_musicPath = settings->m_musicFolder->url();
//TODO: reparse music
}
QString scheme = layoutWidget.getParsableScheme().simplified();
//protect against empty string.
if( !scheme.isEmpty() )
m_musicFilenameScheme = scheme;
}
else
{
debug() << "music support is disabled";
m_musicPath = QUrl();
//TODO: remove all tracks from the MemoryCollection.
}
m_asciiOnly = optionsWidget.asciiOnly();
m_postfixThe = optionsWidget.postfixThe();
m_replaceSpaces = optionsWidget.replaceSpaces();
m_regexText = optionsWidget.regexpText();
m_replaceText = optionsWidget.replaceText();
m_collectionName = settings->m_collectionName->text();
if( settings->m_podcastCheckBox->isChecked() )
{
if( settings->m_podcastFolder->url() != m_podcastPath )
{
debug() << "podcast location changed from " << m_podcastPath << " to ";
debug() << settings->m_podcastFolder->url().url();
m_podcastPath = QUrl(settings->m_podcastFolder->url().toLocalFile());
//TODO: reparse podcasts
}
}
else
{
debug() << "podcast support is disabled";
m_podcastPath = QUrl();
//TODO: remove the PodcastProvider
}
m_autoConnect = settings->m_autoConnect->isChecked();
if( !m_musicPath.isEmpty() && m_autoConnect )
QTimer::singleShot( 0, this, SLOT(slotParseTracks()) );
// write the data to the on-disk file
KConfig config( m_mountPoint + '/' + s_settingsFileName, KConfig::SimpleConfig );
KConfigGroup entries = config.group( QString() ); // default group
if( !m_musicPath.isEmpty() )
entries.writePathEntry( s_musicFolderKey, QDir( m_mountPoint ).relativeFilePath( m_musicPath.toLocalFile() ));
else
entries.deleteEntry( s_musicFolderKey );
entries.writeEntry( s_musicFilenameSchemeKey, m_musicFilenameScheme );
entries.writeEntry( s_vfatSafeKey, m_vfatSafe );
entries.writeEntry( s_asciiOnlyKey, m_asciiOnly );
entries.writeEntry( s_postfixTheKey, m_postfixThe );
entries.writeEntry( s_replaceSpacesKey, m_replaceSpaces );
entries.writeEntry( s_regexTextKey, m_regexText );
entries.writeEntry( s_replaceTextKey, m_replaceText );
if( !m_podcastPath.isEmpty() )
entries.writePathEntry( s_podcastFolderKey, QDir( m_mountPoint ).relativeFilePath( m_podcastPath.toLocalFile() ));
else
entries.deleteEntry( s_podcastFolderKey );
entries.writeEntry( s_autoConnectKey, m_autoConnect );
entries.writeEntry( s_collectionName, m_collectionName );
config.sync();
tc->setSavedConfiguration( settings->m_transcodeConfig->currentChoice() );
}
delete settings;
}
void
UmsCollection::slotDirectoryScanned( QSharedPointer<CollectionScanner::Directory> dir )
{
debug() << "directory scanned: " << dir->path();
if( dir->tracks().isEmpty() )
{
debug() << "does not have tracks";
return;
}
foreach( const CollectionScanner::Track *scannerTrack, dir->tracks() )
{
//TODO: use proxy tracks so no real file read is required
// following method calls startUpdateTimer(), no need to emit updated()
slotTrackAdded( QUrl(scannerTrack->path()) );
}
//TODO: read playlists
}
void
UmsCollection::slotStartUpdateTimer()
{
// there are no concurrency problems, this method can only be called from the main
// thread and that's where the timer fires
if( m_updateTimer.isActive() )
return; // already running, nothing to do
// number of milliseconds to next desired update, may be negative
int timeout = m_lastUpdated + 1000 - QDateTime::currentMSecsSinceEpoch();
// give at least 50 msecs to catch multi-tracks edits nicely on the first frame
m_updateTimer.start( qBound( 50, timeout, 1000 ) );
}
diff --git a/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp b/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp
index 201e5ecd12..5d59ed10b1 100644
--- a/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp
+++ b/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp
@@ -1,273 +1,273 @@
/****************************************************************************************
* Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "UmsCollectionLocation.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/interfaces/Logger.h"
#include "core/transcoding/TranscodingController.h"
#include "core-impl/meta/file/File.h"
#include "transcoding/TranscodingJob.h"
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/Job>
#include <QUrl>
#include <KIO/JobClasses>
#include <KLocalizedString>
#include <QDir>
UmsCollectionLocation::UmsCollectionLocation( UmsCollection *umsCollection )
: CollectionLocation( umsCollection )
, m_umsCollection( umsCollection )
{
}
UmsCollectionLocation::~UmsCollectionLocation()
{
}
QString
UmsCollectionLocation::prettyLocation() const
{
return m_umsCollection->musicPath().adjusted(QUrl::StripTrailingSlash).toLocalFile();
}
QStringList
UmsCollectionLocation::actualLocation() const
{
return QStringList() << prettyLocation();
}
bool
UmsCollectionLocation::isWritable() const
{
const QFileInfo info( m_umsCollection->musicPath().toLocalFile() );
return info.isWritable();
}
bool
UmsCollectionLocation::isOrganizable() const
{
return isWritable();
}
void
UmsCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration )
{
//TODO: disable scanning until we are done with copying
UmsTransferJob *transferJob = new UmsTransferJob( this, configuration );
QMapIterator<Meta::TrackPtr, QUrl> i( sources );
while( i.hasNext() )
{
i.next();
Meta::TrackPtr track = i.key();
QUrl destination;
bool isJustCopy = configuration.isJustCopy( track );
if( isJustCopy )
destination = m_umsCollection->organizedUrl( track );
else
destination = m_umsCollection->organizedUrl( track, Amarok::Components::
transcodingController()->format( configuration.encoder() )->fileExtension() );
debug() << "destination is " << destination.toLocalFile();
QDir dir( destination.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() );
if( !dir.exists() && !dir.mkpath( "." ) )
{
error() << "could not create directory to copy into.";
abort();
}
m_sourceUrlToTrackMap.insert( i.value(), track ); // needed for slotTrackTransferred()
if( isJustCopy )
transferJob->addCopy( i.value(), destination );
else
transferJob->addTranscode( i.value(), destination );
}
- connect( transferJob, SIGNAL(sourceFileTransferDone(QUrl)),
- this, SLOT(slotTrackTransferred(QUrl)) );
- connect( transferJob, SIGNAL(fileTransferDone(QUrl)),
- m_umsCollection, SLOT(slotTrackAdded(QUrl)) );
- connect( transferJob, SIGNAL(finished(KJob*)),
- this, SLOT(slotCopyOperationFinished()) );
+ connect( transferJob, &UmsTransferJob::sourceFileTransferDone,
+ this, &UmsCollectionLocation::slotTrackTransferred );
+ connect( transferJob, &UmsTransferJob::fileTransferDone,
+ m_umsCollection, &UmsCollection::slotTrackAdded );
+ connect( transferJob, &UmsTransferJob::finished,
+ this, &UmsCollectionLocation::slotCopyOperationFinished );
QString loggerText = operationInProgressText( configuration, sources.count(), m_umsCollection->prettyName() );
Amarok::Components::logger()->newProgressOperation( transferJob, loggerText, transferJob,
SLOT(slotCancel()) );
transferJob->start();
}
void
UmsCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources )
{
QList<QUrl> sourceUrls;
foreach( const Meta::TrackPtr track, sources )
{
QUrl trackUrl = track->playableUrl();
m_sourceUrlToTrackMap.insert( trackUrl, track );
sourceUrls.append( trackUrl );
}
QString loggerText = i18np( "Removing one track from %2",
"Removing %1 tracks from %2", sourceUrls.count(),
m_umsCollection->prettyName() );
KIO::DeleteJob *delJob = KIO::del( sourceUrls, KIO::HideProgressInfo );
Amarok::Components::logger()->newProgressOperation( delJob, loggerText, delJob, SLOT(kill()) );
- connect( delJob, SIGNAL(finished(KJob*)), SLOT(slotRemoveOperationFinished()) );
+ connect( delJob, &KIO::DeleteJob::finished, this, &UmsCollectionLocation::slotRemoveOperationFinished );
}
void
UmsCollectionLocation::slotTrackTransferred( const QUrl &sourceTrackUrl )
{
Meta::TrackPtr sourceTrack = m_sourceUrlToTrackMap.value( sourceTrackUrl );
if( !sourceTrack )
warning() << __PRETTY_FUNCTION__ << ": I don't know about" << sourceTrackUrl;
else
// this is needed for example for "move" operation to actually remove source tracks
source()->transferSuccessful( sourceTrack );
}
void UmsCollectionLocation::slotRemoveOperationFinished()
{
foreach( Meta::TrackPtr track, m_sourceUrlToTrackMap )
{
QUrl trackUrl = track->playableUrl();
if( !trackUrl.isLocalFile() // just pretend it was deleted
|| !QFileInfo( trackUrl.toLocalFile() ).exists() )
{
// good, the file was deleted. following is needed to trigger
// CollectionLocation's functionality to remove empty dirs:
transferSuccessful( track );
m_umsCollection->slotTrackRemoved( track );
}
}
CollectionLocation::slotRemoveOperationFinished();
}
UmsTransferJob::UmsTransferJob( UmsCollectionLocation* location,
const Transcoding::Configuration &configuration )
: KCompositeJob( location )
, m_location( location )
, m_transcodingConfiguration( configuration )
, m_abort( false )
{
setCapabilities( KJob::Killable );
}
void
UmsTransferJob::addCopy( const QUrl &from, const QUrl &to )
{
m_copyList << KUrlPair( from, to );
}
void
UmsTransferJob::addTranscode( const QUrl &from, const QUrl &to )
{
m_transcodeList << KUrlPair( from, to );
}
void
UmsTransferJob::start()
{
DEBUG_BLOCK;
if( m_copyList.isEmpty() && m_transcodeList.isEmpty() )
return;
m_totalTracks = m_transcodeList.size() + m_copyList.size();
startNextJob();
}
void
UmsTransferJob::slotCancel()
{
m_abort = true;
}
void
UmsTransferJob::startNextJob()
{
if( m_abort )
{
emitResult();
return;
}
KJob *job;
if( !m_transcodeList.isEmpty() )
{
KUrlPair urlPair = m_transcodeList.takeFirst();
job = new Transcoding::Job( urlPair.first, urlPair.second, m_transcodingConfiguration );
}
else if( !m_copyList.isEmpty() )
{
KUrlPair urlPair = m_copyList.takeFirst();
job = KIO::file_copy( urlPair.first, urlPair.second, -1, KIO::HideProgressInfo );
}
else
{
emitResult();
return;
}
connect( job, SIGNAL(percent(KJob*,ulong)),
SLOT(slotChildJobPercent(KJob*,ulong)) );
addSubjob( job );
job->start(); // no-op for KIO job, but matters for transcoding job
}
void
UmsTransferJob::slotChildJobPercent( KJob *job, unsigned long percentage )
{
Q_UNUSED(job)
// the -1 is for the current track that is being processed but already removed from transferList
int alreadyTransferred = m_totalTracks - m_transcodeList.size() - m_copyList.size() - 1;
emitPercent( alreadyTransferred * 100.0 + percentage, 100.0 * m_totalTracks );
}
void
UmsTransferJob::slotResult( KJob *job )
{
removeSubjob( job );
if( job->error() == KJob::NoError )
{
KIO::FileCopyJob *copyJob = dynamic_cast<KIO::FileCopyJob *>( job );
Transcoding::Job *transcodingJob = dynamic_cast<Transcoding::Job *>( job );
if( copyJob )
{
emit sourceFileTransferDone( copyJob->srcUrl() );
emit fileTransferDone( copyJob->destUrl() );
}
else if( transcodingJob )
{
emit sourceFileTransferDone( transcodingJob->srcUrl() );
emit fileTransferDone( transcodingJob->destUrl() );
}
else
Debug::warning() << __PRETTY_FUNCTION__ << "invalid job passed to me!";
}
else
Debug::warning() << __PRETTY_FUNCTION__ << "job failed with" << job->error();
// transcoding job currently doesn't emit percentage, so emit it at least once for track
emitPercent( m_totalTracks - ( m_transcodeList.size() + m_copyList.size() ),
m_totalTracks );
startNextJob();
}
diff --git a/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp b/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp
index ef2db1ca23..836334097a 100644
--- a/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp
+++ b/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp
@@ -1,563 +1,562 @@
/****************************************************************************************
* Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "UmsPodcastProvider.h"
#include "core/support/Debug.h"
#include <KDialog>
#include <KIO/DeleteJob>
#include <KIO/FileCopyJob>
#include <KIO/Job>
#include <KMimeType>
#include <QAction>
#include <QDirIterator>
#include <QLabel>
#include <QListWidget>
#include <QObject>
#include <QVBoxLayout>
#include <QMimeDatabase>
#include <QMimeType>
#include <KConfigGroup>
using namespace Podcasts;
UmsPodcastProvider::UmsPodcastProvider( QUrl scanDirectory )
: m_scanDirectory( scanDirectory )
, m_deleteEpisodeAction( 0 )
, m_deleteChannelAction( 0 )
{
}
UmsPodcastProvider::~UmsPodcastProvider()
{
}
bool
UmsPodcastProvider::possiblyContainsTrack( const QUrl &url ) const
{
Q_UNUSED( url )
return false;
}
Meta::TrackPtr
UmsPodcastProvider::trackForUrl( const QUrl &url )
{
Q_UNUSED( url )
return Meta::TrackPtr();
}
PodcastEpisodePtr
UmsPodcastProvider::episodeForGuid( const QString &guid )
{
Q_UNUSED( guid )
return PodcastEpisodePtr();
}
void
UmsPodcastProvider::addPodcast( const QUrl &url )
{
Q_UNUSED( url );
}
PodcastChannelPtr
UmsPodcastProvider::addChannel( PodcastChannelPtr channel )
{
UmsPodcastChannelPtr umsChannel = UmsPodcastChannelPtr(
new UmsPodcastChannel( channel, this ) );
m_umsChannels << umsChannel;
emit playlistAdded( Playlists::PlaylistPtr( umsChannel.data() ) );
return PodcastChannelPtr( umsChannel.data() );
}
PodcastEpisodePtr
UmsPodcastProvider::addEpisode( PodcastEpisodePtr episode )
{
QUrl localFilePath = episode->playableUrl();
if( !localFilePath.isLocalFile() )
return PodcastEpisodePtr();
QUrl destination = QUrl( m_scanDirectory );
destination = destination.adjusted(QUrl::StripTrailingSlash);
destination.setPath(destination.path() + '/' + ( Amarok::vfatPath( episode->channel()->prettyName() ) ));
KIO::mkdir( destination );
destination = destination.adjusted(QUrl::StripTrailingSlash);
destination.setPath(destination.path() + '/' + ( Amarok::vfatPath( localFilePath.fileName() ) ));
debug() << QString( "Copy episode \"%1\" to %2" ).arg( localFilePath.path())
.arg( destination.path() );
KIO::FileCopyJob *copyJob = KIO::file_copy( localFilePath, destination );
- connect( copyJob, SIGNAL(result(KJob*)), SLOT(slotCopyComplete(KJob*)) );
+ connect( copyJob, &KJob::result, this, &UmsPodcastProvider::slotCopyComplete );
copyJob->start();
//we have not copied the data over yet so we can't return an episode yet
//TODO: return a proxy for the episode we are still copying.
return PodcastEpisodePtr();
}
void
UmsPodcastProvider::slotCopyComplete( KJob *job )
{
KIO::FileCopyJob *copyJob = dynamic_cast<KIO::FileCopyJob *>( job );
if( !copyJob )
return;
QUrl localFilePath = copyJob->destUrl();
MetaFile::Track *fileTrack = new MetaFile::Track( localFilePath );
UmsPodcastEpisodePtr umsEpisode = addFile( MetaFile::TrackPtr( fileTrack ) );
}
PodcastChannelList
UmsPodcastProvider::channels()
{
return UmsPodcastChannel::toPodcastChannelList( m_umsChannels );
}
void
UmsPodcastProvider::removeSubscription( PodcastChannelPtr channel )
{
UmsPodcastChannelPtr umsChannel = UmsPodcastChannelPtr::dynamicCast( channel );
if( umsChannel.isNull() )
{
error() << "trying to remove a podcast channel of the wrong type";
return;
}
if( !m_umsChannels.contains( umsChannel ) )
{
error() << "trying to remove a podcast channel that is not in the list";
return;
}
m_umsChannels.removeAll( umsChannel );
}
void
UmsPodcastProvider::configureProvider()
{
}
void
UmsPodcastProvider::configureChannel( PodcastChannelPtr channel )
{
Q_UNUSED( channel );
}
QString
UmsPodcastProvider::prettyName() const
{
return i18nc( "Podcasts on a media device", "Podcasts on %1", QString("TODO: replace me") );
}
QIcon
UmsPodcastProvider::icon() const
{
return QIcon::fromTheme("drive-removable-media-usb-pendrive");
}
Playlists::PlaylistList
UmsPodcastProvider::playlists()
{
Playlists::PlaylistList playlists;
foreach( UmsPodcastChannelPtr channel, m_umsChannels )
playlists << Playlists::PlaylistPtr::dynamicCast( channel );
return playlists;
}
QActionList
UmsPodcastProvider::episodeActions( PodcastEpisodeList episodes )
{
QActionList actions;
if( episodes.isEmpty() )
return actions;
if( m_deleteEpisodeAction == 0 )
{
m_deleteEpisodeAction = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete Episode" ), this );
m_deleteEpisodeAction->setProperty( "popupdropper_svg_id", "delete" );
- connect( m_deleteEpisodeAction, SIGNAL(triggered()), SLOT(slotDeleteEpisodes()) );
+ connect( m_deleteEpisodeAction, &QAction::triggered, this, &UmsPodcastProvider::slotDeleteEpisodes );
}
// set the episode list as data that we'll retrieve in the slot
m_deleteEpisodeAction->setData( QVariant::fromValue( episodes ) );
actions << m_deleteEpisodeAction;
return actions;
}
void
UmsPodcastProvider::slotDeleteEpisodes()
{
DEBUG_BLOCK
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
//get the list of episodes to apply to, then clear that data.
PodcastEpisodeList episodes =
action->data().value<PodcastEpisodeList>();
action->setData( QVariant() );
UmsPodcastEpisodeList umsEpisodes;
foreach( PodcastEpisodePtr episode, episodes )
{
UmsPodcastEpisodePtr umsEpisode =
UmsPodcastEpisode::fromPodcastEpisodePtr( episode );
if( !umsEpisode )
{
error() << "Could not cast to UmsPodcastEpisode";
continue;
}
PodcastChannelPtr channel = umsEpisode->channel();
if( !channel )
{
error() << "episode did not have a valid channel";
continue;
}
UmsPodcastChannelPtr umsChannel =
UmsPodcastChannel::fromPodcastChannelPtr( channel );
if( !umsChannel )
{
error() << "Could not cast to UmsPodcastChannel";
continue;
}
umsEpisodes << umsEpisode;
}
deleteEpisodes( umsEpisodes );
}
void
UmsPodcastProvider::deleteEpisodes( UmsPodcastEpisodeList umsEpisodes )
{
QList<QUrl> urlsToDelete;
foreach( UmsPodcastEpisodePtr umsEpisode, umsEpisodes )
urlsToDelete << umsEpisode->playableUrl();
KDialog dialog;
dialog.setCaption( i18n( "Confirm Delete" ) );
dialog.setButtons( KDialog::Ok | KDialog::Cancel );
QLabel label( i18np( "Are you sure you want to delete this episode?",
"Are you sure you want to delete these %1 episodes?",
urlsToDelete.count() )
, &dialog
);
QListWidget listWidget( &dialog );
listWidget.setSelectionMode( QAbstractItemView::NoSelection );
foreach( const QUrl &url, urlsToDelete )
{
new QListWidgetItem( url.toLocalFile(), &listWidget );
}
QWidget *widget = new QWidget( &dialog );
QVBoxLayout *layout = new QVBoxLayout( widget );
layout->addWidget( &label );
layout->addWidget( &listWidget );
dialog.setButtonText( KDialog::Ok, i18n( "Yes, delete from %1.",
QString("TODO: replace me") ) );
dialog.setMainWidget( widget );
if( dialog.exec() != QDialog::Accepted )
return;
KIO::DeleteJob *deleteJob = KIO::del( urlsToDelete, KIO::HideProgressInfo );
//keep track of these episodes until the job is done
m_deleteJobMap.insert( deleteJob, umsEpisodes );
- connect( deleteJob, SIGNAL(result(KJob*)),
- SLOT(deleteJobComplete(KJob*)) );
+ connect( deleteJob, &KJob::result, this, &UmsPodcastProvider::deleteJobComplete );
}
void
UmsPodcastProvider::deleteJobComplete( KJob *job )
{
DEBUG_BLOCK
if( job->error() )
{
error() << "problem deleting episode(s): " << job->errorString();
return;
}
UmsPodcastEpisodeList deletedEpisodes = m_deleteJobMap.take( job );
foreach( UmsPodcastEpisodePtr deletedEpisode, deletedEpisodes )
{
PodcastChannelPtr channel = deletedEpisode->channel();
UmsPodcastChannelPtr umsChannel =
UmsPodcastChannel::fromPodcastChannelPtr( channel );
if( !umsChannel )
{
error() << "Could not cast to UmsPodcastChannel";
continue;
}
umsChannel->removeEpisode( deletedEpisode );
if( umsChannel->m_umsEpisodes.isEmpty() )
{
debug() << "channel is empty now, remove it";
m_umsChannels.removeAll( umsChannel );
emit( playlistRemoved( Playlists::PlaylistPtr::dynamicCast( umsChannel ) ) );
}
}
}
QActionList
UmsPodcastProvider::channelActions( PodcastChannelList channels )
{
QActionList actions;
if( channels.isEmpty() )
return actions;
if( m_deleteChannelAction == 0 )
{
m_deleteChannelAction = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete "
"Channel and Episodes" ), this );
m_deleteChannelAction->setProperty( "popupdropper_svg_id", "delete" );
- connect( m_deleteChannelAction, SIGNAL(triggered()), SLOT(slotDeleteChannels()) );
+ connect( m_deleteChannelAction, &QAction::triggered, this, &UmsPodcastProvider::slotDeleteChannels );
}
// set the episode list as data that we'll retrieve in the slot
m_deleteChannelAction->setData( QVariant::fromValue( channels ) );
actions << m_deleteChannelAction;
return actions;
}
void
UmsPodcastProvider::slotDeleteChannels()
{
DEBUG_BLOCK
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
//get the list of episodes to apply to, then clear that data.
PodcastChannelList channels =
action->data().value<PodcastChannelList>();
action->setData( QVariant() );
foreach( PodcastChannelPtr channel, channels )
{
UmsPodcastChannelPtr umsChannel =
UmsPodcastChannel::fromPodcastChannelPtr( channel );
if( !umsChannel )
{
error() << "Could not cast to UmsPodcastChannel";
continue;
}
deleteEpisodes( umsChannel->m_umsEpisodes );
//slot deleteJobComplete() will emit signal once all tracks are gone.
}
}
QActionList
UmsPodcastProvider::playlistActions( const Playlists::PlaylistList &playlists )
{
PodcastChannelList channels;
foreach( const Playlists::PlaylistPtr &playlist, playlists )
{
PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist );
if( channel )
channels << channel;
}
return channelActions( channels );
}
QActionList
UmsPodcastProvider::trackActions( const QMultiHash<Playlists::PlaylistPtr, int> &playlistTracks )
{
PodcastEpisodeList episodes;
foreach( const Playlists::PlaylistPtr &playlist, playlistTracks.uniqueKeys() )
{
PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist );
if( !channel )
continue;
PodcastEpisodeList channelEpisodes = channel->episodes();
QList<int> trackPositions = playlistTracks.values( playlist );
qSort( trackPositions );
foreach( int trackPosition, trackPositions )
{
if( trackPosition >= 0 && trackPosition < channelEpisodes.count() )
episodes << channelEpisodes.at( trackPosition );
}
}
return episodeActions( episodes );
}
void
UmsPodcastProvider::completePodcastDownloads()
{
}
void
UmsPodcastProvider::updateAll() //slot
{
}
void
UmsPodcastProvider::update( Podcasts::PodcastChannelPtr channel ) //slot
{
Q_UNUSED( channel );
}
void
UmsPodcastProvider::downloadEpisode( Podcasts::PodcastEpisodePtr episode ) //slot
{
Q_UNUSED( episode );
}
void
UmsPodcastProvider::deleteDownloadedEpisode( Podcasts::PodcastEpisodePtr episode ) //slot
{
Q_UNUSED( episode );
}
void
UmsPodcastProvider::slotUpdated() //slot
{
}
void
UmsPodcastProvider::scan()
{
if( m_scanDirectory.isEmpty() )
return;
m_dirList.clear();
debug() << "scan directory for podcasts: " <<
m_scanDirectory.toLocalFile();
QDirIterator it( m_scanDirectory.toLocalFile(), QDirIterator::Subdirectories );
while( it.hasNext() )
addPath( it.next() );
}
int
UmsPodcastProvider::addPath( const QString &path )
{
DEBUG_BLOCK
int acc = 0;
QMimeDatabase db;
debug() << path;
QMimeType mime = db.mimeTypeForFile( path, QMimeDatabase::MatchContent );
if( !mime.isValid() || mime.isDefault() )
{
debug() << "Trying again with findByPath:" ;
mime = db.mimeTypeForFile( path, QMimeDatabase::MatchExtension);
if( mime.isDefault() )
return 0;
}
debug() << "Got type: " << mime.name() << ", with accuracy: " << acc;
QFileInfo info( path );
if( info.isDir() )
{
if( m_dirList.contains( path ) )
return 0;
m_dirList << info.canonicalPath();
return 1;
}
else if( info.isFile() )
{
// foreach( const QString &mimetype, m_handler->mimetypes() )
// {
// if( mime.inherits( mimetype ) )
// {
addFile( MetaFile::TrackPtr( new MetaFile::Track(
QUrl( info.canonicalFilePath() ) ) ) );
return 2;
// }
// }
}
return 0;
}
UmsPodcastEpisodePtr
UmsPodcastProvider::addFile( MetaFile::TrackPtr metafileTrack )
{
DEBUG_BLOCK
debug() << metafileTrack->playableUrl().url();
debug() << "album: " << metafileTrack->album()->name();
debug() << "title: " << metafileTrack->name();
if( metafileTrack->album()->name().isEmpty() )
{
debug() << "Can't figure out channel without album tag.";
return UmsPodcastEpisodePtr();
}
if( metafileTrack->name().isEmpty() )
{
debug() << "Can not use a track without a title.";
return UmsPodcastEpisodePtr();
}
//see if there is already a UmsPodcastEpisode for this track
UmsPodcastChannelPtr channel;
UmsPodcastEpisodePtr episode;
foreach( UmsPodcastChannelPtr c, m_umsChannels )
{
if( c->name() == metafileTrack->album()->name() )
{
channel = c;
break;
}
}
if( channel )
{
foreach( UmsPodcastEpisodePtr e, channel->umsEpisodes() )
{
if( e->title() == metafileTrack->name() )
{
episode = e;
break;
}
}
}
else
{
debug() << "there is no channel for this episode yet";
channel = UmsPodcastChannelPtr( new UmsPodcastChannel( this ) );
channel->setTitle( metafileTrack->album()->name() );
m_umsChannels << channel;
emit playlistAdded( Playlists::PlaylistPtr( channel.data() ) );
}
if( episode.isNull() )
{
debug() << "this episode was not found in an existing channel";
episode = UmsPodcastEpisodePtr( new UmsPodcastEpisode( channel ) );
episode->setLocalFile( metafileTrack );
channel->addUmsEpisode( episode );
}
episode->setLocalFile( metafileTrack );
return episode;
}
diff --git a/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp b/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp
index 92b4534ab9..d805a0ae45 100644
--- a/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp
+++ b/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp
@@ -1,291 +1,285 @@
/****************************************************************************************
* Copyright (c) 2010 Nikhil Marathe <nsm.nikhil@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "UpnpBrowseCollection"
#include "UpnpBrowseCollection.h"
#include "core/interfaces/Logger.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "MemoryQueryMaker.h"
#include "UpnpMemoryQueryMaker.h"
#include "UpnpQueryMaker.h"
#include "UpnpMeta.h"
#include "UpnpCache.h"
#include <QDir>
#include <QFileInfo>
#include <QStringList>
#include <QTimer>
#include <KLocale>
#include <kdatetime.h>
#include "upnptypes.h"
#include <KIO/Scheduler>
#include <KIO/JobClasses>
using namespace Meta;
namespace Collections {
//UpnpBrowseCollection
// TODO register for the device bye bye and emit remove()
UpnpBrowseCollection::UpnpBrowseCollection( const DeviceInfo& dev )
: UpnpCollectionBase( dev )
, m_mc( new MemoryCollection() )
, m_fullScanInProgress( false )
, m_cache( new UpnpCache( this ) )
{
DEBUG_BLOCK
// experimental code, will probably be moved to a better place
OrgKdeKDirNotifyInterface *notify = new OrgKdeKDirNotifyInterface("", "", QDBusConnection::sessionBus(), this );
- connect( notify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)) );
+ connect( notify, &OrgKdeKDirNotifyInterface::FilesChanged, this, &UpnpBrowseCollection::slotFilesChanged );
}
UpnpBrowseCollection::~UpnpBrowseCollection()
{
}
void UpnpBrowseCollection::slotFilesChanged(const QStringList &list )
{
if( m_fullScanInProgress )
return;
m_updateQueue += list;
debug() << "Files changed" << list;
}
void UpnpBrowseCollection::processUpdates()
{
if( m_updateQueue.isEmpty() )
return;
QString urlString = m_updateQueue.dequeue();
debug() << "Update URL is" << urlString;
invalidateTracksIn( urlString );
QUrl url( urlString );
if( url.scheme() != "upnp-ms" || m_device.uuid() != url.host() )
return;
debug() << "Now incremental scanning" << url;
startIncrementalScan( url.path() );
}
void UpnpBrowseCollection::invalidateTracksIn( const QString &dir )
{
debug() << "INVALIDATING" << m_tracksInContainer[dir].length();
/*
* when we get dir as / a / b we also have to invalidate
* any tracks in / a / b / * so we need to iterate over keys
* If performance is really affected we can use some
* kind of a prefix tree instead of a hash.
*/
foreach( const QString &key, m_tracksInContainer.keys() ) {
if( key.startsWith( dir ) ) {
debug() << key << " matches " << dir;
foreach( TrackPtr track, m_tracksInContainer[dir] ) {
removeTrack( track );
}
}
}
m_tracksInContainer.remove( dir );
}
void
UpnpBrowseCollection::startFullScan()
{
DEBUG_BLOCK;
// TODO probably set abort slot
// TODO figure out what to do with the total steps
Amarok::Components::logger()->newProgressOperation( this, i18n( "Scanning %1", prettyName() ) );
startIncrementalScan( "/" );
m_fullScanInProgress = true;
m_fullScanTimer = new QTimer( this );
- Q_ASSERT(
- connect( m_fullScanTimer,
- SIGNAL(timeout()),
- this,
- SLOT(updateMemoryCollection()) )
- );
+ connect( m_fullScanTimer, &QTimer::timeout,
+ this, &UpnpBrowseCollection::updateMemoryCollection );
m_fullScanTimer->start(5000);
}
void
UpnpBrowseCollection::startIncrementalScan( const QString &directory )
{
if( m_fullScanInProgress ) {
debug() << "Full scan in progress, aborting";
return;
}
debug() << "Scanning directory" << directory;
QUrl url;
url.setScheme( "upnp-ms" );
url.setHost( m_device.uuid() );
url.setPath( directory );
KIO::ListJob *listJob = KIO::listRecursive( url, KIO::HideProgressInfo );
addJob( listJob );
- Q_ASSERT( connect( listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
- this, SLOT(entries(KIO::Job*,KIO::UDSEntryList)), Qt::UniqueConnection ) );
- Q_ASSERT( connect( listJob, SIGNAL(result(KJob*)),
- this, SLOT(done(KJob*)), Qt::UniqueConnection ) );
+ Q_ASSERT( connect( listJob, &KIO::ListJob::entries, this, &UpnpBrowseCollection::entries, Qt::UniqueConnection ) );
+ Q_ASSERT( connect( listJob, &KJob::result, this, &UpnpBrowseCollection::done, Qt::UniqueConnection ) );
listJob->start();
}
void
UpnpBrowseCollection::entries( KIO::Job *job, const KIO::UDSEntryList &list )
{
DEBUG_BLOCK;
int count = 0;
KIO::SimpleJob *sj = static_cast<KIO::SimpleJob *>( job );
foreach( const KIO::UDSEntry &entry, list ) {
if( entry.contains( KIO::UPNP_CLASS )
&& entry.stringValue( KIO::UPNP_CLASS ).startsWith( "object.item.audioItem" ) ) {
createTrack( entry, sj->url().toDisplayString() );
}
count++;
emit totalSteps( count );
emit incrementProgress();
}
updateMemoryCollection();
}
void
UpnpBrowseCollection::updateMemoryCollection()
{
memoryCollection()->setTrackMap( m_cache->tracks() );
memoryCollection()->setArtistMap( m_cache->artists() );
memoryCollection()->setAlbumMap( m_cache->albums() );
memoryCollection()->setGenreMap( m_cache->genres() );
memoryCollection()->setYearMap( m_cache->years() );
emit updated();
}
void
UpnpBrowseCollection::createTrack( const KIO::UDSEntry &entry, const QString &baseUrl )
{
DEBUG_BLOCK
TrackPtr t = m_cache->getTrack( entry );
QFileInfo info( entry.stringValue( KIO::UDSEntry::UDS_NAME ) );
QString container = QDir(baseUrl).filePath( info.dir().path() );
debug() << "CONTAINER" << container;
m_tracksInContainer[container] << t;
}
void
UpnpBrowseCollection::removeTrack( TrackPtr t )
{
m_cache->removeTrack( t );
}
void
UpnpBrowseCollection::done( KJob *job )
{
DEBUG_BLOCK
if( job->error() )
{
Amarok::Components::logger()->longMessage( i18n("UPnP Error: %1", job->errorString() ),
Amarok::Logger::Error );
return;
}
updateMemoryCollection();
if( m_fullScanInProgress )
{
m_fullScanTimer->stop();
m_fullScanInProgress = false;
emit endProgressOperation( this );
debug() << "Full Scan done";
}
// process new updates if any
// this is the only place processUpdates()
// should be called since a full scan at the very beginning
// will always call done().
processUpdates();
}
bool
UpnpBrowseCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
return ( type == Capabilities::Capability::CollectionScan );
}
Capabilities::Capability*
UpnpBrowseCollection::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( type == Capabilities::Capability::CollectionScan )
return new UpnpBrowseCollectionScanCapability( this );
else
return 0;
}
QueryMaker*
UpnpBrowseCollection::queryMaker()
{
DEBUG_BLOCK;
UpnpMemoryQueryMaker *umqm = new UpnpMemoryQueryMaker(m_mc.toWeakRef(), collectionId() );
- Q_ASSERT( connect( umqm, SIGNAL(startFullScan()), this, SLOT(startFullScan()) ) );
+ connect( umqm, &UpnpMemoryQueryMaker::startFullScan, this, &UpnpBrowseCollection::startFullScan );
return umqm;
}
Meta::TrackPtr
UpnpBrowseCollection::trackForUrl( const QUrl &url )
{
debug() << "TRACK FOR URL " << url;
if( url.scheme() == "upnptrack" && url.host() == collectionId() )
return m_cache->tracks()[url.url()];
debug() << "NONE FOUND";
return Collection::trackForUrl( url );
}
// ---------- CollectionScanCapability ------------
UpnpBrowseCollectionScanCapability::UpnpBrowseCollectionScanCapability( UpnpBrowseCollection* collection )
: m_collection( collection )
{ }
UpnpBrowseCollectionScanCapability::~UpnpBrowseCollectionScanCapability()
{ }
void
UpnpBrowseCollectionScanCapability::startFullScan()
{
m_collection->startFullScan();
}
void
UpnpBrowseCollectionScanCapability::startIncrementalScan( const QString &directory )
{
m_collection->startIncrementalScan( directory );
}
void
UpnpBrowseCollectionScanCapability::stopScan()
{
// the UpnpBrowseCollection does not yet know how to stop a scan
}
} //~ namespace
diff --git a/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp b/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp
index 5402e5669d..5369572a56 100644
--- a/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp
+++ b/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp
@@ -1,129 +1,129 @@
/****************************************************************************************
* Copyright (c) 2010 Nikhil Marathe <nsm.nikhil@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "UpnpCollectionBase"
#include "UpnpCollectionBase.h"
#include "upnptypes.h"
#include <KIO/Scheduler>
#include <KIO/JobClasses>
#include <KIO/Slave>
#include "core/support/Debug.h"
namespace Collections {
static const int MAX_JOB_FAILURES_BEFORE_ABORT = 5;
UpnpCollectionBase::UpnpCollectionBase( const DeviceInfo& dev )
: Collection()
, m_device( dev )
, m_slave( 0 )
, m_slaveConnected( false )
, m_continuousJobFailureCount( 0 )
{
KIO::Scheduler::connect( SIGNAL(slaveError(KIO::Slave*,int,QString)),
this, SLOT(slotSlaveError(KIO::Slave*,int,QString)) );
KIO::Scheduler::connect( SIGNAL(slaveConnected(KIO::Slave*)),
this, SLOT(slotSlaveConnected(KIO::Slave*)) );
m_slave = KIO::Scheduler::getConnectedSlave( QUrl(collectionId()) );
}
UpnpCollectionBase::~UpnpCollectionBase()
{
foreach( KIO::SimpleJob *job, m_jobSet )
KIO::Scheduler::cancelJob( job );
m_jobSet.clear();
if( m_slave ) {
KIO::Scheduler::disconnectSlave( m_slave );
m_slave = 0;
m_slaveConnected = false;
}
}
QString UpnpCollectionBase::collectionId() const
{
return QString("upnp-ms://") + m_device.uuid();
}
QString UpnpCollectionBase::prettyName() const
{
return m_device.friendlyName();
}
bool UpnpCollectionBase::possiblyContainsTrack( const QUrl &url ) const
{
if( url.scheme() == "upnp-ms" )
// && url.host() == m_device.host()
// && url.port() == m_device.port() )
return true;
return false;
}
void UpnpCollectionBase::addJob( KIO::SimpleJob *job )
{
- connect( job, SIGNAL(result(KJob*)), this, SLOT(slotRemoveJob(KJob*)) );
+ connect( job, &KJob::result, this, &UpnpCollectionBase::slotRemoveJob );
m_jobSet.insert( job );
KIO::Scheduler::assignJobToSlave( m_slave, job );
}
void UpnpCollectionBase::slotRemoveJob(KJob* job)
{
KIO::SimpleJob *sj = static_cast<KIO::SimpleJob*>( job );
m_jobSet.remove( sj );
if( sj->error() ) {
m_continuousJobFailureCount++;
if( m_continuousJobFailureCount >= MAX_JOB_FAILURES_BEFORE_ABORT ) {
debug() << prettyName() << "Had" << m_continuousJobFailureCount << "continuous job failures, something wrong with the device. Removing this collection.";
emit remove();
}
}
else {
m_continuousJobFailureCount = 0;
}
}
void UpnpCollectionBase::slotSlaveError(KIO::Slave* slave, int err, const QString& msg)
{
debug() << "SLAVE ERROR" << slave << err << msg;
if( m_slave != slave )
return;
if( err == KIO::ERR_COULD_NOT_CONNECT
|| err == KIO::ERR_CONNECTION_BROKEN ) {
debug() << "COULD NOT CONNECT TO " << msg << "REMOVING THE COLLECTION";
emit remove();
}
if( err == KIO::ERR_SLAVE_DIED ) {
m_slave = 0;
emit remove();
}
}
void UpnpCollectionBase::slotSlaveConnected(KIO::Slave* slave)
{
if( m_slave != slave )
return;
debug() << "SLAVE IS CONNECTED";
m_slaveConnected = true;
}
} //namespace Collections
diff --git a/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp b/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp
index d6c0de4192..92b18dae4c 100644
--- a/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp
+++ b/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp
@@ -1,259 +1,263 @@
/****************************************************************************************
* Copyright (c) 2010 Nikhil Marathe <nsm.nikhil@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "UpnpCollectionFactory"
#include "UpnpCollectionFactory.h"
#include <KIO/JobClasses>
#include <KIO/Scheduler>
#include <KIO/NetAccess>
#include <kdirlister.h>
#include <QUrl>
#include <QDBusMetaType>
#include <QDBusInterface>
#include <QDBusReply>
-#include <solid/device.h>
-#include <solid/devicenotifier.h>
-#include <solid/storageaccess.h>
+#include <KDirLister>
+#include <KIO/ListJob>
+#include <KIO/Scheduler>
+#include <KPluginFactory>
+
+#include <Solid/Device>
+#include <Solid/DeviceNotifier>
+#include <Solid/StorageAccess>
#include "core/support/Debug.h"
#include "UpnpBrowseCollection.h"
#include "UpnpSearchCollection.h"
#include "dbuscodec.h"
namespace Collections {
AMAROK_EXPORT_COLLECTION( UpnpCollectionFactory, upnpcollection )
UpnpCollectionFactory::UpnpCollectionFactory( QObject *parent, const QVariantList &args )
: Collections::CollectionFactory( parent, args )
{
m_info = KPluginInfo( "amarok_collection-upnpcollection.desktop" );
qRegisterMetaType<DeviceInfo>();
qDBusRegisterMetaType< QHash<QString, QString> >();
qDBusRegisterMetaType<DeviceInfo0_1_0>();
qDBusRegisterMetaType<DeviceDetailsMap>();
}
UpnpCollectionFactory::~UpnpCollectionFactory()
{
}
void UpnpCollectionFactory::init()
{
DEBUG_BLOCK
if( !cagibi0_1_0Init( QDBusConnection::sessionBus() )
&& !cagibi0_1_0Init( QDBusConnection::systemBus() )
&& !cagibi0_2_0Init( QDBusConnection::sessionBus() )
&& !cagibi0_2_0Init( QDBusConnection::systemBus() ) )
{
// we had problems with Cagibi
return;
}
}
bool UpnpCollectionFactory::cagibi0_1_0Init( QDBusConnection bus )
{
bus.connect( "org.kde.Cagibi",
"/org/kde/Cagibi",
"org.kde.Cagibi",
"devicesAdded",
this,
SLOT(slotDeviceAdded(DeviceTypeMap)) );
bus.connect( "org.kde.Cagibi",
"/org/kde/Cagibi",
"org.kde.Cagibi",
"devicesRemoved",
this,
SLOT(slotDeviceRemoved(DeviceTypeMap)) );
m_iface = new QDBusInterface( "org.kde.Cagibi",
"/org/kde/Cagibi",
"org.kde.Cagibi",
bus,
this );
QDBusReply<DeviceTypeMap> reply = m_iface->call( "allDevices" );
if( !reply.isValid() )
{
debug() << "ERROR" << reply.error().message();
return false;
}
else
{
slotDeviceAdded( reply.value() );
}
//Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
//connect( notifier, SIGNAL(deviceAdded(QString)), this, SLOT(slotDeviceAdded(QString)) );
//connect( notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)) );
//foreach( Solid::Device device, Solid::Device::allDevices() ) {
// slotDeviceAdded(device.udi());
//}
m_initialized = true;
return true;
}
bool UpnpCollectionFactory::cagibi0_2_0Init( QDBusConnection bus )
{
bus.connect( "org.kde.Cagibi",
"/org/kde/Cagibi/DeviceList",
"org.kde.Cagibi.DeviceList",
"devicesAdded",
this,
SLOT(slotDeviceAdded(DeviceTypeMap)) );
bus.connect( "org.kde.Cagibi",
"/org/kde/Cagibi/DeviceList",
"org.kde.Cagibi.DeviceList",
"devicesRemoved",
this,
SLOT(slotDeviceRemoved(DeviceTypeMap)) );
m_iface = new QDBusInterface( "org.kde.Cagibi",
"/org/kde/Cagibi/DeviceList",
"org.kde.Cagibi.DeviceList",
bus,
this );
QDBusReply<DeviceTypeMap> reply = m_iface->call( "allDevices" );
if( !reply.isValid() )
{
debug() << "ERROR" << reply.error().message();
debug() << "Maybe cagibi is not installed.";
return false;
}
else
{
slotDeviceAdded( reply.value() );
}
//Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
//connect( notifier, SIGNAL(deviceAdded(QString)), this, SLOT(slotDeviceAdded(QString)) );
//connect( notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)) );
//foreach( Solid::Device device, Solid::Device::allDevices() ) {
// slotDeviceAdded(device.udi());
//}
m_initialized = true;
return true;
}
void UpnpCollectionFactory::slotDeviceAdded( const DeviceTypeMap &map )
{
foreach( const QString &udn, map.keys() ) {
QString type = map[udn];
debug() << "|||| DEVICE" << udn << type;
if( type.startsWith("urn:schemas-upnp-org:device:MediaServer") )
createCollection( udn );
}
}
void UpnpCollectionFactory::slotDeviceRemoved( const DeviceTypeMap &map )
{
foreach( QString udn, map.keys() ) {
udn.remove("uuid:");
if( m_devices.contains(udn) ) {
m_devices[udn]->removeCollection();
m_devices.remove(udn);
}
}
}
void UpnpCollectionFactory::createCollection( const QString &udn )
{
debug() << "|||| Creating collection " << udn;
DeviceInfo info;
if( !cagibi0_1_0DeviceDetails( udn, &info )
&& !cagibi0_2_0DeviceDetails( udn, &info ) )
{
return;
}
debug() << "|||| Creating collection " << info.uuid();
KIO::ListJob *job = KIO::listDir( QUrl( "upnp-ms://" + info.uuid() + "/?searchcapabilities=1" ) );
job->setProperty( "deviceInfo", QVariant::fromValue( info ) );
- connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
- this, SLOT(slotSearchEntries(KIO::Job*,KIO::UDSEntryList)) );
- connect( job, SIGNAL(result(KJob*)), this, SLOT(slotSearchCapabilitiesDone(KJob*)) );
+ connect( job, &KIO::ListJob::entries, this, &UpnpCollectionFactory::slotSearchEntries );
+ connect( job, &KJob::result, this, &UpnpCollectionFactory::slotSearchCapabilitiesDone );
}
bool UpnpCollectionFactory::cagibi0_1_0DeviceDetails( const QString &udn, DeviceInfo *info )
{
QDBusReply<DeviceInfo0_1_0> reply = m_iface->call( "deviceDetails", udn );
if( !reply.isValid() ) {
debug() << "Invalid reply from deviceDetails for" << udn << ". Skipping";
debug() << "Error" << reply.error().message();
return false;
}
*info = reply.value();
return true;
}
bool UpnpCollectionFactory::cagibi0_2_0DeviceDetails( const QString &udn, DeviceInfo *info )
{
QDBusReply<DeviceDetailsMap> reply = m_iface->call( "deviceDetails", udn );
if( !reply.isValid() ) {
debug() << "Invalid reply from deviceDetails for" << udn << ". Skipping";
debug() << "Error" << reply.error().message();
return false;
}
foreach( const QString &k, reply.value().keys() )
debug() << k << reply.value()[k];
DeviceInfo0_2_0 v( reply.value() );
*info = v;
return true;
}
void UpnpCollectionFactory::slotSearchEntries( KIO::Job *job, const KIO::UDSEntryList &list )
{
Q_UNUSED( job );
KIO::ListJob *lj = static_cast<KIO::ListJob*>( job );
foreach( const KIO::UDSEntry &entry, list )
m_capabilities[lj->url().host()] << entry.stringValue( KIO::UDSEntry::UDS_NAME );
}
void UpnpCollectionFactory::slotSearchCapabilitiesDone( KJob *job )
{
KIO::ListJob *lj = static_cast<KIO::ListJob*>( job );
QStringList searchCaps = m_capabilities[lj->url().host()];
if( !job->error() ) {
DeviceInfo dev = job->property( "deviceInfo" ).value<DeviceInfo>();
if( searchCaps.contains( "upnp:class" )
&& searchCaps.contains( "dc:title" )
&& searchCaps.contains( "upnp:artist" )
&& searchCaps.contains( "upnp:album" ) ) {
kDebug() << "Supports all search meta-data required, using UpnpSearchCollection";
m_devices[dev.uuid()] = new UpnpSearchCollection( dev, searchCaps );
}
else {
kDebug() << "Supported Search() meta-data" << searchCaps << "not enough. Using UpnpBrowseCollection";
m_devices[dev.uuid()] = new UpnpBrowseCollection( dev );
}
emit newCollection( m_devices[dev.uuid()] );
}
}
}
diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp b/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp
index c563e7f268..ef007f506b 100644
--- a/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp
+++ b/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp
@@ -1,543 +1,543 @@
/****************************************************************************************
* Copyright (c) 2010 Nikhil Marathe <nsm.nikhil@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "UpnpQueryMaker"
#include "UpnpQueryMaker.h"
#include <kdatetime.h>
#include "upnptypes.h"
#include <kio/scheduler.h>
#include <kio/jobclasses.h>
#include "core/support/Debug.h"
#include "UpnpSearchCollection.h"
#include "UpnpQueryMakerInternal.h"
#include "UpnpMeta.h"
#include "UpnpCache.h"
namespace Collections {
UpnpQueryMaker::UpnpQueryMaker( UpnpSearchCollection *collection )
: QueryMaker()
, m_collection( collection )
, m_internalQM( new UpnpQueryMakerInternal( collection ) )
{
reset();
- connect( m_internalQM, SIGNAL(done()), this, SLOT(slotDone()) );
+ connect( m_internalQM, &UpnpQueryMakerInternal::done, this, &UpnpQueryMaker::slotDone );
- connect( m_internalQM, SIGNAL(newResultReady(Meta::TrackList)),
- this, SLOT(handleTracks(Meta::TrackList)) );
- connect( m_internalQM, SIGNAL(newResultReady(Meta::ArtistList)),
- this, SLOT(handleArtists(Meta::ArtistList)) );
- connect( m_internalQM, SIGNAL(newResultReady(Meta::AlbumList)),
- this, SLOT(handleAlbums(Meta::AlbumList)) );
- connect( m_internalQM, SIGNAL(newResultReady(KIO::UDSEntryList)),
- this, SLOT(handleCustom(KIO::UDSEntryList)) );
+ connect( m_internalQM, &UpnpQueryMakerInternal::newTracksReady,
+ this, &UpnpQueryMaker::handleTracks );
+ connect( m_internalQM, &UpnpQueryMakerInternal::newArtistsReady,
+ this, &UpnpQueryMaker::handleArtists );
+ connect( m_internalQM, &UpnpQueryMakerInternal::newAlbumsReady,
+ this, &UpnpQueryMaker::handleAlbums );
+// connect( m_internalQM, &UpnpQueryMakerInternal::newResultReady,
+// this, &UpnpQueryMaker::handleCustom );
}
UpnpQueryMaker::~UpnpQueryMaker()
{
m_internalQM->deleteLater();
}
QueryMaker* UpnpQueryMaker::reset()
{
// TODO kill all jobs here too
m_queryType = None;
m_albumMode = AllAlbums;
m_query.reset();
m_jobCount = 0;
m_numericFilters.clear();
m_internalQM->reset();
// the Amarok Collection Model expects at least one entry
// otherwise it will harass us continuously for more entries.
// of course due to the poor quality of UPnP servers I've
// had experience with :P, some may not have sub-results
// for something ( they may have a track with an artist, but
// not be able to give any album for it )
m_noResults = true;
return this;
}
void UpnpQueryMaker::run()
{
DEBUG_BLOCK
QUrl baseUrl( m_collection->collectionId() );
baseUrl.addQueryItem( "search", "1" );
if( m_queryType == Custom ) {
switch( m_returnFunction ) {
case Count:
m_query.reset();
m_query.setType( "( upnp:class derivedfrom \"object.item.audioItem\" )" );
baseUrl.addQueryItem( "getCount", "1" );
break;
case Sum:
case Max:
case Min:
break;
}
}
// we don't deal with compilations
else if( m_queryType == Album && m_albumMode == OnlyCompilations ) {
// we don't support any other attribute
- emit newResultReady( Meta::TrackList() );
- emit newResultReady( Meta::ArtistList() );
- emit newResultReady( Meta::AlbumList() );
- emit newResultReady( Meta::GenreList() );
- emit newResultReady( Meta::ComposerList() );
- emit newResultReady( Meta::YearList() );
+ emit newTracksReady( Meta::TrackList() );
+ emit newArtistsReady( Meta::ArtistList() );
+ emit newAlbumsReady( Meta::AlbumList() );
+ emit newGenresReady( Meta::GenreList() );
+ emit newComposersReady( Meta::ComposerList() );
+ emit newYearsReady( Meta::YearList() );
emit newResultReady( QStringList() );
- emit newResultReady( Meta::LabelList() );
+ emit newLabelsReady( Meta::LabelList() );
emit queryDone();
return;
}
QStringList queryList;
if( m_query.hasMatchFilter() || !m_numericFilters.empty() ) {
queryList = m_query.queries();
}
else {
switch( m_queryType ) {
case Artist:
debug() << this << "Query type Artist";
queryList << "( upnp:class derivedfrom \"object.container.person.musicArtist\" )";
break;
case Album:
debug() << this << "Query type Album";
queryList << "( upnp:class derivedfrom \"object.container.album.musicAlbum\" )";
break;
case Track:
debug() << this << "Query type Track";
queryList << "( upnp:class derivedfrom \"object.item.audioItem\" )";
break;
case Genre:
debug() << this << "Query type Genre";
queryList << "( upnp:class derivedfrom \"object.container.genre.musicGenre\" )";
break;
case Custom:
debug() << this << "Query type Custom";
queryList << "( upnp:class derivedfrom \"object.item.audioItem\" )";
break;
default:
debug() << this << "Default case: Query type";
// we don't support any other attribute
- emit newResultReady( Meta::TrackList() );
- emit newResultReady( Meta::ArtistList() );
- emit newResultReady( Meta::AlbumList() );
- emit newResultReady( Meta::GenreList() );
- emit newResultReady( Meta::ComposerList() );
- emit newResultReady( Meta::YearList() );
+ emit newTracksReady( Meta::TrackList() );
+ emit newArtistsReady( Meta::ArtistList() );
+ emit newAlbumsReady( Meta::AlbumList() );
+ emit newGenresReady( Meta::GenreList() );
+ emit newComposersReady( Meta::ComposerList() );
+ emit newYearsReady( Meta::YearList() );
emit newResultReady( QStringList() );
- emit newResultReady( Meta::LabelList() );
+ emit newLabelsReady( Meta::LabelList() );
emit queryDone();
return;
}
}
// and experiment in using the filter only for the query
// and checking the returned upnp:class
// based on your query types.
for( int i = 0; i < queryList.length() ; i++ ) {
if( queryList[i].isEmpty() )
continue;
QUrl url( baseUrl );
url.addQueryItem( "query", queryList[i] );
debug() << this << "Running query" << url;
m_internalQM->runQuery( url );
}
}
void UpnpQueryMaker::abortQuery()
{
DEBUG_BLOCK
Q_ASSERT( false );
// TODO implement this to kill job
}
QueryMaker* UpnpQueryMaker::setQueryType( QueryType type )
{
DEBUG_BLOCK
// TODO allow all, based on search capabilities
// which should be passed on by the factory
m_queryType = type;
m_query.setType( "( upnp:class derivedfrom \"object.item.audioItem\" )" );
m_internalQM->setQueryType( type );
return this;
}
QueryMaker* UpnpQueryMaker::addReturnValue( qint64 value )
{
DEBUG_BLOCK
debug() << this << "Add return value" << value;
m_returnValue = value;
return this;
}
QueryMaker* UpnpQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
DEBUG_BLOCK
Q_UNUSED( function )
debug() << this << "Return function with value" << value;
m_returnFunction = function;
m_returnValue = value;
return this;
}
QueryMaker* UpnpQueryMaker::orderBy( qint64 value, bool descending )
{
DEBUG_BLOCK
debug() << this << "Order by " << value << "Descending?" << descending;
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::TrackPtr &track )
{
DEBUG_BLOCK
debug() << this << "Adding track match" << track->name();
// TODO: CHECK query type before searching by dc:title?
m_query.addMatch( "( dc:title = \"" + track->name() + "\" )" );
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::ArtistPtr &artist, QueryMaker::ArtistMatchBehaviour behaviour )
{
DEBUG_BLOCK
Q_UNUSED( behaviour ); // TODO: does UPnP tell between track and album artists?
debug() << this << "Adding artist match" << artist->name();
m_query.addMatch( "( upnp:artist = \"" + artist->name() + "\" )" );
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
DEBUG_BLOCK
debug() << this << "Adding album match" << album->name();
m_query.addMatch( "( upnp:album = \"" + album->name() + "\" )" );
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
DEBUG_BLOCK
debug() << this << "Adding composer match" << composer->name();
// NOTE unsupported
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
DEBUG_BLOCK
debug() << this << "Adding genre match" << genre->name();
m_query.addMatch( "( upnp:genre = \"" + genre->name() + "\" )" );
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::YearPtr &year )
{
DEBUG_BLOCK
debug() << this << "Adding year match" << year->name();
// TODO
return this;
}
QueryMaker* UpnpQueryMaker::addMatch( const Meta::LabelPtr &label )
{
DEBUG_BLOCK
debug() << this << "Adding label match" << label->name();
// NOTE how?
return this;
}
QueryMaker* UpnpQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
DEBUG_BLOCK
debug() << this << "Adding filter" << value << filter << matchBegin << matchEnd;
// theoretically this should be '=' I think and set to contains below if required
QString cmpOp = "contains";
//TODO should we add filters ourselves
// eg. we always query for audioItems, but how do we decide
// whether to add a dc:title filter or others.
// for example, for the artist list
// our query should be like ( pseudocode )
// ( upnp:class = audioItem ) and ( dc:title contains "filter" )
// OR
// ( upnp:class = audioItem ) and ( upnp:artist contains "filter" );
// ...
// so who adds the second query?
QString property = propertyForValue( value );
if( property.isNull() )
return this;
if( matchBegin || matchEnd )
cmpOp = "contains";
QString filterString = "( " + property + " " + cmpOp + " \"" + filter + "\" ) ";
m_query.addFilter( filterString );
return this;
}
QueryMaker* UpnpQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
DEBUG_BLOCK
debug() << this << "Excluding filter" << value << filter << matchBegin << matchEnd;
QString cmpOp = "!=";
QString property = propertyForValue( value );
if( property.isNull() )
return this;
if( matchBegin || matchEnd )
cmpOp = "doesNotContain";
QString filterString = "( " + property + " " + cmpOp + " \"" + filter + "\" ) ";
m_query.addFilter( filterString );
return this;
}
QueryMaker* UpnpQueryMaker::addNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
DEBUG_BLOCK
debug() << this << "Adding number filter" << value << filter << compare;
NumericFilter f = { value, filter, compare };
m_numericFilters << f;
return this;
}
QueryMaker* UpnpQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare )
{
DEBUG_BLOCK
debug() << this << "Excluding number filter" << value << filter << compare;
return this;
}
QueryMaker* UpnpQueryMaker::limitMaxResultSize( int size )
{
DEBUG_BLOCK
debug() << this << "Limit max results to" << size;
return this;
}
QueryMaker* UpnpQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
DEBUG_BLOCK
debug() << this << "Set album query mode" << mode;
m_albumMode = mode;
return this;
}
QueryMaker* UpnpQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
DEBUG_BLOCK
debug() << this << "Set label query mode" << mode;
return this;
}
QueryMaker* UpnpQueryMaker::beginAnd()
{
DEBUG_BLOCK
m_query.beginAnd();
return this;
}
QueryMaker* UpnpQueryMaker::beginOr()
{
DEBUG_BLOCK
m_query.beginOr();
return this;
}
QueryMaker* UpnpQueryMaker::endAndOr()
{
DEBUG_BLOCK
debug() << this << "End AND/OR";
m_query.endAndOr();
return this;
}
QueryMaker* UpnpQueryMaker::setAutoDelete( bool autoDelete )
{
DEBUG_BLOCK
debug() << this << "Auto delete" << autoDelete;
return this;
}
int UpnpQueryMaker::validFilterMask()
{
int mask = 0;
QStringList caps = m_collection->searchCapabilities();
if( caps.contains( "dc:title" ) )
mask |= TitleFilter;
if( caps.contains( "upnp:album" ) )
mask |= AlbumFilter;
if( caps.contains( "upnp:artist" ) )
mask |= ArtistFilter;
if( caps.contains( "upnp:genre" ) )
mask |= GenreFilter;
return mask;
}
void UpnpQueryMaker::handleArtists( Meta::ArtistList list )
{
// TODO Post filtering
- emit newResultReady( list );
+ emit newArtistsReady( list );
}
void UpnpQueryMaker::handleAlbums( Meta::AlbumList list )
{
// TODO Post filtering
- emit newResultReady( list );
+ emit newAlbumsReady( list );
}
void UpnpQueryMaker::handleTracks( Meta::TrackList list )
{
// TODO Post filtering
- emit newResultReady( list );
+ emit newTracksReady( list );
}
/*
void UpnpQueryMaker::handleCustom( const KIO::UDSEntryList& list )
{
if( m_returnFunction == Count )
{
{
Q_ASSERT( !list.empty() );
QString count = list.first().stringValue( KIO::UDSEntry::UDS_NAME );
m_collection->setProperty( "numberOfTracks", count.toUInt() );
emit newResultReady( QStringList( count ) );
}
default:
debug() << "Custom result functions other than \"Count\" are not supported by UpnpQueryMaker";
}
}
*/
void UpnpQueryMaker::slotDone()
{
DEBUG_BLOCK
if( m_noResults ) {
debug() << "++++++++++++++++++++++++++++++++++++ NO RESULTS ++++++++++++++++++++++++";
// TODO proper data types not just DataPtr
Meta::DataList ret;
Meta::UpnpTrack *fake = new Meta::UpnpTrack( m_collection );
fake->setTitle( "No results" );
fake->setYear( Meta::UpnpYearPtr( new Meta::UpnpYear( 2010 ) ) );
Meta::DataPtr ptr( fake );
ret << ptr;
//emit newResultReady( ret );
}
switch( m_queryType ) {
case Artist:
{
Meta::ArtistList list;
foreach( Meta::DataPtr ptr, m_cacheEntries )
list << Meta::ArtistPtr::staticCast( ptr );
- emit newResultReady( list );
+ emit newArtistsReady( list );
break;
}
case Album:
{
Meta::AlbumList list;
foreach( Meta::DataPtr ptr, m_cacheEntries )
list << Meta::AlbumPtr::staticCast( ptr );
- emit newResultReady( list );
+ emit newAlbumsReady( list );
break;
}
case Track:
{
Meta::TrackList list;
foreach( Meta::DataPtr ptr, m_cacheEntries )
list << Meta::TrackPtr::staticCast( ptr );
- emit newResultReady( list );
+ emit newTracksReady( list );
break;
}
default:
{
debug() << "Query type not supported by UpnpQueryMaker";
}
}
debug() << "ALL JOBS DONE< TERMINATING THIS QM" << this;
emit queryDone();
}
QString UpnpQueryMaker::propertyForValue( qint64 value )
{
switch( value ) {
case Meta::valTitle:
return "dc:title";
case Meta::valArtist:
{
//if( m_queryType != Artist )
return "upnp:artist";
}
case Meta::valAlbum:
{
//if( m_queryType != Album )
return "upnp:album";
}
case Meta::valGenre:
return "upnp:genre";
break;
default:
debug() << "UNSUPPORTED QUERY TYPE" << value;
return QString();
}
}
bool UpnpQueryMaker::postFilter( const KIO::UDSEntry &entry )
{
//numeric filters
foreach( const NumericFilter &filter, m_numericFilters ) {
// should be set by the filter based on filter.type
qint64 aValue = 0;
switch( filter.type ) {
case Meta::valCreateDate:
{
// TODO might use UDSEntry::UDS_CREATION_TIME instead later
QString dateString = entry.stringValue( KIO::UPNP_DATE );
QDateTime time = QDateTime::fromString( dateString, Qt::ISODate );
if( !time.isValid() )
return false;
aValue = time.toTime_t();
debug() << "FILTER BY creation timestamp entry:" << aValue << "query:" << filter.value << "OP:" << filter.compare;
break;
}
}
if( ( filter.compare == Equals ) && ( filter.value != aValue ) )
return false;
else if( ( filter.compare == GreaterThan ) && ( filter.value >= aValue ) )
return false; // since only allow entries with aValue > filter.value
else if( ( filter.compare == LessThan ) && ( filter.value <= aValue ) )
return false;
}
return true;
}
} //namespace Collections
diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMaker.h b/src/core-impl/collections/upnpcollection/UpnpQueryMaker.h
index cabcf2e7b9..3da24f0d1f 100644
--- a/src/core-impl/collections/upnpcollection/UpnpQueryMaker.h
+++ b/src/core-impl/collections/upnpcollection/UpnpQueryMaker.h
@@ -1,143 +1,143 @@
/****************************************************************************************
* Copyright (c) 2010 Nikhil Marathe <nsm.nikhil@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef UPNP_QUERYMAKER_H
#define UPNP_QUERYMAKER_H
#include "core/collections/QueryMaker.h"
#include <QObject>
#include <QStringList>
#include <QtGlobal>
#include <QStack>
#include "UpnpQuery.h"
namespace KIO {
class UDSEntry;
typedef QList<UDSEntry> UDSEntryList;
class Job;
class ListJob;
}
class KJob;
namespace Collections {
class UpnpSearchCollection;
class UpnpQueryMakerInternal;
class UpnpQueryMaker : public QueryMaker
{
Q_OBJECT
public:
UpnpQueryMaker( UpnpSearchCollection * );
~UpnpQueryMaker();
QueryMaker* reset();
void run() ;
void abortQuery() ;
QueryMaker* setQueryType( QueryType type ) ;
QueryMaker* addReturnValue( qint64 value ) ;
QueryMaker* addReturnFunction( ReturnFunction function, qint64 value ) ;
QueryMaker* orderBy( qint64 value, bool descending = false ) ;
QueryMaker* addMatch( const Meta::TrackPtr &track ) ;
QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists );
QueryMaker* addMatch( const Meta::AlbumPtr &album ) ;
QueryMaker* addMatch( const Meta::ComposerPtr &composer ) ;
QueryMaker* addMatch( const Meta::GenrePtr &genre ) ;
QueryMaker* addMatch( const Meta::YearPtr &year ) ;
QueryMaker* addMatch( const Meta::LabelPtr &label );
QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin = false, bool matchEnd = false ) ;
QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin = false, bool matchEnd = false ) ;
QueryMaker* addNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) ;
QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) ;
QueryMaker* limitMaxResultSize( int size ) ;
QueryMaker* setAlbumQueryMode( AlbumQueryMode mode );
QueryMaker* setLabelQueryMode( LabelQueryMode mode );
QueryMaker* beginAnd() ;
QueryMaker* beginOr() ;
QueryMaker* endAndOr() ;
QueryMaker* setAutoDelete( bool autoDelete );
int validFilterMask();
Q_SIGNALS:
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
+ void newComposersReady( Meta::ComposerList );
+ void newYearsReady( Meta::YearList );
void newResultReady( QStringList );
- void newResultReady( Meta::LabelList );
+ void newLabelsReady( Meta::LabelList );
void queryDone();
private Q_SLOTS:
void slotDone();
void handleArtists( Meta::ArtistList );
void handleAlbums( Meta::AlbumList );
void handleTracks( Meta::TrackList );
private:
/*
* apply numeric filters and such which UPnP doesn't handle.
*/
bool postFilter( const KIO::UDSEntry& entry );
QString propertyForValue( qint64 value );
UpnpSearchCollection *m_collection;
UpnpQueryMakerInternal *m_internalQM;
QueryType m_queryType;
AlbumQueryMode m_albumMode;
bool m_asDataPtrs;
UpnpQuery m_query;
bool m_noResults;
int m_jobCount;
Meta::DataList m_cacheEntries;
ReturnFunction m_returnFunction;
qint64 m_returnValue;
struct NumericFilter {
qint64 type;
qint64 value;
NumberComparison compare;
};
QList<NumericFilter> m_numericFilters;
};
} //namespace Collections
#endif
diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp
index 62cfcc7d71..bfde21b0ee 100644
--- a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp
+++ b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp
@@ -1,250 +1,250 @@
/****************************************************************************************
* Copyright (c) 2010 Nikhil Marathe <nsm.nikhil@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "UpnpQueryMakerInternal"
#include "UpnpQueryMakerInternal.h"
#include "upnptypes.h"
#include <kio/scheduler.h>
#include <kio/jobclasses.h>
#include "UpnpSearchCollection.h"
#include "UpnpCache.h"
#include "UpnpMeta.h"
#include "core/support/Debug.h"
namespace Collections {
// use filter for faster data transfer and parsing
// if cached tracks > remote tracks * CACHE_CHECK_THRESHOLD
static const float CACHE_CHECK_THRESHOLD = 0.75f;
UpnpQueryMakerInternal::UpnpQueryMakerInternal( UpnpSearchCollection *collection )
: m_collection( collection )
{
reset();
}
void UpnpQueryMakerInternal::reset()
{
m_queryType = QueryMaker::None;
m_jobCount = 0;
}
UpnpQueryMakerInternal::~UpnpQueryMakerInternal()
{
}
void UpnpQueryMakerInternal::queueJob(KIO::SimpleJob* job)
{
QUrl url = job->url();
debug() << "+-+- RUNNING JOB WITH" << url.toDisplayString();
m_collection->addJob( job );
m_jobCount++;
job->start();
}
void UpnpQueryMakerInternal::runQuery( QUrl query, bool filter )
{
// insert this query as a job
// first check cache size vs remote size
// if over threshold, apply filter, otherwise pass on as normal
int remoteCount = m_collection->property( "numberOfTracks" ).toInt();
debug() << "REMOTE COUNT" << remoteCount << "Cache size" << m_collection->cache()->tracks().size();
if( m_collection->cache()->tracks().size() > remoteCount * CACHE_CHECK_THRESHOLD
&& remoteCount > 0
&& filter ) {
debug() << "FILTERING BY CLASS ONLY";
query.addQueryItem( "filter", "upnp:class" );
}
KIO::ListJob *job = KIO::listDir( query, KIO::HideProgressInfo );
- connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
- this, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)) );
- connect( job, SIGNAL(result(KJob*)), this, SLOT(slotDone(KJob*)) );
+ connect( job, &KIO::ListJob::entries,
+ this, &UpnpQueryMakerInternal::slotEntries );
+ connect( job, &KJob::result, this, &UpnpQueryMakerInternal::slotDone );
queueJob( job );
}
void UpnpQueryMakerInternal::runStat( const QString& id )
{
QUrl url( m_collection->collectionId() );
url.addQueryItem( "id", id );
debug() << "STAT URL" << url;
KIO::StatJob *job = KIO::stat( url, KIO::HideProgressInfo );
- connect( job, SIGNAL(result(KJob*)), this, SLOT(slotStatDone(KJob*)) );
+ connect( job, &KJob::result, this, &UpnpQueryMakerInternal::slotStatDone );
queueJob( job );
}
void UpnpQueryMakerInternal::slotEntries( KIO::Job *job, const KIO::UDSEntryList &list )
{
debug() << "+-+- JOB DONE" << static_cast<KIO::SimpleJob*>(job)->url() << job->error();
foreach( const KIO::UDSEntry &entry, list )
debug() << "GOT ENTRY " << entry.stringValue( KIO::UDSEntry::UDS_NAME );
// actually iterate over the list, check for cache hits
// if hit, get the relevant Meta::*Ptr object and pass it on
// for post filtering etc.
// if miss, queue up another job to fetch all details
// job->url() can be used to decide if complete details
// are available or not using queryItem("filter") details
//
if( job->error() )
emit results( true, KIO::UDSEntryList() );
else
emit results( false, list );
debug() << this << "SLOT ENTRIES" << list.length() << m_queryType;
switch( m_queryType ) {
case QueryMaker::Artist:
handleArtists( list );
break;
case QueryMaker::Album:
handleAlbums( list );
break;
case QueryMaker::Track:
handleTracks( list );
break;
case QueryMaker::Custom:
handleCustom( list );
break;
default:
break;
// TODO handle remaining cases
}
if( !list.empty() ) {
debug() << "_______________________ RESULTS! ____________________________";
}
}
void UpnpQueryMakerInternal::slotDone( KJob *job )
{
// here check if all jobs done, then we might want to emit done()
// clean up this job, remove it from the hash and so on.
m_jobCount--;
job->deleteLater();
if( m_jobCount <= 0 ) {
//emit newResultReady( list );
debug() << "ALL JOBS DONE< TERMINATING THIS QM" << this;
emit done();
}
}
void UpnpQueryMakerInternal::slotStatDone( KJob *job )
{
m_jobCount--;
KIO::StatJob *sj = static_cast<KIO::StatJob*>( job );
if( sj->error() ) {
debug() << "STAT ERROR ON" << sj->url() << sj->errorString();
}
else {
KIO::UDSEntry entry = sj->statResult();
slotEntries( static_cast<KIO::Job*>( job ), KIO::UDSEntryList() << entry );
}
sj->deleteLater();
if( m_jobCount <= 0 ) {
//emit newResultReady( list );
debug() << "ALL JOBS DONE< TERMINATING THIS QM" << this;
emit done();
}
}
void UpnpQueryMakerInternal::handleArtists( const KIO::UDSEntryList &list )
{
Meta::ArtistList ret;
foreach( const KIO::UDSEntry &entry, list ) {
if( entry.stringValue( KIO::UPNP_CLASS ) == "object.container.person.musicArtist" ) {
debug() << this << "ARTIST" << entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
ret << m_collection->cache()->getArtist( entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ) );
}
else {
if( entry.contains( KIO::UPNP_ARTIST ) ) {
ret << m_collection->cache()->getArtist( entry.stringValue( KIO::UPNP_ARTIST ) );
}
else {
runStat( entry.stringValue( KIO::UPNP_ID ) );
}
}
}
- emit newResultReady( ret );
+ emit newArtistsReady( ret );
}
void UpnpQueryMakerInternal::handleAlbums( const KIO::UDSEntryList &list )
{
DEBUG_BLOCK
debug() << "HANDLING ALBUMS" << list.length();
Meta::AlbumList ret;
foreach( const KIO::UDSEntry &entry, list ) {
if( entry.stringValue( KIO::UPNP_CLASS ) == "object.container.album.musicAlbum" ) {
debug() << this << "ALBUM" << entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ) << entry.stringValue(KIO::UPNP_ARTIST);
ret << m_collection->cache()->getAlbum( entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ), entry.stringValue( KIO::UPNP_ARTIST ) );
}
else {
if( entry.contains( KIO::UPNP_ALBUM ) ) {
ret << m_collection->cache()->getAlbum( entry.stringValue( KIO::UPNP_ALBUM ), entry.stringValue( KIO::UPNP_ARTIST ) );
}
else {
runStat( entry.stringValue( KIO::UPNP_ID ) );
}
}
}
- emit newResultReady( ret );
+ emit newAlbumsReady( ret );
}
void UpnpQueryMakerInternal::handleTracks( const KIO::UDSEntryList &list )
{
DEBUG_BLOCK
debug() << "HANDLING TRACKS" << list.length();
Meta::TrackList ret;
foreach( const KIO::UDSEntry &entry, list ) {
// If we did a list job with an attempt to check the cache (ie. no meta-data requested from server )
// we might have an incomplete cache entry for the track.
// if we have an incomplete entry, we queue a stat job which fetches
// the entry. Now this stat job is going to call handleTracks again
// When called from a StatJob, we want to fill up the cache
// with valid values.
// So if the cache entry is incomplete, but the UDSEntry is complete
// set the refresh option to true when calling getTrack
Meta::TrackPtr track = m_collection->cache()->getTrack( entry );
if( track->playableUrl().isEmpty() ) {
debug() << "TRACK HAS INCOMPLETE ENTRY" << track->name() << track->album()->name();
if( !entry.stringValue( KIO::UDSEntry::UDS_TARGET_URL ).isEmpty() ) {
debug() << "GOT TRACK DETAILS FROM STAT JOB";
// reached from a StatJob
// fill up valid values AND add this track to ret since it is now valid
track = m_collection->cache()->getTrack( entry, true );
debug() << "NOW TRACK DETAILS ARE" << track->name() << track->album()->name();
}
else {
// start a StatJob, but DON'T insert this incomplete entry into ret
debug() << "FETCHING COMPLETE TRACK DATA" << track->name();
runStat( entry.stringValue( KIO::UPNP_ID ) );
continue;
}
}
ret << m_collection->cache()->getTrack( entry );
}
- emit newResultReady( ret );
+ emit newTracksReady( ret );
}
void UpnpQueryMakerInternal::handleCustom( const KIO::UDSEntryList &list )
{
emit newResultReady( list );
}
}
diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h
index 2ffee5851a..fbbf008cbb 100644
--- a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h
+++ b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h
@@ -1,82 +1,82 @@
/*
Copyright (C) 2010 Nikhil Marathe <nsm.nikhil@gmail.com>
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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef UPNPQUERYMAKERINTERNAL_H
#define UPNPQUERYMAKERINTERNAL_H
#include <QObject>
#include <QUrl>
#include <kio/udsentry.h>
#include "core/collections/QueryMaker.h"
class KJob;
namespace KIO {
class Job;
class SimpleJob;
}
namespace Collections {
class UpnpSearchCollection;
class UpnpQueryMakerInternal : public QObject
{
Q_OBJECT
public:
UpnpQueryMakerInternal( UpnpSearchCollection *collection );
~UpnpQueryMakerInternal();
void setQueryType( Collections::QueryMaker::QueryType type ) { m_queryType = type; }
void reset();
void runQuery( QUrl query, bool filter=true );
Q_SIGNALS:
void results( bool error, const KIO::UDSEntryList list );
void done();
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
void newResultReady( const KIO::UDSEntryList & );
private Q_SLOTS:
void slotEntries( KIO::Job *, const KIO::UDSEntryList & );
void slotDone( KJob * );
void slotStatDone( KJob * );
private:
void handleArtists( const KIO::UDSEntryList &list );
void handleAlbums( const KIO::UDSEntryList &list );
void handleTracks( const KIO::UDSEntryList &list );
void handleCustom( const KIO::UDSEntryList &list );
void queueJob( KIO::SimpleJob *job );
void runStat( const QString &id );
private:
UpnpSearchCollection *m_collection;
QueryMaker::QueryType m_queryType;
int m_jobCount;
};
}
#endif // UPNPQUERYMAKERINTERNAL_H
diff --git a/src/core-impl/logger/ProxyLogger.cpp b/src/core-impl/logger/ProxyLogger.cpp
index e7cef0c028..6900b29e49 100644
--- a/src/core-impl/logger/ProxyLogger.cpp
+++ b/src/core-impl/logger/ProxyLogger.cpp
@@ -1,186 +1,186 @@
/****************************************************************************************
* Copyright (c) 2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ProxyLogger.h"
#include <QCoreApplication>
#include <QMutexLocker>
#include <QNetworkReply>
ProxyLogger::ProxyLogger()
: Amarok::Logger()
, m_logger( 0 )
, m_timer( 0 )
{
// ensure that the object livs in the GUI thread
Q_ASSERT( thread() == QCoreApplication::instance()->thread() );
m_timer = new QTimer( this );
- connect( m_timer, SIGNAL(timeout()), this, SLOT(forwardNotifications()) );
+ connect( m_timer, &QTimer::timeout, this, &ProxyLogger::forwardNotifications );
m_timer->setSingleShot( true );
m_timer->setInterval( 0 );
- connect( this, SIGNAL(startTimer()), SLOT(slotStartTimer()) );
+ connect( this, &ProxyLogger::startTimer, this, &ProxyLogger::slotStartTimer );
}
ProxyLogger::~ProxyLogger()
{
//nothing to do
}
void
ProxyLogger::setLogger( Amarok::Logger *logger )
{
m_logger = logger;
emit startTimer();
}
Amarok::Logger *
ProxyLogger::logger() const
{
return m_logger;
}
void
ProxyLogger::slotStartTimer()
{
if( m_logger && !m_timer->isActive() )
m_timer->start();
}
void
ProxyLogger::shortMessage( const QString &text )
{
QMutexLocker locker( &m_lock );
m_shortMessageQueue.enqueue( text );
emit startTimer();
}
void
ProxyLogger::longMessage( const QString &text, MessageType type )
{
QMutexLocker locker( &m_lock );
LongMessage msg;
msg.first = text;
msg.second = type;
m_longMessageQueue.enqueue( msg );
emit startTimer();
}
void
ProxyLogger::newProgressOperation( KJob *job, const QString &text, QObject *obj, const char *slot, Qt::ConnectionType type )
{
QMutexLocker locker( &m_lock );
ProgressData data;
data.job = job;
data.text = text;
data.cancelObject = obj;
data.slot = slot;
data.type = type;
m_progressQueue.enqueue( data );
emit startTimer();
}
void
ProxyLogger::newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj, const char *slot, Qt::ConnectionType type )
{
QMutexLocker locker( &m_lock );
ProgressData data;
data.reply = reply;
data.text = text;
data.cancelObject = obj;
data.slot = slot;
data.type = type;
m_progressQueue.enqueue( data );
emit startTimer();
}
void
ProxyLogger::newProgressOperation( QObject *sender, const QString &text, int maximum, QObject *obj,
const char *slot, Qt::ConnectionType type )
{
QMutexLocker locker( &m_lock );
ProgressData data;
data.sender = sender;
data.text = text;
data.maximum = maximum;
data.cancelObject = obj;
data.slot = slot;
data.type = type;
m_progressQueue.enqueue( data );
connect( sender, SIGNAL(totalSteps(int)), SLOT(slotTotalSteps(int)) );
emit startTimer();
}
void
ProxyLogger::forwardNotifications()
{
QMutexLocker locker( &m_lock );
if( !m_logger )
return; //can't do anything before m_logger is created.
while( !m_shortMessageQueue.isEmpty() )
{
m_logger->shortMessage( m_shortMessageQueue.dequeue() );
}
while( !m_longMessageQueue.isEmpty() )
{
LongMessage msg = m_longMessageQueue.dequeue();
m_logger->longMessage( msg.first, msg.second );
}
while( !m_progressQueue.isEmpty() )
{
ProgressData d = m_progressQueue.dequeue();
if( d.job )
{
m_logger->newProgressOperation( d.job.data(), d.text, d.cancelObject.data(),
d.cancelObject.data() ? d.slot : 0 , d.type );
}
else if( d.reply )
{
m_logger->newProgressOperation( d.reply.data(), d.text, d.cancelObject.data(),
d.cancelObject.data() ? d.slot : 0 , d.type );
}
else if( d.sender )
{
// m_logger handles the signals from now on
disconnect( d.sender.data(), 0, this, 0 );
m_logger->newProgressOperation( d.sender.data(), d.text, d.maximum,
d.cancelObject.data(),
d.cancelObject.data() ? d.slot : 0 , d.type );
}
}
}
void
ProxyLogger::slotTotalSteps( int totalSteps )
{
QObject *operation = sender();
if( !operation )
// warning, slotTotalSteps can only be connected to progress operation QObject signal
return;
QMutableListIterator<ProgressData> it( m_progressQueue );
while( it.hasNext() )
{
ProgressData &data = it.next();
if( data.sender.data() != operation )
continue;
data.maximum = totalSteps;
return;
}
// warning, operation not found in m_progressQueue
}
diff --git a/src/core-impl/meta/proxy/MetaProxy.cpp b/src/core-impl/meta/proxy/MetaProxy.cpp
index b8db0f2de0..f139f027e2 100644
--- a/src/core-impl/meta/proxy/MetaProxy.cpp
+++ b/src/core-impl/meta/proxy/MetaProxy.cpp
@@ -1,526 +1,526 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MetaProxy.h"
#include "core/meta/Statistics.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/meta/proxy/MetaProxy_p.h"
#include "core-impl/meta/proxy/MetaProxyWorker.h"
#include <KSharedPtr>
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/Job>
#include <KLocalizedString>
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QWeakPointer>
using namespace MetaProxy;
class ProxyArtist;
class ProxyFmAlbum;
class ProxyGenre;
class ProxyComposer;
class ProxyYear;
MetaProxy::Track::Track( const QUrl &url, LookupType lookupType )
: Meta::Track()
, d( new Private() )
{
d->url = url;
d->proxy = this;
d->cachedLength = 0;
d->albumPtr = Meta::AlbumPtr( new ProxyAlbum( d ) );
d->artistPtr = Meta::ArtistPtr( new ProxyArtist( d ) );
d->genrePtr = Meta::GenrePtr( new ProxyGenre( d ) );
d->composerPtr = Meta::ComposerPtr( new ProxyComposer( d ) );
d->yearPtr = Meta::YearPtr( new ProxyYear( d ) );
QThread *mainThread = QCoreApplication::instance()->thread();
bool foreignThread = QThread::currentThread() != mainThread;
if( foreignThread )
d->moveToThread( mainThread );
if( lookupType == AutomaticLookup )
{
Worker *worker = new Worker( d->url );
if( foreignThread )
worker->moveToThread( mainThread );
- QObject::connect( worker, SIGNAL(finishedLookup(Meta::TrackPtr)),
- d, SLOT(slotUpdateTrack(Meta::TrackPtr)) );
+ QObject::connect( worker, &Worker::finishedLookup,
+ d, &Track::Private::slotUpdateTrack );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(worker) );
}
}
MetaProxy::Track::~Track()
{
delete d;
}
void
MetaProxy::Track::lookupTrack( Collections::TrackProvider *provider )
{
Worker *worker = new Worker( d->url, provider );
QThread *mainThread = QCoreApplication::instance()->thread();
if( QThread::currentThread() != mainThread )
worker->moveToThread( mainThread );
- QObject::connect( worker, SIGNAL(finishedLookup(Meta::TrackPtr)),
- d, SLOT(slotUpdateTrack(Meta::TrackPtr)) );
+ QObject::connect( worker, &MetaProxy::Worker::finishedLookup,
+ d, &Private::slotUpdateTrack );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(worker) );
}
QString
MetaProxy::Track::name() const
{
if( d->realTrack )
return d->realTrack->name();
else
return d->cachedName;
}
void
MetaProxy::Track::setTitle( const QString &name )
{
d->cachedName = name;
}
QString
MetaProxy::Track::prettyName() const
{
if( d->realTrack )
return d->realTrack->prettyName();
else
return Meta::Track::prettyName();
}
QString
MetaProxy::Track::sortableName() const
{
if( d->realTrack )
return d->realTrack->sortableName();
else
return Meta::Track::sortableName();
}
QUrl
MetaProxy::Track::playableUrl() const
{
if( d->realTrack )
return d->realTrack->playableUrl();
else
/* don't return d->url here, it may be something like
* amarok-sqltrackuid://2f9277bb7e49962c1c4c5612811807a1 and Phonon may choke
* on such urls trying to find a codec and causing hang (bug 308371) */
return QUrl();
}
QString
MetaProxy::Track::prettyUrl() const
{
if( d->realTrack )
return d->realTrack->prettyUrl();
else
return d->url.toDisplayString();
}
QString
MetaProxy::Track::uidUrl() const
{
if( d->realTrack )
return d->realTrack->uidUrl();
else
return d->url.url();
}
QString
MetaProxy::Track::notPlayableReason() const
{
if( !d->realTrack )
return i18n( "When Amarok was last closed, this track was at %1, but Amarok "
"cannot find this track on the filesystem or in any of your collections "
"anymore. You may try plugging in the device this track might be on.",
prettyUrl() );
return d->realTrack->notPlayableReason();
}
Meta::AlbumPtr
MetaProxy::Track::album() const
{
return d->albumPtr;
}
void
MetaProxy::Track::setAlbum( const QString &album )
{
d->cachedAlbum = album;
}
void
Track::setAlbumArtist( const QString &artist )
{
Q_UNUSED( artist );
}
Meta::ArtistPtr
MetaProxy::Track::artist() const
{
return d->artistPtr;
}
void
MetaProxy::Track::setArtist( const QString &artist )
{
d->cachedArtist = artist;
}
Meta::GenrePtr
MetaProxy::Track::genre() const
{
return d->genrePtr;
}
void
MetaProxy::Track::setGenre( const QString &genre )
{
d->cachedGenre = genre;
}
Meta::ComposerPtr
MetaProxy::Track::composer() const
{
return d->composerPtr;
}
void
MetaProxy::Track::setComposer( const QString &composer )
{
d->cachedComposer = composer;
}
Meta::YearPtr
MetaProxy::Track::year() const
{
return d->yearPtr;
}
void
MetaProxy::Track::setYear( int year )
{
d->cachedYear = year;
}
Meta::LabelList
Track::labels() const
{
if( d->realTrack )
return d->realTrack->labels();
else
return Meta::Track::labels();
}
qreal
MetaProxy::Track::bpm() const
{
if( d->realTrack )
return d->realTrack->bpm();
else
return d->cachedBpm;
}
void
MetaProxy::Track::setBpm( const qreal bpm )
{
d->cachedBpm = bpm;
}
QString
MetaProxy::Track::comment() const
{
if( d->realTrack )
return d->realTrack->comment();
else
return QString(); // we don't cache comment
}
void
Track::setComment( const QString & )
{
// we don't cache comment
}
int
MetaProxy::Track::trackNumber() const
{
if( d->realTrack )
return d->realTrack->trackNumber();
else
return d->cachedTrackNumber;
}
void
MetaProxy::Track::setTrackNumber( int number )
{
d->cachedTrackNumber = number;
}
int
MetaProxy::Track::discNumber() const
{
if( d->realTrack )
return d->realTrack->discNumber();
else
return d->cachedDiscNumber;
}
void
MetaProxy::Track::setDiscNumber( int discNumber )
{
d->cachedDiscNumber = discNumber;
}
qint64
MetaProxy::Track::length() const
{
if( d->realTrack )
return d->realTrack->length();
else
return d->cachedLength;
}
void
MetaProxy::Track::setLength( qint64 length )
{
d->cachedLength = length;
}
int
MetaProxy::Track::filesize() const
{
if( d->realTrack )
return d->realTrack->filesize();
else
return 0;
}
int
MetaProxy::Track::sampleRate() const
{
if( d->realTrack )
return d->realTrack->sampleRate();
else
return 0;
}
int
MetaProxy::Track::bitrate() const
{
if( d->realTrack )
return d->realTrack->bitrate();
else
return 0;
}
QDateTime
MetaProxy::Track::createDate() const
{
if( d->realTrack )
return d->realTrack->createDate();
else
return Meta::Track::createDate();
}
QDateTime
Track::modifyDate() const
{
if( d->realTrack )
return d->realTrack->modifyDate();
else
return Meta::Track::modifyDate();
}
qreal
Track::replayGain( Meta::ReplayGainTag mode ) const
{
if( d->realTrack )
return d->realTrack->replayGain( mode );
else
return Meta::Track::replayGain( mode );
}
QString
MetaProxy::Track::type() const
{
if( d->realTrack )
return d->realTrack->type();
else
// just debugging, normal users shouldn't hit this
return QString( "MetaProxy::Track" );
}
void
Track::prepareToPlay()
{
if( d->realTrack )
d->realTrack->prepareToPlay();
}
void
MetaProxy::Track::finishedPlaying( double playedFraction )
{
if( d->realTrack )
d->realTrack->finishedPlaying( playedFraction );
}
bool
MetaProxy::Track::inCollection() const
{
if( d->realTrack )
return d->realTrack->inCollection();
else
return false;
}
Collections::Collection *
MetaProxy::Track::collection() const
{
if( d->realTrack )
return d->realTrack->collection();
else
return 0;
}
QString
Track::cachedLyrics() const
{
if( d->realTrack )
return d->realTrack->cachedLyrics();
else
return Meta::Track::cachedLyrics();
}
void
Track::setCachedLyrics(const QString& lyrics)
{
if( d->realTrack )
d->realTrack->setCachedLyrics( lyrics );
else
Meta::Track::setCachedLyrics( lyrics );
}
void
Track::addLabel( const QString &label )
{
if( d->realTrack )
d->realTrack->addLabel( label );
else
Meta::Track::addLabel( label );
}
void
Track::addLabel( const Meta::LabelPtr &label )
{
if( d->realTrack )
d->realTrack->addLabel( label );
else
Meta::Track::addLabel( label );
}
void
Track::removeLabel( const Meta::LabelPtr &label )
{
if( d->realTrack )
d->realTrack->removeLabel( label );
else
Meta::Track::removeLabel( label );
}
void
MetaProxy::Track::updateTrack( Meta::TrackPtr track )
{
d->slotUpdateTrack( track );
}
bool
MetaProxy::Track::hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
if( d->realTrack )
return d->realTrack->hasCapabilityInterface( type );
else
return false;
}
Capabilities::Capability *
MetaProxy::Track::createCapabilityInterface( Capabilities::Capability::Type type )
{
if( d->realTrack )
return d->realTrack->createCapabilityInterface( type );
else
return 0;
}
bool
MetaProxy::Track::operator==( const Meta::Track &track ) const
{
const MetaProxy::Track *proxy = dynamic_cast<const MetaProxy::Track *>( &track );
if( proxy && d->realTrack )
return d->realTrack == proxy->d->realTrack;
else if( proxy )
return d->url == proxy->d->url;
return d->realTrack && d->realTrack.data() == &track;
}
Meta::TrackEditorPtr
Track::editor()
{
if( d->realTrack )
return d->realTrack->editor();
else
return Meta::TrackEditorPtr( this );
}
Meta::StatisticsPtr
Track::statistics()
{
if( d->realTrack )
return d->realTrack->statistics();
else
return Meta::Track::statistics();
}
void
Track::beginUpdate()
{
// nothing to do
}
void
Track::endUpdate()
{
// we intentionally don't call metadataUpdated() so that thi first thing that
// triggers metadataUpdated() is when the real track is found.
}
bool
Track::isResolved() const
{
return d->realTrack;
}
diff --git a/src/core-impl/meta/proxy/MetaProxyWorker.cpp b/src/core-impl/meta/proxy/MetaProxyWorker.cpp
index 400ef91639..961c169707 100644
--- a/src/core-impl/meta/proxy/MetaProxyWorker.cpp
+++ b/src/core-impl/meta/proxy/MetaProxyWorker.cpp
@@ -1,114 +1,110 @@
/****************************************************************************************
* Copyright (c) 2012 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MetaProxyWorker.h"
#include "core/meta/Meta.h"
#include "core-impl/collections/support/CollectionManager.h"
using namespace MetaProxy;
Worker::Worker( const QUrl &url, Collections::TrackProvider *provider )
: QObject()
, ThreadWeaver::Job()
, m_url( url )
, m_provider( provider )
, m_stepsDoneReceived( 0 )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(slotStepDone()) );
- connect( this, SIGNAL(finishedLookup(Meta::TrackPtr)), SLOT(slotStepDone()) );
+ connect( this, &Worker::done, this, &Worker::slotStepDone );
+ connect( this, &Worker::finishedLookup, this, &Worker::slotStepDone );
}
void
Worker::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
Meta::TrackPtr track;
if( m_provider )
{
track = m_provider->trackForUrl( m_url );
emit finishedLookup( track );
return;
}
track = CollectionManager::instance()->trackForUrl( m_url );
if( track )
{
emit finishedLookup( track );
return;
}
// no TrackProvider has a track for us yet, query new ones that are added.
if( !track )
{
- connect( CollectionManager::instance(),
- SIGNAL(trackProviderAdded(Collections::TrackProvider*)),
- SLOT(slotNewTrackProvider(Collections::TrackProvider*)),
- Qt::DirectConnection ); // we may live in a thread w/out event loop
- connect( CollectionManager::instance(),
- SIGNAL(collectionAdded(Collections::Collection*)),
- SLOT(slotNewCollection(Collections::Collection*)),
- Qt::DirectConnection ); // we may live in a thread w/out event loop
+ connect( CollectionManager::instance(), &CollectionManager::trackProviderAdded,
+ this, &Worker::slotNewTrackProvider, Qt::DirectConnection ); // we may live in a thread w/out event loop
+ connect( CollectionManager::instance(),&CollectionManager::collectionAdded,
+ this, &Worker::slotNewCollection, Qt::DirectConnection ); // we may live in a thread w/out event loop
return;
}
}
void
Worker::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
Worker::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
Worker::slotNewTrackProvider( Collections::TrackProvider *newTrackProvider )
{
if( !newTrackProvider )
return;
if( newTrackProvider->possiblyContainsTrack( m_url ) )
{
Meta::TrackPtr track = newTrackProvider->trackForUrl( m_url );
emit finishedLookup( track );
}
}
void
Worker::slotNewCollection( Collections::Collection *newCollection )
{
slotNewTrackProvider( newCollection );
}
void
Worker::slotStepDone()
{
m_stepsDoneReceived++;
if( m_stepsDoneReceived >= 2 )
deleteLater();
}
diff --git a/src/core-impl/meta/timecode/TimecodeObserver.cpp b/src/core-impl/meta/timecode/TimecodeObserver.cpp
index c6f9323d7e..ebc3f07aaf 100644
--- a/src/core-impl/meta/timecode/TimecodeObserver.cpp
+++ b/src/core-impl/meta/timecode/TimecodeObserver.cpp
@@ -1,95 +1,95 @@
/****************************************************************************************
* Copyright (c) 2009 Casey Link <unnamedrambler@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TimecodeObserver.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/capabilities/timecode/TimecodeWriteCapability.h"
const qint64 TimecodeObserver::m_threshold = 600 * 1000; // 6000000ms = 10 minutes
TimecodeObserver::TimecodeObserver( QObject *parent )
: QObject( parent )
, m_trackTimecodeable ( false )
, m_currentTrack ( 0 )
, m_currPos ( 0 )
{
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped(qint64,qint64)) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(trackPlaying(Meta::TrackPtr)) );
- connect( engine, SIGNAL(trackPositionChanged(qint64,bool)),
- this, SLOT(trackPositionChanged(qint64,bool)) );
+ connect( engine, &EngineController::stopped,
+ this, &TimecodeObserver::stopped );
+ connect( engine, &EngineController::trackPlaying,
+ this, &TimecodeObserver::trackPlaying );
+ connect( engine, &EngineController::trackPositionChanged,
+ this, &TimecodeObserver::trackPositionChanged );
}
TimecodeObserver::~TimecodeObserver()
{}
void
TimecodeObserver::stopped( qint64 finalPosition, qint64 trackLength )
{
DEBUG_BLOCK
if( m_trackTimecodeable && finalPosition != trackLength && trackLength > m_threshold && finalPosition > 60 * 1000 )
{
Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
if( currentTrack )
{
Capabilities::TimecodeWriteCapability *tcw = currentTrack->create<Capabilities::TimecodeWriteCapability>();
if( tcw )
{
tcw->writeAutoTimecode ( finalPosition ); // save the timecode
delete tcw;
}
}
}
}
void
TimecodeObserver::trackPlaying( Meta::TrackPtr track )
{
if( track == m_currentTrack ) // no change, so do nothing
return;
if( m_currentTrack ) // this is really the track _just_ played
{
if( m_trackTimecodeable && m_currPos != m_currentTrack->length() && m_currentTrack->length() > m_threshold && m_currPos > 60 * 1000 )
{
QScopedPointer<Capabilities::TimecodeWriteCapability> tcw( m_currentTrack->create<Capabilities::TimecodeWriteCapability>() );
if( tcw )
tcw->writeAutoTimecode ( m_currPos ); // save the timecode
}
}
// now update to the new track
if( track && track->has<Capabilities::TimecodeWriteCapability>() )
m_trackTimecodeable = true;
m_currentTrack = track;
m_currPos = 0;
}
void TimecodeObserver::trackPositionChanged( qint64 position, bool userSeek )
{
Q_UNUSED ( userSeek )
m_currPos = position;
}
diff --git a/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp b/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp
index a6b448b1ae..912282a291 100644
--- a/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp
+++ b/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp
@@ -1,138 +1,138 @@
/****************************************************************************************
* Copyright (c) 2013 Tatjana Gornak <t.gornak@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core-impl/playlists/types/file/PlaylistFileLoaderJob.h"
#include "core/meta/Meta.h"
#include "core/playlists/PlaylistFormat.h"
#include "core/interfaces/Logger.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/support/SemaphoreReleaser.h"
#include <KLocale>
#include <KMessageBox>
#include <QUrl>
#include <KIO/Job>
#include <QFile>
#include <QFileInfo>
#include <QString>
#include <QTextStream>
using namespace Playlists;
PlaylistFileLoaderJob::PlaylistFileLoaderJob( const PlaylistFilePtr &playlist )
: QObject()
, m_playlist( playlist )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDone()) );
+ connect( this, &PlaylistFileLoaderJob::done, this, &PlaylistFileLoaderJob::slotDone );
// we must handle remove downloading here as KIO is coupled with GUI as is not
// designed to work from another thread
const QUrl url = playlist->uidUrl();
if( url.isLocalFile() )
{
m_actualPlaylistFile = url.toLocalFile();
m_downloadSemaphore.release(); // pretend file "already downloaded"
}
else
{
m_tempFile.setSuffix( '.' + Amarok::extension( url.url() ) );
if( !m_tempFile.open() )
{
Amarok::Components::logger()->longMessage(
i18n( "Could not create a temporary file to download playlist." ) );
m_downloadSemaphore.release(); // prevent deadlock
return;
}
KIO::FileCopyJob *job = KIO::file_copy( url , QUrl::fromLocalFile(m_tempFile.fileName()), 0774,
KIO::Overwrite | KIO::HideProgressInfo );
Amarok::Components::logger()->newProgressOperation( job,
i18n("Downloading remote playlist" ) );
if( playlist->isLoadingAsync() )
// job is started automatically by KIO
- connect( job, SIGNAL(finished(KJob*)), SLOT(slotDonwloadFinished(KJob*)) );
+ connect( job, &KIO::FileCopyJob::finished, this, &PlaylistFileLoaderJob::slotDonwloadFinished );
else
{
job->exec();
slotDonwloadFinished( job );
}
}
}
void
PlaylistFileLoaderJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
SemaphoreReleaser releaser( m_playlist->isLoadingAsync() ? 0 : &m_playlist->m_loadingDone );
m_downloadSemaphore.acquire(); // wait for possible download to finish
if( m_actualPlaylistFile.isEmpty() )
return; // previous error, already reported
QFile file( m_actualPlaylistFile );
if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
using namespace Amarok;
Components::logger()->longMessage( i18nc( "%1 is file path",
"Cannot read playlist from %1", m_actualPlaylistFile ), Logger::Error );
return;
}
QByteArray content = file.readAll();
file.close();
m_playlist->load( content );
}
void
PlaylistFileLoaderJob::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
PlaylistFileLoaderJob::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
PlaylistFileLoaderJob::slotDonwloadFinished( KJob *job )
{
if( job->error() )
{
using namespace Amarok;
warning() << job->errorString();
}
else
m_actualPlaylistFile = m_tempFile.fileName();
m_downloadSemaphore.release();
}
void
PlaylistFileLoaderJob::slotDone()
{
m_playlist->notifyObserversTracksLoaded();
deleteLater();
}
diff --git a/src/core-impl/podcasts/sql/PodcastFilenameLayoutConfigDialog.cpp b/src/core-impl/podcasts/sql/PodcastFilenameLayoutConfigDialog.cpp
index 3ab8622d9d..8d5acd40a8 100644
--- a/src/core-impl/podcasts/sql/PodcastFilenameLayoutConfigDialog.cpp
+++ b/src/core-impl/podcasts/sql/PodcastFilenameLayoutConfigDialog.cpp
@@ -1,96 +1,96 @@
/****************************************************************************************
* Copyright (c) 2011 Sandeep Raghuraman <sandy.8925@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PodcastFilenameLayoutConfigDialog.h"
#include "ui_PodcastFilenameLayoutConfigWidget.h"
#include <QApplication>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
PodcastFilenameLayoutConfigDialog::PodcastFilenameLayoutConfigDialog( Podcasts::SqlPodcastChannelPtr channel, QWidget *parent )
: KPageDialog( parent )
, m_channel( channel )
, m_pflc( new Ui::PodcastFilenameLayoutConfigWidget )
{
QWidget* main = new QWidget( this );
m_pflc->setupUi( main );
setWindowTitle( i18nc( "Change filename layout", "Podcast Episode Filename Configuration" ) );
setModal( true );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
// showButtonSeparator( true ); TODO KF5: Replace with a Qt5 equivalent (if any equivalent exists)
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &PodcastFilenameLayoutConfigDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &PodcastFilenameLayoutConfigDialog::reject);
mainLayout->addWidget(main);
mainLayout->addWidget(buttonBox);
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
init();
}
void
PodcastFilenameLayoutConfigDialog::init()
{
//initialize state of the various gui items based on the channel settings
QString filenameLayout = m_channel->filenameLayout();
if( filenameLayout == QLatin1String( "%default%" ) )
{
m_pflc->m_filenameLayoutDefault->setChecked( true );
m_pflc->m_filenameLayoutCustom->setChecked( false );
m_choice = 0;
}
else
{
m_pflc->m_filenameLayoutDefault->setChecked( false );
m_pflc->m_filenameLayoutCustom->setChecked( true );
m_pflc->m_filenameLayoutText->setText( filenameLayout );
m_choice = 1;
}
- connect( buttonBox()->button(QDialogButtonBox::Ok) , SIGNAL(clicked()), this, SLOT(slotApply()) );
+ connect( buttonBox()->button(QDialogButtonBox::Ok) , &QAbstractButton::clicked, this, &PodcastFilenameLayoutConfigDialog::slotApply );
}
void
PodcastFilenameLayoutConfigDialog::slotApply()
{
if( m_pflc->m_filenameLayoutCustom->isChecked() )
m_channel->setFilenameLayout( m_pflc->m_filenameLayoutText->text() );
else
m_channel->setFilenameLayout( "%default%" );
}
bool
PodcastFilenameLayoutConfigDialog::configure()
{
return exec() == QDialog::Accepted;
}
diff --git a/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp b/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp
index 614c5bf0aa..508c6cb028 100644
--- a/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp
+++ b/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp
@@ -1,175 +1,177 @@
/****************************************************************************************
* Copyright (c) 2006-2008 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PodcastSettingsDialog.h"
#include "ui_PodcastSettingsBase.h"
#include "PodcastFilenameLayoutConfigDialog.h"
#include "core/support/Debug.h"
#include <QApplication>
#include <QClipboard>
#include <QFontMetrics>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
PodcastSettingsDialog::PodcastSettingsDialog( Podcasts::SqlPodcastChannelPtr channel, QWidget* parent )
: KPageDialog( parent )
, m_ps( new Ui::PodcastSettingsBase() )
, m_channel( channel )
{
QWidget* main = new QWidget( this );
m_ps->setupUi( main );
setWindowTitle( i18nc("change options", "Configure %1", m_channel->title() ) );
setModal( true );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(main);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &PodcastSettingsDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &PodcastSettingsDialog::reject);
okButton->setDefault(true);
mainLayout->addWidget(buttonBox);
init();
}
void
PodcastSettingsDialog::init()
{
QString url = m_channel->url().url();
m_ps->m_urlLineEdit->setText( url );
m_ps->m_saveLocation->setMode( KFile::Directory | KFile::ExistingOnly );
m_ps->m_saveLocation->setUrl( m_channel->saveLocation() );
m_ps->m_autoFetchCheck->setChecked( m_channel->autoScan() );
if( m_channel->fetchType() == Podcasts::PodcastChannel::StreamOrDownloadOnDemand )
{
m_ps->m_streamRadio->setChecked( true );
m_ps->m_downloadRadio->setChecked( false );
}
else if( m_channel->fetchType() == Podcasts::PodcastChannel::DownloadWhenAvailable )
{
m_ps->m_streamRadio->setChecked( false );
m_ps->m_downloadRadio->setChecked( true );
}
m_ps->m_purgeCheck->setChecked( m_channel->hasPurge() );
m_ps->m_purgeCountSpinBox->setValue( m_channel->purgeCount() );
m_ps->m_purgeCountSpinBox->setSuffix( ki18np( " Item", " Items" ) );
if( !m_channel->hasPurge() )
{
m_ps->m_purgeCountSpinBox->setEnabled( false );
m_ps->m_purgeCountLabel->setEnabled( false );
}
m_ps->m_writeTagsCheck->setChecked( m_channel->writeTags() );
buttonBox()->button(QDialogButtonBox::Apply)->setEnabled( false );
// Connects for modification check
- connect( m_ps->m_urlLineEdit, SIGNAL(textChanged(QString)),
- SLOT(checkModified()) );
- connect( m_ps->m_saveLocation, SIGNAL(textChanged(QString)),
- SLOT(checkModified()) );
- connect( m_ps->m_autoFetchCheck, SIGNAL(clicked()), SLOT(checkModified()) );
- connect( m_ps->m_streamRadio, SIGNAL(clicked()), SLOT(checkModified()) );
- connect( m_ps->m_downloadRadio, SIGNAL(clicked()), SLOT(checkModified()) );
- connect( m_ps->m_purgeCheck, SIGNAL(clicked()), SLOT(checkModified()) );
- connect( m_ps->m_purgeCountSpinBox, SIGNAL(valueChanged(int)), SLOT(checkModified()) );
- connect( m_ps->m_writeTagsCheck, SIGNAL(clicked()), SLOT(checkModified()) );
- connect( m_ps->m_filenameLayoutConfigWidgetButton, SIGNAL(clicked()), SLOT(launchFilenameLayoutConfigDialog()) );
-
- connect(buttonBox()->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this ,SLOT(slotApply()) );
- connect(buttonBox()->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(slotApply()) );
+ connect( m_ps->m_urlLineEdit, &QLineEdit::textChanged,
+ this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_saveLocation, &KUrlRequester::textChanged,
+ this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_autoFetchCheck, &QAbstractButton::clicked, this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_streamRadio, &QAbstractButton::clicked, this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_downloadRadio, &QAbstractButton::clicked, this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_purgeCheck, &QAbstractButton::clicked, this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_purgeCountSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
+ this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_writeTagsCheck, &QAbstractButton::clicked, this, &PodcastSettingsDialog::checkModified );
+ connect( m_ps->m_filenameLayoutConfigWidgetButton, &QAbstractButton::clicked,
+ this, &PodcastSettingsDialog::launchFilenameLayoutConfigDialog );
+
+ connect(buttonBox()->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &PodcastSettingsDialog::slotApply );
+ connect(buttonBox()->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &PodcastSettingsDialog::slotApply );
}
void
PodcastSettingsDialog::slotFeedUrlClicked( const QString &url ) //SLOT
{
//adding url to clipboard for users convenience
QApplication::clipboard()->setText( url );
}
bool
PodcastSettingsDialog::hasChanged()
{
bool fetchTypeChanged = true;
if( ( m_ps->m_streamRadio->isChecked() && m_channel->fetchType() == Podcasts::PodcastChannel::StreamOrDownloadOnDemand ) ||
( m_ps->m_downloadRadio->isChecked() && m_channel->fetchType() == Podcasts::PodcastChannel::DownloadWhenAvailable ) )
{
fetchTypeChanged = false;
}
return( m_channel->url() != QUrl::fromUserInput(m_ps->m_urlLineEdit->text()) ||
m_channel->saveLocation() != m_ps->m_saveLocation->url() ||
m_channel->autoScan() != m_ps->m_autoFetchCheck->isChecked() ||
m_channel->hasPurge() != m_ps->m_purgeCheck->isChecked() ||
m_channel->purgeCount() != m_ps->m_purgeCountSpinBox->value() ||
fetchTypeChanged ||
m_channel->writeTags() != m_ps->m_writeTagsCheck->isChecked()
);
}
void
PodcastSettingsDialog::checkModified() //slot
{
buttonBox()->button(QDialogButtonBox::Apply)->setEnabled( hasChanged() );
}
void
PodcastSettingsDialog::slotApply() //slot
{
m_channel->setUrl( QUrl( m_ps->m_urlLineEdit->text() ) );
m_channel->setAutoScan( m_ps->m_autoFetchCheck->isChecked() );
m_channel->setFetchType(
m_ps->m_downloadRadio->isChecked() ?
Podcasts::PodcastChannel::DownloadWhenAvailable :
Podcasts::PodcastChannel::StreamOrDownloadOnDemand
);
m_channel->setSaveLocation( m_ps->m_saveLocation->url() );
m_channel->setPurge( m_ps->m_purgeCheck->isChecked() );
m_channel->setPurgeCount( m_ps->m_purgeCountSpinBox->value() );
m_channel->setWriteTags( m_ps->m_writeTagsCheck->isChecked() );
buttonBox()->button(QDialogButtonBox::Apply)->setEnabled( false );
}
bool PodcastSettingsDialog::configure()
{
return exec() == QDialog::Accepted;
}
void PodcastSettingsDialog::launchFilenameLayoutConfigDialog()
{
PodcastFilenameLayoutConfigDialog pflcDialog( m_channel, this );
pflcDialog.configure();
}
diff --git a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp
index c6c0a29dcd..8b7fbe568f 100644
--- a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp
+++ b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp
@@ -1,1633 +1,1625 @@
/****************************************************************************************
* Copyright (c) 2007-2009 Bart Cerneels <bart.cerneels@kde.org> *
* Copyright (c) 2009 Frank Meerkoetter <frank@meerkoetter.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SqlPodcastProvider.h"
#include "MainWindow.h"
#include "OpmlWriter.h"
#include "SvgHandler.h"
#include "QStringx.h"
#include "browsers/playlistbrowser/PodcastModel.h"
#include "context/popupdropper/libpud/PopupDropper.h"
#include "context/popupdropper/libpud/PopupDropperItem.h"
#include <core/storage/SqlStorage.h>
#include "core/interfaces/Logger.h"
#include "core/podcasts/PodcastImageFetcher.h"
#include "core/podcasts/PodcastReader.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/storage/StorageManager.h"
#include "core-impl/podcasts/sql/PodcastSettingsDialog.h"
#include "playlistmanager/sql/SqlPlaylistGroup.h"
#include "ui_SqlPodcastProviderSettingsWidget.h"
#include <KCodecs>
#include <KFileDialog>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/Job>
#include <KIO/NetAccess>
#include <KLocale>
#include <KProgressDialog>
#include <KStandardDirs>
#include <QUrl>
#include <Solid/Networking>
#include <QAction>
#include <QCheckBox>
#include <QDir>
#include <QFile>
#include <QMap>
#include <QTimer>
using namespace Podcasts;
static const int PODCAST_DB_VERSION = 6;
static const QString key( "AMAROK_PODCAST" );
static const QString PODCAST_TMP_POSTFIX( ".tmp" );
SqlPodcastProvider::SqlPodcastProvider()
: m_updateTimer( new QTimer( this ) )
, m_updatingChannels( 0 )
, m_completedDownloads( 0 )
, m_providerSettingsDialog( 0 )
, m_providerSettingsWidget( 0 )
, m_configureChannelAction( 0 )
, m_deleteAction( 0 )
, m_downloadAction( 0 )
, m_keepAction( 0 )
, m_removeAction( 0 )
, m_updateAction( 0 )
, m_writeTagsAction( 0 )
, m_podcastImageFetcher( 0 )
{
- connect( m_updateTimer, SIGNAL(timeout()), SLOT(autoUpdate()) );
+ connect( m_updateTimer, &QTimer::timeout, this, &SqlPodcastProvider::autoUpdate );
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
{
error() << "Could not get a SqlStorage instance";
return;
}
m_autoUpdateInterval = Amarok::config( "Podcasts" )
.readEntry( "AutoUpdate Interval", 30 );
m_maxConcurrentDownloads = Amarok::config( "Podcasts" )
.readEntry( "Maximum Simultaneous Downloads", 4 );
m_maxConcurrentUpdates = Amarok::config( "Podcasts" )
.readEntry( "Maximum Simultaneous Updates", 4 );
m_baseDownloadDir = QUrl::fromUserInput( Amarok::config( "Podcasts" ).readEntry( "Base Download Directory",
Amarok::saveLocation( "podcasts" ) ) );
QStringList values;
values = sqlStorage->query(
QString( "SELECT version FROM admin WHERE component = '%1';" )
.arg( sqlStorage->escape( key ) )
);
if( values.isEmpty() )
{
debug() << "creating Podcast Tables";
createTables();
sqlStorage->query( "INSERT INTO admin(component,version) "
"VALUES('" + key + "',"
+ QString::number( PODCAST_DB_VERSION ) + ");" );
}
else
{
int version = values.first().toInt();
if( version == PODCAST_DB_VERSION )
loadPodcasts();
else
updateDatabase( version /*from*/, PODCAST_DB_VERSION /*to*/ );
startTimer();
}
}
void
SqlPodcastProvider::startTimer()
{
if( !m_autoUpdateInterval )
return; //timer is disabled
if( m_updateTimer->isActive() &&
m_updateTimer->interval() == ( m_autoUpdateInterval * 1000 * 60 ) )
return; //already started with correct interval
//and only start if at least one channel has autoscan enabled
foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels )
{
if( channel->autoScan() )
{
m_updateTimer->start( 1000 * 60 * m_autoUpdateInterval );
return;
}
}
}
SqlPodcastProvider::~SqlPodcastProvider()
{
foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels )
{
channel->updateInDb();
foreach( Podcasts::SqlPodcastEpisodePtr episode, channel->sqlEpisodes() )
episode->updateInDb();
}
m_channels.clear();
Amarok::config( "Podcasts" )
.writeEntry( "AutoUpdate Interval", m_autoUpdateInterval );
Amarok::config( "Podcasts" )
.writeEntry( "Maximum Simultaneous Downloads", m_maxConcurrentDownloads );
Amarok::config( "Podcasts" )
.writeEntry( "Maximum Simultaneous Updates", m_maxConcurrentUpdates );
}
void
SqlPodcastProvider::loadPodcasts()
{
m_channels.clear();
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return;
QStringList results = sqlStorage->query( "SELECT id, url, title, weblink, image"
", description, copyright, directory, labels, subscribedate, autoscan, fetchtype"
", haspurge, purgecount, writetags, filenamelayout FROM podcastchannels;" );
int rowLength = 16;
for( int i = 0; i < results.size(); i += rowLength )
{
QStringList channelResult = results.mid( i, rowLength );
SqlPodcastChannelPtr channel =
SqlPodcastChannelPtr( new SqlPodcastChannel( this, channelResult ) );
if( channel->image().isNull() )
fetchImage( channel );
m_channels << channel;
}
if( m_podcastImageFetcher )
m_podcastImageFetcher->run();
emit updated();
}
SqlPodcastEpisodePtr
SqlPodcastProvider::sqlEpisodeForString( const QString &string )
{
if( string.isEmpty() )
return SqlPodcastEpisodePtr();
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return SqlPodcastEpisodePtr();
QString command = "SELECT id, url, channel, localurl, guid, "
"title, subtitle, sequencenumber, description, mimetype, pubdate, "
"duration, filesize, isnew, iskeep FROM podcastepisodes "
"WHERE guid='%1' OR url='%1' OR localurl='%1' ORDER BY id DESC;";
command = command.arg( sqlStorage->escape( string ) );
QStringList dbResult = sqlStorage->query( command );
if( dbResult.isEmpty() )
return SqlPodcastEpisodePtr();
int episodeId = dbResult[0].toInt();
int channelId = dbResult[2].toInt();
bool found = false;
Podcasts::SqlPodcastChannelPtr channel;
foreach( channel, m_channels )
{
if( channel->dbId() == channelId )
{
found = true;
break;
}
}
if( !found )
{
error() << QString( "There is a track in the database with url/guid=%1 (%2) "
"but there is no channel with dbId=%3 in our list!" )
.arg( string ).arg( episodeId ).arg( channelId );
return SqlPodcastEpisodePtr();
}
Podcasts::SqlPodcastEpisodePtr episode;
foreach( episode, channel->sqlEpisodes() )
if( episode->dbId() == episodeId )
return episode;
//The episode was found in the database but it's channel didn't have it in it's list.
//That probably is because it's beyond the purgecount limit or the tracks were not loaded yet.
return SqlPodcastEpisodePtr( new SqlPodcastEpisode( dbResult.mid( 0, 15 ), channel ) );
}
bool
SqlPodcastProvider::possiblyContainsTrack( const QUrl &url ) const
{
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return false;
QString command = "SELECT id FROM podcastepisodes WHERE guid='%1' OR url='%1' "
"OR localurl='%1';";
command = command.arg( sqlStorage->escape( url.url() ) );
QStringList dbResult = sqlStorage->query( command );
return !dbResult.isEmpty();
}
Meta::TrackPtr
SqlPodcastProvider::trackForUrl( const QUrl &url )
{
if( url.isEmpty() )
return Meta::TrackPtr();
SqlPodcastEpisodePtr episode = sqlEpisodeForString( url.url() );
return Meta::TrackPtr::dynamicCast( episode );
}
Playlists::PlaylistList
SqlPodcastProvider::playlists()
{
Playlists::PlaylistList playlistList;
QListIterator<Podcasts::SqlPodcastChannelPtr> i( m_channels );
while( i.hasNext() )
{
playlistList << Playlists::PlaylistPtr::staticCast( i.next() );
}
return playlistList;
}
QActionList
SqlPodcastProvider::providerActions()
{
if( m_providerActions.isEmpty() )
{
QAction *updateAllAction = new QAction( QIcon::fromTheme( "view-refresh-amarok" ),
i18n( "&Update All Channels" ), this );
updateAllAction->setProperty( "popupdropper_svg_id", "update" );
- connect( updateAllAction, SIGNAL(triggered()), this, SLOT(updateAll()) );
+ connect( updateAllAction, &QAction::triggered, this, &SqlPodcastProvider::updateAll );
m_providerActions << updateAllAction;
QAction *configureAction = new QAction( QIcon::fromTheme( "configure" ),
i18n( "&Configure General Settings" ), this );
configureAction->setProperty( "popupdropper_svg_id", "configure" );
- connect( configureAction, SIGNAL(triggered()), this, SLOT(slotConfigureProvider()) );
+ connect( configureAction, &QAction::triggered, this, &SqlPodcastProvider::slotConfigureProvider );
m_providerActions << configureAction;
QAction *exportOpmlAction = new QAction( QIcon::fromTheme( "document-export" ),
i18n( "&Export subscriptions to OPML file" ), this );
- connect( exportOpmlAction, SIGNAL(triggered()), SLOT(slotExportOpml()) );
+ connect( exportOpmlAction, &QAction::triggered, this, &SqlPodcastProvider::slotExportOpml );
m_providerActions << exportOpmlAction;
}
return m_providerActions;
}
QActionList
SqlPodcastProvider::playlistActions( const Playlists::PlaylistList &playlists )
{
QActionList actions;
SqlPodcastChannelList sqlChannels;
foreach( const Playlists::PlaylistPtr &playlist, playlists )
{
SqlPodcastChannelPtr sqlChannel = SqlPodcastChannel::fromPlaylistPtr( playlist );
if( sqlChannel )
sqlChannels << sqlChannel;
}
if( sqlChannels.isEmpty() )
return actions;
//TODO: add export OPML action for selected playlists only. Use the QAction::data() trick.
if( m_configureChannelAction == 0 )
{
m_configureChannelAction = new QAction( QIcon::fromTheme( "configure" ), i18n( "&Configure" ), this );
m_configureChannelAction->setProperty( "popupdropper_svg_id", "configure" );
- connect( m_configureChannelAction, SIGNAL(triggered()), SLOT(slotConfigureChannel()) );
+ connect( m_configureChannelAction, &QAction::triggered, this, &SqlPodcastProvider::slotConfigureChannel );
}
//only one channel can be configured at a time.
if( sqlChannels.count() == 1 )
{
m_configureChannelAction->setData( QVariant::fromValue( sqlChannels.first() ) );
actions << m_configureChannelAction;
}
if( m_removeAction == 0 )
{
m_removeAction = new QAction( QIcon::fromTheme( "news-unsubscribe" ), i18n( "&Remove Subscription" ), this );
m_removeAction->setProperty( "popupdropper_svg_id", "remove" );
- connect( m_removeAction, SIGNAL(triggered()), SLOT(slotRemoveChannels()) );
+ connect( m_removeAction, &QAction::triggered, this, &SqlPodcastProvider::slotRemoveChannels );
}
m_removeAction->setData( QVariant::fromValue( sqlChannels ) );
actions << m_removeAction;
if( m_updateAction == 0 )
{
m_updateAction = new QAction( QIcon::fromTheme( "view-refresh-amarok" ), i18n( "&Update Channel" ), this );
m_updateAction->setProperty( "popupdropper_svg_id", "update" );
- connect( m_updateAction, SIGNAL(triggered()), SLOT(slotUpdateChannels()) );
+ connect( m_updateAction, &QAction::triggered, this, &SqlPodcastProvider::slotUpdateChannels );
}
m_updateAction->setData( QVariant::fromValue( sqlChannels ) );
actions << m_updateAction;
return actions;
}
QActionList
SqlPodcastProvider::trackActions( const QMultiHash<Playlists::PlaylistPtr, int> &playlistTracks )
{
SqlPodcastEpisodeList episodes;
foreach( const Playlists::PlaylistPtr &playlist, playlistTracks.uniqueKeys() )
{
SqlPodcastChannelPtr sqlChannel = SqlPodcastChannel::fromPlaylistPtr( playlist );
if( !sqlChannel )
continue;
SqlPodcastEpisodeList channelEpisodes = sqlChannel->sqlEpisodes();
QList<int> trackPositions = playlistTracks.values( playlist );
qSort( trackPositions );
foreach( int trackPosition, trackPositions )
{
if( trackPosition >= 0 && trackPosition < channelEpisodes.count() )
episodes << channelEpisodes.at( trackPosition );
}
}
QActionList actions;
if( episodes.isEmpty() )
return actions;
if( m_downloadAction == 0 )
{
m_downloadAction = new QAction( QIcon::fromTheme( "go-down" ), i18n( "&Download Episode" ), this );
m_downloadAction->setProperty( "popupdropper_svg_id", "download" );
- connect( m_downloadAction, SIGNAL(triggered()), SLOT(slotDownloadEpisodes()) );
+ connect( m_downloadAction, &QAction::triggered, this, &SqlPodcastProvider::slotDownloadEpisodes );
}
if( m_deleteAction == 0 )
{
m_deleteAction = new QAction( QIcon::fromTheme( "edit-delete" ),
i18n( "&Delete Downloaded Episode" ), this );
m_deleteAction->setProperty( "popupdropper_svg_id", "delete" );
m_deleteAction->setObjectName( "deleteAction" );
- connect( m_deleteAction, SIGNAL(triggered()), SLOT(slotDeleteDownloadedEpisodes()) );
+ connect( m_deleteAction, &QAction::triggered, this, &SqlPodcastProvider::slotDeleteDownloadedEpisodes );
}
if( m_writeTagsAction == 0 )
{
m_writeTagsAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ),
i18n( "&Write Feed Information to File" ), this );
m_writeTagsAction->setProperty( "popupdropper_svg_id", "edit" );
- connect( m_writeTagsAction, SIGNAL(triggered()), SLOT(slotWriteTagsToFiles()) );
+ connect( m_writeTagsAction, &QAction::triggered, this, &SqlPodcastProvider::slotWriteTagsToFiles );
}
if( m_keepAction == 0 )
{
m_keepAction = new QAction( QIcon::fromTheme( "podcast-amarok" ),
i18n( "&Keep downloaded file" ), this );
m_keepAction->setToolTip( i18n( "Toggle the \"keep\" downloaded file status of "
"this podcast episode. Downloaded files with this status wouldn't be "
"deleted even if we apply a purge." ) );
m_keepAction->setProperty( "popupdropper_svg_id", "keep" );
m_keepAction->setCheckable( true );
- connect( m_keepAction, SIGNAL(triggered(bool)), SLOT(slotSetKeep()) );
+ connect( m_keepAction, &QAction::triggered, this, &SqlPodcastProvider::slotSetKeep );
}
SqlPodcastEpisodeList remoteEpisodes;
SqlPodcastEpisodeList keptDownloadedEpisodes, unkeptDownloadedEpisodes;
foreach( const SqlPodcastEpisodePtr &episode, episodes )
{
if( episode->localUrl().isEmpty() )
remoteEpisodes << episode;
else
{
if( episode->isKeep() )
keptDownloadedEpisodes << episode;
else
unkeptDownloadedEpisodes << episode;
}
}
if( !remoteEpisodes.isEmpty() )
{
m_downloadAction->setData( QVariant::fromValue( remoteEpisodes ) );
actions << m_downloadAction;
}
if( !( keptDownloadedEpisodes + unkeptDownloadedEpisodes ).isEmpty() )
{
m_deleteAction->setData( QVariant::fromValue( keptDownloadedEpisodes + unkeptDownloadedEpisodes ) );
actions << m_deleteAction;
m_keepAction->setChecked( unkeptDownloadedEpisodes.isEmpty() );
m_keepAction->setData( QVariant::fromValue( keptDownloadedEpisodes + unkeptDownloadedEpisodes ) );
actions << m_keepAction;
}
return actions;
}
Podcasts::PodcastEpisodePtr
SqlPodcastProvider::episodeForGuid( const QString &guid )
{
return PodcastEpisodePtr::dynamicCast( sqlEpisodeForString( guid ) );
}
void
SqlPodcastProvider::addPodcast( const QUrl &url )
{
QUrl kurl = QUrl( url );
debug() << "importing " << kurl.url();
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return;
QString command = "SELECT title FROM podcastchannels WHERE url='%1';";
command = command.arg( sqlStorage->escape( kurl.url() ) );
QStringList dbResult = sqlStorage->query( command );
if( !dbResult.isEmpty() )
{
//Already subscribed to this Channel
//notify the user.
Amarok::Components::logger()->longMessage(
i18n( "Already subscribed to %1.", dbResult.first() ), Amarok::Logger::Error );
}
else
{
subscribe( kurl );
}
}
void
SqlPodcastProvider::updateAll()
{
foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels )
updateSqlChannel( channel );
}
void
SqlPodcastProvider::subscribe( const QUrl &url )
{
if( !url.isValid() )
return;
if( m_updatingChannels >= m_maxConcurrentUpdates )
{
debug() << QString( "Maximum concurrent updates (%1) reached. "
"Queueing \"%2\" for subscribing." )
.arg( m_maxConcurrentUpdates )
.arg( url.url() );
m_subscribeQueue << url;
return;
}
PodcastReader *podcastReader = new PodcastReader( this );
- connect( podcastReader, SIGNAL(finished(PodcastReader*)),
- SLOT(slotReadResult(PodcastReader*)) );
- connect( podcastReader, SIGNAL(statusBarSorryMessage(QString)),
- this, SLOT(slotStatusBarSorryMessage(QString)) );
- connect( podcastReader,
- SIGNAL(statusBarNewProgressOperation( KIO::TransferJob *, const QString &,
- Podcasts::PodcastReader* )),
- SLOT(slotStatusBarNewProgressOperation( KIO::TransferJob *, const QString &,
- Podcasts::PodcastReader* ))
- );
+ connect( podcastReader, &PodcastReader::finished,
+ this, &SqlPodcastProvider::slotReadResult );
+ connect( podcastReader, &PodcastReader::statusBarSorryMessage,
+ this, &SqlPodcastProvider::slotStatusBarSorryMessage );
+ connect( podcastReader, &PodcastReader::statusBarNewProgressOperation,
+ this, &SqlPodcastProvider::slotStatusBarNewProgressOperation );
m_updatingChannels++;
podcastReader->read( url );
}
Podcasts::PodcastChannelPtr
SqlPodcastProvider::addChannel( Podcasts::PodcastChannelPtr channel )
{
Podcasts::SqlPodcastChannelPtr sqlChannel =
SqlPodcastChannelPtr( new Podcasts::SqlPodcastChannel( this, channel ) );
m_channels << sqlChannel;
if( sqlChannel->episodes().count() == 0 )
updateSqlChannel( sqlChannel );
emit playlistAdded( Playlists::PlaylistPtr( sqlChannel.data() ) );
return PodcastChannelPtr( sqlChannel.data() );
}
Podcasts::PodcastEpisodePtr
SqlPodcastProvider::addEpisode( Podcasts::PodcastEpisodePtr episode )
{
Podcasts::SqlPodcastEpisodePtr sqlEpisode =
Podcasts::SqlPodcastEpisodePtr::dynamicCast( episode );
if( sqlEpisode.isNull() )
return Podcasts::PodcastEpisodePtr();
if( sqlEpisode->channel().isNull() )
{
debug() << "channel is null";
return Podcasts::PodcastEpisodePtr();
}
if( sqlEpisode->channel()->fetchType() == Podcasts::PodcastChannel::DownloadWhenAvailable )
downloadEpisode( sqlEpisode );
return Podcasts::PodcastEpisodePtr::dynamicCast( sqlEpisode );
}
Podcasts::PodcastChannelList
SqlPodcastProvider::channels()
{
PodcastChannelList list;
QListIterator<SqlPodcastChannelPtr> i( m_channels );
while( i.hasNext() )
{
list << PodcastChannelPtr::dynamicCast( i.next() );
}
return list;
}
void
SqlPodcastProvider::removeSubscription( Podcasts::SqlPodcastChannelPtr sqlChannel )
{
debug() << "Deleting channel " << sqlChannel->title();
sqlChannel->deleteFromDb();
m_channels.removeOne( sqlChannel );
//HACK: because of a database "leak" in the past we have orphan data in the tables.
//Remove it when we know it's supposed to be empty.
if( m_channels.isEmpty() )
{
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return;
debug() << "Unsubscribed from last channel, cleaning out the podcastepisodes table.";
sqlStorage->query( "DELETE FROM podcastepisodes WHERE 1;" );
}
emit playlistRemoved( Playlists::PlaylistPtr::dynamicCast( sqlChannel ) );
}
void
SqlPodcastProvider::configureProvider()
{
m_providerSettingsDialog = new KDialog( The::mainWindow() );
QWidget *settingsWidget = new QWidget( m_providerSettingsDialog );
m_providerSettingsDialog->setObjectName( "SqlPodcastProviderSettings" );
Ui::SqlPodcastProviderSettingsWidget settings;
m_providerSettingsWidget = &settings;
settings.setupUi( settingsWidget );
settings.m_baseDirUrl->setMode( KFile::Directory );
settings.m_baseDirUrl->setUrl( m_baseDownloadDir );
settings.m_autoUpdateInterval->setValue( m_autoUpdateInterval );
settings.m_autoUpdateInterval->setPrefix(
i18nc( "prefix to 'x minutes'", "every " ) );
settings.m_autoUpdateInterval->setSuffix( ki18np( " minute", " minutes" ) );
m_providerSettingsDialog->setButtons( KDialog::Ok | KDialog::Cancel | KDialog::Apply );
m_providerSettingsDialog->setMainWidget( settingsWidget );
- connect( settings.m_baseDirUrl, SIGNAL(textChanged(QString)), SLOT(slotConfigChanged()) );
- connect( settings.m_autoUpdateInterval, SIGNAL(valueChanged(int)),
- SLOT(slotConfigChanged()) );
+ connect( settings.m_baseDirUrl, &KUrlRequester::textChanged, this, &SqlPodcastProvider::slotConfigChanged );
+ connect( settings.m_autoUpdateInterval, QOverload<int>::of(&KIntSpinBox::valueChanged),
+ this, &SqlPodcastProvider::slotConfigChanged );
m_providerSettingsDialog->setWindowTitle( i18n( "Configure Local Podcasts" ) );
m_providerSettingsDialog->enableButtonApply( false );
if( m_providerSettingsDialog->exec() == QDialog::Accepted )
{
m_autoUpdateInterval = settings.m_autoUpdateInterval->value();
if( m_autoUpdateInterval )
startTimer();
else
m_updateTimer->stop();
QUrl adjustedNewPath = settings.m_baseDirUrl->url();
adjustedNewPath = adjustedNewPath.adjusted(QUrl::StripTrailingSlash);
if( adjustedNewPath != m_baseDownloadDir )
{
m_baseDownloadDir = adjustedNewPath;
Amarok::config( "Podcasts" ).writeEntry( "Base Download Directory", m_baseDownloadDir );
if( !m_channels.isEmpty() )
{
//TODO: check if there actually are downloaded episodes
KDialog moveAllDialog;
moveAllDialog.setCaption( i18n( "Move Podcasts" ) );
KVBox *vbox = new KVBox( &moveAllDialog );
QString question( i18n( "Do you want to move all downloaded episodes to the "
"new location?") );
QLabel *label = new QLabel( question, vbox );
label->setWordWrap( true );
label->setMaximumWidth( 400 );
moveAllDialog.setMainWidget( vbox );
moveAllDialog.setButtons( KDialog::Yes | KDialog::No );
if( moveAllDialog.exec() == KDialog::Yes )
{
foreach( SqlPodcastChannelPtr sqlChannel, m_channels )
{
QUrl oldSaveLocation = sqlChannel->saveLocation();
QUrl newSaveLocation = m_baseDownloadDir;
newSaveLocation = newSaveLocation.adjusted(QUrl::StripTrailingSlash);
newSaveLocation.setPath(newSaveLocation.path() + '/' + ( oldSaveLocation.fileName() ));
sqlChannel->setSaveLocation( newSaveLocation );
debug() << newSaveLocation.path();
moveDownloadedEpisodes( sqlChannel );
if( !QDir().rmdir( oldSaveLocation.toLocalFile() ) )
debug() << "Could not remove old directory "
<< oldSaveLocation.toLocalFile();
}
}
}
}
}
delete m_providerSettingsDialog;
m_providerSettingsDialog = 0;
m_providerSettingsWidget = 0;
}
void
SqlPodcastProvider::slotConfigChanged()
{
if( !m_providerSettingsWidget )
return;
if( m_providerSettingsWidget->m_autoUpdateInterval->value() != m_autoUpdateInterval
|| m_providerSettingsWidget->m_baseDirUrl->url() != m_baseDownloadDir )
{
m_providerSettingsDialog->enableButtonApply( true );
}
}
void
SqlPodcastProvider::slotExportOpml()
{
QList<OpmlOutline *> rootOutlines;
QMap<QString,QString> headerData;
//TODO: set header data such as date
//TODO: folder outline support
foreach( SqlPodcastChannelPtr channel, m_channels )
{
OpmlOutline *channelOutline = new OpmlOutline();
#define addAttr( k, v ) channelOutline->addAttribute( k, v )
addAttr( "text", channel->title() );
addAttr( "type", "rss" );
addAttr( "xmlUrl", channel->url().url() );
rootOutlines << channelOutline;
}
//TODO: add checkbox as widget to filedialog to include podcast settings.
KFileDialog fileDialog( QUrl("kfiledialog:///podcast/amarok_podcasts.opml"), "*.opml",
The::mainWindow() );
fileDialog.setMode( KFile::File );
fileDialog.setWindowTitle( i18n( "Select file for OPML export") );
if( fileDialog.exec() != KDialog::Accepted )
return;
QUrl filePath = fileDialog.selectedUrl();
QFile *opmlFile = new QFile( filePath.toLocalFile(), this );
if( !opmlFile->open( QIODevice::WriteOnly | QIODevice::Truncate ) )
{
error() << "could not open OPML file " << filePath.url();
return;
}
OpmlWriter *opmlWriter = new OpmlWriter( rootOutlines, headerData, opmlFile );
- connect( opmlWriter, SIGNAL(result(int)), SLOT(slotOpmlWriterDone(int)) );
+ connect( opmlWriter, &OpmlWriter::result, this, &SqlPodcastProvider::slotOpmlWriterDone );
opmlWriter->run();
}
void
SqlPodcastProvider::slotOpmlWriterDone( int result )
{
Q_UNUSED( result )
OpmlWriter *writer = qobject_cast<OpmlWriter *>( QObject::sender() );
Q_ASSERT( writer );
writer->device()->close();
delete writer;
}
void
SqlPodcastProvider::configureChannel( Podcasts::SqlPodcastChannelPtr sqlChannel )
{
if( !sqlChannel )
return;
QUrl oldUrl = sqlChannel->url();
QUrl oldSaveLocation = sqlChannel->saveLocation();
bool oldHasPurge = sqlChannel->hasPurge();
int oldPurgeCount = sqlChannel->purgeCount();
bool oldAutoScan = sqlChannel->autoScan();
PodcastSettingsDialog dialog( sqlChannel, The::mainWindow() );
dialog.configure();
sqlChannel->updateInDb();
if( ( oldHasPurge && !sqlChannel->hasPurge() )
|| ( oldPurgeCount < sqlChannel->purgeCount() ) )
{
/* changed from purge to no-purge or increase purge count:
we need to reload all episodes from the database. */
sqlChannel->loadEpisodes();
}
else
sqlChannel->applyPurge();
emit updated();
if( oldSaveLocation != sqlChannel->saveLocation() )
{
moveDownloadedEpisodes( sqlChannel );
if( !QDir().rmdir( oldSaveLocation.toLocalFile() ) )
debug() << "Could not remove old directory " << oldSaveLocation.toLocalFile();
}
//if the url changed force an update.
if( oldUrl != sqlChannel->url() )
updateSqlChannel( sqlChannel );
//start autoscan in case it wasn't already
if( sqlChannel->autoScan() && !oldAutoScan )
startTimer();
}
void
SqlPodcastProvider::deleteDownloadedEpisodes( Podcasts::SqlPodcastEpisodeList &episodes )
{
foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes )
deleteDownloadedEpisode( episode );
}
void
SqlPodcastProvider::moveDownloadedEpisodes( Podcasts::SqlPodcastChannelPtr sqlChannel )
{
debug() << QString( "We need to move downloaded episodes of \"%1\" to %2" )
.arg( sqlChannel->title() )
.arg( sqlChannel->saveLocation().toDisplayString() );
QList<QUrl> filesToMove;
foreach( Podcasts::SqlPodcastEpisodePtr episode, sqlChannel->sqlEpisodes() )
{
if( !episode->localUrl().isEmpty() )
{
QUrl newLocation = sqlChannel->saveLocation();
QDir dir( newLocation.toLocalFile() );
dir.mkpath( "." );
newLocation = newLocation.adjusted(QUrl::StripTrailingSlash);
newLocation.setPath(newLocation.path() + '/' + ( episode->localUrl().fileName() ));
debug() << "Moving from " << episode->localUrl() << " to " << newLocation;
KIO::Job *moveJob = KIO::move( episode->localUrl(), newLocation,
KIO::HideProgressInfo );
//wait until job is finished.
if( KIO::NetAccess::synchronousRun( moveJob, The::mainWindow() ) )
episode->setLocalUrl( newLocation );
}
}
}
void
SqlPodcastProvider::slotDeleteDownloadedEpisodes()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastEpisodeList episodes = action->data().value<Podcasts::SqlPodcastEpisodeList>();
deleteDownloadedEpisodes( episodes );
}
void
SqlPodcastProvider::slotDownloadEpisodes()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastEpisodeList episodes = action->data().value<Podcasts::SqlPodcastEpisodeList>();
foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes )
downloadEpisode( episode );
}
void
SqlPodcastProvider::slotSetKeep()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastEpisodeList episodes = action->data().value<Podcasts::SqlPodcastEpisodeList>();
foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes )
episode->setKeep( action->isChecked() );
}
QPair<bool, bool>
SqlPodcastProvider::confirmUnsubscribe( Podcasts::SqlPodcastChannelPtr channel )
{
KDialog unsubscribeDialog;
unsubscribeDialog.setCaption( i18n( "Unsubscribe" ) );
KVBox *vbox = new KVBox( &unsubscribeDialog );
QString question( i18n( "Do you really want to unsubscribe from \"%1\"?", channel->title() ) );
QLabel *label = new QLabel( question, vbox );
label->setWordWrap( true );
label->setMaximumWidth( 400 );
QCheckBox *deleteMediaCheckBox = new QCheckBox( i18n( "Delete downloaded episodes" ), vbox );
unsubscribeDialog.setMainWidget( vbox );
unsubscribeDialog.setButtons( KDialog::Ok | KDialog::Cancel );
QPair<bool, bool> result;
result.first = unsubscribeDialog.exec() == QDialog::Accepted;
result.second = deleteMediaCheckBox->isChecked();
return result;
}
void
SqlPodcastProvider::slotRemoveChannels()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastChannelList channels = action->data().value<Podcasts::SqlPodcastChannelList>();
foreach( Podcasts::SqlPodcastChannelPtr channel, channels )
{
QPair<bool, bool> result = confirmUnsubscribe( channel );
if( result.first )
{
debug() << "unsubscribing " << channel->title();
if( result.second )
{
debug() << "removing all episodes";
Podcasts::SqlPodcastEpisodeList sqlEpisodes = channel->sqlEpisodes();
deleteDownloadedEpisodes( sqlEpisodes );
}
removeSubscription( channel );
}
}
}
void
SqlPodcastProvider::slotUpdateChannels()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastChannelList channels = action->data().value<Podcasts::SqlPodcastChannelList>();
foreach( Podcasts::SqlPodcastChannelPtr channel, channels )
updateSqlChannel( channel );
}
void
SqlPodcastProvider::slotDownloadProgress( KJob *job, unsigned long percent )
{
Q_UNUSED( job );
Q_UNUSED( percent );
unsigned int totalDownloadPercentage = 0;
foreach( const KJob *jobKey, m_downloadJobMap.keys() )
totalDownloadPercentage += jobKey->percent();
//keep the completed jobs in mind as well.
totalDownloadPercentage += m_completedDownloads * 100;
emit totalPodcastDownloadProgress(
totalDownloadPercentage / ( m_downloadJobMap.count() + m_completedDownloads ) );
}
void
SqlPodcastProvider::slotWriteTagsToFiles()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastEpisodeList episodes = action->data().value<Podcasts::SqlPodcastEpisodeList>();
foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes )
episode->writeTagsToFile();
}
void
SqlPodcastProvider::slotConfigureChannel()
{
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
Podcasts::SqlPodcastChannelPtr podcastChannel = action->data().value<Podcasts::SqlPodcastChannelPtr>();
if( !podcastChannel.isNull() )
configureChannel( podcastChannel );
}
void
SqlPodcastProvider::deleteDownloadedEpisode( Podcasts::SqlPodcastEpisodePtr episode )
{
if( !episode || episode->localUrl().isEmpty() )
return;
debug() << "deleting " << episode->title();
KIO::del( episode->localUrl(), KIO::HideProgressInfo );
episode->setLocalUrl( QUrl() );
emit episodeDeleted( Podcasts::PodcastEpisodePtr::dynamicCast( episode ) );
}
Podcasts::SqlPodcastChannelPtr
SqlPodcastProvider::podcastChannelForId( int podcastChannelId )
{
QListIterator<Podcasts::SqlPodcastChannelPtr> i( m_channels );
while( i.hasNext() )
{
int id = i.next()->dbId();
if( id == podcastChannelId )
return i.previous();
}
return Podcasts::SqlPodcastChannelPtr();
}
void
SqlPodcastProvider::completePodcastDownloads()
{
//check to see if there are still downloads in progress
if( !m_downloadJobMap.isEmpty() )
{
debug() << QString( "There are still %1 podcast download jobs running!" )
.arg( m_downloadJobMap.count() );
KProgressDialog progressDialog( The::mainWindow(),
i18n( "Waiting for Podcast Downloads to Finish" ),
i18np( "There is still a podcast download in progress",
"There are still %1 podcast downloads in progress",
m_downloadJobMap.count() )
);
progressDialog.setButtonText( i18n("Cancel Download and Quit.") );
m_completedDownloads = 0;
foreach( KJob *job, m_downloadJobMap.keys() )
{
connect( job, SIGNAL(percent(KJob*,ulong)),
- SLOT(slotDownloadProgress(KJob*,ulong))
+ this, SLOT(slotDownloadProgress(KJob*,ulong))
);
}
- connect( this, SIGNAL(totalPodcastDownloadProgress(int)),
- progressDialog.progressBar(), SLOT(setValue(int)) );
+ connect( this, &SqlPodcastProvider::totalPodcastDownloadProgress,
+ progressDialog.progressBar(), &QProgressBar::setValue );
int result = progressDialog.exec();
if( result == QDialog::Rejected )
{
foreach( KJob *job, m_downloadJobMap.keys() )
{
job->kill();
}
}
}
}
void
SqlPodcastProvider::autoUpdate()
{
if( Solid::Networking::status() != Solid::Networking::Connected
&& Solid::Networking::status() != Solid::Networking::Unknown )
{
debug() << "Solid reports we are not online, canceling podcast auto-update";
return;
}
foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels )
{
if( channel->autoScan() )
updateSqlChannel( channel );
}
}
void
SqlPodcastProvider::updateSqlChannel( Podcasts::SqlPodcastChannelPtr channel )
{
if( channel.isNull() )
return;
if( m_updatingChannels >= m_maxConcurrentUpdates )
{
debug() << QString( "Maximum concurrent updates (%1) reached. "
"Queueing \"%2\" for download." )
.arg( m_maxConcurrentUpdates )
.arg( channel->title() );
m_updateQueue << channel;
return;
}
PodcastReader *podcastReader = new PodcastReader( this );
- connect( podcastReader, SIGNAL(finished(PodcastReader*)),
- SLOT(slotReadResult(PodcastReader*)) );
- connect( podcastReader, SIGNAL(statusBarSorryMessage(QString)),
- this, SLOT(slotStatusBarSorryMessage(QString)) );
- connect( podcastReader, SIGNAL(statusBarNewProgressOperation(KIO::TransferJob*,QString,Podcasts::PodcastReader*)),
- this, SLOT(slotStatusBarNewProgressOperation(KIO::TransferJob*,QString,Podcasts::PodcastReader*)) );
+ connect( podcastReader, &PodcastReader::finished,
+ this, &SqlPodcastProvider::slotReadResult );
+ connect( podcastReader, &PodcastReader::statusBarSorryMessage,
+ this, &SqlPodcastProvider::slotStatusBarSorryMessage );
+ connect( podcastReader, &PodcastReader::statusBarNewProgressOperation,
+ this, &SqlPodcastProvider::slotStatusBarNewProgressOperation );
m_updatingChannels++;
podcastReader->update( Podcasts::PodcastChannelPtr::dynamicCast( channel ) );
}
void
SqlPodcastProvider::slotReadResult( Podcasts::PodcastReader *podcastReader )
{
if( podcastReader->error() != QXmlStreamReader::NoError )
{
debug() << podcastReader->errorString();
Amarok::Components::logger()->longMessage( podcastReader->errorString(),
Amarok::Logger::Error );
}
debug() << "Finished updating: " << podcastReader->url();
--m_updatingChannels;
debug() << "Updating counter reached " << m_updatingChannels;
Podcasts::SqlPodcastChannelPtr channel =
Podcasts::SqlPodcastChannelPtr::dynamicCast( podcastReader->channel() );
if( channel.isNull() )
{
error() << "Could not cast to SqlPodcastChannel " << __FILE__ << ":" << __LINE__;
return;
}
if( channel->image().isNull() )
{
fetchImage( channel );
}
channel->updateInDb();
podcastReader->deleteLater();
//first we work through the list of new subscriptions
if( !m_subscribeQueue.isEmpty() )
{
subscribe( m_subscribeQueue.takeFirst() );
}
else if( !m_updateQueue.isEmpty() )
{
updateSqlChannel( m_updateQueue.takeFirst() );
}
else if( m_updatingChannels == 0 )
{
//TODO: start downloading episodes here.
if( m_podcastImageFetcher )
m_podcastImageFetcher->run();
}
}
void
SqlPodcastProvider::slotStatusBarNewProgressOperation( KIO::TransferJob * job,
const QString &description,
Podcasts::PodcastReader* reader )
{
Amarok::Components::logger()->newProgressOperation( job, description, reader, SLOT(slotAbort()) );
}
void
SqlPodcastProvider::downloadEpisode( Podcasts::SqlPodcastEpisodePtr sqlEpisode )
{
if( sqlEpisode.isNull() )
{
error() << "SqlPodcastProvider::downloadEpisode( Podcasts::SqlPodcastEpisodePtr sqlEpisode ) was called for a non-SqlPodcastEpisode";
return;
}
foreach( struct PodcastEpisodeDownload download, m_downloadJobMap )
{
if( download.episode == sqlEpisode )
{
debug() << "already downloading " << sqlEpisode->uidUrl();
return;
}
}
if( m_downloadJobMap.size() >= m_maxConcurrentDownloads )
{
debug() << QString( "Maximum concurrent downloads (%1) reached. "
"Queueing \"%2\" for download." )
.arg( m_maxConcurrentDownloads )
.arg( sqlEpisode->title() );
//put into a FIFO which is used in downloadResult() to start a new download
m_downloadQueue << sqlEpisode;
return;
}
KIO::TransferJob *transferJob =
KIO::get( QUrl::fromUserInput(sqlEpisode->uidUrl()), KIO::Reload, KIO::HideProgressInfo );
QFile *tmpFile = createTmpFile( sqlEpisode );
struct PodcastEpisodeDownload download = { sqlEpisode,
tmpFile,
/* Unless a redirect happens the filename from the enclosure is used. This is a potential source
of filename conflicts in downloadResult() */
QUrl( sqlEpisode->uidUrl() ).fileName(),
false
};
m_downloadJobMap.insert( transferJob, download );
if( tmpFile->exists() )
{
qint64 offset = tmpFile->size();
debug() << "temporary file exists, resume download from offset " << offset;
QMap<QString, QString> resumeData;
resumeData.insert( "resume", QString::number( offset ) );
transferJob->addMetaData( resumeData );
}
if( !tmpFile->open( QIODevice::WriteOnly | QIODevice::Append ) )
{
Amarok::Components::logger()->longMessage( i18n( "Unable to save podcast episode file to %1",
tmpFile->fileName() ) );
delete tmpFile;
return;
}
debug() << "starting download for " << sqlEpisode->title()
<< " url: " << sqlEpisode->prettyUrl();
Amarok::Components::logger()->newProgressOperation( transferJob
, sqlEpisode->title().isEmpty()
? i18n( "Downloading Podcast Media" )
: i18n( "Downloading Podcast \"%1\""
, sqlEpisode->title() ),
transferJob,
SLOT(kill())
);
- connect( transferJob, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(addData(KIO::Job*,QByteArray)) );
+ connect( transferJob, &KIO::TransferJob::data,
+ this, &SqlPodcastProvider::addData );
//need to connect to finished instead of result because it's always emitted.
//We need to cleanup after a download is cancled regardless of the argument in
//KJob::kill()
- connect( transferJob, SIGNAL(finished(KJob*)),
- SLOT(downloadResult(KJob*)) );
- connect( transferJob, SIGNAL(redirection(KIO::Job*,QUrl)),
- SLOT(redirected(KIO::Job*,QUrl)) );
+ connect( transferJob, &KIO::TransferJob::finished,
+ this, &SqlPodcastProvider::downloadResult );
+ connect( transferJob, &KIO::TransferJob::redirection,
+ this, &SqlPodcastProvider::redirected );
}
void
SqlPodcastProvider::downloadEpisode( Podcasts::PodcastEpisodePtr episode )
{
downloadEpisode( SqlPodcastEpisodePtr::dynamicCast( episode ) );
}
void
SqlPodcastProvider::cleanupDownload( KJob *job, bool downloadFailed )
{
struct PodcastEpisodeDownload download = m_downloadJobMap.value( job );
QFile *tmpFile = download.tmpFile;
if( downloadFailed && tmpFile )
{
debug() << "deleting temporary podcast file: " << tmpFile->fileName();
tmpFile->remove();
}
m_downloadJobMap.remove( job );
delete tmpFile;
}
QFile *
SqlPodcastProvider::createTmpFile( Podcasts::SqlPodcastEpisodePtr sqlEpisode )
{
if( sqlEpisode.isNull() )
{
error() << "sqlEpisodePtr is NULL after download";
return 0;
}
Podcasts::SqlPodcastChannelPtr sqlChannel =
Podcasts::SqlPodcastChannelPtr::dynamicCast( sqlEpisode->channel() );
if( sqlChannel.isNull() )
{
error() << "sqlChannelPtr is NULL after download";
return 0;
}
QDir dir( sqlChannel->saveLocation().toLocalFile() );
dir.mkpath( "." ); // ensure that the path is there
//TODO: what if result is false?
QUrl localUrl = QUrl::fromLocalFile( dir.absolutePath() );
QString tempName;
if( !sqlEpisode->guid().isEmpty() )
tempName = QUrl::toPercentEncoding( sqlEpisode->guid() );
else
tempName = QUrl::toPercentEncoding( sqlEpisode->uidUrl() );
QString tempNameMd5( KMD5( tempName.toUtf8() ).hexDigest() );
localUrl = localUrl.adjusted(QUrl::StripTrailingSlash);
localUrl.setPath(localUrl.path() + '/' + ( tempNameMd5 + PODCAST_TMP_POSTFIX ));
return new QFile( localUrl.toLocalFile() );
}
bool
SqlPodcastProvider::checkEnclosureLocallyAvailable( KIO::Job *job )
{
struct PodcastEpisodeDownload download = m_downloadJobMap.value( job );
Podcasts::SqlPodcastEpisodePtr sqlEpisode = download.episode;
if( sqlEpisode.isNull() )
{
error() << "sqlEpisodePtr is NULL after download";
return false;
}
Podcasts::SqlPodcastChannelPtr sqlChannel =
Podcasts::SqlPodcastChannelPtr::dynamicCast( sqlEpisode->channel() );
if( sqlChannel.isNull() )
{
error() << "sqlChannelPtr is NULL after download";
return false;
}
QString fileName = sqlChannel->saveLocation().adjusted(QUrl::StripTrailingSlash).toLocalFile();
fileName += download.fileName;
debug() << "checking " << fileName;
QFileInfo fileInfo( fileName );
if( !fileInfo.exists() )
return false;
debug() << fileName << " already exists, no need to redownload";
// NOTE: we need to emit because the KJobProgressBar relies on it to clean up
job->kill( KJob::EmitResult );
sqlEpisode->setLocalUrl( QUrl::fromLocalFile(fileName) );
//TODO: repaint icons, probably with signal metadataUpdate()
return true;
}
void
SqlPodcastProvider::addData( KIO::Job *job, const QByteArray &data )
{
if( !data.size() )
{
return; // EOF
}
struct PodcastEpisodeDownload &download = m_downloadJobMap[job];
// NOTE: if there is a tmpfile we are already downloading, no need to
// checkEnclosureLocallyAvailable() on every data chunk. performance optimization.
if( !download.finalNameReady )
{
download.finalNameReady = true;
if( checkEnclosureLocallyAvailable( job ) )
return;
}
if( download.tmpFile->write( data ) == -1 )
{
error() << "write error for " << download.tmpFile->fileName() << ": "
<< download.tmpFile->errorString();
job->kill();
}
}
void
SqlPodcastProvider::deleteDownloadedEpisode( Podcasts::PodcastEpisodePtr episode )
{
deleteDownloadedEpisode( SqlPodcastEpisodePtr::dynamicCast( episode ) );
}
void
SqlPodcastProvider::slotStatusBarSorryMessage( const QString &message )
{
Amarok::Components::logger()->longMessage( message, Amarok::Logger::Error );
}
void
SqlPodcastProvider::downloadResult( KJob *job )
{
struct PodcastEpisodeDownload download = m_downloadJobMap.value( job );
QFile *tmpFile = download.tmpFile;
bool downloadFailed = false;
if( job->error() )
{
// NOTE: prevents empty error notifications from popping up
// in the statusbar when the user cancels a download
if( job->error() != KJob::KilledJobError )
{
Amarok::Components::logger()->longMessage( job->errorText() );
}
error() << "Unable to retrieve podcast media. KIO Error: " << job->errorText();
error() << "keeping temporary file for download restart";
downloadFailed = false;
}
else
{
Podcasts::SqlPodcastEpisodePtr sqlEpisode = download.episode;
if( sqlEpisode.isNull() )
{
error() << "sqlEpisodePtr is NULL after download";
cleanupDownload( job, true );
return;
}
Podcasts::SqlPodcastChannelPtr sqlChannel =
Podcasts::SqlPodcastChannelPtr::dynamicCast( sqlEpisode->channel() );
if( sqlChannel.isNull() )
{
error() << "sqlChannelPtr is NULL after download";
cleanupDownload( job, true );
return;
}
Amarok::QStringx filenameLayout = Amarok::QStringx( sqlChannel->filenameLayout() );
QMap<QString,QString> layoutmap;
QString sequenceNumber;
if( sqlEpisode->artist() )
layoutmap.insert( "artist", sqlEpisode->artist()->prettyName() );
layoutmap.insert( "title", sqlEpisode->title() );
if( sqlEpisode->genre() )
layoutmap.insert( "genre", sqlEpisode->genre()->prettyName() );
if( sqlEpisode->year() )
layoutmap.insert( "year", sqlEpisode->year()->prettyName() );
if( sqlEpisode->composer() )
layoutmap.insert( "composer", sqlEpisode->composer()->prettyName() );
layoutmap.insert( "pubdate", sqlEpisode->pubDate().toString() );
sequenceNumber.sprintf( "%.6d", sqlEpisode->sequenceNumber() );
layoutmap.insert( "number", sequenceNumber );
if( sqlEpisode->album() )
layoutmap.insert( "album", sqlEpisode->album()->prettyName() );
if( !filenameLayout.isEmpty() &&
Amarok::QStringx::compare( filenameLayout, "%default%", Qt::CaseInsensitive ) )
{
filenameLayout = filenameLayout.namedArgs( layoutmap );
//add the file extension to the filename
filenameLayout.append( QString( "." ) );
filenameLayout.append( sqlEpisode->type() );
download.fileName = QString( filenameLayout );
}
QString finalName = sqlChannel->saveLocation().adjusted(QUrl::StripTrailingSlash).toLocalFile()
+ download.fileName;
if( tmpFile->rename( finalName ) )
{
debug() << "successfully written Podcast Episode " << sqlEpisode->title()
<< " to " << finalName;
sqlEpisode->setLocalUrl( QUrl::fromLocalFile(finalName) );
if( sqlChannel->writeTags() )
sqlEpisode->writeTagsToFile();
//TODO: force a redraw of the view so the icon can be updated in the PlaylistBrowser
emit episodeDownloaded( Podcasts::PodcastEpisodePtr::dynamicCast( sqlEpisode ) );
}
else
{
Amarok::Components::logger()->longMessage( i18n( "Unable to save podcast episode file to %1",
finalName ) );
downloadFailed = true;
}
}
//remove it from the jobmap
m_completedDownloads++;
cleanupDownload( job, downloadFailed );
//start a new download. We just finished one so there is at least one slot free.
if( !m_downloadQueue.isEmpty() )
downloadEpisode( m_downloadQueue.takeFirst() );
}
void
SqlPodcastProvider::redirected( KIO::Job *job, const QUrl &redirectedUrl )
{
debug() << "redirecting to " << redirectedUrl << ". filename: "
<< redirectedUrl.fileName();
m_downloadJobMap[job].fileName = redirectedUrl.fileName();
}
void
SqlPodcastProvider::createTables() const
{
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return;
sqlStorage->query( QString( "CREATE TABLE podcastchannels ("
"id " + sqlStorage->idType() +
",url " + sqlStorage->longTextColumnType() +
",title " + sqlStorage->longTextColumnType() +
",weblink " + sqlStorage->longTextColumnType() +
",image " + sqlStorage->longTextColumnType() +
",description " + sqlStorage->longTextColumnType() +
",copyright " + sqlStorage->textColumnType() +
",directory " + sqlStorage->textColumnType() +
",labels " + sqlStorage->textColumnType() +
",subscribedate " + sqlStorage->textColumnType() +
",autoscan BOOL, fetchtype INTEGER"
",haspurge BOOL, purgecount INTEGER"
",writetags BOOL, filenamelayout VARCHAR(1024) ) ENGINE = MyISAM;" ) );
sqlStorage->query( QString( "CREATE TABLE podcastepisodes ("
"id " + sqlStorage->idType() +
",url " + sqlStorage->longTextColumnType() +
",channel INTEGER"
",localurl " + sqlStorage->longTextColumnType() +
",guid " + sqlStorage->exactTextColumnType() +
",title " + sqlStorage->longTextColumnType() +
",subtitle " + sqlStorage->longTextColumnType() +
",sequencenumber INTEGER" +
",description " + sqlStorage->longTextColumnType() +
",mimetype " + sqlStorage->textColumnType() +
",pubdate " + sqlStorage->textColumnType() +
",duration INTEGER"
",filesize INTEGER"
",isnew BOOL"
",iskeep BOOL) ENGINE = MyISAM;" ) );
sqlStorage->query( "CREATE FULLTEXT INDEX url_podchannel ON podcastchannels( url );" );
sqlStorage->query( "CREATE FULLTEXT INDEX url_podepisode ON podcastepisodes( url );" );
sqlStorage->query(
"CREATE FULLTEXT INDEX localurl_podepisode ON podcastepisodes( localurl );" );
}
void
SqlPodcastProvider::updateDatabase( int fromVersion, int toVersion )
{
debug() << QString( "Updating Podcast tables from version %1 to version %2" )
.arg( fromVersion ).arg( toVersion );
SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage();
if( !sqlStorage )
return;
#define escape(x) sqlStorage->escape(x)
if( fromVersion == 1 && toVersion == 2 )
{
QString updateChannelQuery = QString( "ALTER TABLE podcastchannels"
" ADD subscribedate " + sqlStorage->textColumnType() + ';' );
sqlStorage->query( updateChannelQuery );
QString setDateQuery = QString(
"UPDATE podcastchannels SET subscribedate='%1' WHERE subscribedate='';" )
.arg( escape( QDate::currentDate().toString() ) );
sqlStorage->query( setDateQuery );
}
else if( fromVersion < 3 && toVersion == 3 )
{
sqlStorage->query( QString( "CREATE TABLE podcastchannels_temp ("
"id " + sqlStorage->idType() +
",url " + sqlStorage->exactTextColumnType() + " UNIQUE"
",title " + sqlStorage->textColumnType() +
",weblink " + sqlStorage->exactTextColumnType() +
",image " + sqlStorage->exactTextColumnType() +
",description " + sqlStorage->longTextColumnType() +
",copyright " + sqlStorage->textColumnType() +
",directory " + sqlStorage->textColumnType() +
",labels " + sqlStorage->textColumnType() +
",subscribedate " + sqlStorage->textColumnType() +
",autoscan BOOL, fetchtype INTEGER"
",haspurge BOOL, purgecount INTEGER ) ENGINE = MyISAM;" ) );
sqlStorage->query( QString( "CREATE TABLE podcastepisodes_temp ("
"id " + sqlStorage->idType() +
",url " + sqlStorage->exactTextColumnType() + " UNIQUE"
",channel INTEGER"
",localurl " + sqlStorage->exactTextColumnType() +
",guid " + sqlStorage->exactTextColumnType() +
",title " + sqlStorage->textColumnType() +
",subtitle " + sqlStorage->textColumnType() +
",sequencenumber INTEGER" +
",description " + sqlStorage->longTextColumnType() +
",mimetype " + sqlStorage->textColumnType() +
",pubdate " + sqlStorage->textColumnType() +
",duration INTEGER"
",filesize INTEGER"
",isnew BOOL"
",iskeep BOOL) ENGINE = MyISAM;" ) );
sqlStorage->query( "INSERT INTO podcastchannels_temp SELECT * FROM podcastchannels;" );
sqlStorage->query( "INSERT INTO podcastepisodes_temp SELECT * FROM podcastepisodes;" );
sqlStorage->query( "DROP TABLE podcastchannels;" );
sqlStorage->query( "DROP TABLE podcastepisodes;" );
createTables();
sqlStorage->query( "INSERT INTO podcastchannels SELECT * FROM podcastchannels_temp;" );
sqlStorage->query( "INSERT INTO podcastepisodes SELECT * FROM podcastepisodes_temp;" );
sqlStorage->query( "DROP TABLE podcastchannels_temp;" );
sqlStorage->query( "DROP TABLE podcastepisodes_temp;" );
}
if( fromVersion < 4 && toVersion == 4 )
{
QString updateChannelQuery = QString( "ALTER TABLE podcastchannels"
" ADD writetags BOOL;" );
sqlStorage->query( updateChannelQuery );
QString setWriteTagsQuery = QString( "UPDATE podcastchannels SET writetags=" +
sqlStorage->boolTrue() +
" WHERE 1;" );
sqlStorage->query( setWriteTagsQuery );
}
if( fromVersion < 5 && toVersion == 5 )
{
QString updateChannelQuery = QString ( "ALTER TABLE podcastchannels"
" ADD filenamelayout VARCHAR(1024);" );
sqlStorage->query( updateChannelQuery );
QString setWriteTagsQuery = QString( "UPDATE podcastchannels SET filenamelayout='%default%'" );
sqlStorage->query( setWriteTagsQuery );
}
if( fromVersion < 6 && toVersion == 6 )
{
QString updateEpisodeQuery = QString ( "ALTER TABLE podcastepisodes"
" ADD iskeep BOOL;" );
sqlStorage->query( updateEpisodeQuery );
QString setIsKeepQuery = QString( "UPDATE podcastepisodes SET iskeep=FALSE;" );
sqlStorage->query( setIsKeepQuery );
}
QString updateAdmin = QString( "UPDATE admin SET version=%1 WHERE component='%2';" );
sqlStorage->query( updateAdmin.arg( toVersion ).arg( escape( key ) ) );
loadPodcasts();
}
void
SqlPodcastProvider::fetchImage( SqlPodcastChannelPtr channel )
{
if( m_podcastImageFetcher == 0 )
{
m_podcastImageFetcher = new PodcastImageFetcher();
- connect( m_podcastImageFetcher,
- SIGNAL(imageReady(Podcasts::PodcastChannelPtr,QImage)),
- SLOT(channelImageReady(Podcasts::PodcastChannelPtr,QImage))
- );
- connect( m_podcastImageFetcher,
- SIGNAL(done(PodcastImageFetcher*)),
- SLOT(podcastImageFetcherDone(PodcastImageFetcher*))
- );
+ connect( m_podcastImageFetcher, &PodcastImageFetcher::channelImageReady,
+ this, &SqlPodcastProvider::channelImageReady );
+ connect( m_podcastImageFetcher,&PodcastImageFetcher::done,
+ this, &SqlPodcastProvider::podcastImageFetcherDone );
}
m_podcastImageFetcher->addChannel( PodcastChannelPtr::dynamicCast( channel ) );
}
void
SqlPodcastProvider::channelImageReady( Podcasts::PodcastChannelPtr channel, QImage image )
{
if( image.isNull() )
return;
channel->setImage( image );
}
void
SqlPodcastProvider::podcastImageFetcherDone( PodcastImageFetcher *fetcher )
{
fetcher->deleteLater();
m_podcastImageFetcher = 0;
}
void
SqlPodcastProvider::slotConfigureProvider()
{
configureProvider();
}
diff --git a/src/core-impl/storage/StorageManager.cpp b/src/core-impl/storage/StorageManager.cpp
index 164b3bdf47..b7fc186431 100644
--- a/src/core-impl/storage/StorageManager.cpp
+++ b/src/core-impl/storage/StorageManager.cpp
@@ -1,209 +1,209 @@
/****************************************************************************************
* Copyright (c) 2014 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "StorageManager"
#include "StorageManager.h"
#include <core/storage/SqlStorage.h>
#include <core/storage/StorageFactory.h>
#include <core/support/Amarok.h>
#include <core/support/Debug.h>
#include <KConfigGroup>
#include <KLocalizedString>
/** A SqlStorage that doesn't do anything.
*
* An object of this type is used whenever we couldn't
* load a better SqlStorage.
*
* The reason is that plugins don't have to check for
* a null pointer as SqlStorage every time.
*/
class EmptySqlStorage : public SqlStorage
{
public:
EmptySqlStorage() {}
virtual ~EmptySqlStorage() {}
virtual int sqlDatabasePriority() const
{ return 10; }
virtual QString type() const { return QLatin1String("Empty"); }
virtual QString escape( const QString &text) const { return text; }
virtual QStringList query( const QString &) { return QStringList(); }
virtual int insert( const QString &, const QString &) { return 0; }
virtual QString boolTrue() const { return QString(); }
virtual QString boolFalse() const { return QString(); }
virtual QString idType() const { return QString(); };
virtual QString textColumnType( int ) const { return QString(); }
virtual QString exactTextColumnType( int ) const { return QString(); }
virtual QString exactIndexableTextColumnType( int ) const { return QString(); }
virtual QString longTextColumnType() const { return QString(); }
virtual QString randomFunc() const { return QString(); }
virtual QStringList getLastErrors() const { return QStringList(); }
/** Clears the list of the last errors. */
virtual void clearLastErrors() { }
};
static EmptySqlStorage emptyStorage;
struct StorageManager::Private
{
SqlStorage* sqlDatabase;
/** A list that collects errors from database plugins
*
* StoragePlugin factories can report errors that
* prevent the storage from even being created.
*
* This list collects them.
*/
QStringList errorList;
};
StorageManager *StorageManager::s_instance = 0;
StorageManager *
StorageManager::instance()
{
if( !s_instance ) {
s_instance = new StorageManager();
s_instance->init();
}
return s_instance;
}
void
StorageManager::destroy()
{
if( s_instance ) {
delete s_instance;
s_instance = 0;
}
}
StorageManager::StorageManager()
: QObject()
, d( new Private )
{
DEBUG_BLOCK
setObjectName( "StorageManager" );
qRegisterMetaType<SqlStorage *>( "SqlStorage*" );
d->sqlDatabase = &emptyStorage;
}
StorageManager::~StorageManager()
{
DEBUG_BLOCK
if( d->sqlDatabase != &emptyStorage )
delete d->sqlDatabase;
delete d;
}
SqlStorage*
StorageManager::sqlStorage() const
{
return d->sqlDatabase;
}
void
StorageManager::init()
{
}
void
StorageManager::setFactories( const QList<Plugins::PluginFactory*> &factories )
{
foreach( Plugins::PluginFactory* pFactory, factories )
{
StorageFactory *factory = qobject_cast<StorageFactory*>( pFactory );
if( !factory )
continue;
- connect( factory, SIGNAL(newStorage(SqlStorage*)),
- this, SLOT(slotNewStorage(SqlStorage*)) );
- connect( factory, SIGNAL(newError(QStringList)),
- this, SLOT(slotNewError(QStringList)) );
+ connect( factory, &StorageFactory::newStorage,
+ this, &StorageManager::slotNewStorage );
+ connect( factory, &StorageFactory::newError,
+ this, &StorageManager::slotNewError );
}
}
QStringList
StorageManager::getLastErrors() const
{
if( !d->errorList.isEmpty() )
return d->errorList;
if( d->sqlDatabase == &emptyStorage )
{
QStringList list;
list << i18n( "The configured database plugin could not be loaded." );
return list;
}
return d->errorList;
}
void
StorageManager::clearLastErrors()
{
d->errorList.clear();
}
void
StorageManager::slotNewStorage( SqlStorage* newStorage )
{
DEBUG_BLOCK
if( !newStorage )
{
warning() << "Warning, newStorage in slotNewStorage is 0";
return;
}
if( d->sqlDatabase && d->sqlDatabase != &emptyStorage )
{
warning() << "Warning, newStorage when we already have a storage";
delete newStorage;
return; // once we have the database set we can't change it since
// plugins might have already created their tables in the old database
// or caching data from it.
}
d->sqlDatabase = newStorage;
}
void
StorageManager::slotNewError( QStringList errorMessageList )
{
d->errorList << errorMessageList;
}
diff --git a/src/core-impl/support/TrackLoader.cpp b/src/core-impl/support/TrackLoader.cpp
index 3aaa7950ac..cb11655611 100644
--- a/src/core-impl/support/TrackLoader.cpp
+++ b/src/core-impl/support/TrackLoader.cpp
@@ -1,285 +1,284 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2013 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TrackLoader.h"
#include "core/playlists/PlaylistFormat.h"
#include "core/support/Debug.h"
#include "core-impl/meta/file/File.h"
#include "core-impl/meta/proxy/MetaProxy.h"
#include "core-impl/meta/multi/MultiTrack.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include <KIO/Job>
#include <KFileItem>
#include <QFileInfo>
#include <QTimer>
TrackLoader::TrackLoader( Flags flags, int timeout )
: m_status( LoadingTracks )
, m_flags( flags )
, m_timeout( timeout )
{
}
TrackLoader::~TrackLoader()
{
}
void
TrackLoader::init( const QUrl &url )
{
init( QList<QUrl>() << url );
}
void
TrackLoader::init( const QList<QUrl> &qurls )
{
m_sourceUrls = qurls;
- QTimer::singleShot( 0, this, SLOT(processNextSourceUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextSourceUrl );
}
void
TrackLoader::init( const Playlists::PlaylistList &playlists )
{
m_resultPlaylists = playlists;
// no need to process source urls here, short-cut to result urls (just playlists)
- QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl );
}
void
TrackLoader::processNextSourceUrl()
{
if( m_sourceUrls.isEmpty() )
{
- QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl );
return;
}
QUrl sourceUrl = m_sourceUrls.takeFirst();
if( sourceUrl.isLocalFile() && QFileInfo( sourceUrl.toLocalFile() ).isDir() )
{
// KJobs delete themselves
KIO::ListJob *lister = KIO::listRecursive( sourceUrl, KIO::HideProgressInfo );
- connect( lister, SIGNAL(finished(KJob*)), SLOT(listJobFinished()) );
- connect( lister, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
- SLOT(directoryListResults(KIO::Job*,KIO::UDSEntryList)) );
+ connect( lister, &KIO::ListJob::finished, this, &TrackLoader::listJobFinished );
+ connect( lister, &KIO::ListJob::entries, this, &TrackLoader::directoryListResults );
// listJobFinished() calls processNextSourceUrl() in the end, don't do it here:
return;
}
else
m_resultUrls.append( sourceUrl );
- QTimer::singleShot( 0, this, SLOT(processNextSourceUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextSourceUrl );
}
void
TrackLoader::directoryListResults( KIO::Job *job, const KIO::UDSEntryList &list )
{
//dfaure says that job->redirectionUrl().isValid() ? job->redirectionUrl() : job->url(); might be needed
//but to wait until an issue is actually found, since it might take more work
const QUrl dir = static_cast<KIO::SimpleJob *>( job )->url();
foreach( const KIO::UDSEntry &entry, list )
{
KFileItem item( entry, dir, true, true );
QUrl url = item.url();
if( MetaFile::Track::isTrack( url ) )
m_listJobResults << url;
}
}
void
TrackLoader::listJobFinished()
{
qSort( m_listJobResults.begin(), m_listJobResults.end(), directorySensitiveLessThan );
m_resultUrls << m_listJobResults;
m_listJobResults.clear();
- QTimer::singleShot( 0, this, SLOT(processNextSourceUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextSourceUrl );
}
void
TrackLoader::processNextResultUrl()
{
using namespace Playlists;
if( !m_resultPlaylists.isEmpty() )
{
PlaylistPtr playlist = m_resultPlaylists.takeFirst();
PlaylistObserver::subscribeTo( playlist );
playlist->triggerTrackLoad(); // playlist track loading is on demand.
// will trigger tracksLoaded() which in turn calls processNextResultUrl(),
// therefore we shouldn't call trigger processNextResultUrl() here:
return;
}
if( m_resultUrls.isEmpty() )
{
mayFinish();
return;
}
QUrl resultUrl = m_resultUrls.takeFirst();
if( isPlaylist( resultUrl ) )
{
PlaylistFilePtr playlist = loadPlaylistFile( resultUrl );
if( playlist )
{
PlaylistObserver::subscribeTo( PlaylistPtr::staticCast( playlist ) );
playlist->triggerTrackLoad(); // playlist track loading is on demand.
// will trigger tracksLoaded() which in turn calls processNextResultUrl(),
// therefore we shouldn't call trigger processNextResultUrl() here:
return;
}
else
warning() << __PRETTY_FUNCTION__ << "cannot load playlist" << resultUrl;
}
else if( MetaFile::Track::isTrack( resultUrl ) )
{
MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( resultUrl ) );
proxyTrack->setTitle( resultUrl.fileName() ); // set temporary name
Meta::TrackPtr track( proxyTrack.data() );
m_tracks << Meta::TrackPtr( track );
if( m_flags.testFlag( FullMetadataRequired ) && !proxyTrack->isResolved() )
{
m_unresolvedTracks.insert( track );
Observer::subscribeTo( track );
}
}
else
warning() << __PRETTY_FUNCTION__ << resultUrl
<< "is neither a playlist or a track, skipping";
- QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl );
}
void
TrackLoader::tracksLoaded( Playlists::PlaylistPtr playlist )
{
// this method needs to be thread-safe!
// some playlists used to emit tracksLoaded() in ->tracks(), prevent infinite
// recursion by unsubscribing early
PlaylistObserver::unsubscribeFrom( playlist );
// accessing m_tracks is thread-safe as nothing else is happening in this class in
// the main thread while we are waiting for tracksLoaded() to trigger:
Meta::TrackList tracks = playlist->tracks();
if( m_flags.testFlag( FullMetadataRequired ) )
{
foreach( const Meta::TrackPtr &track, tracks )
{
MetaProxy::TrackPtr proxyTrack = MetaProxy::TrackPtr::dynamicCast( track );
if( !proxyTrack )
{
debug() << __PRETTY_FUNCTION__ << "strange, playlist" << playlist->name()
<< "doesn't use MetaProxy::Tracks";
continue;
}
if( !proxyTrack->isResolved() )
{
m_unresolvedTracks.insert( track );
Observer::subscribeTo( track );
}
}
}
static const QSet<QString> remoteProtocols = QSet<QString>()
<< "http" << "https" << "mms" << "smb"; // consider unifying with CollectionManager::trackForUrl()
if( m_flags.testFlag( RemotePlaylistsAreStreams ) && tracks.count() > 1
&& remoteProtocols.contains( playlist->uidUrl().scheme() ) )
{
m_tracks << Meta::TrackPtr( new Meta::MultiTrack( playlist ) );
}
else
m_tracks << tracks;
// this also ensures that processNextResultUrl() will resume in the main thread
- QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) );
+ QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl );
}
void
TrackLoader::metadataChanged( Meta::TrackPtr track )
{
// first metadataChanged() from a MetaProxy::Track means that it has found the real track
bool isEmpty;
{
QMutexLocker locker( &m_unresolvedTracksMutex );
m_unresolvedTracks.remove( track );
isEmpty = m_unresolvedTracks.isEmpty();
}
Observer::unsubscribeFrom( track );
if( m_status == MayFinish && isEmpty )
- QTimer::singleShot( 0, this, SLOT(finish()) );
+ QTimer::singleShot( 0, this, &TrackLoader::finish );
}
void
TrackLoader::mayFinish()
{
m_status = MayFinish;
bool isEmpty;
{
QMutexLocker locker( &m_unresolvedTracksMutex );
isEmpty = m_unresolvedTracks.isEmpty();
}
if( isEmpty )
{
finish();
return;
}
// we must wait for tracks to resolve, but with a timeout
- QTimer::singleShot( m_timeout, this, SLOT(finish()) );
+ QTimer::singleShot( m_timeout, this, &TrackLoader::finish );
}
void
TrackLoader::finish()
{
// prevent double emit of finished(), race between singleshot QTimers from mayFinish()
// and metadataChanged()
if( m_status != MayFinish )
return;
m_status = Finished;
emit finished( m_tracks );
deleteLater();
}
bool
TrackLoader::directorySensitiveLessThan( const QUrl &left, const QUrl &right )
{
QString leftDir = left.adjusted(QUrl::RemoveFilename).path();
QString rightDir = right.adjusted(QUrl::RemoveFilename).path();
// filter out tracks from same directories:
if( leftDir == rightDir )
return QString::localeAwareCompare( left.fileName(), right.fileName() ) < 0;
// left is "/a/b/c/", right is "/a/b/"
if( leftDir.startsWith( rightDir ) )
return true; // we sort directories above files
// left is "/a/b/", right is "/a/b/c/"
if( rightDir.startsWith( leftDir ) )
return false;
return QString::localeAwareCompare( leftDir, rightDir ) < 0;
}
diff --git a/src/core/collections/CollectionLocation.cpp b/src/core/collections/CollectionLocation.cpp
index 289757e8ae..09983e8a2c 100644
--- a/src/core/collections/CollectionLocation.cpp
+++ b/src/core/collections/CollectionLocation.cpp
@@ -1,743 +1,745 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CollectionLocation"
#include "CollectionLocation.h"
#include "core/capabilities/TranscodeCapability.h"
#include "core/collections/Collection.h"
#include "core/collections/CollectionLocationDelegate.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/transcoding/TranscodingConfiguration.h"
#include "core/transcoding/TranscodingController.h"
#include <KLocalizedString>
#include <QDir>
#include <QTimer>
using namespace Collections;
CollectionLocation::CollectionLocation()
:QObject()
, m_destination( 0 )
, m_source( 0 )
, m_sourceTracks()
, m_parentCollection( 0 )
, m_removeSources( false )
, m_isRemoveAction( false )
, m_noRemoveConfirmation( false )
, m_transcodingConfiguration( Transcoding::JUST_COPY )
{
//nothing to do
}
CollectionLocation::CollectionLocation( Collections::Collection *parentCollection)
:QObject()
, m_destination( 0 )
, m_source( 0 )
, m_sourceTracks()
, m_parentCollection( parentCollection )
, m_removeSources( false )
, m_isRemoveAction( false )
, m_noRemoveConfirmation( false )
, m_transcodingConfiguration( Transcoding::JUST_COPY )
{
//nothing to do
}
CollectionLocation::~CollectionLocation()
{
//nothing to do
}
Collections::Collection*
CollectionLocation::collection() const
{
return m_parentCollection;
}
QString
CollectionLocation::prettyLocation() const
{
return QString();
}
QStringList
CollectionLocation::actualLocation() const
{
return QStringList();
}
bool
CollectionLocation::isWritable() const
{
return false;
}
bool
CollectionLocation::isOrganizable() const
{
return false;
}
void
CollectionLocation::prepareCopy( Meta::TrackPtr track, CollectionLocation *destination )
{
Q_ASSERT(destination);
Meta::TrackList list;
list.append( track );
prepareCopy( list, destination );
}
void
CollectionLocation::prepareCopy( const Meta::TrackList &tracks, CollectionLocation *destination )
{
if( !destination->isWritable() )
{
CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
destination->deleteLater();
deleteLater();
return;
}
m_destination = destination;
m_destination->setSource( this );
startWorkflow( tracks, false );
}
void
CollectionLocation::prepareCopy( Collections::QueryMaker *qm, CollectionLocation *destination )
{
DEBUG_BLOCK
if( !destination->isWritable() )
{
CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
destination->deleteLater();
qm->deleteLater();
deleteLater();
return;
}
m_destination = destination;
m_removeSources = false;
m_isRemoveAction = false;
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) );
- connect( qm, SIGNAL(queryDone()), SLOT(queryDone()) );
+ connect( qm, &Collections::QueryMaker::newTracksReady, this, &CollectionLocation::resultReady );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionLocation::queryDone );
qm->setQueryType( Collections::QueryMaker::Track );
qm->run();
}
void
CollectionLocation::prepareMove( Meta::TrackPtr track, CollectionLocation *destination )
{
Meta::TrackList list;
list.append( track );
prepareMove( list, destination );
}
void
CollectionLocation::prepareMove( const Meta::TrackList &tracks, CollectionLocation *destination )
{
DEBUG_BLOCK
if( !destination->isWritable() )
{
CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
destination->deleteLater();
deleteLater();
return;
}
m_destination = destination;
m_destination->setSource( this );
startWorkflow( tracks, true );
}
void
CollectionLocation::prepareMove( Collections::QueryMaker *qm, CollectionLocation *destination )
{
DEBUG_BLOCK
if( !destination->isWritable() )
{
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
destination->deleteLater();
qm->deleteLater();
deleteLater();
return;
}
m_destination = destination;
m_isRemoveAction = false;
m_removeSources = true;
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) );
- connect( qm, SIGNAL(queryDone()), SLOT(queryDone()) );
+ connect( qm, &Collections::QueryMaker::newTracksReady, this, &CollectionLocation::resultReady );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionLocation::queryDone );
qm->setQueryType( Collections::QueryMaker::Track );
qm->run();
}
void
CollectionLocation::prepareRemove( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
if( !isWritable() )
{
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
deleteLater();
return;
}
startRemoveWorkflow( tracks );
}
void
CollectionLocation::prepareRemove( Collections::QueryMaker *qm )
{
DEBUG_BLOCK
if( !isWritable() )
{
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
delegate->notWriteable( this );
qm->deleteLater();
deleteLater();
return;
}
m_isRemoveAction = true;
m_removeSources = false;
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) );
- connect( qm, SIGNAL(queryDone()), SLOT(queryDone()) );
+ connect( qm, &Collections::QueryMaker::newTracksReady, this, &CollectionLocation::resultReady );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionLocation::queryDone );
qm->setQueryType( Collections::QueryMaker::Track );
qm->run();
}
bool
CollectionLocation::insert( const Meta::TrackPtr &track, const QString &url )
{
Q_UNUSED( track )
Q_UNUSED( url )
warning() << __PRETTY_FUNCTION__ << "Don't call this method. It exists only because"
<< "database importers need it. Call prepareCopy() instead.";
return false;
}
void
CollectionLocation::abort()
{
emit aborted();
}
void
CollectionLocation::getKIOCopyableUrls( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
QMap<Meta::TrackPtr, QUrl> urls;
foreach( Meta::TrackPtr track, tracks )
{
if( track->isPlayable() )
{
urls.insert( track, track->playableUrl() );
debug() << "adding url " << track->playableUrl();
}
}
slotGetKIOCopyableUrlsDone( urls );
}
void
CollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration )
{
DEBUG_BLOCK
//reimplement in implementations which are writable
Q_UNUSED( sources )
Q_UNUSED( configuration )
slotCopyOperationFinished();
}
void
CollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources )
{
DEBUG_BLOCK
//reimplement in implementations which are writable
Q_UNUSED( sources )
slotRemoveOperationFinished();
}
void
CollectionLocation::showSourceDialog( const Meta::TrackList &tracks, bool removeSources )
{
Q_UNUSED( tracks )
Q_UNUSED( removeSources )
m_transcodingConfiguration = getDestinationTranscodingConfig();
if( m_transcodingConfiguration.isValid() )
slotShowSourceDialogDone();
else
abort();
}
Transcoding::Configuration
CollectionLocation::getDestinationTranscodingConfig()
{
Transcoding::Configuration configuration( Transcoding::JUST_COPY );
Collection *destCollection = destination() ? destination()->collection() : 0;
if( !destCollection )
return configuration;
if( !destCollection->has<Capabilities::TranscodeCapability>() )
return configuration;
QScopedPointer<Capabilities::TranscodeCapability> tc(
destCollection->create<Capabilities::TranscodeCapability>() );
if( !tc )
return configuration;
Transcoding::Controller* tcC = Amarok::Components::transcodingController();
QSet<Transcoding::Encoder> availableEncoders;
if( tcC )
availableEncoders = tcC->availableEncoders();
Transcoding::Configuration saved = tc->savedConfiguration();
if( saved.isValid() && ( saved.isJustCopy() || availableEncoders.contains( saved.encoder() ) ) )
return saved;
// saved configuration was not available or was invalid, ask user
CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
bool saveConfiguration = false;
CollectionLocationDelegate::OperationType operation = CollectionLocationDelegate::Copy;
if( isGoingToRemoveSources() )
operation = CollectionLocationDelegate::Move;
if( collection() && collection() == destination()->collection() )
operation = CollectionLocationDelegate::Move; // organizing
configuration = delegate->transcode( tc->playableFileTypes(), &saveConfiguration,
operation, destCollection->prettyName(),
saved );
if( configuration.isValid() )
{
if( saveConfiguration )
tc->setSavedConfiguration( configuration );
else //save the trackSelection value for restore anyway
tc->setSavedConfiguration( Transcoding::Configuration( Transcoding::INVALID,
configuration.trackSelection() ) );
}
return configuration; // may be invalid, it means user has hit cancel
}
void
CollectionLocation::showDestinationDialog( const Meta::TrackList &tracks,
bool removeSources,
const Transcoding::Configuration &configuration )
{
Q_UNUSED( tracks )
Q_UNUSED( configuration )
setGoingToRemoveSources( removeSources );
slotShowDestinationDialogDone();
}
void
CollectionLocation::showRemoveDialog( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
if( !isHidingRemoveConfirm() )
{
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
const bool del = delegate->reallyDelete( this, tracks );
if( !del )
slotFinishRemove();
else
slotShowRemoveDialogDone();
} else
slotShowRemoveDialogDone();
}
QString
CollectionLocation::operationText( const Transcoding::Configuration &configuration )
{
if( source()->collection() == collection() )
{
if( configuration.isJustCopy() )
return i18n( "Organize tracks" );
else
return i18n( "Transcode and organize tracks" );
}
if( isGoingToRemoveSources() )
{
if( configuration.isJustCopy() )
return i18n( "Move tracks" );
else
return i18n( "Transcode and move tracks" );
}
else
{
if( configuration.isJustCopy() )
return i18n( "Copy tracks" );
else
return i18n( "Transcode and copy tracks" );
}
}
QString
CollectionLocation::operationInProgressText( const Transcoding::Configuration &configuration,
int trackCount, QString destinationName )
{
if( destinationName.isEmpty() )
destinationName = prettyLocation();
if( source()->collection() == collection() )
{
if( configuration.isJustCopy() )
return i18np( "Organizing one track",
"Organizing %1 tracks", trackCount );
else
return i18np( "Transcoding and organizing one track",
"Transcoding and organizing %1 tracks", trackCount );
}
if( isGoingToRemoveSources() )
{
if( configuration.isJustCopy() )
return i18np( "Moving one track to %2",
"Moving %1 tracks to %2", trackCount, destinationName );
else
return i18np( "Transcoding and moving one track to %2",
"Transcoding and moving %1 tracks to %2", trackCount, destinationName );
}
else
{
if( configuration.isJustCopy() )
return i18np( "Copying one track to %2",
"Copying %1 tracks to %2", trackCount, destinationName );
else
return i18np( "Transcoding and copying one track to %2",
"Transcoding and copying %1 tracks to %2", trackCount, destinationName );
}
}
void
CollectionLocation::slotGetKIOCopyableUrlsDone( const QMap<Meta::TrackPtr, QUrl> &sources )
{
emit startCopy( sources, m_transcodingConfiguration );
}
void
CollectionLocation::slotCopyOperationFinished()
{
emit finishCopy();
}
void
CollectionLocation::slotRemoveOperationFinished()
{
emit finishRemove();
}
void
CollectionLocation::slotShowSourceDialogDone()
{
emit prepareOperation( m_sourceTracks, m_removeSources, m_transcodingConfiguration );
}
void
CollectionLocation::slotShowDestinationDialogDone()
{
emit operationPrepared();
}
void
CollectionLocation::slotShowRemoveDialogDone()
{
emit startRemove();
}
void
CollectionLocation::slotShowSourceDialog()
{
showSourceDialog( m_sourceTracks, m_removeSources );
}
void
CollectionLocation::slotPrepareOperation( const Meta::TrackList &tracks, bool removeSources,
const Transcoding::Configuration &configuration )
{
m_removeSources = removeSources;
showDestinationDialog( tracks, removeSources, configuration );
}
void
CollectionLocation::slotOperationPrepared()
{
getKIOCopyableUrls( m_sourceTracks );
}
void
CollectionLocation::slotStartCopy( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration )
{
DEBUG_BLOCK
copyUrlsToCollection( sources, configuration );
}
void
CollectionLocation::slotFinishCopy()
{
DEBUG_BLOCK
if( m_removeSources )
{
removeSourceTracks( m_tracksSuccessfullyTransferred );
m_sourceTracks.clear();
m_tracksSuccessfullyTransferred.clear();
}
else
{
m_sourceTracks.clear();
m_tracksSuccessfullyTransferred.clear();
if( m_destination )
m_destination->deleteLater();
m_destination = 0;
this->deleteLater();
}
}
void
CollectionLocation::slotStartRemove()
{
DEBUG_BLOCK
removeUrlsFromCollection( m_sourceTracks );
}
void
CollectionLocation::slotFinishRemove()
{
DEBUG_BLOCK
Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
if( m_tracksWithError.size() > 0 )
{
delegate->errorDeleting( this, m_tracksWithError.keys() );
m_tracksWithError.clear();
}
QStringList dirsToRemove;
debug() << "remove finished updating";
foreach( Meta::TrackPtr track, m_tracksSuccessfullyTransferred )
{
if(!track)
continue;
if( track->playableUrl().isLocalFile() )
dirsToRemove.append( track->playableUrl().adjusted( QUrl::RemoveFilename ).path() );
}
if( !dirsToRemove.isEmpty() && delegate->deleteEmptyDirs( this ) )
{
debug() << "Removing empty directories";
dirsToRemove.removeDuplicates();
dirsToRemove.sort();
while( !dirsToRemove.isEmpty() )
{
QDir dir( dirsToRemove.takeLast() );
if( !dir.exists() )
continue;
dir.setFilter( QDir::NoDotAndDotDot );
while( !dir.isRoot() && dir.count() == 0 )
{
const QString name = dir.dirName();
dir.cdUp();
if( !dir.rmdir( name ) )
{
debug() << "Unable to remove " << name;
break;
}
}
}
}
m_tracksSuccessfullyTransferred.clear();
m_sourceTracks.clear();
this->deleteLater();
}
void
CollectionLocation::slotAborted()
{
m_destination->deleteLater();
deleteLater();
}
void
CollectionLocation::resultReady( const Meta::TrackList &tracks )
{
m_sourceTracks << tracks;
}
void
CollectionLocation::queryDone()
{
DEBUG_BLOCK
QObject *obj = sender();
if( obj )
{
obj->deleteLater();
}
if( m_isRemoveAction )
{
debug() << "we were about to remove something, lets proceed";
prepareRemove( m_sourceTracks );
}
else if( m_removeSources )
{
debug() << "we were about to move something, lets proceed";
prepareMove( m_sourceTracks, m_destination );
}
else
{
debug() << "we were about to copy something, lets proceed";
prepareCopy( m_sourceTracks, m_destination );
}
}
void
CollectionLocation::setupConnections()
{
- connect( this, SIGNAL(prepareOperation(Meta::TrackList,bool,Transcoding::Configuration)),
- m_destination, SLOT(slotPrepareOperation(Meta::TrackList,bool,Transcoding::Configuration)) );
- connect( m_destination, SIGNAL(operationPrepared()), SLOT(slotOperationPrepared()) );
- connect( this, SIGNAL(startCopy(QMap<Meta::TrackPtr,QUrl>,Transcoding::Configuration)),
- m_destination, SLOT(slotStartCopy(QMap<Meta::TrackPtr,QUrl>,Transcoding::Configuration)) );
- connect( m_destination, SIGNAL(finishCopy()),
- this, SLOT(slotFinishCopy()) );
- connect( this, SIGNAL(aborted()), SLOT(slotAborted()) );
- connect( m_destination, SIGNAL(aborted()), SLOT(slotAborted()) );
+ connect( this, &CollectionLocation::prepareOperation,
+ m_destination, &Collections::CollectionLocation::slotPrepareOperation );
+ connect( m_destination, &Collections::CollectionLocation::operationPrepared,
+ this, &CollectionLocation::slotOperationPrepared );
+ connect( this, &CollectionLocation::startCopy,
+ m_destination, &Collections::CollectionLocation::slotStartCopy );
+ connect( m_destination, &Collections::CollectionLocation::finishCopy,
+ this, &CollectionLocation::slotFinishCopy );
+ connect( this, &CollectionLocation::aborted, this, &CollectionLocation::slotAborted );
+ connect( m_destination, &Collections::CollectionLocation::aborted, this, &CollectionLocation::slotAborted );
}
void
CollectionLocation::setupRemoveConnections()
{
- connect( this, SIGNAL(aborted()), SLOT(slotAborted()) );
- connect( this, SIGNAL(startRemove()),
- this, SLOT(slotStartRemove()) );
- connect( this, SIGNAL(finishRemove()),
- this, SLOT(slotFinishRemove()) );
+ connect( this, &CollectionLocation::aborted,
+ this, &CollectionLocation::slotAborted );
+ connect( this, &CollectionLocation::startRemove,
+ this, &CollectionLocation::slotStartRemove );
+ connect( this, &CollectionLocation::finishRemove,
+ this, &CollectionLocation::slotFinishRemove );
}
void
CollectionLocation::startWorkflow( const Meta::TrackList &tracks, bool removeSources )
{
DEBUG_BLOCK
m_removeSources = removeSources;
m_sourceTracks = tracks;
setupConnections();
if( tracks.size() <= 0 )
abort();
else
// show dialog in next mainloop iteration so that prepare[Something]() returns quickly
- QTimer::singleShot( 0, this, SLOT(slotShowSourceDialog()) );
+ QTimer::singleShot( 0, this, &CollectionLocation::slotShowSourceDialog );
}
void
CollectionLocation::startRemoveWorkflow( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
m_sourceTracks = tracks;
setupRemoveConnections();
if( tracks.isEmpty() )
abort();
else
showRemoveDialog( tracks );
}
void
CollectionLocation::removeSourceTracks( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
debug() << "Transfer errors:" << m_tracksWithError.count() << "of" << tracks.count();
foreach( Meta::TrackPtr track, m_tracksWithError.keys() )
{
debug() << "transfer error for track" << track->playableUrl();
}
QSet<Meta::TrackPtr> toRemove = QSet<Meta::TrackPtr>::fromList( tracks );
QSet<Meta::TrackPtr> errored = QSet<Meta::TrackPtr>::fromList( m_tracksWithError.keys() );
toRemove.subtract( errored );
// start the remove workflow
setHidingRemoveConfirm( true );
prepareRemove( toRemove.toList() );
}
CollectionLocation*
CollectionLocation::source() const
{
return m_source;
}
CollectionLocation*
CollectionLocation::destination() const
{
return m_destination;
}
void
CollectionLocation::setSource( CollectionLocation *source )
{
m_source = source;
}
void
CollectionLocation::transferSuccessful( const Meta::TrackPtr &track )
{
m_tracksSuccessfullyTransferred.append( track );
}
bool
CollectionLocation::isGoingToRemoveSources() const
{
return m_removeSources;
}
void
CollectionLocation::setGoingToRemoveSources( bool removeSources )
{
m_removeSources = removeSources;
}
bool
CollectionLocation::isHidingRemoveConfirm() const
{
return m_noRemoveConfirmation;
}
void
CollectionLocation::setHidingRemoveConfirm( bool hideRemoveConfirm )
{
m_noRemoveConfirmation = hideRemoveConfirm;
}
void
CollectionLocation::transferError( const Meta::TrackPtr &track, const QString &error )
{
m_tracksWithError.insert( track, error );
}
diff --git a/src/core/collections/CollectionLocation.h b/src/core/collections/CollectionLocation.h
index 5e7604900f..f4305daa63 100644
--- a/src/core/collections/CollectionLocation.h
+++ b/src/core/collections/CollectionLocation.h
@@ -1,425 +1,424 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_COLLECTIONLOCATION_H
#define AMAROK_COLLECTIONLOCATION_H
#include "core/amarokcore_export.h"
#include "core/meta/forward_declarations.h"
#include "core/transcoding/TranscodingConfiguration.h"
#include <QList>
#include <QObject>
#include <QUrl>
namespace Collections {
class Collection;
class QueryMaker;
/**
This base class defines the methods necessary to allow the copying and moving
of tracks between different collections in a generic way.
This class should be used as follows in client code:
- select a source and a destination CollectionLocation
- call prepareCopy or prepareMove on the source CollectionLocation
- forget about the rest of the workflow
Implementations which are writable must reimplement the following methods
- prettyLocation()
- isWritable()
- remove( Meta::Track )
- copyUrlsToCollection( QMap<Meta::TrackPtr, QUrl> )
Writable collections that are also organizable should reimplement isOrganizable().
Organizable means that the user is able to decide (to varying degrees, the details
depend on the actual collection) where the files are stored in the filesystem (or some
kind of VFS). An example would be the local collection, where the user can select the directory
structure that Amarok uses to store the files. An example for a writable collection that is not
organizable are ipods, where the user has no control about the actual location of the music files
(they are automatically stored in a not human-readable form).
Implementations which are only readable can reimplement getKIOCopyableUrls( Meta::TrackList )
if it is necessary, but can use the default implementations if possible.
Implementations that have a string expressable location(s), such as a URL or path on disk
should reimplement actualLocation().
Implementations that need additional information provided by the user have to implement
showSourceDialog() and showDestinationDialog(), depending on whether the information is required
in the source, the destination, or both.
The methods will be called in the following order:
startWorkflow (source)
showSourceDialog (source) (reimplementable)
slotShowSourceDialogDone (source)
slotPrepareOperation (destination)
showDestinationDialog (destination) (reimplementable)
slotShowDestinationDialogDone (destination)
slotOperationPrepared (source)
getKIOCopyableUrls (source) (reimplementable)
slotGetKIOCopyableUrlsDone (source)
slotStartCopy (destination)
copyUrlsToCollection (destination) (reimplementable)
slotCopyOperationFinished (destination)
slotFinishCopy (source)
To provide removal ability, it is required to reimplement removeUrlsFromCollection,
and this function must call slotRemoveOperationFinished() when it is done. Optionally,
showRemoveDialog can be reimplemented to customize the warning displayed before a removal,
and this function must call slotShowRemoveDialogDone when finished.
The methods for remove will be called in the following order:
startRemoveWorkflow
showRemoveDialog (reimplementable)
slotShowRemoveDialogDone
slotStartRemove
removeUrlsFromCollection (reimplementable)
slotRemoveOperationFinished
slotFinishRemove
*/
class AMAROK_CORE_EXPORT CollectionLocation : public QObject
{
Q_OBJECT
//testing only do not use these properties in anything but tests
Q_PROPERTY( bool removeSources
READ getRemoveSources
WRITE setRemoveSources
DESIGNABLE false
SCRIPTABLE false )
public:
CollectionLocation();
CollectionLocation( Collections::Collection *parentCollection );
virtual ~CollectionLocation();
/**
Returns a pointer to the collection location's corresponding collection.
@return a pointer to the collection location's corresponding collection
*/
virtual Collections::Collection *collection() const;
/**
a displayable string representation of the collection location. use the return
value of this method to display the collection location to the user.
@return a string representation of the collection location
*/
virtual QString prettyLocation() const;
/**
Returns a list of machine usable strings representingthe collection location.
For example, a local collection would return a list of paths where tracks are
stored, while an Ampache collection would return a list with one string
containing the URL of an ampache server. An iPod collection and a MTP device
collection are examples of collections that do not need to reimplement this method.
*/
virtual QStringList actualLocation() const;
/**
Returns whether the collection location is writable or not. For example, a
local collection or an ipod collection would return true, a daap collection
or a service collection false. The value returned by this method indicates
if it is possible to copy tracks to the collection, and if it is generally
possible to remove/delete files from the collection.
@return @c true if the collection location is writable
@return @c false if the collection location is not writable
*/
virtual bool isWritable() const;
/**
Returns whether the collection is organizable or not. Organizable collections
allow move operations where the source and destination collection are the same.
@return @c true if the collection location is organizable, false otherwise
*/
virtual bool isOrganizable() const;
/**
Convenience method for copying a single track.
@see prepareCopy( Meta::TrackList, CollectionLocation* )
*/
void prepareCopy( Meta::TrackPtr track, CollectionLocation *destination );
/**
Schedule copying of @param tracks to collection location @param destination.
This method takes ownership of the @param destination, you may not reference
or delete it after this call. This method returns immediately and the actual
copy is performed in the event loop and/or another thread.
*/
void prepareCopy( const Meta::TrackList &tracks, CollectionLocation *destination );
/**
Convenience method for copying tracks based on QueryMaker restults,
takes ownership of the @param qm.
@see prepareCopy( Meta::TrackList, CollectionLocation* )
*/
void prepareCopy( Collections::QueryMaker *qm, CollectionLocation *destination );
/**
* Convenience method for moving a single track.
* @see prepareMove( Meta::TrackList, CollectionLocation* )
*/
void prepareMove( Meta::TrackPtr track, CollectionLocation *destination );
/**
Schedule moving of @param tracks to collection location @param destination.
This method takes ownership of the @param destination, you may not reference
or delete it after this call. This method returns immediately and the actual
move is performed in the event loop and/or another thread.
*/
void prepareMove( const Meta::TrackList &tracks, CollectionLocation *destination );
/**
Convenience method for moving tracks based on QueryMaker restults,
takes ownership of the @param qm.
@see prepareMove( Meta::TrackList, CollectionLocation* )
*/
void prepareMove( Collections::QueryMaker *qm, CollectionLocation *destination );
/**
Starts workflow for removing tracks.
*/
void prepareRemove( const Meta::TrackList &tracks );
/**
Convenience method for removing tracks selected by QueryMaker,
takes ownership of the @param qm.
@see prepareRemove( Meta::TrackList )
*/
void prepareRemove( Collections::QueryMaker *qm );
/**
* Adds or merges a track to the collection (not to the disk)
* Inserts a set of TrackPtrs directly into the database without needing to actual move any files
* This is a hack required by the DatabaseImporter
* TODO: Remove this hack
* @return true if the database entry was inserted, false otherwise
*/
virtual bool insert( const Meta::TrackPtr &track, const QString &url );
/**
explicitly inform the source collection of successful transfer.
The source collection will only remove files (if necessary)
for which this method was called.
*/
void transferSuccessful( const Meta::TrackPtr &track );
/**
* tells the source location that an error occurred during the transfer of the file
*/
virtual void transferError( const Meta::TrackPtr &track, const QString &error );
Q_SIGNALS:
void startCopy( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration & );
void finishCopy();
void startRemove();
void finishRemove();
void prepareOperation( const Meta::TrackList &tracks, bool removeSources,
const Transcoding::Configuration & );
void operationPrepared();
void aborted();
protected:
/**
* aborts the workflow
*/
void abort();
/**
* allows the destination location to access the source CollectionLocation.
* note: subclasses do not take ownership of the pointer
*/
CollectionLocation* source() const;
/**
* allows the source location to access the destination CollectionLocation.
* Pointer may be null!
* note: subclasses do not take ownership of the pointer
*/
CollectionLocation* destination() const;
/**
this method is called on the source location, and should return a list of urls
which the destination location can copy using KIO. You must call
slotGetKIOCopyableUrlsDone( QMap<Meta::TrackPtr, QUrl> ) after retrieving the
urls. The order of urls passed to that method has to be the same as the order
of the tracks passed to this method.
*/
virtual void getKIOCopyableUrls( const Meta::TrackList &tracks );
/**
this method is called on the destination. reimplement it if your implementation
is writable. You must call slotCopyOperationFinished() when you are done copying
the files.
Before calling slotCopyOperationFinished(), you should call
source()->transferSuccessful() for every source track that was for sure
successfully copied to destination collection. Only such marked tracks are
then removed in case of a "move" action.
*/
virtual void copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration );
/**
this method is called on the collection you want to remove tracks from. it must
be reimplemented if your collection is writable and you wish to implement
removing tracks. You must call slotRemoveOperationFinished() when you are done
removing the files.
Before calling slotRemoveOperationFinished(), you should call transferSuccessful()
for every track that was successfully deleted. CollectionLocation then scans
directories of such tracks and allows user to remove empty ones.
*/
virtual void removeUrlsFromCollection( const Meta::TrackList &sources );
/**
* this method is called on the source. It allows the source CollectionLocation to
* show a dialog. Classes that reimplement this method must call
* slotShowSourceDialogDone() after they have acquired all necessary information from the user.
*
* Default implementation calls getDestinationTranscodingConfig() which may ask
* user. If you reimplement this you may (or not) call this base method instead
* of calling slotShowDestinationDialogDone() to support transcoding.
*/
virtual void showSourceDialog( const Meta::TrackList &tracks, bool removeSources );
/**
* Get transcoding configuration to use when transferring tracks to destination.
* If destination collection doesn't support transcoding, JUST_COPY configuration
* is returned, otherwise preferred configuration is read or user is asked.
* Returns invalid configuration in case user has hit cancel or closed the dialog.
*
* This method is meant to be called by source collection.
*/
virtual Transcoding::Configuration getDestinationTranscodingConfig();
/**
* This method is called on the destination. It allows the destination
* CollectionLocation to show a dialog. Classes that reimplement this method
* must call slotShowDestinationDialogDone() after they have acquired all necessary
* information from the user.
*
* Default implementation calls setGoingToRemoveSources( removeSources ) so that
* isGoingToRemoveSources() is available on destination, too and then calls
* slotShowDestinationDialogDone()
*/
virtual void showDestinationDialog( const Meta::TrackList &tracks,
bool removeSources,
const Transcoding::Configuration &configuration );
/**
* this methods allows the collection to show a warning dialog before tracks are removed,
* rather than using the default provided. Classes that reimplement this method must call
* slotShowRemoveDialogDone() after they are finished.
*/
virtual void showRemoveDialog( const Meta::TrackList &tracks );
/**
* Get nice localised string describing the current operation based on transcoding
* configuraiton and isGoingToRemoveSources(); meant to be called by destination
* collection.
*
* @return "Copy Tracks", "Transcode and Organize Tracks" etc.
*/
QString operationText( const Transcoding::Configuration &configuration );
/**
* Get nice localised string that can be used as progress bar text for the current
* operation; meant to be called by the destination collection.
*
* @param trackCount number of tracks in the transfer
* @param destinationName pretty localised name of the destination collection;
* prettyLocation() is used if the string is empty or not specified
*
* @return "Transcoding and moving <trackCount> tracks to <destinationName>" etc.
*/
QString operationInProgressText( const Transcoding::Configuration &configuration,
int trackCount, QString destinationName = QString() );
/**
* Sets or gets whether some source files may be removed
*/
virtual bool isGoingToRemoveSources() const;
virtual void setGoingToRemoveSources( bool removeSources );
/**
* Sets or gets whether to stifle the removal confirmation
*/
virtual bool isHidingRemoveConfirm() const;
virtual void setHidingRemoveConfirm( bool hideRemoveConfirm );
protected Q_SLOTS:
/**
* this slot has to be called from getKIOCopyableUrls( Meta::TrackList )
* Please note: the order of urls in the argument has to be the same as in the
* tracklist
*/
void slotGetKIOCopyableUrlsDone( const QMap<Meta::TrackPtr, QUrl> &sources );
void slotCopyOperationFinished();
void slotRemoveOperationFinished();
void slotShowSourceDialogDone();
void slotShowRemoveDialogDone();
void slotShowDestinationDialogDone();
private Q_SLOTS:
void slotShowSourceDialog(); // trick to show dialog in next mainloop iteration
void slotPrepareOperation( const Meta::TrackList &tracks, bool removeSources,
const Transcoding::Configuration &configuration );
void slotOperationPrepared();
void slotStartCopy( const QMap<Meta::TrackPtr, QUrl> &sources,
const Transcoding::Configuration &configuration );
void slotFinishCopy();
void slotStartRemove();
void slotFinishRemove();
void slotAborted();
void resultReady( const Meta::TrackList &tracks );
void queryDone();
private:
void setupConnections();
void setupRemoveConnections();
void startWorkflow( const Meta::TrackList &tracks, bool removeSources );
void startRemoveWorkflow( const Meta::TrackList &tracks );
- void startRemove( const Meta::TrackList &tracks );
void removeSourceTracks( const Meta::TrackList &tracks );
void setSource( CollectionLocation *source );
//only used in the source CollectionLocation
CollectionLocation *m_destination;
//only used in destination CollectionLocation
CollectionLocation *m_source;
Meta::TrackList getSourceTracks() const { return m_sourceTracks; }
void setSourceTracks( Meta::TrackList tracks ) { m_sourceTracks = tracks; }
Meta::TrackList m_sourceTracks;
Collections::Collection *m_parentCollection;
bool getRemoveSources() const { return m_removeSources; }
void setRemoveSources( bool removeSources ) { m_removeSources = removeSources; }
bool m_removeSources;
bool m_isRemoveAction;
bool m_noRemoveConfirmation;
Transcoding::Configuration m_transcodingConfiguration;
//used by the source collection to store the tracks that were successfully
//copied by the destination and can be removed as part of a move
Meta::TrackList m_tracksSuccessfullyTransferred;
QMap<Meta::TrackPtr, QString> m_tracksWithError;
};
} //namespace Collections
#endif
diff --git a/src/core/collections/MetaQueryMaker.cpp b/src/core/collections/MetaQueryMaker.cpp
index 57c83fcc44..8b19821465 100644
--- a/src/core/collections/MetaQueryMaker.cpp
+++ b/src/core/collections/MetaQueryMaker.cpp
@@ -1,274 +1,275 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core/collections/MetaQueryMaker.h"
+#include "core/meta/Meta.h"
using namespace Collections;
MetaQueryMaker::MetaQueryMaker( const QList<Collections::Collection*> &collections )
: QueryMaker()
, m_queryDoneCount( 0 )
, m_queryDoneCountMutex()
{
foreach( Collections::Collection *c, collections )
{
QueryMaker *b = c->queryMaker();
builders.append( b );
- connect( b, SIGNAL(queryDone()), this, SLOT(slotQueryDone()) );
+ connect( b, &QueryMaker::queryDone, this, &MetaQueryMaker::slotQueryDone );
//relay signals directly
// actually this is wrong. We would need to combine the results
// to prevent duplicate album name results.
// On the other hand we need duplicate AlbumPtr results.
// Summary: be carefull when using this class. (Ralf)
- connect( b, SIGNAL(newResultReady(Meta::TrackList)), this, SIGNAL(newResultReady(Meta::TrackList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::ArtistList)), this, SIGNAL(newResultReady(Meta::ArtistList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::AlbumList)), this, SIGNAL(newResultReady(Meta::AlbumList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::GenreList)), this, SIGNAL(newResultReady(Meta::GenreList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::ComposerList)), this, SIGNAL(newResultReady(Meta::ComposerList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::YearList)), this, SIGNAL(newResultReady(Meta::YearList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(QStringList)), this, SIGNAL(newResultReady(QStringList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::LabelList)), this, SIGNAL(newResultReady(Meta::LabelList)), Qt::DirectConnection );
+ connect( b, &QueryMaker::newTracksReady, this, &MetaQueryMaker::newTracksReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newArtistsReady, this, &MetaQueryMaker::newArtistsReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newAlbumsReady, this, &MetaQueryMaker::newAlbumsReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newGenresReady, this, &MetaQueryMaker::newGenresReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newComposersReady, this, &MetaQueryMaker::newComposersReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newYearsReady, this, &MetaQueryMaker::newYearsReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newResultReady, this, &MetaQueryMaker::newResultReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newLabelsReady, this, &MetaQueryMaker::newLabelsReady, Qt::DirectConnection );
}
}
MetaQueryMaker::MetaQueryMaker( const QList<QueryMaker*> &queryMakers )
: QueryMaker()
, builders( queryMakers )
, m_queryDoneCount( 0 )
, m_queryDoneCountMutex()
{
foreach( QueryMaker *b, builders )
{
- connect( b, SIGNAL(queryDone()), this, SLOT(slotQueryDone()) );
+ connect( b, &QueryMaker::queryDone, this, &MetaQueryMaker::slotQueryDone );
//relay signals directly
- connect( b, SIGNAL(newResultReady(Meta::TrackList)), this, SIGNAL(newResultReady(Meta::TrackList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::ArtistList)), this, SIGNAL(newResultReady(Meta::ArtistList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::AlbumList)), this, SIGNAL(newResultReady(Meta::AlbumList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::GenreList)), this, SIGNAL(newResultReady(Meta::GenreList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::ComposerList)), this, SIGNAL(newResultReady(Meta::ComposerList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::YearList)), this, SIGNAL(newResultReady(Meta::YearList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(QStringList)), this, SIGNAL(newResultReady(QStringList)), Qt::DirectConnection );
- connect( b, SIGNAL(newResultReady(Meta::LabelList)), this, SIGNAL(newResultReady(Meta::LabelList)), Qt::DirectConnection );
+ connect( b, &QueryMaker::newTracksReady, this, &MetaQueryMaker::newTracksReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newArtistsReady, this, &MetaQueryMaker::newArtistsReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newAlbumsReady, this, &MetaQueryMaker::newAlbumsReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newGenresReady, this, &MetaQueryMaker::newGenresReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newComposersReady, this, &MetaQueryMaker::newComposersReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newYearsReady, this, &MetaQueryMaker::newYearsReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newResultReady, this, &MetaQueryMaker::newResultReady, Qt::DirectConnection );
+ connect( b, &QueryMaker::newLabelsReady, this, &MetaQueryMaker::newLabelsReady, Qt::DirectConnection );
}
}
MetaQueryMaker::~MetaQueryMaker()
{
foreach( QueryMaker *b, builders )
delete b;
}
void
MetaQueryMaker::run()
{
foreach( QueryMaker *b, builders )
b->run();
}
void
MetaQueryMaker::abortQuery()
{
foreach( QueryMaker *b, builders )
b->abortQuery();
}
QueryMaker*
MetaQueryMaker::setQueryType( QueryType type )
{
foreach( QueryMaker *qm, builders )
qm->setQueryType( type );
return this;
}
QueryMaker*
MetaQueryMaker::addReturnValue( qint64 value )
{
foreach( QueryMaker *b, builders )
b->addReturnValue( value );
return this;
}
QueryMaker*
MetaQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
foreach( QueryMaker *qm, builders )
qm->addReturnFunction( function, value );
return this;
}
/* Ok. That doesn't work. First connecting the signals directly and then
doing "orderBy" directly */
QueryMaker*
MetaQueryMaker::orderBy( qint64 value, bool descending )
{
foreach( QueryMaker *b, builders )
b->orderBy( value, descending );
return this;
}
QueryMaker*
MetaQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
foreach( QueryMaker *b, builders )
b->addFilter( value, filter, matchBegin, matchEnd );
return this;
}
QueryMaker*
MetaQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
foreach( QueryMaker *b, builders )
b->excludeFilter( value, filter, matchBegin, matchEnd );
return this;
}
QueryMaker*
MetaQueryMaker::addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
foreach( QueryMaker *b, builders )
b->addNumberFilter( value, filter, compare);
return this;
}
QueryMaker*
MetaQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
foreach( QueryMaker *b, builders )
b->excludeNumberFilter( value, filter, compare );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::TrackPtr &track )
{
foreach( QueryMaker *b, builders )
b->addMatch( track );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::ArtistPtr &artist, QueryMaker::ArtistMatchBehaviour behaviour )
{
foreach( QueryMaker *b, builders )
b->addMatch( artist, behaviour );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
foreach( QueryMaker *b, builders )
b->addMatch( album );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
foreach( QueryMaker *b, builders )
b->addMatch( genre );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
foreach( QueryMaker *b, builders )
b->addMatch( composer );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::YearPtr &year )
{
foreach( QueryMaker *b, builders )
b->addMatch( year );
return this;
}
QueryMaker*
MetaQueryMaker::addMatch( const Meta::LabelPtr &label )
{
foreach( QueryMaker *b, builders )
b->addMatch( label );
return this;
}
QueryMaker*
MetaQueryMaker::limitMaxResultSize( int size )
{
foreach( QueryMaker *b, builders )
b->limitMaxResultSize( size );
return this;
}
QueryMaker*
MetaQueryMaker::beginAnd()
{
foreach( QueryMaker *b, builders )
b->beginAnd();
return this;
}
QueryMaker*
MetaQueryMaker::beginOr()
{
foreach( QueryMaker *b, builders )
b->beginOr();
return this;
}
QueryMaker*
MetaQueryMaker::endAndOr()
{
foreach( QueryMaker *b, builders )
b->endAndOr();
return this;
}
QueryMaker*
MetaQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
foreach( QueryMaker *qm, builders )
qm->setAlbumQueryMode( mode );
return this;
}
QueryMaker*
MetaQueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
foreach( QueryMaker *qm, builders )
qm->setLabelQueryMode( mode );
return this;
}
void
MetaQueryMaker::slotQueryDone()
{
m_queryDoneCountMutex.lock();
m_queryDoneCount++;
if ( m_queryDoneCount == builders.size() )
{
//make sure we don't give control to code outside this class while holding the lock
m_queryDoneCountMutex.unlock();
emit queryDone();
}
else
m_queryDoneCountMutex.unlock();
}
diff --git a/src/core/collections/QueryMaker.cpp b/src/core/collections/QueryMaker.cpp
index 5c5b9bd894..1a8cb1b179 100644
--- a/src/core/collections/QueryMaker.cpp
+++ b/src/core/collections/QueryMaker.cpp
@@ -1,69 +1,69 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core/collections/QueryMaker.h"
using namespace Collections;
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
QueryMaker::QueryMaker() : QObject()
{
}
QueryMaker::~QueryMaker()
{
}
QueryMaker*
QueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
Q_UNUSED( mode )
return this;
}
QueryMaker*
QueryMaker::setLabelQueryMode( LabelQueryMode mode )
{
Q_UNUSED( mode )
return this;
}
int QueryMaker::validFilterMask()
{
return AllFilters;
}
QueryMaker*
QueryMaker::setAutoDelete( bool autoDelete )
{
if( autoDelete )
- connect( this, SIGNAL(queryDone()), this, SLOT(deleteLater()) );
+ connect( this, &QueryMaker::queryDone, this, &QueryMaker::deleteLater );
else
- disconnect( this, SIGNAL(queryDone()), this, SLOT(deleteLater()) );
+ disconnect( this, &QueryMaker::queryDone, this, &QueryMaker::deleteLater );
return this;
}
QueryMaker*
QueryMaker::addMatch( const Meta::LabelPtr &label )
{
debug() << metaObject()->className() << " does not support label queries, ignoring label " << label->name();
return this;
}
diff --git a/src/core/collections/QueryMaker.h b/src/core/collections/QueryMaker.h
index 9abb05fd24..b90a77b1a5 100644
--- a/src/core/collections/QueryMaker.h
+++ b/src/core/collections/QueryMaker.h
@@ -1,245 +1,245 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_COLLECTION_QUERYMAKER_H
#define AMAROK_COLLECTION_QUERYMAKER_H
#include "core/amarokcore_export.h"
#include "core/meta/forward_declarations.h"
#include "core/meta/support/MetaConstants.h"
#include <QObject>
#include <QStringList>
#include <QtGlobal>
namespace Collections {
class AMAROK_CORE_EXPORT QueryMaker : public QObject
{
Q_OBJECT
public:
enum AlbumQueryMode {
AllAlbums,
OnlyCompilations ,
OnlyNormalAlbums
};
enum LabelQueryMode {
NoConstraint,
OnlyWithLabels,
OnlyWithoutLabels
};
enum ArtistMatchBehaviour {
TrackArtists,
AlbumArtists,
AlbumOrTrackArtists
};
/**
* Filters that the QueryMaker accepts for searching.
* not all implementations will accept all filter levels, so make it possible to
* specify which ones make sense for a given qm. Add to this as needed
*/
enum ValidFilters {
TitleFilter = 1,
AlbumFilter = 2,
ArtistFilter = 4,
AlbumArtistFilter = 8,
GenreFilter = 16,
ComposerFilter = 32,
YearFilter = 64,
UrlFilter = 128,
AllFilters = 65535
};
enum ReturnFunction {
Count,
Sum,
Max,
Min
};
enum NumberComparison {
Equals,
GreaterThan,
LessThan
};
enum QueryType {
None, // Set to faciliate using this in subclasses
Track,
Artist,
Album,
AlbumArtist,
Genre,
Composer,
Year,
Custom,
Label
};
QueryMaker();
virtual ~QueryMaker();
/**
* starts the query. This method returns immediately. All processing is done in one or more
* separate worker thread(s). One of the newResultReady signals will be emitted at least once,
* followed by the queryDone() signal exactly once.
*/
virtual void run() = 0;
/**
* aborts a running query. Calling this method aborts a running query as soon as possible.
* This method returns immediately. No signals will be emitted after calling this method.
* This method has no effect if no query is running.
*/
virtual void abortQuery() = 0;
/**
* Sets the type of objects the querymaker will query for. These are mutually
* exclusive. The results of the query will be returned as objects of the
* appropriate type, therefore it is necessary to connect the client to the
* newResultReady( Meta::Type ) signal
*
* if you set QueryType custom, this starts a custom query. Unlike other query types, you have to set up the return
* values yourself using addReturnValue( qint64 ) and addReturnFunction(). The results will
* be returned as a QStringList. Threfore you have to connect to the
* newResultReady( QStringList ) signal to receive the results.
* @return this
*/
virtual QueryMaker* setQueryType( QueryType type ) = 0;
/**
* only works after starting a custom query with setQueryType( Custom )
* Use this to inform the query maker you are looking for results of value @param value.
* @return this
*/
virtual QueryMaker* addReturnValue( qint64 value ) = 0;
/**
* Returns the output of the function specified by function.
* Only works after starting a custom query
* @return this
*/
virtual QueryMaker* addReturnFunction( ReturnFunction function, qint64 value ) = 0;
/**
* Return results sorted by @p value.
* @return this
*/
virtual QueryMaker* orderBy( qint64 value, bool descending = false ) = 0;
virtual QueryMaker* addMatch( const Meta::TrackPtr &track ) = 0;
/**
* Match given artist. Depending on @param behaviour matches:
* track artist if TrackArtists is given,
* album artist if AlbumArtists is given,
* any of track or album artist if AlbumOrTrackArtists is given.
*
* By default matches only track artist.
*/
virtual QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists ) = 0;
virtual QueryMaker* addMatch( const Meta::AlbumPtr &album ) = 0;
virtual QueryMaker* addMatch( const Meta::ComposerPtr &composer ) = 0;
virtual QueryMaker* addMatch( const Meta::GenrePtr &genre ) = 0;
virtual QueryMaker* addMatch( const Meta::YearPtr &year ) = 0;
virtual QueryMaker* addMatch( const Meta::LabelPtr &label );
/**
* Add a filter of type @p value and value @p filter. The querymaker applies this to all queries.
* @param text the text to match
* @param matchBegin If set then wildcard match the beginning of @p text (*text)
* @param matchEnd If set then wildcard match the end of @p text (text*)
* @return this
*/
virtual QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin = false, bool matchEnd = false ) = 0;
/**
* Exclude filter of type @p value and value @p filter. The querymaker applies this to all queries.
* @param text the text to match
* @param matchBegin If set then wildcard match the beginning of @p text (*text)
* @param matchEnd If set then wildcard match the end of @p text (text*)
* @return this
*/
virtual QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin = false, bool matchEnd = false ) = 0;
virtual QueryMaker* addNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) = 0;
virtual QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) = 0;
/**
* limit the maximum number of items in a result. the result will have [0..@p size ] items. When this function
* is not used, the result size is unbounded. Note: the maximum size applies to each result individually, so if
* the newResultReady signal is emitted multiple times, each result may have up to @p size items.
*/
virtual QueryMaker* limitMaxResultSize( int size ) = 0;
/**
* select the mode for querying albums. If this method is not called,
* QueryMaker defaults to AlbumQueryMode::AllAlbums.
*/
virtual QueryMaker* setAlbumQueryMode( AlbumQueryMode mode );
/**
* Sets the label query mode. This method restricts a query to tracks
* that have labels assigned to them, no labels assigned to them, or no constraint.
* The default is no constraint.
* @param mode The LabelQueryMode that will be used by the query.
* @see LabelQueryMode
*/
virtual QueryMaker* setLabelQueryMode( LabelQueryMode mode );
virtual QueryMaker* beginAnd() = 0;
virtual QueryMaker* beginOr() = 0;
virtual QueryMaker* endAndOr() = 0;
/**
* Choose whether the query maker instance should delete itself after the query.
* By passing true the query maker instance will delete itself after emitting queryDone().
* Otherwise it is the responsibility of the owner (the code which called ::queryMaker() usually) to delete the instance
* when it is not needed anymore.
*
* Defaults to false, i.e. the querymaker instance will not delete itself.
*/
QueryMaker* setAutoDelete( bool autoDelete );
virtual int validFilterMask();
Q_SIGNALS:
/**
* newResultReady will be emitted every time new results from the query maker are received.
* This signal can be emitted zero times (in case of no results) one (the usual case) or multiple times
* (e.g. in case when the result is received in several batches).
* The results will be terminated by a queryDone signal.
*/
- void newResultReady( Meta::TrackList );
- void newResultReady( Meta::ArtistList );
- void newResultReady( Meta::AlbumList );
- void newResultReady( Meta::GenreList );
- void newResultReady( Meta::ComposerList );
- void newResultReady( Meta::YearList );
void newResultReady( QStringList );
- void newResultReady( Meta::LabelList );
- void newResultReady( Meta::DataList );
+ void newTracksReady( Meta::TrackList );
+ void newArtistsReady( Meta::ArtistList );
+ void newAlbumsReady( Meta::AlbumList );
+ void newGenresReady( Meta::GenreList );
+ void newComposersReady( Meta::ComposerList );
+ void newYearsReady( Meta::YearList );
+ void newLabelsReady( Meta::LabelList );
+ void newDataReady( Meta::DataList );
/**
* This signal is emitted after all the results have been submitted via zero or more newResultReady signals.
*/
void queryDone();
};
} //namespace Collections
Q_DECLARE_METATYPE( Collections::QueryMaker* )
#endif /* AMAROK_COLLECTION_QUERYMAKER_H */
diff --git a/src/core/collections/support/TrackForUrlWorker.cpp b/src/core/collections/support/TrackForUrlWorker.cpp
index 6a42d8fc1a..f6f608b103 100644
--- a/src/core/collections/support/TrackForUrlWorker.cpp
+++ b/src/core/collections/support/TrackForUrlWorker.cpp
@@ -1,45 +1,45 @@
/****************************************************************************************
* Copyright (c) 2009 Casey Link <unnamedrambler@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TrackForUrlWorker.h"
#include "core/meta/Meta.h"
Amarok::TrackForUrlWorker::TrackForUrlWorker( const QUrl &url )
: QObject()
, ThreadWeaver::Job()
, m_url( url )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(completeJob()) );
+ connect( this, &TrackForUrlWorker::done, &TrackForUrlWorker::completeJob );
}
Amarok::TrackForUrlWorker::TrackForUrlWorker( const QString &url )
: QObject()
, ThreadWeaver::Job()
, m_url( QUrl( url ) )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(completeJob()) );
+ connect( this, &TrackForUrlWorker::done, &TrackForUrlWorker::completeJob );
}
Amarok::TrackForUrlWorker::~TrackForUrlWorker()
{}
void
Amarok::TrackForUrlWorker::completeJob()
{
emit finishedLookup( m_track );
- deleteLater();
+// deleteLater();
}
diff --git a/src/core/podcasts/PodcastImageFetcher.cpp b/src/core/podcasts/PodcastImageFetcher.cpp
index bc4e1c49f1..e3c780f746 100644
--- a/src/core/podcasts/PodcastImageFetcher.cpp
+++ b/src/core/podcasts/PodcastImageFetcher.cpp
@@ -1,152 +1,152 @@
/****************************************************************************************
* Copyright (c) 2009 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core/podcasts/PodcastImageFetcher.h"
#include "core/support/Debug.h"
#include <KIO/Job>
#include <KMD5>
#include <Solid/Networking>
PodcastImageFetcher::PodcastImageFetcher()
{
}
void
PodcastImageFetcher::addChannel( Podcasts::PodcastChannelPtr channel )
{
DEBUG_BLOCK
if( channel->imageUrl().isEmpty() )
{
debug() << channel->title() << " does not have an imageUrl";
return;
}
if( hasCachedImage( channel ) )
{
debug() << "using cached image for " << channel->title();
QString imagePath = cachedImagePath( channel ).toLocalFile();
QImage image( imagePath );
if( image.isNull() )
error() << "could not load pixmap from " << imagePath;
else
channel->setImage( image );
return;
}
debug() << "Adding " << channel->title() << " to fetch queue";
m_channels.append( channel );
}
void
PodcastImageFetcher::addEpisode( Podcasts::PodcastEpisodePtr episode )
{
Q_UNUSED( episode );
}
QUrl
PodcastImageFetcher::cachedImagePath( Podcasts::PodcastChannelPtr channel )
{
return cachedImagePath( channel.data() );
}
QUrl
PodcastImageFetcher::cachedImagePath( Podcasts::PodcastChannel *channel )
{
QUrl imagePath = channel->saveLocation();
if( imagePath.isEmpty() )
imagePath = QUrl::fromLocalFile(Amarok::saveLocation( "podcasts" ));
KMD5 md5( channel->url().url().toLocal8Bit() );
QString extension = Amarok::extension( channel->imageUrl().fileName() );
imagePath = imagePath.adjusted(QUrl::StripTrailingSlash);
imagePath.setPath(imagePath.path() + '/' + ( md5.hexDigest() + '.' + extension ));
return QUrl::fromLocalFile(imagePath.toLocalFile());
}
bool
PodcastImageFetcher::hasCachedImage( Podcasts::PodcastChannelPtr channel )
{
DEBUG_BLOCK
return QFile( PodcastImageFetcher::cachedImagePath(
Podcasts::PodcastChannelPtr::dynamicCast( channel ) ).toLocalFile() ).exists();
}
void
PodcastImageFetcher::run()
{
if( m_channels.isEmpty() && m_episodes.isEmpty()
&& m_jobChannelMap.isEmpty() && m_jobEpisodeMap.isEmpty() )
{
//nothing to do
emit( done( this ) );
return;
}
if( Solid::Networking::status() != Solid::Networking::Connected
&& Solid::Networking::status() != Solid::Networking::Unknown )
{
debug() << "Solid reports we are not online, canceling podcast image download";
emit( done( this ) );
//TODO: schedule another run after Solid reports we are online again
return;
}
foreach( Podcasts::PodcastChannelPtr channel, m_channels )
{
QUrl cachedPath = cachedImagePath( channel );
KIO::mkdir( QUrl::fromLocalFile(cachedPath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()) );
KIO::FileCopyJob *job = KIO::file_copy( channel->imageUrl(), cachedPath,
-1, KIO::HideProgressInfo | KIO::Overwrite );
//remove channel from the todo list
m_channels.removeAll( channel );
m_jobChannelMap.insert( job, channel );
- connect( job, SIGNAL(finished(KJob*)), SLOT(slotDownloadFinished(KJob*)) );
+ connect( job, &KIO::FileCopyJob::finished, this, &PodcastImageFetcher::slotDownloadFinished );
}
//TODO: episodes
}
void
PodcastImageFetcher::slotDownloadFinished( KJob *job )
{
DEBUG_BLOCK
//QMap::take() also removes the entry from the map.
Podcasts::PodcastChannelPtr channel = m_jobChannelMap.take( job );
if( channel.isNull() )
{
error() << "got null PodcastChannelPtr " << __FILE__ << ":" << __LINE__;
return;
}
if( job->error() )
{
error() << "downloading podcast image " << job->errorString();
}
else
{
QString imagePath = cachedImagePath( channel ).toLocalFile();
QImage image( imagePath );
if( image.isNull() )
error() << "could not load pixmap from " << imagePath;
else
channel->setImage( image );
}
//call run again to start the next batch of transfers.
run();
}
diff --git a/src/core/podcasts/PodcastImageFetcher.h b/src/core/podcasts/PodcastImageFetcher.h
index 3e5ac86347..49c271789b 100644
--- a/src/core/podcasts/PodcastImageFetcher.h
+++ b/src/core/podcasts/PodcastImageFetcher.h
@@ -1,55 +1,55 @@
/****************************************************************************************
* Copyright (c) 2009 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef PODCASTIMAGEFETCHER_H
#define PODCASTIMAGEFETCHER_H
#include "core/podcasts/PodcastMeta.h"
#include <KJob>
class AMAROK_CORE_EXPORT PodcastImageFetcher : public QObject
{
Q_OBJECT
public:
PodcastImageFetcher();
void addChannel( Podcasts::PodcastChannelPtr channel );
void addEpisode( Podcasts::PodcastEpisodePtr episode );
void run();
static QUrl cachedImagePath( Podcasts::PodcastChannelPtr channel );
static QUrl cachedImagePath( Podcasts::PodcastChannel *channel );
Q_SIGNALS:
- void imageReady( Podcasts::PodcastChannelPtr channel, QImage image );
- void imageReady( Podcasts::PodcastEpisodePtr episode, QImage image );
+ void channelImageReady( Podcasts::PodcastChannelPtr channel, QImage image );
+ void episodeImageReady( Podcasts::PodcastEpisodePtr episode, QImage image );
void done( PodcastImageFetcher * );
private Q_SLOTS:
void slotDownloadFinished( KJob *job );
private:
static bool hasCachedImage( Podcasts::PodcastChannelPtr channel );
Podcasts::PodcastChannelList m_channels;
Podcasts::PodcastEpisodeList m_episodes;
QMap<KJob *, Podcasts::PodcastChannelPtr> m_jobChannelMap;
QMap<KJob *, Podcasts::PodcastEpisodePtr> m_jobEpisodeMap;
};
#endif // PODCASTIMAGEFETCHER_H
diff --git a/src/core/podcasts/PodcastReader.cpp b/src/core/podcasts/PodcastReader.cpp
index 51b9f494cb..a7f6601616 100644
--- a/src/core/podcasts/PodcastReader.cpp
+++ b/src/core/podcasts/PodcastReader.cpp
@@ -1,1693 +1,1691 @@
/****************************************************************************************
* Copyright (c) 2007 Bart Cerneels <bart.cerneels@kde.org> *
* 2009 Mathias Panzenböck <grosser.meister.morti@gmx.net> *
* 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core/podcasts/PodcastReader.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/meta/support/MetaUtility.h"
#include <kio/job.h>
#include <QUrl>
#include <KDateTime>
#include <QTextDocument>
#include <QDate>
#include <QSet>
using namespace Podcasts;
#define ITUNES_NS "http://www.itunes.com/dtds/podcast-1.0.dtd"
#define RDF_NS "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
#define RSS10_NS "http://purl.org/rss/1.0/"
#define RSS20_NS ""
#define ATOM_NS "http://www.w3.org/2005/Atom"
#define ENC_NS "http://purl.oclc.org/net/rss_2.0/enc#"
#define CONTENT_NS "http://purl.org/rss/1.0/modules/content"
#define DC_NS "http://purl.org/dc/elements/1.1/"
// regular expressions for linkification:
#define RE_USER "[-+_%\\.\\w]+"
#define RE_PASSWD RE_USER
#define RE_DOMAIN "[-a-zA-Z0-9]+(?:\\.[-a-zA-Z0-9]+)*"
#define RE_PROT "[a-zA-Z]+://"
#define RE_URL RE_PROT "(?:" RE_USER "(?::" RE_PASSWD ")?@)?" RE_DOMAIN \
"(?::\\d+)?(?:/[-\\w\\?&=%+.,;:_#~/!@]*)?"
#define RE_MAIL RE_USER "@" RE_DOMAIN
const PodcastReader::StaticData PodcastReader::sd;
PodcastReader::PodcastReader( PodcastProvider *podcastProvider, QObject *parent )
: QObject( parent )
, m_xmlReader()
, m_podcastProvider( podcastProvider )
, m_transferJob( )
, m_current( 0 )
, m_actionStack()
, m_contentType( TextContent )
, m_buffer()
{}
void
PodcastReader::Action::begin( PodcastReader *podcastReader ) const
{
if( m_begin )
(( *podcastReader ).*m_begin )();
}
void
PodcastReader::Action::end( PodcastReader *podcastReader ) const
{
if( m_end )
(( *podcastReader ).*m_end )();
}
void
PodcastReader::Action::characters( PodcastReader *podcastReader ) const
{
if( m_characters )
(( *podcastReader ).*m_characters )();
}
// initialization of the feed parser automata:
PodcastReader::StaticData::StaticData()
: removeScripts( "<script[^<]*</script>|<script[^>]*>", Qt::CaseInsensitive )
, mightBeHtml( "<\\?xml[^>]*\\?>|<br[^>]*>|<p[^>]*>|&lt;|&gt;|&amp;|&quot;|"
"<([-:\\w\\d]+)[^>]*(/>|>.*</\\1>)|<hr[>]*>|&#\\d+;|&#x[a-fA-F\\d]+;", Qt::CaseInsensitive )
, linkify( "\\b(" RE_URL ")|\\b(" RE_MAIL ")|(\n)" )
, startAction( rootMap )
, docAction(
docMap,
0,
&PodcastReader::endDocument )
, xmlAction(
xmlMap,
&PodcastReader::beginXml,
&PodcastReader::endXml,
&PodcastReader::readEscapedCharacters )
, skipAction( skipMap )
, noContentAction(
noContentMap,
&PodcastReader::beginNoElement,
0,
&PodcastReader::readNoCharacters )
, rdfAction(
rdfMap,
&PodcastReader::beginRdf )
, rssAction(
rssMap,
&PodcastReader::beginRss )
, feedAction(
feedMap,
&PodcastReader::beginFeed )
, htmlAction(
skipMap,
&PodcastReader::beginHtml )
, unknownFeedTypeAction(
skipMap,
&PodcastReader::beginUnknownFeedType )
// RSS 1.0+2.0
, rss10ChannelAction(
rss10ChannelMap,
&PodcastReader::beginChannel )
, rss20ChannelAction(
rss20ChannelMap,
&PodcastReader::beginChannel )
, titleAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endTitle,
&PodcastReader::readCharacters )
, subtitleAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endSubtitle,
&PodcastReader::readCharacters )
, descriptionAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endDescription,
&PodcastReader::readCharacters )
, encodedAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endEncoded,
&PodcastReader::readCharacters )
, bodyAction(
xmlMap,
&PodcastReader::beginText,
&PodcastReader::endBody,
&PodcastReader::readEscapedCharacters )
, linkAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endLink,
&PodcastReader::readCharacters )
, imageAction( imageMap,
&PodcastReader::beginImage )
, itemAction(
itemMap,
&PodcastReader::beginItem,
&PodcastReader::endItem )
, urlAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endImageUrl,
&PodcastReader::readCharacters )
, authorAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endAuthor,
&PodcastReader::readCharacters )
, creatorAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endCreator,
&PodcastReader::readCharacters )
, enclosureAction(
noContentMap,
&PodcastReader::beginEnclosure )
, guidAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endGuid,
&PodcastReader::readCharacters )
, pubDateAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endPubDate,
&PodcastReader::readCharacters )
, keywordsAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endKeywords,
&PodcastReader::readCharacters )
, newFeedUrlAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endNewFeedUrl,
&PodcastReader::readCharacters )
// Atom
, atomLogoAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endImageUrl,
&PodcastReader::readCharacters )
, atomIconAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endAtomIcon,
&PodcastReader::readCharacters )
, atomEntryAction(
atomEntryMap,
&PodcastReader::beginItem,
&PodcastReader::endItem )
, atomTitleAction(
atomTextMap,
&PodcastReader::beginAtomText,
&PodcastReader::endAtomTitle,
&PodcastReader::readAtomTextCharacters )
, atomSubtitleAction(
atomTextMap,
&PodcastReader::beginAtomText,
&PodcastReader::endAtomSubtitle,
&PodcastReader::readAtomTextCharacters )
, atomAuthorAction(
atomAuthorMap )
, atomFeedLinkAction(
noContentMap,
&PodcastReader::beginAtomFeedLink,
0,
&PodcastReader::readNoCharacters )
, atomEntryLinkAction(
noContentMap,
&PodcastReader::beginAtomEntryLink,
0,
&PodcastReader::readNoCharacters )
, atomIdAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endGuid,
&PodcastReader::readCharacters )
, atomPublishedAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endAtomPublished,
&PodcastReader::readCharacters )
, atomUpdatedAction(
textMap,
&PodcastReader::beginText,
&PodcastReader::endAtomUpdated,
&PodcastReader::readCharacters )
, atomSummaryAction(
atomTextMap,
&PodcastReader::beginAtomText,
&PodcastReader::endAtomSummary,
&PodcastReader::readAtomTextCharacters )
, atomContentAction(
atomTextMap,
&PodcastReader::beginAtomText,
&PodcastReader::endAtomContent,
&PodcastReader::readAtomTextCharacters )
, atomTextAction(
atomTextMap,
&PodcastReader::beginAtomTextChild,
&PodcastReader::endAtomTextChild,
&PodcastReader::readAtomTextCharacters )
{
// known elements:
knownElements[ "rss" ] = Rss;
knownElements[ "RDF" ] = Rdf;
knownElements[ "feed" ] = Feed;
knownElements[ "channel" ] = Channel;
knownElements[ "item" ] = Item;
knownElements[ "image" ] = Image;
knownElements[ "link" ] = Link;
knownElements[ "url" ] = Url;
knownElements[ "title" ] = Title;
knownElements[ "author" ] = Author;
knownElements[ "enclosure" ] = EnclosureElement;
knownElements[ "guid" ] = Guid;
knownElements[ "pubDate" ] = PubDate;
knownElements[ "description" ] = Description;
knownElements[ "summary" ] = Summary;
knownElements[ "body" ] = Body;
knownElements[ "entry" ] = Entry;
knownElements[ "content" ] = Content;
knownElements[ "name" ] = Name;
knownElements[ "id" ] = Id;
knownElements[ "subtitle" ] = Subtitle;
knownElements[ "updated" ] = Updated;
knownElements[ "published" ] = Published;
knownElements[ "logo" ] = Logo;
knownElements[ "icon" ] = Icon;
knownElements[ "encoded" ] = Encoded;
knownElements[ "creator" ] = Creator;
knownElements[ "keywords" ] = Keywords;
knownElements[ "new-feed-url" ] = NewFeedUrl;
knownElements[ "html" ] = Html;
knownElements[ "HTML" ] = Html;
// before start document/after end document
rootMap.insert( Document, &docAction );
// parse document
docMap.insert( Rss, &rssAction );
docMap.insert( Html, &htmlAction );
docMap.insert( Rdf, &rdfAction );
docMap.insert( Feed, &feedAction );
docMap.insert( Any, &unknownFeedTypeAction );
// parse <rss> "RSS 2.0"
rssMap.insert( Channel, &rss20ChannelAction );
// parse <RDF> "RSS 1.0"
rdfMap.insert( Channel, &rss10ChannelAction );
rdfMap.insert( Item, &itemAction );
// parse <channel> "RSS 2.0"
rss20ChannelMap.insert( Title, &titleAction );
rss20ChannelMap.insert( ItunesSubtitle, &subtitleAction );
rss20ChannelMap.insert( ItunesAuthor, &authorAction );
rss20ChannelMap.insert( Creator, &creatorAction );
rss20ChannelMap.insert( Description, &descriptionAction );
rss20ChannelMap.insert( Encoded, &encodedAction );
rss20ChannelMap.insert( ItunesSummary, &descriptionAction );
rss20ChannelMap.insert( Body, &bodyAction );
rss20ChannelMap.insert( Link, &linkAction );
rss20ChannelMap.insert( Image, &imageAction );
rss20ChannelMap.insert( ItunesKeywords, &keywordsAction );
rss20ChannelMap.insert( NewFeedUrl, &newFeedUrlAction );
rss20ChannelMap.insert( Item, &itemAction );
// parse <channel> "RSS 1.0"
rss10ChannelMap.insert( Title, &titleAction );
rss10ChannelMap.insert( ItunesSubtitle, &subtitleAction );
rss10ChannelMap.insert( ItunesAuthor, &authorAction );
rss10ChannelMap.insert( Creator, &creatorAction );
rss10ChannelMap.insert( Description, &descriptionAction );
rss10ChannelMap.insert( Encoded, &encodedAction );
rss10ChannelMap.insert( ItunesSummary, &descriptionAction );
rss10ChannelMap.insert( Body, &bodyAction );
rss10ChannelMap.insert( Link, &linkAction );
rss10ChannelMap.insert( Image, &imageAction );
rss10ChannelMap.insert( ItunesKeywords, &keywordsAction );
rss10ChannelMap.insert( NewFeedUrl, &newFeedUrlAction );
// parse <image>
imageMap.insert( Title, &skipAction );
imageMap.insert( Link, &skipAction );
imageMap.insert( Url, &urlAction );
// parse <item>
itemMap.insert( Title, &titleAction );
itemMap.insert( ItunesSubtitle, &subtitleAction );
itemMap.insert( Author, &authorAction );
itemMap.insert( ItunesAuthor, &authorAction );
itemMap.insert( Creator, &creatorAction );
itemMap.insert( Description, &descriptionAction );
itemMap.insert( Encoded, &encodedAction );
itemMap.insert( ItunesSummary, &descriptionAction );
itemMap.insert( Body, &bodyAction );
itemMap.insert( EnclosureElement, &enclosureAction );
itemMap.insert( Guid, &guidAction );
itemMap.insert( PubDate, &pubDateAction );
itemMap.insert( ItunesKeywords, &keywordsAction );
// TODO: move the link field from PodcastChannel to PodcastMetaCommon
// itemMap.insert( Link, &linkAction );
// parse <feed> "Atom"
feedMap.insert( Title, &atomTitleAction );
feedMap.insert( Subtitle, &atomSubtitleAction );
feedMap.insert( Icon, &atomIconAction );
feedMap.insert( Logo, &atomLogoAction );
feedMap.insert( Author, &atomAuthorAction );
feedMap.insert( Link, &atomFeedLinkAction );
feedMap.insert( Entry, &atomEntryAction );
// parse <entry> "Atom"
atomEntryMap.insert( Title, &atomTitleAction );
atomEntryMap.insert( Subtitle, &atomSubtitleAction );
atomEntryMap.insert( Author, &atomAuthorAction );
atomEntryMap.insert( Id, &atomIdAction );
atomEntryMap.insert( Published, &atomPublishedAction );
atomEntryMap.insert( Updated, &atomUpdatedAction );
atomEntryMap.insert( Summary, &atomSummaryAction );
atomEntryMap.insert( Link, &atomEntryLinkAction );
atomEntryMap.insert( SupportedContent, &atomContentAction );
// parse <author> "Atom"
atomAuthorMap.insert( Name, &authorAction );
// parse atom text
atomTextMap.insert( Any, &atomTextAction );
// parse arbitrary xml
xmlMap.insert( Any, &xmlAction );
// skip elements
skipMap.insert( Any, &skipAction );
}
PodcastReader::~PodcastReader()
{
DEBUG_BLOCK
}
bool
PodcastReader::mightBeHtml( const QString& text ) //Static
{
return sd.mightBeHtml.indexIn( text ) != -1;
}
bool PodcastReader::read( QIODevice *device )
{
DEBUG_BLOCK
m_xmlReader.setDevice( device );
return read();
}
bool
PodcastReader::read( const QUrl &url )
{
DEBUG_BLOCK
m_url = url;
m_transferJob = KIO::get( m_url, KIO::Reload, KIO::HideProgressInfo );
- connect( m_transferJob, SIGNAL(data(KIO::Job*,QByteArray)),
- SLOT(slotAddData(KIO::Job*,QByteArray)) );
+ connect( m_transferJob, &KIO::TransferJob::data,
+ this, &PodcastReader::slotAddData );
- connect( m_transferJob, SIGNAL(result(KJob*)),
- SLOT(downloadResult(KJob*)) );
+ connect( m_transferJob, &KIO::TransferJob::result,
+ this, &PodcastReader::downloadResult );
- connect( m_transferJob, SIGNAL(redirection(KIO::Job*,QUrl)),
- SLOT(slotRedirection(KIO::Job*,QUrl)) );
+ connect( m_transferJob, &KIO::TransferJob::redirection,
+ this, &PodcastReader::slotRedirection );
- connect( m_transferJob, SIGNAL( permanentRedirection( KIO::Job *,
- const QUrl &, const QUrl & ) ),
- SLOT( slotPermanentRedirection( KIO::Job *, const QUrl &,
- const QUrl & ) ) );
+ connect( m_transferJob, &KIO::TransferJob::permanentRedirection,
+ this, &PodcastReader::slotPermanentRedirection );
QString description = i18n( "Importing podcast channel from %1", url.url() );
if( m_channel )
{
description = m_channel->title().isEmpty()
? i18n( "Updating podcast channel" )
: i18n( "Updating \"%1\"", m_channel->title() );
}
emit statusBarNewProgressOperation( m_transferJob, description, this );
// parse data
return read();
}
void
PodcastReader::slotAbort()
{
DEBUG_BLOCK
}
bool
PodcastReader::update( PodcastChannelPtr channel )
{
DEBUG_BLOCK
m_channel = channel;
return read( m_channel->url() );
}
void
PodcastReader::slotAddData( KIO::Job *job, const QByteArray &data )
{
DEBUG_BLOCK
Q_UNUSED( job )
m_xmlReader.addData( data );
// parse more data
continueRead();
}
void
PodcastReader::downloadResult( KJob * job )
{
DEBUG_BLOCK
// parse more data
continueRead();
KIO::TransferJob *transferJob = dynamic_cast<KIO::TransferJob *>( job );
if( transferJob && transferJob->isErrorPage() )
{
QString errorMessage =
i18n( "Importing podcast from %1 failed with error:\n", m_url.url() );
if( m_channel )
{
errorMessage = m_channel->title().isEmpty()
? i18n( "Updating podcast from %1 failed with error:\n", m_url.url() )
: i18n( "Updating \"%1\" failed with error:\n", m_channel->title() );
}
errorMessage = errorMessage.append( job->errorString() );
emit statusBarSorryMessage( errorMessage );
}
else if( job->error() )
{
QString errorMessage =
i18n( "Importing podcast from %1 failed with error:\n", m_url.url() );
if( m_channel )
{
errorMessage = m_channel->title().isEmpty()
? i18n( "Updating podcast from %1 failed with error:\n", m_url.url() )
: i18n( "Updating \"%1\" failed with error:\n", m_channel->title() );
}
errorMessage = errorMessage.append( job->errorString() );
emit statusBarSorryMessage( errorMessage );
}
m_transferJob = 0;
}
PodcastReader::ElementType
PodcastReader::elementType() const
{
if( m_xmlReader.isEndDocument() || m_xmlReader.isStartDocument() )
return Document;
if( m_xmlReader.isCDATA() || m_xmlReader.isCharacters() )
return CharacterData;
ElementType elementType = sd.knownElements[ m_xmlReader.name().toString()];
// This is a bit hacky because my automata does not support conditions.
// Therefore I put the decision logic in here and declare some pseudo elements.
// I don't think it is worth it to extend the automata to support such conditions.
switch( elementType )
{
case Summary:
if( m_xmlReader.namespaceUri() == ITUNES_NS )
{
elementType = ItunesSummary;
}
break;
case Subtitle:
if( m_xmlReader.namespaceUri() == ITUNES_NS )
{
elementType = ItunesSubtitle;
}
break;
case Author:
if( m_xmlReader.namespaceUri() == ITUNES_NS )
{
elementType = ItunesAuthor;
}
break;
case Keywords:
if( m_xmlReader.namespaceUri() == ITUNES_NS )
{
elementType = ItunesKeywords;
}
break;
case Content:
if( m_xmlReader.namespaceUri() == ATOM_NS &&
// ignore atom:content elements that do not
// have content but only refer to some url:
!hasAttribute( ATOM_NS, "src" ) )
{
// Atom supports arbitrary Base64 encoded content.
// Because we can only something with text/html/xhtml I ignore
// anything else.
// See:
// http://tools.ietf.org/html/rfc4287#section-4.1.3
if( hasAttribute( ATOM_NS, "type" ) )
{
QStringRef type( attribute( ATOM_NS, "type" ) );
if( type == "text" || type == "html" || type == "xhtml" )
{
elementType = SupportedContent;
}
}
else
{
elementType = SupportedContent;
}
}
break;
default:
break;
}
return elementType;
}
bool
PodcastReader::read()
{
DEBUG_BLOCK
m_current = 0;
m_item = 0;
m_contentType = TextContent;
m_buffer.clear();
m_actionStack.clear();
m_actionStack.push( &( PodcastReader::sd.startAction ) );
m_xmlReader.setNamespaceProcessing( true );
return continueRead();
}
bool
PodcastReader::continueRead()
{
// this is some kind of pushdown automata
// with this it should be possible to parse feeds in parallel
// woithout using threads
DEBUG_BLOCK
while( !m_xmlReader.atEnd() && m_xmlReader.error() != QXmlStreamReader::CustomError )
{
QXmlStreamReader::TokenType token = m_xmlReader.readNext();
if( m_xmlReader.error() == QXmlStreamReader::PrematureEndOfDocumentError && m_transferJob )
{
return true;
}
if( m_xmlReader.hasError() )
{
emit finished( this );
return false;
}
if( m_actionStack.isEmpty() )
{
debug() << "expected element on stack!";
return false;
}
const Action* action = m_actionStack.top();
const Action* subAction = 0;
switch( token )
{
case QXmlStreamReader::Invalid:
return false;
case QXmlStreamReader::StartDocument:
case QXmlStreamReader::StartElement:
subAction = action->actionMap()[ elementType()];
if( !subAction )
subAction = action->actionMap()[ Any ];
if( !subAction )
subAction = &( PodcastReader::sd.skipAction );
m_actionStack.push( subAction );
subAction->begin( this );
break;
case QXmlStreamReader::EndDocument:
case QXmlStreamReader::EndElement:
action->end( this );
if( m_actionStack.pop() != action )
{
debug() << "popped other element than expected!";
}
break;
case QXmlStreamReader::Characters:
if( !m_xmlReader.isWhitespace() || m_xmlReader.isCDATA() )
{
action->characters( this );
}
// ignoreable whitespaces
case QXmlStreamReader::Comment:
case QXmlStreamReader::EntityReference:
case QXmlStreamReader::ProcessingInstruction:
case QXmlStreamReader::DTD:
case QXmlStreamReader::NoToken:
// ignore
break;
}
}
return !m_xmlReader.hasError();
}
void
PodcastReader::stopWithError( const QString &message )
{
m_xmlReader.raiseError( message );
if( m_transferJob )
{
m_transferJob->kill(KJob::EmitResult);
m_transferJob = 0;
}
emit finished( this );
}
void
PodcastReader::beginText()
{
m_buffer.clear();
}
void
PodcastReader::endTitle()
{
m_current->setTitle( m_buffer.trimmed() );
}
void
PodcastReader::endSubtitle()
{
m_current->setSubtitle( m_buffer.trimmed() );
}
QString
PodcastReader::atomTextAsText()
{
switch( m_contentType )
{
case HtmlContent:
case XHtmlContent:
// TODO: strip tags (there should not be any non-xml entities here)
return unescape( m_buffer );
case TextContent:
default:
return m_buffer;
}
}
QString
PodcastReader::atomTextAsHtml()
{
switch( m_contentType )
{
case HtmlContent:
case XHtmlContent:
// strip <script> elements
// This will work because there aren't <![CDATA[ ]]> sections
// in m_buffer, because we have (re)escape the code manually.
// XXX: But it does not remove event handlers like onclick="..."
// and JavaScript links like href="javascript:..."
return m_buffer.remove( sd.removeScripts );
case TextContent:
default:
return textToHtml( m_buffer );
}
}
QString
PodcastReader::unescape( const QString &text )
{
// TODO: resolve predefined html entities
QString buf;
for ( int i = 0; i < text.size(); ++ i )
{
QChar c( text[ i ] );
if( c == '&' )
{
int endIndex = text.indexOf( ';', i );
if( endIndex == -1 )
{
// fix invalid input
buf += c;
}
else if( text[ i + 1 ] == '#' )
{
int num = 0;
bool ok = false;
if( text[ i + 2 ] == 'x' )
{
QString entity( text.mid( i + 3, endIndex - i - 3 ) );
num = entity.toInt( &ok, 16 );
}
else
{
QString entity( text.mid( i + 2, endIndex - i - 2 ) );
num = entity.toInt( &ok, 10 );
}
if( !ok || num < 0 )
{
// fix invalid input
buf += c;
}
else
{
buf += QChar( num );
i = endIndex;
}
}
else
{
QString entity( text.mid( i + 1, endIndex - i - 1 ) );
if( entity == "lt" )
{
buf += '<';
i = endIndex;
}
else if( entity == "gt" )
{
buf += '>';
i = endIndex;
}
else if( entity == "amp" )
{
buf += '&';
i = endIndex;
}
else if( entity == "apos" )
{
buf += '\'';
i = endIndex;
}
else if( entity == "quot" )
{
buf += '"';
i = endIndex;
}
else
{
// fix invalid input
buf += c;
}
}
}
else
{
buf += c;
}
}
return buf;
}
void
PodcastReader::setSummary( const QString &description )
{
if( m_current->summary().size() < description.size() )
{
m_current->setSummary( description );
}
}
void
PodcastReader::setDescription( const QString &description )
{
// The content of the <description>, <itunes:summary> or <body>
// elements might be assigned to the field description, unless
// there is already longer data in it. Then it will be assigned
// to summary, unless summary depending on whether there
// already is some (longer) information in the description
// field.
// If there is already data in the description field, instead of
// overwriting, it will be moved to the summary field, unless
// there is already longer data there.
if( m_current->description().size() < description.size() )
{
setSummary( m_current->description() );
m_current->setDescription( description );
}
else
{
setSummary( description );
}
}
void
PodcastReader::endDescription()
{
QString description( m_buffer.trimmed() );
if( !mightBeHtml( description ) )
{
// content type is plain text
description = textToHtml( description );
}
// else: content type is html
setDescription( description );
}
QString
PodcastReader::textToHtml( const QString &text )
{
QString buf;
QRegExp re( sd.linkify );
int index = 0;
for(;;)
{
int next = re.indexIn( text, index );
if( next == -1 )
break;
if( next != index )
{
buf += Qt::escape( text.mid( index, next - index ) );
}
QString s;
if( !(s = re.cap( 1 )).isEmpty() )
{
if( s.startsWith( QLatin1String( "javascript:" ), Qt::CaseInsensitive ) ||
s.startsWith( QLatin1String( "exec:" ), Qt::CaseInsensitive ) )
{
buf += Qt::escape( s );
}
else
{
buf += QString( "<a href=\"%1\">%1</a>" )
.arg( Qt::escape( s ) );
}
}
else if( !(s = re.cap( 2 )).isEmpty() )
{
buf += QString( "<a href=\"mailto:%1\">%1</a>" )
.arg( Qt::escape( s ) );
}
else if( !re.cap( 3 ).isEmpty() )
{
buf += "<br/>\n";
}
index = re.pos() + re.matchedLength();
}
buf += Qt::escape( text.mid( index ) );
return buf;
}
void
PodcastReader::endEncoded()
{
// content type is html
setDescription( m_buffer.trimmed() );
}
void
PodcastReader::endBody()
{
// content type is xhtml
// always prefer <body>, because it's likely to
// contain nice html formatted information
setSummary( m_current->description() );
m_current->setDescription( m_buffer.trimmed() );
}
void
PodcastReader::endLink()
{
// TODO: change to m_current->... when the field
// is moved to the PodcastMetaCommon class.
m_channel->setWebLink( QUrl( m_buffer ) );
}
void
PodcastReader::beginHtml()
{
stopWithError( i18n( "While parsing %1, a feed was expected but an HTML page was received."
"\nDid you enter the correct URL?", m_url.url() ) );
}
void
PodcastReader::beginUnknownFeedType()
{
stopWithError( i18n( "Feed has an unknown type: %1", m_url.url() ) );
}
void
PodcastReader::beginRss()
{
if( m_xmlReader.attributes().value( "version" ) != "2.0" )
{
// TODO: change this string once we support more
stopWithError( i18n( "%1 is not an RSS version 2.0 feed.", m_url.url() ) );
}
}
void
PodcastReader::beginRdf()
{
bool ok = true;
if( m_xmlReader.namespaceUri() != RDF_NS )
{
ok = false;
}
if( ok )
{
bool found = false;
foreach( const QXmlStreamNamespaceDeclaration &nsdecl, m_xmlReader.namespaceDeclarations() )
{
if( nsdecl.namespaceUri() == RSS10_NS )
{
found = true;
break;
}
}
if( !found )
ok = false;
}
if( !ok )
stopWithError( i18n( "%1 is not a valid RSS version 1.0 feed.", m_url.url() ) );
}
void
PodcastReader::beginFeed()
{
if( m_xmlReader.namespaceUri() != ATOM_NS )
{
stopWithError( i18n( "%1 is not a valid Atom feed.", m_url.url() ) );
}
else
{
beginChannel();
}
}
void
PodcastReader::endDocument()
{
debug() << "successfully parsed feed: " << m_url.url();
emit finished( this );
}
void
PodcastReader::createChannel()
{
if( !m_channel )
{
debug() << "new channel";
Podcasts::PodcastChannelPtr channel( new Podcasts::PodcastChannel() );
channel->setUrl( m_url );
channel->setSubscribeDate( QDate::currentDate() );
/* add this new channel to the provider, we get a pointer to a
* PodcastChannelPtr of the correct type which we will use from now on.
*/
m_channel = m_podcastProvider->addChannel( channel );
}
}
void
PodcastReader::beginChannel()
{
createChannel();
m_current = m_channel.data();
// Because the summary and description fields are read from several elements
// they only get changed when longer information is read as there is stored in
// the appropriate field already. In order to still be able to correctly update
// the feed's description/summary I set it here to the empty string:
m_channel->setDescription( "" );
m_channel->setSummary( "" );
m_channel->setKeywords( QStringList() );
}
void
PodcastReader::beginItem()
{
// theoretically it is possible that an ugly RSS 1.0 feed has
// first the <item> elements followed by the <channel> element:
createChannel();
m_item = new Podcasts::PodcastEpisode( m_channel );
m_current = m_item.data();
m_enclosures.clear();
}
void
PodcastReader::endItem()
{
// TODO: change superclass of PodcastEpisode to MultiTrack
/* some feeds contain normal blogposts without
enclosures alongside of podcasts */
if( !m_enclosures.isEmpty() )
{
// just take the first enclosure on multi
m_item->setUidUrl( m_enclosures[ 0 ].url() );
m_item->setFilesize( m_enclosures[ 0 ].fileSize() );
m_item->setMimeType( m_enclosures[ 0 ].mimeType() );
m_enclosures.removeAt( 0 );
// append alternative enclosures to description
if( !m_enclosures.isEmpty() )
{
QString description( m_item->description() );
description += "\n<p><b>";
description += i18n( "Alternative Enclosures:" );
description += "</b><br/>\n<ul>";
foreach( const Enclosure& enclosure, m_enclosures )
{
description += QString( "<li><a href=\"%1\">%2</a> (%3, %4)</li>" )
.arg( Qt::escape( enclosure.url().url() ) )
.arg( Qt::escape( enclosure.url().fileName() ) )
.arg( Meta::prettyFilesize( enclosure.fileSize() ) )
.arg( enclosure.mimeType().isEmpty() ?
i18n( "unknown type" ) :
Qt::escape( enclosure.mimeType() ) );
}
description += "</ul></p>";
m_item->setDescription( description );
}
Podcasts::PodcastEpisodePtr episode;
QString guid = m_item->guid();
if( guid.isEmpty() )
{
episode = Podcasts::PodcastEpisodePtr::dynamicCast(
m_podcastProvider->trackForUrl( QUrl::fromUserInput((m_item->uidUrl())) )
);
}
else
{
episode = m_podcastProvider->episodeForGuid( guid );
}
//make sure that the episode is not a bogus match. The channel has to be correct.
// See http://bugs.kde.org/show_bug.cgi?id=227515
if( !episode.isNull() && episode->channel() == m_channel )
{
debug() << "updating episode: " << episode->title();
episode->setTitle( m_item->title() );
episode->setSubtitle( m_item->subtitle() );
episode->setSummary( m_item->summary() );
episode->setDescription( m_item->description() );
episode->setAuthor( m_item->author() );
episode->setUidUrl( QUrl::fromUserInput(m_item->uidUrl()) );
episode->setFilesize( m_item->filesize() );
episode->setMimeType( m_item->mimeType() );
episode->setPubDate( m_item->pubDate() );
episode->setKeywords( m_item->keywords() );
// set the guid in case it was empty (for some buggy reason):
episode->setGuid( m_item->guid() );
}
else
{
debug() << "new episode: " << m_item->title();
episode = m_channel->addEpisode( m_item );
// also let the provider know an episode has been added
// TODO: change into a signal
m_podcastProvider->addEpisode( episode );
}
}
m_current = m_channel.data();
m_item = 0;
}
void
PodcastReader::beginEnclosure()
{
// This should read both, RSS 2.0 and RSS 1.0 with mod_enclosure
// <enclosure> elements.
// See:
// http://www.rssboard.org/rss-specification
// http://www.xs4all.nl/~foz/mod_enclosure.html
QStringRef str;
str = m_xmlReader.attributes().value( "url" );
if( str.isEmpty() )
str = attribute( RDF_NS, "about" );
if( str.isEmpty() )
{
debug() << "invalid enclosure containing no/empty url";
return;
}
QUrl url( str.toString() );
str = m_xmlReader.attributes().value( "length" );
if( str.isEmpty() )
str = attribute( ENC_NS, "length" );
int length = str.toString().toInt();
str = m_xmlReader.attributes().value( "type" );
if( str.isEmpty() )
str = attribute( ENC_NS, "type" );
QString mimeType( str.toString().trimmed() );
m_enclosures.append( Enclosure( url, length, mimeType ) );
}
void
PodcastReader::endGuid()
{
m_item->setGuid( m_buffer );
}
void
PodcastReader::endPubDate()
{
QDateTime pubDate( parsePubDate( m_buffer ) );
if( !pubDate.isValid() )
{
debug() << "invalid podcast episode pubDate: " << m_buffer;
return;
}
m_item->setPubDate( pubDate );
}
void
PodcastReader::beginImage()
{
if( m_xmlReader.namespaceUri() == ITUNES_NS )
{
m_channel->setImageUrl( QUrl( m_xmlReader.attributes().value( "href" ).toString() ) );
}
}
void
PodcastReader::endImageUrl()
{
// TODO save image data
m_channel->setImageUrl( QUrl( m_buffer ) );
}
void
PodcastReader::endKeywords()
{
QList<QString> keywords( m_current->keywords() );
foreach( const QString &keyword, m_buffer.split( ',' ) )
{
QString kwd( keyword.simplified() );
if( !kwd.isEmpty() && !keywords.contains( kwd ) )
keywords.append( kwd );
}
qSort( keywords );
m_current->setKeywords( keywords );
}
void
PodcastReader::endNewFeedUrl()
{
if( m_xmlReader.namespaceUri() == ITUNES_NS )
{
m_url = QUrl( m_buffer.trimmed() );
if( m_channel && m_channel->url() != m_url )
{
debug() << "feed url changed to: " << m_url.url();
m_channel->setUrl( m_url );
}
}
}
void
PodcastReader::endAuthor()
{
m_current->setAuthor( m_buffer.trimmed() );
}
void
PodcastReader::endCreator()
{
// there are funny people that do not use <author> but <dc:creator>
if( m_xmlReader.namespaceUri() == DC_NS )
{
endAuthor();
}
}
void
PodcastReader::beginXml()
{
m_buffer += '<';
m_buffer += m_xmlReader.name().toString();
foreach( const QXmlStreamAttribute &attr, m_xmlReader.attributes() )
{
m_buffer += QString( " %1=\"%2\"" )
.arg( attr.name().toString() )
.arg( Qt::escape( attr.value().toString() ) );
}
m_buffer += '>';
}
void
PodcastReader::beginNoElement()
{
DEBUG_BLOCK
debug() << "no element expected here, but got element: "
<< m_xmlReader.name();
}
void
PodcastReader::beginAtomText()
{
if( hasAttribute( ATOM_NS, "type" ) )
{
QStringRef type( attribute( ATOM_NS, "type" ) );
if( type == "text" )
{
m_contentType = TextContent;
}
else if( type == "html" )
{
m_contentType = HtmlContent;
}
else if( type == "xhtml" )
{
m_contentType = XHtmlContent;
}
else
{
// this should not happen, see elementType()
debug() << "unsupported atom:content type: " << type.toString();
m_contentType = TextContent;
}
}
else
{
m_contentType = TextContent;
}
m_buffer.clear();
}
void
PodcastReader::beginAtomTextChild()
{
switch( m_contentType )
{
case XHtmlContent:
beginXml();
break;
case HtmlContent:
case TextContent:
// stripping illegal tags
debug() << "read unexpected open tag in atom text: " << m_xmlReader.name();
default:
break;
}
}
void
PodcastReader::endAtomTextChild()
{
switch( m_contentType )
{
case XHtmlContent:
endXml();
break;
case HtmlContent:
case TextContent:
// stripping illegal tags
debug() << "read unexpected close tag in atom text: " << m_xmlReader.name();
default:
break;
}
}
void
PodcastReader::readAtomTextCharacters()
{
switch( m_contentType )
{
case XHtmlContent:
m_buffer += Qt::escape( m_xmlReader.text().toString() );
break;
case HtmlContent:
m_buffer += m_xmlReader.text();
break;
case TextContent:
m_buffer += m_xmlReader.text();
default:
break;
}
}
void
PodcastReader::beginAtomFeedLink()
{
if( !hasAttribute( ATOM_NS, "rel" ) ||
attribute( ATOM_NS, "rel" ) == "alternate" )
{
m_channel->setWebLink( QUrl( attribute( ATOM_NS, "href" ).toString() ) );
}
else if( attribute( ATOM_NS, "rel" ) == "self" )
{
m_url = QUrl( attribute( ATOM_NS, "href" ).toString() );
if( m_channel && m_channel->url() != m_url )
{
debug() << "feed url changed to: " << m_url.url();
m_channel->setUrl( m_url );
}
}
}
void
PodcastReader::beginAtomEntryLink()
{
if( attribute( ATOM_NS, "rel" ) == "enclosure" )
{
QUrl url( attribute( ATOM_NS, "href" ).toString() );
int filesize = 0;
QString mimeType;
if( hasAttribute( ATOM_NS, "length" ) )
{
filesize = attribute( ATOM_NS, "length" ).toString().toInt();
}
if( hasAttribute( ATOM_NS, "type" ) )
{
mimeType = attribute( ATOM_NS, "type" ).toString();
}
m_enclosures.append( Enclosure( url, filesize, mimeType ) );
}
}
void
PodcastReader::endAtomIcon()
{
if( !m_channel->hasImage() )
{
endImageUrl();
}
}
void
PodcastReader::endAtomTitle()
{
// TODO: don't convert text but store m_contentType
m_current->setTitle( atomTextAsText().trimmed() );
}
void
PodcastReader::endAtomSubtitle()
{
// TODO: don't convert text but store m_contentType
m_current->setSubtitle( atomTextAsText().trimmed() );
}
void
PodcastReader::endAtomSummary()
{
// TODO: don't convert text but store m_contentType
m_current->setSummary( atomTextAsHtml().trimmed() );
}
void
PodcastReader::endAtomContent()
{
// TODO: don't convert text but store m_contentType
m_current->setDescription( atomTextAsHtml() );
}
void
PodcastReader::endAtomPublished()
{
QDateTime date( KDateTime::fromString( m_buffer, KDateTime::ISODate ).dateTime() );
if( !date.isValid() )
{
debug() << "invalid podcast episode atom:published date: " << m_buffer;
return;
}
if( !m_item->pubDate().isValid() || m_item->pubDate() < date )
{
m_item->setPubDate( date );
}
}
void
PodcastReader::endAtomUpdated()
{
QDateTime date( KDateTime::fromString( m_buffer, KDateTime::ISODate ).dateTime() );
if( !date.isValid() )
{
debug() << "invalid podcast episode atom:updated date: " << m_buffer;
return;
}
if( !m_item->pubDate().isValid() || m_item->pubDate() < date )
{
// TODO: add field updatedDate and use this (throughout amarok)
m_item->setPubDate( date );
}
}
void
PodcastReader::readNoCharacters()
{
DEBUG_BLOCK
debug() << "no characters expected here";
}
void
PodcastReader::endXml()
{
m_buffer += "</";
m_buffer += m_xmlReader.name().toString();
m_buffer += '>';
}
void
PodcastReader::readCharacters()
{
m_buffer += m_xmlReader.text();
}
void
PodcastReader::readEscapedCharacters()
{
m_buffer += Qt::escape( m_xmlReader.text().toString() );
}
QStringRef
PodcastReader::attribute( const char *namespaceUri, const char *name ) const
{
// workaround, because Qt seems to have a bug:
// when the default namespace is used attributes
// aren't inside this namespace for some reason
if( m_xmlReader.attributes().hasAttribute( namespaceUri, name ) )
return m_xmlReader.attributes().value( namespaceUri, name );
else
return m_xmlReader.attributes().value( NULL, name );
}
bool
PodcastReader::hasAttribute( const char *namespaceUri, const char *name ) const
{
// see PodcastReader::attribute()
if( m_xmlReader.attributes().hasAttribute( namespaceUri, name ) )
return true;
else
return m_xmlReader.attributes().hasAttribute( NULL, name );
}
QDateTime
PodcastReader::parsePubDate( const QString &dateString )
{
DEBUG_BLOCK
QString parseInput = dateString;
debug() << "Parsing pubdate: " << parseInput;
QRegExp rfcDateDayRegex( "^[A-Z]{1}[a-z]{2}\\s*,\\s*(.*)" );
if( rfcDateDayRegex.indexIn( parseInput ) != -1 )
{
parseInput = rfcDateDayRegex.cap(1);
}
//Hack around a to strict RFCDate implementation in KDateTime.
//See http://bugs.kde.org/show_bug.cgi?id=231062
QRegExp rfcMonthLowercase( "^\\d+\\s+\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\b" );
if( rfcMonthLowercase.indexIn( parseInput ) != -1 )
{
QString lowerMonth = rfcMonthLowercase.cap( 1 );
QString upperMonth = lowerMonth;
upperMonth.replace( 0, 1, lowerMonth.at( 0 ).toUpper() );
parseInput.replace( lowerMonth, upperMonth );
}
QDateTime pubDate = KDateTime::fromString( parseInput, KDateTime::RFCDate ).dateTime();
debug() << "result: " << pubDate.toString();
return pubDate;
}
void
PodcastReader::slotRedirection( KIO::Job * job, const QUrl &url )
{
DEBUG_BLOCK
Q_UNUSED( job );
debug() << "redirected to: " << url.url();
}
void
PodcastReader::slotPermanentRedirection( KIO::Job * job, const QUrl &fromUrl,
const QUrl &toUrl )
{
DEBUG_BLOCK
Q_UNUSED( job );
Q_UNUSED( fromUrl );
debug() << "permanently redirected to: " << toUrl.url();
m_url = toUrl;
/* change the url for existing feeds as well. Permanent redirection means the old one
might dissapear soon. */
if( m_channel )
m_channel->setUrl( m_url );
}
Podcasts::PodcastEpisodePtr
PodcastReader::podcastEpisodeCheck( Podcasts::PodcastEpisodePtr episode )
{
// DEBUG_BLOCK
Podcasts::PodcastEpisodePtr episodeMatch = episode;
Podcasts::PodcastEpisodeList episodes = m_channel->episodes();
// debug() << "episode title: " << episode->title();
// debug() << "episode url: " << episode->prettyUrl();
// debug() << "episode guid: " << episode->guid();
foreach( PodcastEpisodePtr match, episodes )
{
// debug() << "match title: " << match->title();
// debug() << "match url: " << match->prettyUrl();
// debug() << "match guid: " << match->guid();
int score = 0;
if( !episode->title().isEmpty() && episode->title() == match->title() )
score += 1;
if( !episode->prettyUrl().isEmpty() && episode->prettyUrl() == match->prettyUrl() )
score += 3;
if( !episode->guid().isEmpty() && episode->guid() == match->guid() )
score += 3;
// debug() << "score: " << score;
if( score >= 3 )
{
episodeMatch = match;
break;
}
}
return episodeMatch;
}
diff --git a/src/core/transcoding/TranscodingController.cpp b/src/core/transcoding/TranscodingController.cpp
index 35bcebd4c6..8d484c92d2 100644
--- a/src/core/transcoding/TranscodingController.cpp
+++ b/src/core/transcoding/TranscodingController.cpp
@@ -1,87 +1,87 @@
/****************************************************************************************
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TranscodingController.h"
#include "formats/TranscodingNullFormat.h"
#include "formats/TranscodingAacFormat.h"
#include "formats/TranscodingAlacFormat.h"
#include "formats/TranscodingFlacFormat.h"
#include "formats/TranscodingMp3Format.h"
#include "formats/TranscodingOpusFormat.h"
#include "formats/TranscodingVorbisFormat.h"
#include "formats/TranscodingWmaFormat.h"
using namespace Transcoding;
Controller::Controller( QObject *parent )
: QObject( parent )
{
m_formats.insert( JUST_COPY, new NullFormat( JUST_COPY ) );
m_formats.insert( INVALID, new NullFormat( INVALID ) );
m_formats.insert( AAC, new AacFormat() );
m_formats.insert( ALAC, new AlacFormat() );
m_formats.insert( FLAC, new FlacFormat() );
m_formats.insert( MP3, new Mp3Format() );
m_formats.insert( OPUS, new OpusFormat() );
m_formats.insert( VORBIS, new VorbisFormat() );
m_formats.insert( WMA2, new WmaFormat() );
KProcess *verifyAvailability = new KProcess( this );
verifyAvailability->setOutputChannelMode( KProcess::MergedChannels );
verifyAvailability->setProgram( "ffmpeg" );
*verifyAvailability << QString( "-codecs" );
- connect( verifyAvailability, SIGNAL(finished(int,QProcess::ExitStatus)),
- this, SLOT(onAvailabilityVerified(int,QProcess::ExitStatus)) );
+ connect( verifyAvailability, QOverload<int, KProcess::ExitStatus>::of(&KProcess::finished),
+ this, &Controller::onAvailabilityVerified );
verifyAvailability->start();
}
Controller::~Controller()
{
qDeleteAll( m_formats );
}
void
Controller::onAvailabilityVerified( int exitCode, QProcess::ExitStatus exitStatus ) //SLOT
{
Q_UNUSED( exitCode )
Q_UNUSED( exitStatus )
sender()->deleteLater();
QString output = qobject_cast< KProcess * >( sender() )->readAllStandardOutput().data();
if( output.simplified().isEmpty() )
return;
QStringList lines = output.split( QRegExp( "\r|\n" ), QString::SkipEmptyParts );
foreach( Format *format, m_formats )
{
bool formatAvailable = false;
foreach( const QString &line, lines )
{
formatAvailable |= format->verifyAvailability( line );
if( formatAvailable )
break;
}
if( formatAvailable )
m_availableEncoders.insert( format->encoder() );
}
}
Format *
Controller::format( Encoder encoder ) const
{
Q_ASSERT(m_formats.contains( encoder ));
return m_formats.value( encoder );
}
diff --git a/src/covermanager/CoverFetcher.cpp b/src/covermanager/CoverFetcher.cpp
index 9e280890df..54f5543f4c 100644
--- a/src/covermanager/CoverFetcher.cpp
+++ b/src/covermanager/CoverFetcher.cpp
@@ -1,480 +1,482 @@
/****************************************************************************************
* Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2004 Stefan Bogner <bochi@online.ms> *
* Copyright (c) 2004 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2009 Martin Sandsmark <sandsmark@samfundet.no> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CoverFetcher"
#include "CoverFetcher.h"
#include "amarokconfig.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "CoverFetchQueue.h"
#include "CoverFoundDialog.h"
#include "CoverFetchUnit.h"
#include <KLocale>
#include <QUrl>
#include <QBuffer>
#include <QImageReader>
#include <KConfigGroup>
CoverFetcher* CoverFetcher::s_instance = 0;
CoverFetcher*
CoverFetcher::instance()
{
return s_instance ? s_instance : new CoverFetcher();
}
void CoverFetcher::destroy()
{
if( s_instance )
{
delete s_instance;
s_instance = 0;
}
}
CoverFetcher::CoverFetcher()
: QObject()
, m_limit( 10 )
{
DEBUG_BLOCK
setObjectName( "CoverFetcher" );
qRegisterMetaType<CoverFetchUnit::Ptr>("CoverFetchUnit::Ptr");
m_queue = new CoverFetchQueue( this );
- connect( m_queue, SIGNAL(fetchUnitAdded(CoverFetchUnit::Ptr)),
- SLOT(slotFetch(CoverFetchUnit::Ptr)) );
+ connect( m_queue, &CoverFetchQueue::fetchUnitAdded,
+ this, &CoverFetcher::slotFetch );
s_instance = this;
- connect( The::networkAccessManager(), SIGNAL(requestRedirected(QNetworkReply*,QNetworkReply*)),
- this, SLOT(fetchRequestRedirected(QNetworkReply*,QNetworkReply*)) );
+ connect( The::networkAccessManager(), &NetworkAccessManagerProxy::requestRedirectedReply,
+ this, &CoverFetcher::fetchRequestRedirected );
}
CoverFetcher::~CoverFetcher()
{
}
void
CoverFetcher::manualFetch( Meta::AlbumPtr album )
{
debug() << QString("Adding interactive cover fetch for: '%1' from %2")
.arg( album->name(),
Amarok::config("Cover Fetcher").readEntry("Interactive Image Source", "LastFm") );
switch( fetchSource() )
{
case CoverFetch::LastFm:
m_queue->add( album, CoverFetch::Interactive, fetchSource() );
break;
case CoverFetch::Discogs:
case CoverFetch::Google:
queueQueryForAlbum( album );
break;
default:
break;
}
}
void
CoverFetcher::queueAlbum( Meta::AlbumPtr album )
{
if( m_queue->size() > m_limit )
m_queueLater.append( album );
else
m_queue->add( album, CoverFetch::Automatic );
debug() << "Queueing automatic cover fetch for:" << album->name();
}
void
CoverFetcher::queueAlbums( Meta::AlbumList albums )
{
foreach( Meta::AlbumPtr album, albums )
{
if( m_queue->size() > m_limit )
m_queueLater.append( album );
else
m_queue->add( album, CoverFetch::Automatic );
}
}
void
CoverFetcher::queueQuery( Meta::AlbumPtr album, const QString &query, int page )
{
m_queue->addQuery( query, fetchSource(), page, album );
debug() << QString( "Queueing cover fetch query: '%1' (page %2)" ).arg( query, QString::number( page ) );
}
void
CoverFetcher::queueQueryForAlbum( Meta::AlbumPtr album )
{
QString query( album->name() );
if( album->hasAlbumArtist() )
query += ' ' + album->albumArtist()->name();
queueQuery( album, query, 0 );
}
void
CoverFetcher::slotFetch( CoverFetchUnit::Ptr unit )
{
if( !unit )
return;
const CoverFetchPayload *payload = unit->payload();
const CoverFetch::Urls urls = payload->urls();
// show the dialog straight away if fetch is interactive
if( !m_dialog && unit->isInteractive() )
{
showCover( unit, QImage() );
}
else if( urls.isEmpty() )
{
finish( unit, NotFound );
return;
}
const QList<QUrl> uniqueUrls = urls.uniqueKeys();
foreach( const QUrl &url, uniqueUrls )
{
if( !url.isValid() )
continue;
QNetworkReply *reply = The::networkAccessManager()->getData( url, this,
SLOT(slotResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) );
m_urls.insert( url, unit );
if( payload->type() == CoverFetchPayload::Art )
{
if( unit->isInteractive() )
Amarok::Components::logger()->newProgressOperation( reply, i18n( "Fetching Cover" ) );
else
return; // only one is needed when the fetch is non-interactive
}
}
}
void
CoverFetcher::slotResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e )
{
DEBUG_BLOCK
if( !m_urls.contains( url ) )
return;
debug() << "Data dump from the result: " << data;
const CoverFetchUnit::Ptr unit( m_urls.take( url ) );
if( !unit )
{
m_queue->remove( unit );
return;
}
if( e.code != QNetworkReply::NoError )
{
finish( unit, Error, i18n("There was an error communicating with cover provider: %1", e.description) );
return;
}
const CoverFetchPayload *payload = unit->payload();
switch( payload->type() )
{
case CoverFetchPayload::Info:
m_queue->add( unit->album(), unit->options(), payload->source(), data );
m_queue->remove( unit );
break;
case CoverFetchPayload::Search:
m_queue->add( unit->options(), fetchSource(), data );
m_queue->remove( unit );
break;
case CoverFetchPayload::Art:
handleCoverPayload( unit, data, url );
break;
}
}
void
CoverFetcher::handleCoverPayload( const CoverFetchUnit::Ptr &unit, const QByteArray &data, const QUrl &url )
{
if( data.isEmpty() )
{
finish( unit, NotFound );
return;
}
QBuffer buffer;
buffer.setData( data );
buffer.open( QIODevice::ReadOnly );
QImageReader reader( &buffer );
if( !reader.canRead() )
{
finish( unit, Error, reader.errorString() );
return;
}
QSize imageSize = reader.size();
const CoverFetchArtPayload *payload = static_cast<const CoverFetchArtPayload*>( unit->payload() );
const CoverFetch::Metadata &metadata = payload->urls().value( url );
if( payload->imageSize() == CoverFetch::ThumbSize )
{
if( imageSize.isEmpty() )
{
imageSize.setWidth( metadata.value( QLatin1String("width") ).toInt() );
imageSize.setHeight( metadata.value( QLatin1String("height") ).toInt() );
}
imageSize.scale( 120, 120, Qt::KeepAspectRatio );
reader.setScaledSize( imageSize );
// This will force the JPEG decoder to use JDCT_IFAST
reader.setQuality( 49 );
}
if( unit->isInteractive() )
{
QImage image;
if( reader.read( &image ) )
{
showCover( unit, image, metadata );
m_queue->remove( unit );
return;
}
}
else
{
QImage image;
if( reader.read( &image ) )
{
m_selectedImages.insert( unit, image );
finish( unit );
return;
}
}
finish( unit, Error, reader.errorString() );
}
void
CoverFetcher::slotDialogFinished()
{
const CoverFetchUnit::Ptr unit = m_dialog.data()->unit();
switch( m_dialog.data()->result() )
{
case QDialog::Accepted:
m_selectedImages.insert( unit, m_dialog.data()->image() );
finish( unit );
break;
case QDialog::Rejected:
finish( unit, Cancelled );
break;
default:
finish( unit, Error );
}
/*
* Remove all manual fetch jobs from the queue if the user accepts, cancels,
* or closes the cover found dialog. This way, the dialog will not reappear
* if there are still covers yet to be retrieved.
*/
QList< CoverFetchUnit::Ptr > units = m_urls.values();
foreach( const CoverFetchUnit::Ptr &unit, units )
{
if( unit->isInteractive() )
abortFetch( unit );
}
m_dialog.data()->delayedDestruct();
}
void
CoverFetcher::fetchRequestRedirected( QNetworkReply *oldReply,
QNetworkReply *newReply )
{
QUrl oldUrl = oldReply->request().url();
QUrl newUrl = newReply->request().url();
// Since we were redirected we have to check if the redirect
// was for one of our URLs and if the new URL is not handled
// already.
if( m_urls.contains( oldUrl ) && !m_urls.contains( newUrl ) )
{
// Get the unit for the old URL.
CoverFetchUnit::Ptr unit = m_urls.value( oldUrl );
// Add the unit with the new URL and remove the old one.
m_urls.insert( newUrl, unit );
m_urls.remove( oldUrl );
// If the unit is an interactive one we have to incidate that we're
// still fetching the cover.
if( unit->isInteractive() )
Amarok::Components::logger()->newProgressOperation( newReply, i18n( "Fetching Cover" ) );
}
}
void
CoverFetcher::showCover( const CoverFetchUnit::Ptr &unit,
const QImage &cover,
const CoverFetch::Metadata &data )
{
if( !m_dialog )
{
const Meta::AlbumPtr album = unit->album();
if( !album )
{
finish( unit, Error );
return;
}
m_dialog = new CoverFoundDialog( unit, data, static_cast<QWidget*>( parent() ) );
- connect( m_dialog.data(), SIGNAL(newCustomQuery(Meta::AlbumPtr,QString,int)),
- SLOT(queueQuery(Meta::AlbumPtr,QString,int)) );
- connect( m_dialog.data(), SIGNAL(accepted()), SLOT(slotDialogFinished()) );
- connect( m_dialog.data(), SIGNAL(rejected()), SLOT(slotDialogFinished()) );
+ connect( m_dialog.data(), &CoverFoundDialog::newCustomQuery,
+ this, &CoverFetcher::queueQuery );
+ connect( m_dialog.data(), &CoverFoundDialog::accepted,
+ this, &CoverFetcher::slotDialogFinished );
+ connect( m_dialog.data(),&CoverFoundDialog::rejected,
+ this, &CoverFetcher::slotDialogFinished );
if( fetchSource() == CoverFetch::LastFm )
queueQueryForAlbum( album );
m_dialog.data()->setQueryPage( 1 );
m_dialog.data()->show();
m_dialog.data()->raise();
m_dialog.data()->activateWindow();
}
else
{
if( !cover.isNull() )
{
typedef CoverFetchArtPayload CFAP;
const CFAP *payload = dynamic_cast< const CFAP* >( unit->payload() );
if( payload )
m_dialog.data()->add( cover, data, payload->imageSize() );
}
}
}
void
CoverFetcher::abortFetch( CoverFetchUnit::Ptr unit )
{
m_queue->remove( unit );
m_queueLater.removeAll( unit->album() );
m_selectedImages.remove( unit );
QList<QUrl> urls = m_urls.keys( unit );
foreach( const QUrl &url, urls )
m_urls.remove( url );
The::networkAccessManager()->abortGet( urls );
}
void
CoverFetcher::finish( const CoverFetchUnit::Ptr unit,
CoverFetcher::FinishState state,
const QString &message )
{
Meta::AlbumPtr album = unit->album();
const QString albumName = album ? album->name() : QString();
switch( state )
{
case Success:
if( !albumName.isEmpty() )
{
const QString text = i18n( "Retrieved cover successfully for '%1'.", albumName );
Amarok::Components::logger()->shortMessage( text );
debug() << "Finished successfully for album" << albumName;
}
album->setImage( m_selectedImages.take( unit ) );
abortFetch( unit );
break;
case Error:
if( !albumName.isEmpty() )
{
const QString text = i18n( "Fetching cover for '%1' failed.", albumName );
Amarok::Components::logger()->shortMessage( text );
QString debugMessage;
if( !message.isEmpty() )
debugMessage = '[' + message + ']';
debug() << "Finished with errors for album" << albumName << debugMessage;
}
m_errors += message;
break;
case Cancelled:
if( !albumName.isEmpty() )
{
const QString text = i18n( "Canceled fetching cover for '%1'.", albumName );
Amarok::Components::logger()->shortMessage( text );
debug() << "Finished, cancelled by user for album" << albumName;
}
break;
case NotFound:
if( !albumName.isEmpty() )
{
const QString text = i18n( "Unable to find a cover for '%1'.", albumName );
//FIXME: Not visible behind cover manager
Amarok::Components::logger()->shortMessage( text );
m_errors += text;
debug() << "Finished due to cover not found for album" << albumName;
}
break;
}
m_queue->remove( unit );
if( !m_queueLater.isEmpty() )
{
const int diff = m_limit - m_queue->size();
if( diff > 0 )
{
for( int i = 0; i < diff && !m_queueLater.isEmpty(); ++i )
{
Meta::AlbumPtr album = m_queueLater.takeFirst();
// automatic fetching only uses Last.fm as source
m_queue->add( album, CoverFetch::Automatic, CoverFetch::LastFm );
}
}
}
emit finishedSingle( static_cast< int >( state ) );
}
CoverFetch::Source
CoverFetcher::fetchSource() const
{
const KConfigGroup config = Amarok::config( "Cover Fetcher" );
const QString sourceEntry = config.readEntry( "Interactive Image Source", "LastFm" );
CoverFetch::Source source;
if( sourceEntry == "LastFm" )
source = CoverFetch::LastFm;
else if( sourceEntry == "Google" )
source = CoverFetch::Google;
else
source = CoverFetch::Discogs;
return source;
}
diff --git a/src/covermanager/CoverFetchingActions.h b/src/covermanager/CoverFetchingActions.h
index 6461e6d3ba..901311f3b8 100644
--- a/src/covermanager/CoverFetchingActions.h
+++ b/src/covermanager/CoverFetchingActions.h
@@ -1,114 +1,114 @@
/****************************************************************************************
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef COVERFETCHINGACTIONS_H
#define COVERFETCHINGACTIONS_H
#include "amarok_export.h"
#include "core/meta/forward_declarations.h"
#include <QAction>
/**
* This collection of actions handles fetching, displaying and removing of album art
*
* @author Seb Ruiz
*/
class AMAROK_EXPORT BaseCoverAction : public QAction
{
Q_OBJECT
public:
BaseCoverAction( QObject *parent, Meta::AlbumPtr album )
: QAction( parent )
{
m_albums.append( album );
- connect( this, SIGNAL( triggered( bool ) ), SLOT( slotTriggered() ) );
+ connect( this, &QAction::triggered, this, &BaseCoverAction::slotTriggered );
}
BaseCoverAction( QObject *parent, Meta::AlbumList albums )
: QAction( parent )
{
m_albums = albums;
- connect( this, SIGNAL( triggered( bool ) ), SLOT( slotTriggered() ) );
+ connect( this, &QAction::triggered, this, &BaseCoverAction::slotTriggered );
}
protected Q_SLOTS:
virtual void slotTriggered() = 0;
protected:
Meta::AlbumList m_albums;
};
class AMAROK_EXPORT FetchCoverAction : public BaseCoverAction
{
Q_OBJECT
public:
FetchCoverAction( QObject *parent, Meta::AlbumPtr album )
: BaseCoverAction( parent, album ) { init(); }
FetchCoverAction( QObject *parent, Meta::AlbumList albums )
: BaseCoverAction( parent, albums ) { init(); }
protected Q_SLOTS:
virtual void slotTriggered();
protected:
virtual void init();
};
class AMAROK_EXPORT DisplayCoverAction : public BaseCoverAction
{
Q_OBJECT
public:
DisplayCoverAction( QObject *parent, Meta::AlbumPtr album )
: BaseCoverAction( parent, album ) { init(); }
DisplayCoverAction( QObject *parent, Meta::AlbumList albums )
: BaseCoverAction( parent, albums ) { init(); }
protected Q_SLOTS:
virtual void slotTriggered();
protected:
virtual void init();
};
class AMAROK_EXPORT UnsetCoverAction : public BaseCoverAction
{
Q_OBJECT
public:
UnsetCoverAction( QObject *parent, Meta::AlbumPtr album )
: BaseCoverAction( parent, album ) { init(); }
UnsetCoverAction( QObject *parent, Meta::AlbumList albums )
: BaseCoverAction( parent, albums ) { init(); }
protected Q_SLOTS:
virtual void slotTriggered();
protected:
virtual void init();
};
class AMAROK_EXPORT SetCustomCoverAction : public BaseCoverAction
{
Q_OBJECT
public:
SetCustomCoverAction( QObject *parent, Meta::AlbumPtr album )
: BaseCoverAction( parent, album ) { init(); }
SetCustomCoverAction( QObject *parent, Meta::AlbumList albums )
: BaseCoverAction( parent, albums ) { init(); }
protected Q_SLOTS:
virtual void slotTriggered();
protected:
virtual void init();
};
#endif
diff --git a/src/covermanager/CoverFoundDialog.cpp b/src/covermanager/CoverFoundDialog.cpp
index 012ae99103..6b86d6d5f5 100644
--- a/src/covermanager/CoverFoundDialog.cpp
+++ b/src/covermanager/CoverFoundDialog.cpp
@@ -1,920 +1,924 @@
/****************************************************************************************
* Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2004 Stefan Bogner <bochi@online.ms> *
* Copyright (c) 2004 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2009 Martin Sandsmark <sandsmark@samfundet.no> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CoverFoundDialog"
#include "CoverFoundDialog.h"
#include "SvgHandler.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "covermanager/CoverViewDialog.h"
#include "statusbar/KJobProgressBar.h"
#include "widgets/AlbumBreadcrumbWidget.h"
#include "widgets/PixmapViewer.h"
#include <KComboBox>
#include <KConfigGroup>
#include <KFileDialog>
#include <KLineEdit>
#include <KListWidget>
#include <KLocalizedString>
#include <KMessageBox>
#include <KPushButton>
#include <KSaveFile>
#include <KStandardDirs>
#include <KGlobalSettings>
#include <QCloseEvent>
#include <QDir>
#include <QFormLayout>
#include <QFrame>
#include <QGridLayout>
#include <QHeaderView>
#include <QMenu>
#include <QScrollArea>
#include <QSplitter>
#include <QTabWidget>
#include <QMimeDatabase>
#include <QMimeType>
CoverFoundDialog::CoverFoundDialog( const CoverFetchUnit::Ptr unit,
const CoverFetch::Metadata &data,
QWidget *parent )
: KDialog( parent )
, m_album( unit->album() )
, m_isSorted( false )
, m_sortEnabled( false )
, m_unit( unit )
, m_queryPage( 0 )
{
DEBUG_BLOCK
setButtons( KDialog::Ok | KDialog::Cancel |
KDialog::User1 ); // User1: clear icon view
setButtonGuiItem( KDialog::User1, KStandardGuiItem::clear() );
- connect( button( KDialog::User1 ), SIGNAL(clicked()), SLOT(clearView()) );
+ connect( button( KDialog::User1 ), &QAbstractButton::clicked, this, &CoverFoundDialog::clearView );
m_save = button( KDialog::Ok );
QSplitter *splitter = new QSplitter( this );
m_sideBar = new CoverFoundSideBar( m_album, splitter );
KVBox *vbox = new KVBox( splitter );
vbox->setSpacing( 4 );
KHBox *breadcrumbBox = new KHBox( vbox );
QLabel *breadcrumbLabel = new QLabel( i18n( "Finding cover for" ), breadcrumbBox );
AlbumBreadcrumbWidget *breadcrumb = new AlbumBreadcrumbWidget( m_album, breadcrumbBox );
QFont breadcrumbLabelFont;
breadcrumbLabelFont.setBold( true );
breadcrumbLabel->setFont( breadcrumbLabelFont );
breadcrumbLabel->setIndent( 4 );
- connect( breadcrumb, SIGNAL(artistClicked(QString)), SLOT(addToCustomSearch(QString)) );
- connect( breadcrumb, SIGNAL(albumClicked(QString)), SLOT(addToCustomSearch(QString)) );
+ connect( breadcrumb, &AlbumBreadcrumbWidget::artistClicked, this, &CoverFoundDialog::addToCustomSearch );
+ connect( breadcrumb, &AlbumBreadcrumbWidget::albumClicked, this, &CoverFoundDialog::addToCustomSearch );
KHBox *searchBox = new KHBox( vbox );
vbox->setSpacing( 4 );
QStringList completionNames;
QString firstRunQuery( m_album->name() );
completionNames << firstRunQuery;
if( m_album->hasAlbumArtist() )
{
const QString &name = m_album->albumArtist()->name();
completionNames << name;
firstRunQuery += ' ' + name;
}
m_query = firstRunQuery;
m_album->setSuppressImageAutoFetch( true );
m_search = new KComboBox( searchBox );
m_search->setEditable( true ); // creates a KLineEdit for the combobox
m_search->setTrapReturnKey( true );
m_search->setInsertPolicy( QComboBox::NoInsert ); // insertion is handled by us
m_search->setCompletionMode( KCompletion::CompletionPopup );
m_search->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
qobject_cast<KLineEdit*>( m_search->lineEdit() )->setClickMessage( i18n( "Enter Custom Search" ) );
m_search->completionObject()->setOrder( KCompletion::Insertion );
m_search->completionObject()->setIgnoreCase( true );
m_search->completionObject()->setItems( completionNames );
m_search->insertItem( 0, KStandardGuiItem::find().icon(), QString() );
m_search->insertSeparator( 1 );
m_search->insertItem( 2, QIcon::fromTheme("filename-album-amarok"), m_album->name() );
if( m_album->hasAlbumArtist() )
m_search->insertItem( 3, QIcon::fromTheme("filename-artist-amarok"), m_album->albumArtist()->name() );
m_searchButton = new KPushButton( KStandardGuiItem::find(), searchBox );
KPushButton *sourceButton = new KPushButton( KStandardGuiItem::configure(), searchBox );
updateSearchButton( firstRunQuery );
QMenu *sourceMenu = new QMenu( sourceButton );
QAction *lastFmAct = new QAction( i18n( "Last.fm" ), sourceMenu );
QAction *googleAct = new QAction( i18n( "Google" ), sourceMenu );
QAction *discogsAct = new QAction( i18n( "Discogs" ), sourceMenu );
lastFmAct->setCheckable( true );
googleAct->setCheckable( true );
discogsAct->setCheckable( true );
- connect( lastFmAct, SIGNAL(triggered()), this, SLOT(selectLastFm()) );
- connect( googleAct, SIGNAL(triggered()), this, SLOT(selectGoogle()) );
- connect( discogsAct, SIGNAL(triggered()), this, SLOT(selectDiscogs()) );
+ connect( lastFmAct, &QAction::triggered, this, &CoverFoundDialog::selectLastFm );
+ connect( googleAct, &QAction::triggered, this, &CoverFoundDialog::selectGoogle );
+ connect( discogsAct, &QAction::triggered, this, &CoverFoundDialog::selectDiscogs );
m_sortAction = new QAction( i18n( "Sort by size" ), sourceMenu );
m_sortAction->setCheckable( true );
- connect( m_sortAction, SIGNAL(triggered(bool)), this, SLOT(sortingTriggered(bool)) );
+ connect( m_sortAction, &QAction::triggered, this, &CoverFoundDialog::sortingTriggered );
QActionGroup *ag = new QActionGroup( sourceButton );
ag->addAction( lastFmAct );
ag->addAction( googleAct );
ag->addAction( discogsAct );
sourceMenu->addActions( ag->actions() );
sourceMenu->addSeparator();
sourceMenu->addAction( m_sortAction );
sourceButton->setMenu( sourceMenu );
- connect( m_search, SIGNAL(returnPressed(QString)), SLOT(insertComboText(QString)) );
- connect( m_search, SIGNAL(returnPressed(QString)), SLOT(processQuery(QString)) );
- connect( m_search, SIGNAL(returnPressed(QString)), SLOT(updateSearchButton(QString)) );
- connect( m_search, SIGNAL(editTextChanged(QString)), SLOT(updateSearchButton(QString)) );
- connect( m_search->lineEdit(), SIGNAL(clearButtonClicked()), SLOT(clearQueryButtonClicked()));
- connect( m_searchButton, SIGNAL(clicked()), SLOT(processQuery()) );
+ connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
+ this, &CoverFoundDialog::insertComboText );
+ connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
+ this, &CoverFoundDialog::processQuery );
+ connect( m_search, QOverload<const QString&>::of(&KComboBox::returnPressed),
+ this, &CoverFoundDialog::updateSearchButton );
+ connect( m_search, &KComboBox::editTextChanged, this, &CoverFoundDialog::updateSearchButton );
+ connect( dynamic_cast<KLineEdit*>(m_search->lineEdit()), &KLineEdit::clearButtonClicked,
+ this, &CoverFoundDialog::clearQueryButtonClicked);
+ connect( m_searchButton, &KPushButton::clicked, this, &CoverFoundDialog::processCurrentQuery );
m_view = new KListWidget( vbox );
m_view->setAcceptDrops( false );
m_view->setContextMenuPolicy( Qt::CustomContextMenu );
m_view->setDragDropMode( QAbstractItemView::NoDragDrop );
m_view->setDragEnabled( false );
m_view->setDropIndicatorShown( false );
m_view->setMovement( QListView::Static );
m_view->setGridSize( QSize( 140, 150 ) );
m_view->setIconSize( QSize( 120, 120 ) );
m_view->setSpacing( 4 );
m_view->setViewMode( QListView::IconMode );
m_view->setResizeMode( QListView::Adjust );
- connect( m_view, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
- this, SLOT(currentItemChanged(QListWidgetItem*,QListWidgetItem*)) );
- connect( m_view, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
- this, SLOT(itemDoubleClicked(QListWidgetItem*)) );
- connect( m_view, SIGNAL(customContextMenuRequested(QPoint)),
- this, SLOT(itemMenuRequested(QPoint)) );
+ connect( m_view, &KListWidget::currentItemChanged,
+ this, &CoverFoundDialog::currentItemChanged );
+ connect( m_view, &KListWidget::itemDoubleClicked,
+ this, &CoverFoundDialog::itemDoubleClicked );
+ connect( m_view, &KListWidget::customContextMenuRequested,
+ this, &CoverFoundDialog::itemMenuRequested );
splitter->addWidget( m_sideBar );
splitter->addWidget( vbox );
setMainWidget( splitter );
const KConfigGroup config = Amarok::config( "Cover Fetcher" );
const QString source = config.readEntry( "Interactive Image Source", "LastFm" );
m_sortEnabled = config.readEntry( "Sort by Size", false );
m_sortAction->setChecked( m_sortEnabled );
m_isSorted = m_sortEnabled;
restoreDialogSize( config ); // call this after setMainWidget()
if( source == "LastFm" )
lastFmAct->setChecked( true );
else if( source == "Discogs" )
discogsAct->setChecked( true );
else
googleAct->setChecked( true );
typedef CoverFetchArtPayload CFAP;
const CFAP *payload = dynamic_cast< const CFAP* >( unit->payload() );
if( !m_album->hasImage() )
m_sideBar->setPixmap( QPixmap::fromImage( m_album->image(190 ) ) );
else if( payload )
add( m_album->image(), data, payload->imageSize() );
else
add( m_album->image(), data );
m_view->setCurrentItem( m_view->item( 0 ) );
updateGui();
- connect( The::networkAccessManager(), SIGNAL(requestRedirected(QNetworkReply*,QNetworkReply*)),
- this, SLOT(fetchRequestRedirected(QNetworkReply*,QNetworkReply*)) );
+ connect( The::networkAccessManager(), &NetworkAccessManagerProxy::requestRedirectedReply,
+ this, &CoverFoundDialog::fetchRequestRedirected );
}
CoverFoundDialog::~CoverFoundDialog()
{
m_album->setSuppressImageAutoFetch( false );
const QList<QListWidgetItem*> &viewItems = m_view->findItems( QChar('*'), Qt::MatchWildcard );
qDeleteAll( viewItems );
delete m_dialog.data();
}
void CoverFoundDialog::hideEvent( QHideEvent *event )
{
KConfigGroup config = Amarok::config( "Cover Fetcher" );
saveDialogSize( config );
event->accept();
}
void CoverFoundDialog::add( const QImage &cover,
const CoverFetch::Metadata &metadata,
const CoverFetch::ImageSize imageSize )
{
if( cover.isNull() )
return;
if( !contains( metadata ) )
{
CoverFoundItem *item = new CoverFoundItem( cover, metadata, imageSize );
addToView( item );
}
}
void CoverFoundDialog::addToView( CoverFoundItem *item )
{
const CoverFetch::Metadata &metadata = item->metadata();
if( m_sortEnabled && metadata.contains( "width" ) && metadata.contains( "height" ) )
{
if( m_isSorted )
{
const int size = metadata.value( "width" ).toInt() * metadata.value( "height" ).toInt();
QList< int >::iterator i = qLowerBound( m_sortSizes.begin(), m_sortSizes.end(), size );
m_sortSizes.insert( i, size );
const int index = m_sortSizes.count() - m_sortSizes.indexOf( size ) - 1;
m_view->insertItem( index, item );
}
else
{
m_view->addItem( item );
sortCoversBySize();
}
}
else
{
m_view->addItem( item );
}
updateGui();
}
bool CoverFoundDialog::contains( const CoverFetch::Metadata &metadata ) const
{
for( int i = 0, count = m_view->count(); i < count; ++i )
{
CoverFoundItem *item = static_cast<CoverFoundItem*>( m_view->item(i) );
if( item->metadata() == metadata )
return true;
}
return false;
}
void CoverFoundDialog::addToCustomSearch( const QString &text )
{
const QString &query = m_search->currentText();
if( !text.isEmpty() && !query.contains( text ) )
{
QStringList q;
if( !query.isEmpty() )
q << query;
q << text;
const QString result = q.join( QChar( ' ' ) );
qobject_cast<KLineEdit*>( m_search->lineEdit() )->setText( result );
}
}
void CoverFoundDialog::clearQueryButtonClicked()
{
m_query.clear();
m_queryPage = 0;
updateGui();
}
void CoverFoundDialog::clearView()
{
m_view->clear();
m_sideBar->clear();
m_sortSizes.clear();
updateGui();
}
void CoverFoundDialog::insertComboText( const QString &text )
{
if( text.isEmpty() )
return;
if( m_search->contains( text ) )
{
m_search->setCurrentIndex( m_search->findText( text ) );
return;
}
m_search->completionObject()->addItem( text );
m_search->insertItem( 0, KStandardGuiItem::find().icon(), text );
m_search->setCurrentIndex( 0 );
}
void CoverFoundDialog::currentItemChanged( QListWidgetItem *current, QListWidgetItem *previous )
{
Q_UNUSED( previous )
if( !current )
return;
CoverFoundItem *it = static_cast< CoverFoundItem* >( current );
QImage image = it->hasBigPix() ? it->bigPix() : it->thumb();
m_image = image;
m_sideBar->setPixmap( QPixmap::fromImage(image), it->metadata() );
}
void CoverFoundDialog::itemDoubleClicked( QListWidgetItem *item )
{
Q_UNUSED( item )
slotButtonClicked( KDialog::Ok );
}
void CoverFoundDialog::itemMenuRequested( const QPoint &pos )
{
const QPoint globalPos = m_view->mapToGlobal( pos );
QModelIndex index = m_view->indexAt( pos );
if( !index.isValid() )
return;
CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->item( index.row() ) );
item->setSelected( true );
QMenu menu( this );
QAction *display = new QAction( QIcon::fromTheme("zoom-original"), i18n("Display Cover"), &menu );
- connect( display, SIGNAL(triggered()), this, SLOT(display()) );
+ connect( display, &QAction::triggered, this, &CoverFoundDialog::display );
QAction *save = new QAction( QIcon::fromTheme("document-save"), i18n("Save As"), &menu );
- connect( save, SIGNAL(triggered()), this, SLOT(saveAs()) );
+ connect( save, &QAction::triggered, this, &CoverFoundDialog::saveAs );
menu.addAction( display );
menu.addAction( save );
menu.exec( globalPos );
}
void CoverFoundDialog::saveAs()
{
CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
if( !item->hasBigPix() && !fetchBigPix() )
return;
Meta::TrackList tracks = m_album->tracks();
if( tracks.isEmpty() )
{
warning() << "no tracks associated with album" << m_album->name();
return;
}
KFileDialog dlg( QUrl::fromLocalFile(tracks.first()->playableUrl().adjusted(QUrl::RemoveFilename).path()), QString(), this );
QWidget::setWindowTitle( i18n("Cover Image Save Location") );
dlg.setMode( KFile::File | KFile::LocalOnly );
dlg.setOperationMode( KFileDialog::Saving );
dlg.setConfirmOverwrite( true );
dlg.setSelection( "cover.jpg" );
QStringList supportedMimeTypes;
supportedMimeTypes << "image/jpeg";
supportedMimeTypes << "image/png";
dlg.setMimeFilter( supportedMimeTypes );
QUrl saveUrl;
int res = dlg.exec();
switch( res )
{
case QDialog::Accepted:
saveUrl = dlg.selectedUrl();
break;
case QDialog::Rejected:
return;
}
KSaveFile saveFile( saveUrl.path() );
if( !saveFile.open() )
{
KMessageBox::detailedError( this,
i18n("Sorry, the cover could not be saved."),
saveFile.errorString() );
return;
}
const QImage &image = item->bigPix();
QMimeDatabase db;
const QString &ext = db.suffixForFileName( saveUrl.path() ).toLower();
if( ext == "jpg" || ext == "jpeg" )
image.save( &saveFile, "JPG" );
else if( ext == "png" )
image.save( &saveFile, "PNG" );
else
image.save( &saveFile );
if( (saveFile.size() == 0) || !saveFile.finalize() )
{
KMessageBox::detailedError( this,
i18n("Sorry, the cover could not be saved."),
saveFile.errorString() );
saveFile.remove();
}
}
void CoverFoundDialog::slotButtonClicked( int button )
{
if( button == KDialog::Ok )
{
CoverFoundItem *item = dynamic_cast< CoverFoundItem* >( m_view->currentItem() );
if( !item )
{
reject();
return;
}
bool gotBigPix( true );
if( !item->hasBigPix() )
gotBigPix = fetchBigPix();
if( gotBigPix )
{
m_image = item->bigPix();
accept();
}
}
else
{
KDialog::slotButtonClicked( button );
}
}
void CoverFoundDialog::fetchRequestRedirected( QNetworkReply *oldReply,
QNetworkReply *newReply )
{
QUrl oldUrl = oldReply->request().url();
QUrl newUrl = newReply->request().url();
// Since we were redirected we have to check if the redirect
// was for one of our URLs and if the new URL is not handled
// already.
if( m_urls.contains( oldUrl ) && !m_urls.contains( newUrl ) )
{
// Get the unit for the old URL.
CoverFoundItem *item = m_urls.value( oldUrl );
// Add the unit with the new URL and remove the old one.
m_urls.insert( newUrl, item );
m_urls.remove( oldUrl );
}
}
void CoverFoundDialog::handleFetchResult( const QUrl &url, QByteArray data,
NetworkAccessManagerProxy::Error e )
{
CoverFoundItem *item = m_urls.take( url );
QImage image;
if( item && e.code == QNetworkReply::NoError && image.loadFromData( data ) )
{
item->setBigPix( image );
m_sideBar->setPixmap( QPixmap::fromImage( image ) );
if( m_dialog )
m_dialog.data()->accept();
}
else
{
QStringList errors;
errors << e.description;
KMessageBox::errorList( this, i18n("Sorry, the cover image could not be retrieved."), errors );
if( m_dialog )
m_dialog.data()->reject();
}
}
bool CoverFoundDialog::fetchBigPix()
{
DEBUG_BLOCK
CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
const QUrl url( item->metadata().value( "normalarturl" ) );
if( !url.isValid() )
return false;
QNetworkReply *reply = The::networkAccessManager()->getData( url, this,
SLOT(handleFetchResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) );
m_urls.insert( url, item );
if( !m_dialog )
{
m_dialog = new KProgressDialog( this );
m_dialog.data()->setCaption( i18n( "Fetching Large Cover" ) );
m_dialog.data()->setLabelText( i18n( "Download Progress" ) );
m_dialog.data()->setModal( true );
m_dialog.data()->setAllowCancel( true );
m_dialog.data()->setAutoClose( false );
m_dialog.data()->setAutoReset( true );
m_dialog.data()->progressBar()->setMinimum( 0 );
m_dialog.data()->setMinimumWidth( 300 );
- connect( reply, SIGNAL(downloadProgress(qint64,qint64)),
- SLOT(downloadProgressed(qint64,qint64)) );
+ connect( reply, &QNetworkReply::downloadProgress,
+ this, &CoverFoundDialog::downloadProgressed );
}
int result = m_dialog.data()->exec();
bool success = (result == QDialog::Accepted) && !m_dialog.data()->wasCancelled();
The::networkAccessManager()->abortGet( url );
if( !success )
m_urls.remove( url );
m_dialog.data()->deleteLater();
return success;
}
void CoverFoundDialog::downloadProgressed( qint64 bytesReceived, qint64 bytesTotal )
{
if( m_dialog )
{
m_dialog.data()->progressBar()->setMaximum( bytesTotal );
m_dialog.data()->progressBar()->setValue( bytesReceived );
}
}
void CoverFoundDialog::display()
{
CoverFoundItem *item = static_cast< CoverFoundItem* >( m_view->currentItem() );
const bool success = item->hasBigPix() ? true : fetchBigPix();
if( !success )
return;
const QImage &image = item->hasBigPix() ? item->bigPix() : item->thumb();
QWeakPointer<CoverViewDialog> dlg = new CoverViewDialog( image, this );
dlg.data()->show();
dlg.data()->raise();
dlg.data()->activateWindow();
}
-void CoverFoundDialog::processQuery()
+void CoverFoundDialog::processCurrentQuery()
{
const QString text = m_search->currentText();
processQuery( text );
}
void CoverFoundDialog::processQuery( const QString &input )
{
const bool inputEmpty( input.isEmpty() );
const bool mQueryEmpty( m_query.isEmpty() );
QString q;
if( inputEmpty && !mQueryEmpty )
{
q = m_query;
}
else if( !inputEmpty || !mQueryEmpty )
{
q = input;
if( m_query != input )
{
m_query = input;
m_queryPage = 0;
}
}
if( !q.isEmpty() )
{
emit newCustomQuery( m_album, q, m_queryPage );
updateSearchButton( q );
m_queryPage++;
}
}
void CoverFoundDialog::selectDiscogs()
{
KConfigGroup config = Amarok::config( "Cover Fetcher" );
config.writeEntry( "Interactive Image Source", "Discogs" );
m_sortAction->setEnabled( true );
m_queryPage = 0;
- processQuery();
+ processCurrentQuery();
debug() << "Select Discogs as source";
}
void CoverFoundDialog::selectLastFm()
{
KConfigGroup config = Amarok::config( "Cover Fetcher" );
config.writeEntry( "Interactive Image Source", "LastFm" );
m_sortAction->setEnabled( false );
m_queryPage = 0;
- processQuery();
+ processCurrentQuery();
debug() << "Select Last.fm as source";
}
void CoverFoundDialog::selectGoogle()
{
KConfigGroup config = Amarok::config( "Cover Fetcher" );
config.writeEntry( "Interactive Image Source", "Google" );
m_sortAction->setEnabled( true );
m_queryPage = 0;
- processQuery();
+ processCurrentQuery();
debug() << "Select Google as source";
}
void CoverFoundDialog::setQueryPage( int page )
{
m_queryPage = page;
}
void CoverFoundDialog::sortingTriggered( bool checked )
{
KConfigGroup config = Amarok::config( "Cover Fetcher" );
config.writeEntry( "Sort by Size", checked );
m_sortEnabled = checked;
m_isSorted = false;
if( m_sortEnabled )
sortCoversBySize();
debug() << "Enable sorting by size:" << checked;
}
void CoverFoundDialog::sortCoversBySize()
{
DEBUG_BLOCK
m_sortSizes.clear();
QList< QListWidgetItem* > viewItems = m_view->findItems( QChar('*'), Qt::MatchWildcard );
QMultiMap<int, CoverFoundItem*> sortItems;
// get a list of cover items sorted (automatically by qmap) by size
foreach( QListWidgetItem *viewItem, viewItems )
{
CoverFoundItem *coverItem = dynamic_cast<CoverFoundItem*>( viewItem );
const CoverFetch::Metadata &meta = coverItem->metadata();
const int itemSize = meta.value( "width" ).toInt() * meta.value( "height" ).toInt();
sortItems.insert( itemSize, coverItem );
m_sortSizes << itemSize;
}
// take items from the view and insert into a temp list in the sorted order
QList<CoverFoundItem*> coverItems = sortItems.values();
QList<CoverFoundItem*> tempItems;
for( int i = 0, count = sortItems.count(); i < count; ++i )
{
CoverFoundItem *item = coverItems.value( i );
const int itemRow = m_view->row( item );
QListWidgetItem *itemFromRow = m_view->takeItem( itemRow );
if( itemFromRow )
tempItems << dynamic_cast<CoverFoundItem*>( itemFromRow );
}
// add the items back to the view in descending order
foreach( CoverFoundItem* item, tempItems )
m_view->insertItem( 0, item );
m_isSorted = true;
}
void CoverFoundDialog::updateSearchButton( const QString &text )
{
const bool isNewSearch = ( text != m_query ) ? true : false;
m_searchButton->setGuiItem( isNewSearch ? KStandardGuiItem::find() : KStandardGuiItem::cont() );
m_searchButton->setToolTip( isNewSearch ? i18n( "Search" ) : i18n( "Search For More Results" ) );
}
void CoverFoundDialog::updateGui()
{
updateTitle();
if( !m_search->hasFocus() )
setButtonFocus( KDialog::Ok );
update();
}
void CoverFoundDialog::updateTitle()
{
const int itemCount = m_view->count();
const QString caption = ( itemCount == 0 )
? i18n( "No Images Found" )
: i18np( "1 Image Found", "%1 Images Found", itemCount );
setCaption( caption );
}
CoverFoundSideBar::CoverFoundSideBar( const Meta::AlbumPtr album, QWidget *parent )
: KVBox( parent )
, m_album( album )
{
m_cover = new QLabel( this );
m_tabs = new QTabWidget( this );
m_notes = new QLabel;
QScrollArea *metaArea = new QScrollArea;
m_metaTable = new QWidget( metaArea );
m_metaTable->setLayout( new QFormLayout );
m_metaTable->setMinimumSize( QSize( 150, 200 ) );
metaArea->setFrameShape( QFrame::NoFrame );
metaArea->setWidget( m_metaTable );
m_notes->setAlignment( Qt::AlignLeft | Qt::AlignTop );
m_notes->setMargin( 4 );
m_notes->setOpenExternalLinks( true );
m_notes->setTextFormat( Qt::RichText );
m_notes->setTextInteractionFlags( Qt::TextBrowserInteraction );
m_notes->setWordWrap( true );
m_cover->setAlignment( Qt::AlignCenter );
m_tabs->addTab( metaArea, i18n( "Information" ) );
m_tabs->addTab( m_notes, i18n( "Notes" ) );
setMaximumWidth( 200 );
setPixmap( QPixmap::fromImage( m_album->image( 190 ) ) );
clear();
}
CoverFoundSideBar::~CoverFoundSideBar()
{
}
void CoverFoundSideBar::clear()
{
clearMetaTable();
m_notes->clear();
m_metadata.clear();
}
void CoverFoundSideBar::setPixmap( const QPixmap &pixmap, CoverFetch::Metadata metadata )
{
m_metadata = metadata;
updateNotes();
setPixmap( pixmap );
}
void CoverFoundSideBar::setPixmap( const QPixmap &pixmap )
{
m_pixmap = pixmap;
QPixmap scaledPix = pixmap.scaled( QSize( 190, 190 ), Qt::KeepAspectRatio );
QPixmap prettyPix = The::svgHandler()->addBordersToPixmap( scaledPix, 5, QString(), true );
m_cover->setPixmap( prettyPix );
updateMetaTable();
}
void CoverFoundSideBar::updateNotes()
{
bool enableNotes( false );
if( m_metadata.contains( "notes" ) )
{
const QString notes = m_metadata.value( "notes" );
if( !notes.isEmpty() )
{
m_notes->setText( notes );
enableNotes = true;
}
else
enableNotes = false;
}
else
{
m_notes->clear();
enableNotes = false;
}
m_tabs->setTabEnabled( m_tabs->indexOf( m_notes ), enableNotes );
}
void CoverFoundSideBar::updateMetaTable()
{
clearMetaTable();
QFormLayout *layout = static_cast< QFormLayout* >( m_metaTable->layout() );
layout->setSizeConstraint( QLayout::SetMinAndMaxSize );
CoverFetch::Metadata::const_iterator mit = m_metadata.constBegin();
while( mit != m_metadata.constEnd() )
{
const QString &value = mit.value();
if( !value.isEmpty() )
{
const QString &tag = mit.key();
QString name;
#define TAGHAS(s) (tag.compare(QLatin1String(s)) == 0)
if( TAGHAS("artist") ) name = i18nc( "@item::intable", "Artist" );
else if( TAGHAS("country") ) name = i18nc( "@item::intable", "Country" );
else if( TAGHAS("date") ) name = i18nc( "@item::intable", "Date" );
else if( TAGHAS("format") ) name = i18nc( "@item::intable File Format", "Format" );
else if( TAGHAS("height") ) name = i18nc( "@item::intable Image Height", "Height" );
else if( TAGHAS("name") ) name = i18nc( "@item::intable Album Title", "Title" );
else if( TAGHAS("type") ) name = i18nc( "@item::intable Release Type", "Type" );
else if( TAGHAS("released") ) name = i18nc( "@item::intable Release Date", "Released" );
else if( TAGHAS("size") ) name = i18nc( "@item::intable File Size", "Size" );
else if( TAGHAS("source") ) name = i18nc( "@item::intable Cover Provider", "Source" );
else if( TAGHAS("title") ) name = i18nc( "@item::intable Album Title", "Title" );
else if( TAGHAS("width") ) name = i18nc( "@item::intable Image Width", "Width" );
#undef TAGHAS
if( !name.isEmpty() )
{
QLabel *label = new QLabel( value, 0 );
label->setToolTip( value );
layout->addRow( QString("<b>%1:</b>").arg(name), label );
}
}
++mit;
}
QString refUrl;
const QString source = m_metadata.value( "source" );
if( source == "Last.fm" || source == "Discogs" )
{
refUrl = m_metadata.value( "releaseurl" );
}
else if( source == "Google" )
{
refUrl = m_metadata.value( "imgrefurl" );
}
if( !refUrl.isEmpty() )
{
QFont font;
QFontMetrics qfm( font );
const QString &toolUrl = refUrl;
const QString &tooltip = qfm.elidedText( toolUrl, Qt::ElideMiddle, 350 );
const QString &decoded = QUrl::fromPercentEncoding( refUrl.toLocal8Bit() );
const QString &url = QString( "<a href=\"%1\">%2</a>" )
.arg( decoded )
.arg( i18nc("@item::intable URL", "link") );
QLabel *label = new QLabel( url, 0 );
label->setOpenExternalLinks( true );
label->setTextInteractionFlags( Qt::TextBrowserInteraction );
label->setToolTip( tooltip );
layout->addRow( QString( "<b>%1:</b>" ).arg( i18nc("@item::intable", "URL") ), label );
}
}
void CoverFoundSideBar::clearMetaTable()
{
QFormLayout *layout = static_cast< QFormLayout* >( m_metaTable->layout() );
int count = layout->count();
while( --count >= 0 )
{
QLayoutItem *child = layout->itemAt( 0 );
layout->removeItem( child );
delete child->widget();
delete child;
}
}
CoverFoundItem::CoverFoundItem( const QImage &cover,
const CoverFetch::Metadata &data,
const CoverFetch::ImageSize imageSize,
QListWidget *parent )
: QListWidgetItem( parent )
, m_metadata( data )
{
switch( imageSize )
{
default:
case CoverFetch::NormalSize:
m_bigPix = cover;
break;
case CoverFetch::ThumbSize:
m_thumb = cover;
break;
}
QPixmap scaledPix = QPixmap::fromImage(cover.scaled( QSize( 120, 120 ), Qt::KeepAspectRatio ));
QPixmap prettyPix = The::svgHandler()->addBordersToPixmap( scaledPix, 5, QString(), true );
setSizeHint( QSize( 140, 150 ) );
setIcon( prettyPix );
setCaption();
setFont( KGlobalSettings::smallestReadableFont() );
setTextAlignment( Qt::AlignHCenter | Qt::AlignTop );
}
CoverFoundItem::~CoverFoundItem()
{
}
bool CoverFoundItem::operator==( const CoverFoundItem &other ) const
{
return m_metadata == other.m_metadata;
}
bool CoverFoundItem::operator!=( const CoverFoundItem &other ) const
{
return !( *this == other );
}
void CoverFoundItem::setCaption()
{
QStringList captions;
const QString &width = m_metadata.value( QLatin1String("width") );
const QString &height = m_metadata.value( QLatin1String("height") );
if( !width.isEmpty() && !height.isEmpty() )
captions << QString( "%1 x %2" ).arg( width ).arg( height );
int size = m_metadata.value( QLatin1String("size") ).toInt();
if( size )
{
const QString source = m_metadata.value( QLatin1String("source") );
captions << ( QString::number( size ) + QLatin1Char('k') );
}
if( !captions.isEmpty() )
setText( captions.join( QLatin1String( " - " ) ) );
}
diff --git a/src/covermanager/CoverFoundDialog.h b/src/covermanager/CoverFoundDialog.h
index 1ff09f8fc3..3bbebf5f67 100644
--- a/src/covermanager/CoverFoundDialog.h
+++ b/src/covermanager/CoverFoundDialog.h
@@ -1,189 +1,189 @@
/****************************************************************************************
* Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2004 Stefan Bogner <bochi@online.ms> *
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2009 Martin Sandsmark <sandsmark@samfundet.no> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_COVERFOUNDDIALOG_H
#define AMAROK_COVERFOUNDDIALOG_H
#include "core/meta/forward_declarations.h"
#include "covermanager/CoverFetchUnit.h"
#include "network/NetworkAccessManagerProxy.h"
#include <KProgressDialog>
#include <KVBox>
#include <QLabel>
#include <QList>
#include <QListWidgetItem>
#include <QObject>
#include <QWeakPointer>
class CoverFoundItem;
class CoverFoundSideBar;
class KDialog;
class KJob;
class KJobProgressBar;
class KComboBox;
class KListWidget;
class KPushButton;
class QFrame;
class QGridLayout;
class QTabWidget;
class CoverFoundDialog : public KDialog
{
Q_OBJECT
public:
explicit CoverFoundDialog( const CoverFetchUnit::Ptr unit,
const CoverFetch::Metadata &data = CoverFetch::Metadata(),
QWidget *parent = 0 );
~CoverFoundDialog();
/**
* @returns the currently selected cover image
*/
const QImage image() const { return m_image; }
void setQueryPage( int page );
const CoverFetchUnit::Ptr unit() const { return m_unit; }
Q_SIGNALS:
void newCustomQuery( Meta::AlbumPtr album, const QString &query, int page );
public Q_SLOTS:
void add( const QImage &cover,
const CoverFetch::Metadata &metadata,
const CoverFetch::ImageSize imageSize = CoverFetch::NormalSize );
protected:
void hideEvent( QHideEvent *event );
protected Q_SLOTS:
void slotButtonClicked( int button );
private Q_SLOTS:
void addToCustomSearch( const QString &text );
void clearQueryButtonClicked();
void clearView();
void downloadProgressed( qint64 bytesReceived, qint64 bytesTotal );
void fetchRequestRedirected( QNetworkReply *oldReply, QNetworkReply *newReply );
void handleFetchResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e );
void insertComboText( const QString &text );
void currentItemChanged( QListWidgetItem *current, QListWidgetItem *previous );
void itemDoubleClicked( QListWidgetItem *item );
void itemMenuRequested( const QPoint &pos );
- void processQuery();
+ void processCurrentQuery(); // Same as processQuery( QString() )
void display(); ///< Opens a pixmap viewer
- void processQuery( const QString &query );
+ void processQuery( const QString &input = QString() );
void saveAs();
void selectDiscogs();
void selectLastFm();
void selectGoogle();
void sortingTriggered( bool checked );
void updateSearchButton( const QString &text );
private:
void addToView( CoverFoundItem *item );
bool contains( const CoverFetch::Metadata &metadata ) const;
bool fetchBigPix(); ///< returns true if full-size image is fetched successfully
void sortCoversBySize();
void updateGui();
void updateTitle();
CoverFoundSideBar *m_sideBar; //!< View of selected cover and its metadata
KComboBox *m_search; //!< Custom search input
KListWidget *m_view; //!< View of retrieved covers
QPushButton *m_save; //!< Save Button
KPushButton *m_searchButton; //!< Button to start search or get more results for last query
Meta::AlbumPtr m_album; //!< Album associated with @ref m_unit;
QAction *m_sortAction; //!< Action to sort covers by size
QList< int > m_sortSizes; //!< List of sorted cover sizes used for indexing
QImage m_image; //!< Currently selected cover image
QString m_query; //!< Cache for the last entered custom query
bool m_isSorted; //!< Are the covers sorted in the view?
bool m_sortEnabled; //!< Sort covers by size
const CoverFetchUnit::Ptr m_unit; //!< Cover fetch unit that initiated this dialog
int m_queryPage; //!< Cache for the page number associated with @ref m_query
QHash<QUrl, CoverFoundItem*> m_urls; //!< Urls hash for network access manager proxy
QWeakPointer<KProgressDialog> m_dialog; //!< Progress dialog for fetching big pix
Q_DISABLE_COPY( CoverFoundDialog )
};
class CoverFoundSideBar : public KVBox
{
Q_OBJECT
public:
explicit CoverFoundSideBar( const Meta::AlbumPtr album, QWidget *parent = 0 );
~CoverFoundSideBar();
public Q_SLOTS:
void clear();
void setPixmap( const QPixmap &pixmap, CoverFetch::Metadata metadata );
void setPixmap( const QPixmap &pixmap );
private:
Meta::AlbumPtr m_album;
QLabel *m_notes;
QLabel *m_cover;
QPixmap m_pixmap;
QTabWidget *m_tabs;
QWidget *m_metaTable;
CoverFetch::Metadata m_metadata;
void updateNotes();
void updateMetaTable();
void clearMetaTable();
Q_DISABLE_COPY( CoverFoundSideBar )
};
class CoverFoundItem : public QListWidgetItem
{
public:
explicit CoverFoundItem( const QImage &cover,
const CoverFetch::Metadata &data,
const CoverFetch::ImageSize imageSize = CoverFetch::NormalSize,
QListWidget *parent = 0 );
~CoverFoundItem();
const CoverFetch::Metadata metadata() const { return m_metadata; }
const QImage bigPix() const { return m_bigPix; }
const QImage thumb() const { return m_thumb; }
bool hasBigPix() const { return !m_bigPix.isNull(); }
void setBigPix( const QImage &image ) { m_bigPix = image; }
bool operator==( const CoverFoundItem &other ) const;
bool operator!=( const CoverFoundItem &other ) const;
private:
void setCaption();
CoverFetch::Metadata m_metadata;
QImage m_thumb;
QImage m_bigPix;
Q_DISABLE_COPY( CoverFoundItem )
};
#endif /* AMAROK_COVERFOUNDDIALOG_H */
diff --git a/src/covermanager/CoverManager.cpp b/src/covermanager/CoverManager.cpp
index 3b48da0078..1b0506062b 100644
--- a/src/covermanager/CoverManager.cpp
+++ b/src/covermanager/CoverManager.cpp
@@ -1,890 +1,901 @@
/****************************************************************************************
* Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it> *
* Copyright (c) 2005 Isaiah Damron <xepo@trifault.net> *
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CoverManager"
#include "CoverManager.h"
#include "amarokconfig.h"
#include <config.h>
#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 <QApplication>
#include <KIO/NetAccess>
#include <KLocalizedString>
#include <QMenu> //showCoverMenu()
#include <KPushButton>
#include <KSqueezedTextLabel> //status label
#include <KStatusBar>
#include <KToolBar>
#include <KVBox>
#include <KIconLoader>
#include <QAction>
#include <QDesktopWidget>
#include <QProgressBar>
#include <QSplitter>
#include <QStringList>
#include <QTimer> //search filter timer
#include <QToolButton>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
static QString artistToSelectInInitFunction;
CoverManager *CoverManager::s_instance = 0;
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 = 0 )
: 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();
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &CoverManager::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &CoverManager::reject);
setWindowTitle( i18n("Cover Manager") );
setAttribute( Qt::WA_DeleteOnClose );
- connect( this, SIGNAL(hidden()), SLOT(delayedDestruct()) );
- connect(buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), SLOT(delayedDestruct()) );
+ // 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, SIGNAL(newResultReady(Meta::ArtistList)),
- this, SLOT(slotArtistQueryResult(Meta::ArtistList)) );
+ connect( qm, &Collections::QueryMaker::newArtistsReady,
+ this, &CoverManager::slotArtistQueryResult );
- connect( qm, SIGNAL(queryDone()), this, SLOT(slotContinueConstruction()) );
+ 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 );
KVBox *vbox = new KVBox( m_splitter );
KHBox *hbox = new KHBox( vbox );
vbox->setSpacing( 4 );
hbox->setSpacing( 4 );
{ //<Search LineEdit>
m_searchEdit = new Amarok::LineEdit( hbox );
m_searchEdit->setClickMessage( i18n( "Enter search terms here" ) );
m_searchEdit->setFrame( true );
m_searchEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum);
m_searchEdit->setClearButtonShown( true );
hbox->setStretchFactor( m_searchEdit, 1 );
} //</Search LineEdit>
// view menu
m_viewButton = new KPushButton( hbox );
m_viewMenu = new QMenu( m_viewButton );
- m_selectAllAlbums = m_viewMenu->addAction( i18n("All Albums"), this, SLOT(slotShowAllAlbums()) );
- m_selectAlbumsWithCover = m_viewMenu->addAction( i18n("Albums With Cover"), this, SLOT(slotShowAlbumsWithCover()) );
- m_selectAlbumsWithoutCover = m_viewMenu->addAction( i18n("Albums Without Cover"), this, SLOT(slotShowAlbumsWithoutCover()) );
+ 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" ) );
- connect( m_viewMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAlbumFilterTriggered(QAction*)) );
+ connect( m_viewMenu, &QMenu::triggered, this, &CoverManager::slotAlbumFilterTriggered );
//fetch missing covers button
m_fetchButton = new KPushButton( KGuiItem( i18n("Fetch Missing Covers"), "get-hot-new-stuff-amarok" ), hbox );
- connect( m_fetchButton, SIGNAL(clicked()), SLOT(fetchMissingCovers()) );
+ 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
KStatusBar *statusBar = new KStatusBar( 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, SIGNAL(allDone()), this, SLOT(progressAllDone()) );
+ 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, SIGNAL(itemSelectionChanged()),
- SLOT(slotArtistSelected()) );
- connect( m_coverView, SIGNAL(itemActivated(QListWidgetItem*)),
- SLOT(coverItemClicked(QListWidgetItem*)) );
- connect( m_timer, SIGNAL(timeout()),
- SLOT(slotSetFilter()) );
- connect( m_searchEdit, SIGNAL(textChanged(QString)),
- SLOT(slotSetFilterTimeout()) );
+ 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 = 0;
}
void
CoverManager::viewCover( Meta::AlbumPtr album, QWidget *parent ) //static
{
//QDialog means "escape" works as expected
QDialog *dialog = new CoverViewDialog( album, parent );
dialog->show();
}
void
CoverManager::metadataChanged( 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<CoverViewItem*>( 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, SIGNAL(finishedSingle(int)), SLOT(updateFetchingProgress(int)) );
+ 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, SIGNAL(newResultReady(Meta::AlbumList)),
- this, SLOT(slotAlbumQueryResult(Meta::AlbumList)) );
+ connect( qm, &Collections::QueryMaker::newAlbumsReady,
+ this, &CoverManager::slotAlbumQueryResult );
- connect( qm, SIGNAL(queryDone()), this, SLOT(slotArtistQueryDone()) );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &CoverManager::slotArtistQueryDone );
qm->run();
}
void
CoverManager::slotAlbumQueryResult( 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, SIGNAL(cancelled()), this, SLOT(cancelCoverViewLoading()) );
+ 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<CoverViewItem*>(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<CoverViewItem*>(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<CoverViewItem*>(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<CoverViewItem*>(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", " <b>%1</b> 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, SIGNAL(finishedSingle(int)), this, SLOT(updateFetchingProgress(int)) );
- QTimer::singleShot( 2000, this, SLOT(updateStatusBar()) );
+ 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 <b>%1</b> 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<CoverViewItem*>( 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 - ( <b>1</b> without cover )", "%2 - ( <b>%1</b> without cover )",
missingCounter, text );
m_fetchButton->setEnabled( missingCounter );
}
m_statusLabel->setText( text );
}
+void
+CoverManager::delayedDestruct()
+{
+ if ( isVisible() )
+ hide();
+
+ deleteLater();
+}
+
void
CoverManager::setStatusText( QString text )
{
m_oldStatusText = m_statusLabel->text();
m_statusLabel->setText( text );
}
//////////////////////////////////////////////////////////////////////
// CLASS CoverView
/////////////////////////////////////////////////////////////////////
CoverView::CoverView( QWidget *parent, const char *name, Qt::WFlags 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, SIGNAL(itemEntered(QListWidgetItem*)), SLOT(setStatusText(QListWidgetItem*)) );
- connect( this, SIGNAL(viewportEntered()), CoverManager::instance(), SLOT(updateStatusBar()) );
+ connect( this, &CoverView::itemEntered, this, &CoverView::setStatusText );
+ connect( this, &CoverView::viewportEntered, CoverManager::instance(), &CoverManager::updateStatusBar );
}
void
CoverView::contextMenuEvent( QContextMenuEvent *event )
{
QList<QListWidgetItem*> 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<CoverViewItem*>( items.first() );
QList<QAction *> actions;
Meta::AlbumPtr album = item->albumPtr();
if( album )
{
QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() );
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<CoverViewItem*>(item);
Meta::AlbumPtr album = cvItem->albumPtr();
if( album )
{
QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() );
if( ac )
{
QList<QAction *> actions = ac->actions();
foreach( QAction *action, actions )
{
if( qobject_cast<FetchCoverAction*>(action) )
fetchAlbums << album;
else if( qobject_cast<UnsetCoverAction*>(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 item static_cast<CoverViewItem *>( item )
if ( !item )
return;
const QString artist = item->albumPtr()->isCompilation() ? i18n( "Various Artists" ) : item->artist();
const QString tipContent = i18n( "%1 - %2", artist , item->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/covermanager/CoverManager.h b/src/covermanager/CoverManager.h
index 9cc04bfe31..ee9f50fac4 100644
--- a/src/covermanager/CoverManager.h
+++ b/src/covermanager/CoverManager.h
@@ -1,188 +1,189 @@
/****************************************************************************************
* Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it> *
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef COVERMANAGER_H
#define COVERMANAGER_H
#include "core/meta/Observer.h"
#include "covermanager/CoverFetcher.h"
#include <KDialog>
#include <QDropEvent>
#include <QLabel>
#include <QListWidget>
#include <QListWidgetItem>
#include <QAction>
namespace Amarok { class LineEdit; }
class CompoundProgressBar;
class CoverViewItem;
class QTreeWidget;
class QTreeWidgetItem;
class KSqueezedTextLabel;
class KPushButton;
class QMenu;
class QLabel;
class CoverView;
class KHBox;
class QProgressBar;
class QHBoxLayout;
class QColorGroup;
class QSplitter;
class CoverManager : public QDialog, public Meta::Observer
{
Q_OBJECT
static CoverManager *s_instance;
static bool s_constructed;
public:
CoverManager( QWidget *parent = 0 );
~CoverManager();
static bool isConstructed() { return s_constructed; }
static CoverManager *instance() { return s_instance; }
static void showOnce( const QString &artist = QString(), QWidget* parent = 0 );
static void viewCover( Meta::AlbumPtr album, QWidget* parent = 0 );
void setStatusText( QString text );
// Reimplemented from Meta::Observer
using Observer::metadataChanged;
void metadataChanged( Meta::AlbumPtr album );
public Q_SLOTS:
void updateStatusBar();
+ void delayedDestruct();
private:
enum View { AllAlbums = 0, AlbumsWithCover, AlbumsWithoutCover };
private Q_SLOTS:
void slotArtistQueryResult( Meta::ArtistList artists );
void slotContinueConstruction();
void slotArtistSelected();
void slotAlbumQueryResult( Meta::AlbumList albums );
void slotAlbumFilterTriggered( QAction *action );
void slotArtistQueryDone();
void coverItemClicked( QListWidgetItem *item );
void slotSetFilter();
void slotSetFilterTimeout();
void slotShowAllAlbums() { changeView( AllAlbums ); }
void slotShowAlbumsWithCover() { changeView( AlbumsWithCover ); }
void slotShowAlbumsWithoutCover() { changeView( AlbumsWithoutCover ); }
void changeView( View id, bool force = false );
void fetchMissingCovers();
void updateFetchingProgress( int state );
void stopFetching();
void progressAllDone();
void cancelCoverViewLoading();
private:
void loadCover( const QString &, const QString & );
QSplitter *m_splitter;
QTreeWidget *m_artistView;
CoverView *m_coverView;
//hack to have something to show while the real list is hidden when loading thumbnails
CoverView *m_coverViewSpacer;
Amarok::LineEdit *m_searchEdit;
KPushButton *m_fetchButton;
KPushButton *m_viewButton;
QMenu *m_viewMenu;
View m_currentView;
Meta::ArtistList m_artistList;
QList< QTreeWidgetItem* > m_items;
Meta::AlbumList m_albumList;
CoverFetcher *m_fetcher;
QAction *m_selectAllAlbums;
QAction *m_selectAlbumsWithCover;
QAction *m_selectAlbumsWithoutCover;
//status bar widgets
CompoundProgressBar *m_progress;
KSqueezedTextLabel *m_statusLabel;
QString m_oldStatusText;
QTimer *m_timer; //search filter timer
QList<CoverViewItem*> m_coverItems; //used for filtering
QString m_filter;
// Used by fetchCoversLoop() for temporary storage
Meta::AlbumList m_fetchCovers;
//used to display information about cover fetching in the status bar
bool m_fetchingCovers;
int m_coversFetched;
int m_coverErrors;
bool m_isLoadingCancelled;
};
class CoverView : public QListWidget
{
Q_OBJECT
public:
explicit CoverView( QWidget *parent = 0, const char *name = 0, Qt::WFlags f = 0 );
protected:
void contextMenuEvent( QContextMenuEvent *event );
private Q_SLOTS:
void setStatusText( QListWidgetItem *item );
};
class CoverViewItem : public QListWidgetItem
{
public:
CoverViewItem( QListWidget *parent, Meta::AlbumPtr album );
~CoverViewItem();
void loadCover();
bool hasCover() const;
bool canRemoveCover() const { return !m_embedded && hasCover(); }
QString artist() const { return m_artist; }
QString album() const { return m_album; }
Meta::AlbumPtr albumPtr() const { return m_albumPtr; }
protected:
void paintFocus(QPainter *, const QColorGroup &) { }
void dragEntered();
void dragLeft();
private:
Meta::AlbumPtr m_albumPtr;
QString m_artist;
QString m_album;
QString m_coverImagePath;
bool m_embedded;
};
#endif
diff --git a/src/covermanager/CoverViewDialog.cpp b/src/covermanager/CoverViewDialog.cpp
index 06fd631e64..f88288b91c 100644
--- a/src/covermanager/CoverViewDialog.cpp
+++ b/src/covermanager/CoverViewDialog.cpp
@@ -1,91 +1,91 @@
/****************************************************************************************
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CoverViewDialog.h"
#include "core/meta/Meta.h"
#include "widgets/PixmapViewer.h"
#include <QApplication>
#include <QDialog>
#include <KLocalizedString>
#include <KWindowSystem>
#include <QDesktopWidget>
#include <QHBoxLayout>
#include <KConfigGroup>
CoverViewDialog::CoverViewDialog( Meta::AlbumPtr album, QWidget *parent )
: QDialog( parent )
, m_title( i18n( "Cover View" ) )
, m_size( album->image().size() )
, m_zoom( 100 )
{
setAttribute( Qt::WA_DeleteOnClose );
updateCaption();
createViewer( album->image(), parent );
}
CoverViewDialog::CoverViewDialog( const QImage &image, QWidget *parent )
: QDialog( parent )
, m_title( i18n( "Cover View" ) )
, m_size( image.size() )
, m_zoom( 100 )
{
setAttribute( Qt::WA_DeleteOnClose );
updateCaption();
createViewer( image, parent );
}
void
CoverViewDialog::updateCaption()
{
QString width = QString::number( m_size.width() );
QString height = QString::number( m_size.height() );
QString zoom = QString::number( m_zoom );
QString size = QString( "%1x%2" ).arg( width, height );
QString caption = QString( "%1 - %2 - %3\%" ).arg( m_title, size, zoom );
setWindowTitle( caption );
}
void
CoverViewDialog::zoomFactorChanged( qreal value )
{
m_zoom = 100 * value;
updateCaption();
}
void
CoverViewDialog::createViewer( const QImage &image, const QWidget *widget )
{
int screenNumber = QApplication::desktop()->screenNumber( widget );
PixmapViewer *pixmapViewer = new PixmapViewer( this, QPixmap::fromImage(image), screenNumber );
QHBoxLayout *layout = new QHBoxLayout( this );
layout->addWidget( pixmapViewer );
layout->setSizeConstraint( QLayout::SetFixedSize );
layout->setContentsMargins( 0, 0, 0, 0 );
- connect( pixmapViewer, SIGNAL(zoomFactorChanged(qreal)), SLOT(zoomFactorChanged(qreal)) );
+ connect( pixmapViewer, &PixmapViewer::zoomFactorChanged, this, &CoverViewDialog::zoomFactorChanged );
qreal zoom = pixmapViewer->zoomFactor();
zoomFactorChanged( zoom );
QPoint topLeft = mapFromParent( widget->geometry().center() );
topLeft -= QPoint( image.width() * zoom / 2, image.height() * zoom / 2 );
move( topLeft );
activateWindow();
raise();
}
diff --git a/src/databaseimporter/SqlBatchImporter.cpp b/src/databaseimporter/SqlBatchImporter.cpp
index e084540f57..6025ddc228 100644
--- a/src/databaseimporter/SqlBatchImporter.cpp
+++ b/src/databaseimporter/SqlBatchImporter.cpp
@@ -1,131 +1,132 @@
/****************************************************************************************
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SqlBatchImporter.h"
#include "SqlBatchImporterConfig.h"
#include "core/collections/Collection.h"
+#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core/capabilities/CollectionImportCapability.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <KLocale>
#include <QFile>
SqlBatchImporter::SqlBatchImporter( QObject *parent )
: QObject( parent )
, m_config( 0 )
, m_count( 0 )
, m_importing( false )
{
- connect( this, SIGNAL(importSucceeded()), SLOT(importingFinished()) );
- connect( this, SIGNAL(importFailed()), SLOT(importingFinished()) );
- connect( this, SIGNAL(trackAdded(Meta::TrackPtr)), SLOT(trackImported(Meta::TrackPtr)) );
- connect( this, SIGNAL(trackMatchFound(Meta::TrackPtr,QString)), SLOT(trackMatched(Meta::TrackPtr,QString)) );
+ connect( this, &SqlBatchImporter::importSucceeded, this, &SqlBatchImporter::importingFinished );
+ connect( this, &SqlBatchImporter::importFailed, this, &SqlBatchImporter::importingFinished );
+ connect( this, &SqlBatchImporter::trackAdded, this, &SqlBatchImporter::trackImported );
+ connect( this, &SqlBatchImporter::trackMatchFound, this, &SqlBatchImporter::trackMatched );
}
SqlBatchImporter::~SqlBatchImporter()
{
}
SqlBatchImporterConfig*
SqlBatchImporter::configWidget( QWidget *parent )
{
if( !m_config )
m_config = new SqlBatchImporterConfig( parent );
return m_config;
}
void
SqlBatchImporter::import()
{
DEBUG_BLOCK
Q_ASSERT( m_config );
if( !m_config )
{
error() << "No configuration exists, bailing out of import";
return;
}
int numStarted = 0;
// search for a collection with the CollectionImportCapability
foreach( Collections::Collection *coll, CollectionManager::instance()->collections().keys() )
{
debug() << "Collection: "<<coll->prettyName() << "id:"<<coll->collectionId();
QScopedPointer<Capabilities::CollectionImportCapability> cic( coll->create<Capabilities::CollectionImportCapability>());
if( cic ) {
QFile *file = new QFile( m_config->inputFilePath() );
if( file->open( QIODevice::ReadOnly ) )
{
debug() << "importing db";
cic->import( file, this );
numStarted++;
} else {
debug() << "could not open";
emit importError( i18n( "Could not open file \"%1\".", m_config->inputFilePath() ) );
delete file;
}
}
}
if( !numStarted )
emit importFailed();
}
int
SqlBatchImporter::importedCount() const
{
return m_count;
}
void
SqlBatchImporter::trackImported( Meta::TrackPtr track )
{
Q_UNUSED( track )
++m_count;
}
void
SqlBatchImporter::trackMatched( Meta::TrackPtr track, QString oldUrl )
{
Q_UNUSED( track )
Q_UNUSED( oldUrl )
++m_count;
}
bool
SqlBatchImporter::importing() const
{
return m_importing;
}
void
SqlBatchImporter::startImporting()
{
DEBUG_BLOCK
m_importing = true;
import();
}
void
SqlBatchImporter::importingFinished()
{
m_importing = false;
}
diff --git a/src/dbus/DBusQueryHelper.cpp b/src/dbus/DBusQueryHelper.cpp
index cd38b2d1cf..3585e01c3c 100644
--- a/src/dbus/DBusQueryHelper.cpp
+++ b/src/dbus/DBusQueryHelper.cpp
@@ -1,81 +1,81 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DBusQueryHelper.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Debug.h"
#include <QTimer>
Q_DECLARE_METATYPE( VariantMapList )
DBusQueryHelper::DBusQueryHelper( QObject *parent, Collections::QueryMaker *qm, const QDBusConnection &conn, const QDBusMessage &msg, bool mprisCompatible )
: QObject( parent )
, m_connection( conn )
, m_message( msg )
, m_mprisCompatibleResult( mprisCompatible )
, m_timeout( false )
{
qm->setAutoDelete( true );
qm->setQueryType( Collections::QueryMaker::Track );
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(slotResultReady(Meta::TrackList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(queryDone()), this, SLOT(slotQueryDone()), Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::newTracksReady, this, &DBusQueryHelper::slotResultReady, Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &DBusQueryHelper::slotQueryDone, Qt::QueuedConnection );
qm->run();
//abort query after 15 seconds in case the query does not return
- QTimer::singleShot( 15000, this, SLOT(abortQuery()) );
+ QTimer::singleShot( 15000, this, &DBusQueryHelper::abortQuery );
}
void
DBusQueryHelper::slotResultReady( const Meta::TrackList &tracks )
{
foreach( const Meta::TrackPtr &track, tracks )
{
if( m_mprisCompatibleResult )
m_result.append( Meta::Field::mprisMapFromTrack( track ) );
else
m_result.append( Meta::Field::mapFromTrack( track ) );
}
}
void
DBusQueryHelper::slotQueryDone()
{
deleteLater();
if( m_timeout )
return;
QDBusMessage reply = m_message.createReply( QVariant::fromValue( m_result ) );
bool success = m_connection.send( reply );
if( !success )
debug() << "sending async reply failed";
}
void
DBusQueryHelper::abortQuery()
{
deleteLater();
m_timeout = true;
QDBusMessage error = m_message.createErrorReply( QDBusError::InternalError, "Internal timeout" );
bool success = m_connection.send( error );
if( !success )
debug() << "sending async error failed";
}
diff --git a/src/dbus/mpris1/PlayerHandler.cpp b/src/dbus/mpris1/PlayerHandler.cpp
index 86db791b53..4ea00eef76 100644
--- a/src/dbus/mpris1/PlayerHandler.cpp
+++ b/src/dbus/mpris1/PlayerHandler.cpp
@@ -1,281 +1,281 @@
/****************************************************************************************
* Copyright (c) 2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlayerHandler.h"
#include "ActionClasses.h"
#include "App.h"
#include "EngineController.h"
#include "Mpris1AmarokPlayerAdaptor.h"
#include "Mpris1PlayerAdaptor.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "widgets/Osd.h"
#include <Phonon/Global>
// Marshall the Status data into a D-BUS argument
QDBusArgument &operator<<(QDBusArgument &argument, const Mpris1::Status &status)
{
argument.beginStructure();
argument << status.Play;
argument << status.Random;
argument << status.Repeat;
argument << status.RepeatPlaylist;
argument.endStructure();
return argument;
}
// Retrieve the Status data from the D-BUS argument
const QDBusArgument &operator>>(const QDBusArgument &argument, Mpris1::Status &status)
{
argument.beginStructure();
argument >> status.Play;
argument >> status.Random;
argument >> status.Repeat;
argument >> status.RepeatPlaylist;
argument.endStructure();
return argument;
}
namespace Mpris1
{
PlayerHandler::PlayerHandler()
: QObject(qApp)
{
qDBusRegisterMetaType<Status>();
setObjectName("PlayerHandler");
new Mpris1PlayerAdaptor( this );
// amarok extensions:
new Mpris1AmarokPlayerAdaptor( this );
QDBusConnection::sessionBus().registerObject("/Player", this);
- connect( The::playlistActions(), SIGNAL(navigatorChanged()),
- this, SLOT(updateStatus()) );
+ connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
+ this, &PlayerHandler::updateStatus );
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(playbackStateChanged()),
- this, SLOT(slotStateChanged()) );
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
- this, SLOT(slotTrackChanged(Meta::TrackPtr)) );
+ connect( engine, &EngineController::playbackStateChanged,
+ this, &PlayerHandler::slotStateChanged );
+ connect( engine, &EngineController::trackChanged,
+ this, &PlayerHandler::slotTrackChanged );
}
Status PlayerHandler::GetStatus()
{
Status status = { 0, 0, 0, 0 };
EngineController *engine = The::engineController();
if( engine->isPlaying() )
status.Play = 0; //Playing
else if( engine->isPaused() )
status.Play = 1; //Paused
else
status.Play = 2; //Stopped
if ( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum )
status.Random = 1;
else
status.Random = 0;
if ( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack )
status.Repeat = 1;
else
status.Repeat = 0;
if ( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum )
status.RepeatPlaylist = 1;
else
status.RepeatPlaylist = 0; //the music will not end if we play random
return status;
}
void PlayerHandler::Pause()
{
The::engineController()->playPause();
}
void PlayerHandler::Play()
{
The::engineController()->play();
}
void PlayerHandler::PlayPause()
{
The::engineController()->playPause();
}
void PlayerHandler::Next()
{
The::playlistActions()->next();
}
void PlayerHandler::Prev()
{
The::playlistActions()->back();
}
void PlayerHandler::Repeat( bool on )
{
debug() << (on ? "Turning repeat on" : "Turning repeat off");
if( on )
{
//if set on, just switch to repeat track
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatTrack );
The::playlistActions()->playlistModeChanged();
}
else if( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist )
{
//if set to off, switch to normal mode if we are currently in one of the repeat modes.
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
The::playlistActions()->playlistModeChanged();
}
//else just ignore event...
}
//position is specified in milliseconds
int PlayerHandler::PositionGet()
{
return The::engineController()->trackPositionMs();
}
void PlayerHandler::PositionSet( int time )
{
if( time > 0 && !The::engineController()->isStopped() )
The::engineController()->seekTo( time );
}
void PlayerHandler::Stop()
{
The::engineController()->stop();
}
void PlayerHandler::StopAfterCurrent()
{
The::playlistActions()->stopAfterPlayingTrack();
}
int PlayerHandler::VolumeGet()
{
return The::engineController()->volume();
}
void PlayerHandler::VolumeSet( int vol )
{
The::engineController()->setVolume(vol);
}
void PlayerHandler::VolumeUp( int step ) const
{
The::engineController()->increaseVolume( step );
}
void PlayerHandler::VolumeDown( int step ) const
{
The::engineController()->decreaseVolume( step );
}
void PlayerHandler::Mute() const
{
The::engineController()->toggleMute();
}
void PlayerHandler::ShowOSD() const
{
Amarok::OSD::instance()->forceToggleOSD();
}
void PlayerHandler::LoadThemeFile( const QString &path ) const
{
The::svgHandler()->setThemeFile( path );
}
void PlayerHandler::Forward( int time )
{
if( time > 0 && !The::engineController()->isStopped() )
The::engineController()->seekTo( The::engineController()->trackPosition() * 1000 + time );
}
void PlayerHandler::Backward( int time )
{
if( time > 0 && !The::engineController()->isStopped() )
The::engineController()->seekTo( The::engineController()->trackPosition() * 1000 - time );
}
QVariantMap PlayerHandler::GetMetadata()
{
return GetTrackMetadata( The::engineController()->currentTrack() );
}
int PlayerHandler::GetCaps()
{
int caps = NONE;
Meta::TrackPtr track = The::engineController()->currentTrack();
caps |= CAN_HAS_TRACKLIST;
if ( track )
caps |= CAN_PROVIDE_METADATA;
if ( GetStatus().Play == 0 /*playing*/ )
caps |= CAN_PAUSE;
if ( ( GetStatus().Play == 1 /*paused*/ ) || ( GetStatus().Play == 2 /*stoped*/ ) )
caps |= CAN_PLAY;
if ( ( GetStatus().Play == 0 /*playing*/ ) || ( GetStatus().Play == 1 /*paused*/ ) )
caps |= CAN_SEEK;
if ( ( The::playlist()->activeRow() >= 0 ) && ( The::playlist()->activeRow() <= The::playlist()->qaim()->rowCount() ) )
{
caps |= CAN_GO_NEXT;
caps |= CAN_GO_PREV;
}
return caps;
}
void PlayerHandler::updateStatus()
{
Status status = GetStatus();
emit StatusChange( status );
emit CapsChange( GetCaps() );
}
QVariantMap PlayerHandler::GetTrackMetadata( Meta::TrackPtr track )
{
return Meta::Field::mprisMapFromTrack( track );
}
void PlayerHandler::slotTrackChanged( Meta::TrackPtr track )
{
emit TrackChange( GetTrackMetadata( track ) );
}
void PlayerHandler::slotStateChanged()
{
updateStatus();
}
} // namespace Amarok
diff --git a/src/dbus/mpris1/TrackListHandler.cpp b/src/dbus/mpris1/TrackListHandler.cpp
index 1dd31faff9..b3893594f7 100644
--- a/src/dbus/mpris1/TrackListHandler.cpp
+++ b/src/dbus/mpris1/TrackListHandler.cpp
@@ -1,148 +1,148 @@
/****************************************************************************************
* Copyright (c) 2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TrackListHandler.h"
#include "ActionClasses.h"
#include "amarokconfig.h"
#include "App.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core/podcasts/PodcastProvider.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "dbus/mpris1/PlayerHandler.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistController.h"
#include "playlistmanager/PlaylistManager.h"
#include "playlist/PlaylistModelStack.h"
#include "Mpris1TrackListAdaptor.h"
namespace Mpris1
{
TrackListHandler::TrackListHandler()
: QObject( qApp )
{
new Mpris1TrackListAdaptor(this);
QDBusConnection::sessionBus().registerObject( "/TrackList", this );
- connect( The::playlist()->qaim(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotTrackListChange()) );
- connect( The::playlist()->qaim(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotTrackListChange()) );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::rowsInserted, this, &TrackListHandler::slotTrackListChange );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::rowsRemoved, this, &TrackListHandler::slotTrackListChange );
}
int TrackListHandler::AddTrack( const QString &url, bool playImmediately )
{
Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( QUrl::fromUserInput(url) );
if( track )
{
Playlist::AddOptions options = playImmediately ?
Playlist::OnPlayMediaAction : Playlist::OnAppendToPlaylistAction;
The::playlistController()->insertOptioned( track, options );
return 0;
}
else
return -1;
}
void TrackListHandler::DelTrack( int index )
{
if( index < GetLength() )
The::playlistController()->removeRow( index );
}
int TrackListHandler::GetCurrentTrack()
{
return The::playlist()->activeRow();
}
int TrackListHandler::GetLength()
{
return The::playlist()->qaim()->rowCount();
}
QVariantMap TrackListHandler::GetMetadata( int position )
{
return Meta::Field::mprisMapFromTrack( The::playlist()->trackAt( position ) );
}
void TrackListHandler::SetLoop( bool enable )
{
if( enable )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatPlaylist );
The::playlistActions()->playlistModeChanged();
}
else if( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
The::playlistActions()->playlistModeChanged();
}
}
void TrackListHandler::SetRandom( bool enable )
{
if( enable )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RandomTrack );
The::playlistActions()->playlistModeChanged();
}
else if( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
The::playlistActions()->playlistModeChanged();
}
}
void TrackListHandler::PlayTrack( int index )
{
The::playlistActions()->play( index );
}
void TrackListHandler::slotTrackListChange()
{
emit TrackListChange( The::playlist()->qaim()->rowCount() );
}
void TrackListHandler::UpdateAllPodcasts()
{
foreach( Playlists::PlaylistProvider *provider,
The::playlistManager()->providersForCategory( PlaylistManager::PodcastChannel ) )
{
Podcasts::PodcastProvider *podcastProvider = dynamic_cast<Podcasts::PodcastProvider*>( provider );
if( podcastProvider )
podcastProvider->updateAll();
}
}
void TrackListHandler::AddPodcast( const QString& url )
{
//RSS 1.0/2.0 or Atom feed URL
Podcasts::PodcastProvider *podcastProvider = The::playlistManager()->defaultPodcasts();
if( podcastProvider )
{
if( !url.isEmpty() )
podcastProvider->addPodcast( Podcasts::PodcastProvider::toFeedUrl( url.trimmed() ) );
else
error() << "Invalid input string";
}
else
error() << "PodcastChannel provider is null";
}
}
diff --git a/src/dbus/mpris2/MediaPlayer2AmarokExtensions.cpp b/src/dbus/mpris2/MediaPlayer2AmarokExtensions.cpp
index ba8b54ea32..515da01d9b 100644
--- a/src/dbus/mpris2/MediaPlayer2AmarokExtensions.cpp
+++ b/src/dbus/mpris2/MediaPlayer2AmarokExtensions.cpp
@@ -1,72 +1,72 @@
/***********************************************************************
* Copyright 2010 Canonical Ltd
* (author: Aurelien Gateau <aurelien.gateau@canonical.com>)
* Copyright 2012 Alex Merry <alex.merry@kdemail.net>
*
* 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 <http://www.gnu.org/licenses/>.
***********************************************************************/
#include "MediaPlayer2AmarokExtensions.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "playlist/PlaylistActions.h"
using namespace Amarok;
MediaPlayer2AmarokExtensions::MediaPlayer2AmarokExtensions(QObject* parent)
: DBusAbstractAdaptor(parent)
{
- connect( The::engineController(), SIGNAL(muteStateChanged(bool)),
- this, SLOT(mutedChanged(bool)) );
+ connect( The::engineController(), &EngineController::muteStateChanged,
+ this, &MediaPlayer2AmarokExtensions::mutedChanged );
}
MediaPlayer2AmarokExtensions::~MediaPlayer2AmarokExtensions()
{
}
bool MediaPlayer2AmarokExtensions::Muted() const
{
return The::engineController()->isMuted();
}
void MediaPlayer2AmarokExtensions::setMuted( bool muted )
{
The::engineController()->setMuted( muted );
}
void MediaPlayer2AmarokExtensions::StopAfterCurrent()
{
The::playlistActions()->stopAfterPlayingTrack();
}
void MediaPlayer2AmarokExtensions::AdjustVolume( double increaseBy )
{
int volume = The::engineController()->volume() + (increaseBy * 100);
if (volume < 0)
volume = 0;
if (volume > 100)
volume = 100;
The::engineController()->setVolume( volume );
}
void MediaPlayer2AmarokExtensions::mutedChanged( bool newValue )
{
signalPropertyChange( "Muted", newValue );
}
diff --git a/src/dbus/mpris2/MediaPlayer2Player.cpp b/src/dbus/mpris2/MediaPlayer2Player.cpp
index 62c7eacc3c..3f510c92ae 100644
--- a/src/dbus/mpris2/MediaPlayer2Player.cpp
+++ b/src/dbus/mpris2/MediaPlayer2Player.cpp
@@ -1,422 +1,422 @@
/***********************************************************************
* Copyright 2010 Canonical Ltd
* (author: Aurelien Gateau <aurelien.gateau@canonical.com>)
* Copyright 2012 Eike Hein <hein@kde.org>
* Copyright 2012 Alex Merry <alex.merry@kdemail.net>
*
* 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 <http://www.gnu.org/licenses/>.
***********************************************************************/
#include "MediaPlayer2Player.h"
#include "EngineController.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistController.h"
#include "playlist/PlaylistModelStack.h"
#include <QCryptographicHash>
#include <QUrl>
static QDBusObjectPath mprisTrackId(quint64 playlistTrackId)
{
QString path;
if( playlistTrackId > 0 ) {
path = QString( "/org/kde/amarok/Track/%1" ).arg( playlistTrackId );
} else {
// dropped out of the playlist
path = QLatin1String( "/org/kde/amarok/OrphanTrack" );
}
return QDBusObjectPath( path );
}
static QDBusObjectPath activeMprisTrackId()
{
return mprisTrackId( The::playlist()->activeId() );
}
using namespace Amarok;
MediaPlayer2Player::MediaPlayer2Player(QObject* parent)
: DBusAbstractAdaptor(parent)
, m_lastPosition(-1)
{
- connect( The::engineController(), SIGNAL(trackPositionChanged(qint64,bool)),
- this, SLOT(trackPositionChanged(qint64,bool)) );
+ connect( The::engineController(), &EngineController::trackPositionChanged,
+ this, &MediaPlayer2Player::trackPositionChanged );
// it is important that we receive this signal *after* the playlist code
// has dealt with it, in order to get the right value for mpris:trackid
- connect( The::engineController(), SIGNAL(trackChanged(Meta::TrackPtr)),
- this, SLOT(trackChanged(Meta::TrackPtr)),
+ connect( The::engineController(), &EngineController::trackChanged,
+ this, &MediaPlayer2Player::trackChanged,
Qt::QueuedConnection );
- connect( The::engineController(), SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- this, SLOT(trackMetadataChanged(Meta::TrackPtr)) );
- connect( The::engineController(), SIGNAL(albumMetadataChanged(Meta::AlbumPtr)),
- this, SLOT(albumMetadataChanged(Meta::AlbumPtr)) );
- connect( The::engineController(), SIGNAL(seekableChanged(bool)),
- this, SLOT(seekableChanged(bool)) );
- connect( The::engineController(), SIGNAL(volumeChanged(int)),
- this, SLOT(volumeChanged(int)) );
- connect( The::engineController(), SIGNAL(trackLengthChanged(qint64)),
- this, SLOT(trackLengthChanged(qint64)) );
- connect( The::engineController(), SIGNAL(playbackStateChanged()),
- this, SLOT(playbackStateChanged()) );
- connect( The::playlistActions(), SIGNAL(navigatorChanged()),
- this, SLOT(playlistNavigatorChanged()) );
- connect( The::playlist()->qaim(), SIGNAL(rowsInserted(QModelIndex,int,int)),
- this, SLOT(playlistRowsInserted(QModelIndex,int,int)) );
- connect( The::playlist()->qaim(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
- this, SLOT(playlistRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
- connect( The::playlist()->qaim(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
- this, SLOT(playlistRowsRemoved(QModelIndex,int,int)) );
- connect( The::playlist()->qaim(), SIGNAL(modelReset()),
- this, SLOT(playlistReplaced()) );
+ connect( The::engineController(), &EngineController::trackMetadataChanged,
+ this, &MediaPlayer2Player::trackMetadataChanged );
+ connect( The::engineController(), &EngineController::albumMetadataChanged,
+ this, &MediaPlayer2Player::albumMetadataChanged );
+ connect( The::engineController(), &EngineController::seekableChanged,
+ this, &MediaPlayer2Player::seekableChanged );
+ connect( The::engineController(), &EngineController::volumeChanged,
+ this, &MediaPlayer2Player::volumeChanged );
+ connect( The::engineController(), &EngineController::trackLengthChanged,
+ this, &MediaPlayer2Player::trackLengthChanged );
+ connect( The::engineController(), &EngineController::playbackStateChanged,
+ this, &MediaPlayer2Player::playbackStateChanged );
+ connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
+ this, &MediaPlayer2Player::playlistNavigatorChanged );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::rowsInserted,
+ this, &MediaPlayer2Player::playlistRowsInserted );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::rowsMoved,
+ this, &MediaPlayer2Player::playlistRowsMoved );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::rowsRemoved,
+ this, &MediaPlayer2Player::playlistRowsRemoved );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::modelReset,
+ this, &MediaPlayer2Player::playlistReplaced );
connect( qobject_cast<Playlist::ProxyBase*>(The::playlist()->qaim()),
- SIGNAL(activeTrackChanged(quint64)),
- this, SLOT(playlistActiveTrackChanged(quint64)) );
+ &Playlist::ProxyBase::activeTrackChanged,
+ this, &MediaPlayer2Player::playlistActiveTrackChanged );
}
MediaPlayer2Player::~MediaPlayer2Player()
{
}
bool MediaPlayer2Player::CanGoNext() const
{
if ( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist )
{
return The::playlist()->qaim()->rowCount() > 0;
}
else
{
int activeRow = The::playlist()->activeRow();
return activeRow < The::playlist()->qaim()->rowCount() - 1;
}
}
void MediaPlayer2Player::Next() const
{
The::playlistActions()->next();
}
bool MediaPlayer2Player::CanGoPrevious() const
{
if ( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist )
{
return The::playlist()->qaim()->rowCount() > 0;
}
else
{
return The::playlist()->activeRow() > 0;
}
}
void MediaPlayer2Player::Previous() const
{
The::playlistActions()->back();
}
bool MediaPlayer2Player::CanPause() const
{
return The::engineController()->currentTrack();
}
void MediaPlayer2Player::Pause() const
{
The::engineController()->pause();
}
void MediaPlayer2Player::PlayPause() const
{
The::engineController()->playPause();
}
void MediaPlayer2Player::Stop() const
{
The::engineController()->stop();
}
bool MediaPlayer2Player::CanPlay() const
{
return The::playlist()->qaim()->rowCount() > 0;
}
void MediaPlayer2Player::Play() const
{
The::engineController()->play();
}
void MediaPlayer2Player::SetPosition( const QDBusObjectPath& TrackId, qlonglong position ) const
{
QDBusObjectPath activeTrackId = activeMprisTrackId();
if( TrackId == activeTrackId )
The::engineController()->seekTo( position / 1000 );
else
debug() << "SetPosition() called with a trackId (" << TrackId.path() << ") which is not for the active track (" << activeTrackId.path() << ")";
}
void MediaPlayer2Player::OpenUri( QString Uri ) const
{
QUrl url( Uri );
The::playlistController()->insertOptioned( url, Playlist::OnPlayMediaAction );
}
QString MediaPlayer2Player::PlaybackStatus() const
{
if( The::engineController()->isPlaying() )
return QLatin1String( "Playing" );
else if ( The::engineController()->isPaused() )
return QLatin1String( "Paused" );
else
return QLatin1String( "Stopped" );
}
QString MediaPlayer2Player::LoopStatus() const
{
switch( AmarokConfig::trackProgression() )
{
case AmarokConfig::EnumTrackProgression::Normal:
case AmarokConfig::EnumTrackProgression::OnlyQueue:
case AmarokConfig::EnumTrackProgression::RandomTrack:
case AmarokConfig::EnumTrackProgression::RandomAlbum:
return QLatin1String( "None" );
case AmarokConfig::EnumTrackProgression::RepeatTrack:
return QLatin1String( "Track" );
case AmarokConfig::EnumTrackProgression::RepeatAlbum:
case AmarokConfig::EnumTrackProgression::RepeatPlaylist:
return QLatin1String( "Playlist" );
default:
Q_ASSERT( false );
return QLatin1String( "None" );
}
}
void MediaPlayer2Player::setLoopStatus( const QString& status ) const
{
AmarokConfig::EnumTrackProgression::type progression;
if( status == QLatin1String( "None" ) )
progression = AmarokConfig::EnumTrackProgression::Normal;
else if( status == QLatin1String( "Track" ) )
progression = AmarokConfig::EnumTrackProgression::RepeatTrack;
else if( status == QLatin1String( "Playlist" ) )
progression = AmarokConfig::EnumTrackProgression::RepeatPlaylist;
else
{
debug() << "Unknown loop status:" << status;
return;
}
AmarokConfig::setTrackProgression( progression );
The::playlistActions()->playlistModeChanged();
}
double MediaPlayer2Player::Rate() const
{
return 1.0;
}
void MediaPlayer2Player::setRate( double rate ) const
{
Q_UNUSED(rate)
}
bool MediaPlayer2Player::Shuffle() const
{
switch( AmarokConfig::trackProgression() )
{
case AmarokConfig::EnumTrackProgression::RandomAlbum:
case AmarokConfig::EnumTrackProgression::RandomTrack:
return true;
default:
return false;
}
}
void MediaPlayer2Player::setShuffle( bool shuffle ) const
{
AmarokConfig::EnumTrackProgression::type progression;
if( shuffle )
progression = AmarokConfig::EnumTrackProgression::RandomTrack;
else
progression = AmarokConfig::EnumTrackProgression::Normal;
AmarokConfig::setTrackProgression( progression );
The::playlistActions()->playlistModeChanged();
}
QVariantMap MediaPlayer2Player::metadataForTrack( Meta::TrackPtr track ) const
{
if ( !track )
return QVariantMap();
QVariantMap metaData = Meta::Field::mpris20MapFromTrack( track );
if ( track == The::playlist()->activeTrack() )
metaData["mpris:trackid"] = QVariant::fromValue<QDBusObjectPath>( activeMprisTrackId() );
else {
// we should be updated shortly
QString path = QLatin1String( "/org/kde/amarok/PendingTrack" );
metaData["mpris:trackid"] = QVariant::fromValue<QDBusObjectPath>( QDBusObjectPath( path ) );
}
return metaData;
}
QVariantMap MediaPlayer2Player::Metadata() const
{
return metadataForTrack( The::engineController()->currentTrack() );
}
double MediaPlayer2Player::Volume() const
{
return static_cast<double>(The::engineController()->volume()) / 100.0;
}
void MediaPlayer2Player::setVolume( double volume ) const
{
if (volume < 0.0)
volume = 0.0;
if (volume > 1.0)
volume = 1.0;
The::engineController()->setVolume( volume * 100 );
}
qlonglong MediaPlayer2Player::Position() const
{
return The::engineController()->trackPositionMs() * 1000;
}
double MediaPlayer2Player::MinimumRate() const
{
return 1.0;
}
double MediaPlayer2Player::MaximumRate() const
{
return 1.0;
}
bool MediaPlayer2Player::CanSeek() const
{
return The::engineController()->isSeekable();
}
void MediaPlayer2Player::Seek( qlonglong Offset ) const
{
qlonglong offsetMs = Offset / 1000;
int position = The::engineController()->trackPositionMs() + offsetMs;
if( position < 0 )
Previous();
else if( position > The::engineController()->trackLength() )
Next();
else
The::engineController()->seekTo( position );
}
bool MediaPlayer2Player::CanControl() const
{
return true;
}
void MediaPlayer2Player::trackPositionChanged( qint64 position, bool userSeek )
{
if ( userSeek )
emit Seeked( position * 1000 );
m_lastPosition = position;
}
void MediaPlayer2Player::trackChanged( Meta::TrackPtr track )
{
signalPropertyChange( "CanPause", CanPause() );
signalPropertyChange( "Metadata", metadataForTrack( track ) );
}
void MediaPlayer2Player::trackMetadataChanged( Meta::TrackPtr track )
{
signalPropertyChange( "Metadata", metadataForTrack( track ) );
}
void MediaPlayer2Player::albumMetadataChanged( Meta::AlbumPtr album )
{
Q_UNUSED( album )
signalPropertyChange( "Metadata", Metadata() );
}
void MediaPlayer2Player::seekableChanged( bool seekable )
{
signalPropertyChange( "CanSeek", seekable );
}
void MediaPlayer2Player::volumeChanged( int percent )
{
DEBUG_BLOCK
signalPropertyChange( "Volume", static_cast<double>(percent) / 100.0 );
}
void MediaPlayer2Player::trackLengthChanged( qint64 milliseconds )
{
// milliseconds < 0 is not a helpful value, and generally happens
// when the track changes or playback is stopped; these cases are
// dealt with better by other signal handlers
if ( milliseconds >= 0 )
signalPropertyChange( "Metadata", Metadata() );
}
void MediaPlayer2Player::playbackStateChanged()
{
signalPropertyChange( "PlaybackStatus", PlaybackStatus() );
}
void MediaPlayer2Player::playlistNavigatorChanged()
{
signalPropertyChange( "CanGoNext", CanGoNext() );
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
signalPropertyChange( "LoopStatus", LoopStatus() );
signalPropertyChange( "Shuffle", Shuffle() );
}
void MediaPlayer2Player::playlistRowsInserted( QModelIndex, int, int )
{
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
}
void MediaPlayer2Player::playlistRowsMoved( QModelIndex, int, int, QModelIndex, int )
{
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
}
void MediaPlayer2Player::playlistRowsRemoved( QModelIndex, int, int )
{
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
}
void MediaPlayer2Player::playlistReplaced()
{
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
}
void MediaPlayer2Player::playlistActiveTrackChanged( quint64 )
{
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
signalPropertyChange( "CanGoPrevious", CanGoPrevious() );
}
diff --git a/src/dialogs/CollectionSetup.cpp b/src/dialogs/CollectionSetup.cpp
index bf7496ed1e..861718f909 100644
--- a/src/dialogs/CollectionSetup.cpp
+++ b/src/dialogs/CollectionSetup.cpp
@@ -1,437 +1,437 @@
/****************************************************************************************
* Copyright (c) 2003 Scott Wheeler <wheeler@kde.org> *
* Copyright (c) 2004 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2004-2008 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2008 Sebastian Trueg <trueg@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CollectionSetup.h"
#include "amarokconfig.h"
#include "core/collections/Collection.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "dialogs/DatabaseImporterDialog.h"
#include <KLocale>
#include <KGlobalSettings>
#include <KPushButton>
#include <KVBox>
#include <QAction>
#include <QApplication>
#include <QCheckBox>
#include <QDir>
#include <QFile>
#include <QLabel>
#include <QMenu>
CollectionSetup* CollectionSetup::s_instance;
CollectionSetup::CollectionSetup( QWidget *parent )
: QWidget( parent )
, m_rescanDirAction( new QAction( this ) )
{
m_ui.setupUi(this);
setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
setObjectName( "CollectionSetup" );
s_instance = this;
if( KGlobalSettings::graphicEffectsLevel() != KGlobalSettings::NoEffects )
m_ui.view->setAnimated( true );
- connect( m_ui.view, SIGNAL(clicked(QModelIndex)),
- this, SIGNAL(changed()) );
+ connect( m_ui.view, &QTreeView::clicked,
+ this, &CollectionSetup::changed );
- connect( m_ui.view, SIGNAL(pressed(QModelIndex)),
- this, SLOT(slotPressed(QModelIndex)) );
- connect( m_rescanDirAction, SIGNAL(triggered()),
- this, SLOT(slotRescanDirTriggered()) );
+ connect( m_ui.view, &QTreeView::pressed,
+ this, &CollectionSetup::slotPressed );
+ connect( m_rescanDirAction, &QAction::triggered,
+ this, &CollectionSetup::slotRescanDirTriggered );
KPushButton *rescan = new KPushButton( QIcon::fromTheme( "collection-rescan-amarok" ), i18n( "Full rescan" ), m_ui.buttonContainer );
rescan->setToolTip( i18n( "Rescan your entire collection. This will <i>not</i> delete any statistics." ) );
- connect( rescan, SIGNAL(clicked()), CollectionManager::instance(), SLOT(startFullScan()) );
+ connect( rescan, &QAbstractButton::clicked, CollectionManager::instance(), &CollectionManager::startFullScan );
KPushButton *import = new KPushButton( QIcon::fromTheme( "tools-wizard" ), i18n( "Import batch file..." ), m_ui.buttonContainer );
import->setToolTip( i18n( "Import collection from file produced by amarokcollectionscanner." ) );
- connect( import, SIGNAL(clicked()), this, SLOT(importCollection()) );
+ connect( import, &QAbstractButton::clicked, this, &CollectionSetup::importCollection );
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addWidget( rescan );
buttonLayout->addWidget( import );
m_ui.buttonContainer->setLayout( buttonLayout );
m_recursive = new QCheckBox( i18n("&Scan folders recursively (requires full rescan if newly checked)"), m_ui.checkboxContainer );
m_monitor = new QCheckBox( i18n("&Watch folders for changes"), m_ui.checkboxContainer );
- connect( m_recursive, SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
- connect( m_monitor , SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
+ connect( m_recursive, &QCheckBox::toggled, this, &CollectionSetup::changed );
+ connect( m_monitor , &QCheckBox::toggled, this, &CollectionSetup::changed );
QVBoxLayout *checkboxLayout = new QVBoxLayout();
checkboxLayout->addWidget( m_recursive );
checkboxLayout->addWidget( m_monitor );
m_ui.checkboxContainer->setLayout( checkboxLayout );
m_recursive->setToolTip( i18n( "If selected, Amarok will read all subfolders." ) );
m_monitor->setToolTip( i18n( "If selected, the collection folders will be watched "
"for changes.\nThe watcher will not notice changes behind symbolic links." ) );
m_recursive->setChecked( AmarokConfig::scanRecursively() );
m_monitor->setChecked( AmarokConfig::monitorChanges() );
// set the model _after_ constructing the checkboxes
m_model = new CollectionFolder::Model( this );
m_ui.view->setModel( m_model );
#ifndef Q_OS_WIN
m_ui.view->setRootIndex( m_model->setRootPath( QDir::rootPath() ) );
#else
m_ui.view->setRootIndex( m_model->setRootPath( m_model->myComputer().toString() ) );
#endif
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
QStringList dirs = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
m_model->setDirectories( dirs );
// make sure that the tree is expanded to show all selected items
foreach( const QString &dir, dirs )
{
QModelIndex index = m_model->index( dir );
m_ui.view->scrollTo( index, QAbstractItemView::EnsureVisible );
}
}
void
CollectionSetup::writeConfig()
{
DEBUG_BLOCK
AmarokConfig::setScanRecursively( recursive() );
AmarokConfig::setMonitorChanges( monitor() );
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
QStringList collectionFolders = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
if( m_model->directories() != collectionFolders )
{
debug() << "Selected collection folders: " << m_model->directories();
if( primaryCollection )
primaryCollection->setProperty( "collectionFolders", m_model->directories() );
debug() << "Old collection folders: " << collectionFolders;
CollectionManager::instance()->startFullScan();
}
}
bool
CollectionSetup::hasChanged() const
{
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
QStringList collectionFolders = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
return
m_model->directories() != collectionFolders ||
m_recursive->isChecked() != AmarokConfig::scanRecursively() ||
m_monitor->isChecked() != AmarokConfig::monitorChanges();
}
bool
CollectionSetup::recursive() const
{ return m_recursive && m_recursive->isChecked(); }
bool
CollectionSetup::monitor() const
{ return m_monitor && m_monitor->isChecked(); }
const QString
CollectionSetup::modelFilePath( const QModelIndex &index ) const
{
return m_model->filePath( index );
}
void
CollectionSetup::importCollection()
{
DatabaseImporterDialog *dlg = new DatabaseImporterDialog( this );
dlg->exec(); // be modal to avoid messing about by the user in the application
}
void
CollectionSetup::slotPressed( const QModelIndex &index )
{
DEBUG_BLOCK
// --- show context menu on right mouse button
if( ( QApplication::mouseButtons() & Qt::RightButton ) )
{
m_currDir = modelFilePath( index );
debug() << "Setting current dir to " << m_currDir;
// check if there is an sql collection covering the directory
// it's covered, so we can show the rescan option
if( isDirInCollection( m_currDir ) )
{
m_rescanDirAction->setText( i18n( "Rescan '%1'", m_currDir ) );
QMenu menu;
menu.addAction( m_rescanDirAction );
menu.exec( QCursor::pos() );
}
}
}
void
CollectionSetup::slotRescanDirTriggered()
{
DEBUG_BLOCK
CollectionManager::instance()->startIncrementalScan( m_currDir );
}
bool
CollectionSetup::isDirInCollection( const QString& path ) const
{
DEBUG_BLOCK
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
QStringList collectionFolders = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
QUrl url = QUrl( path );
QUrl parentUrl;
foreach( const QString &dir, collectionFolders )
{
debug() << "Collection Location: " << dir;
debug() << "path: " << path;
debug() << "scan Recursively: " << AmarokConfig::scanRecursively();
parentUrl.setPath( dir );
if ( !AmarokConfig::scanRecursively() )
{
if ( ( dir == path ) || ( QString( dir + '/' ) == path ) )
return true;
}
else //scan recursively
{
if (parentUrl.isParentOf( QUrl::fromLocalFile(path) ) || parentUrl.matches(QUrl::fromLocalFile(path), QUrl::StripTrailingSlash))
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////
// CLASS Model
//////////////////////////////////////////////////////////////////////////////////////////
namespace CollectionFolder {
Model::Model( QObject *parent )
: QFileSystemModel( parent )
{
setFilter( QDir::AllDirs | QDir::NoDotAndDotDot );
}
Qt::ItemFlags
Model::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QFileSystemModel::flags( index );
const QString path = filePath( index );
if( isForbiddenPath( path ) )
flags ^= Qt::ItemIsEnabled; //disabled!
flags |= Qt::ItemIsUserCheckable;
return flags;
}
QVariant
Model::data( const QModelIndex& index, int role ) const
{
if( index.isValid() && index.column() == 0 && role == Qt::CheckStateRole )
{
const QString path = filePath( index );
if( recursive() && ancestorChecked( path ) )
return Qt::Checked; // always set children of recursively checked parents to checked
if( isForbiddenPath( path ) )
return Qt::Unchecked; // forbidden paths can never be checked
if( !m_checked.contains( path ) && descendantChecked( path ) )
return Qt::PartiallyChecked;
return m_checked.contains( path ) ? Qt::Checked : Qt::Unchecked;
}
return QFileSystemModel::data( index, role );
}
bool
Model::setData( const QModelIndex& index, const QVariant& value, int role )
{
if( index.isValid() && index.column() == 0 && role == Qt::CheckStateRole )
{
const QString path = filePath( index );
if( value.toInt() == Qt::Checked )
{
// New path selected
if( recursive() )
{
// Recursive, so clear any paths in m_checked that are made
// redundant by this new selection
QString _path = normalPath( path );
foreach( const QString &elem, m_checked )
{
if( normalPath( elem ).startsWith( _path ) )
m_checked.remove( elem );
}
}
m_checked << path;
}
else
{
// Path un-selected
m_checked.remove( path );
if( recursive() && ancestorChecked( path ) )
{
// Recursive, so we need to deal with the case of un-selecting
// an implicitly selected path
const QStringList ancestors = allCheckedAncestors( path );
QString topAncestor;
// Remove all selected ancestor of path, and find shallowest
// ancestor
foreach( QString elem, ancestors )
{
m_checked.remove( elem );
if( elem < topAncestor || topAncestor.isEmpty() )
topAncestor = elem;
}
// Check all paths reachable from topAncestor, except for
// those that are ancestors of path
checkRecursiveSubfolders( topAncestor, path );
}
}
// A check or un-check can possibly require the whole view to change,
// so we signal that the root's data is changed
emit dataChanged( QModelIndex(), QModelIndex() );
return true;
}
return QFileSystemModel::setData( index, value, role );
}
void
Model::setDirectories( QStringList &dirs )
{
m_checked.clear();
foreach( const QString &dir, dirs )
{
m_checked.insert( dir );
}
}
QStringList
Model::directories() const
{
QStringList dirs = m_checked.toList();
qSort( dirs.begin(), dirs.end() );
// we need to remove any children of selected items as
// they are redundant when recursive mode is chosen
if( recursive() )
{
foreach( const QString &dir, dirs )
{
if( ancestorChecked( dir ) )
dirs.removeAll( dir );
}
}
return dirs;
}
inline bool
Model::isForbiddenPath( const QString &path ) const
{
// we need the trailing slash otherwise we could forbid "/dev-music" for example
QString _path = normalPath( path );
return _path.startsWith( "/proc/" ) || _path.startsWith( "/dev/" ) || _path.startsWith( "/sys/" );
}
bool
Model::ancestorChecked( const QString &path ) const
{
// we need the trailing slash otherwise sibling folders with one as the prefix of the other are seen as parent/child
const QString _path = normalPath( path );
foreach( const QString &element, m_checked )
{
const QString _element = normalPath( element );
if( _path.startsWith( _element ) && _element != _path )
return true;
}
return false;
}
/**
* Get a list of all checked paths that are an ancestor of
* the given path.
*/
QStringList
Model::allCheckedAncestors( const QString &path ) const
{
const QString _path = normalPath( path );
QStringList rtn;
foreach( const QString &element, m_checked )
{
const QString _element = normalPath( element );
if ( _path.startsWith( _element ) && _element != _path )
rtn << element;
}
return rtn;
}
bool
Model::descendantChecked( const QString &path ) const
{
// we need the trailing slash otherwise sibling folders with one as the prefix of the other are seen as parent/child
const QString _path = normalPath( path );
foreach( const QString& element, m_checked )
{
const QString _element = normalPath( element );
if( _element.startsWith( _path ) && _element != _path )
return true;
}
return false;
}
void
Model::checkRecursiveSubfolders( const QString &root, const QString &excludePath )
{
QString _root = normalPath( root );
QString _excludePath = normalPath( excludePath );
if( _root == _excludePath )
return;
QDirIterator it( _root );
while( it.hasNext() )
{
QString nextPath = it.next();
if( nextPath.endsWith( "/." ) || nextPath.endsWith( "/.." ) )
continue;
if( !_excludePath.startsWith( nextPath ) )
m_checked << nextPath;
else
checkRecursiveSubfolders( nextPath, excludePath );
}
}
} //namespace Collection
diff --git a/src/dialogs/DatabaseImporterDialog.cpp b/src/dialogs/DatabaseImporterDialog.cpp
index 1b7ce3dccd..1bbc725873 100644
--- a/src/dialogs/DatabaseImporterDialog.cpp
+++ b/src/dialogs/DatabaseImporterDialog.cpp
@@ -1,200 +1,198 @@
/****************************************************************************************
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DatabaseImporterDialog.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "databaseimporter/SqlBatchImporter.h"
#include "databaseimporter/SqlBatchImporterConfig.h"
#include <KLocale>
#include <KPageWidgetItem>
#include <KVBox>
#include <KDialog>
#include <QButtonGroup>
#include <QLabel>
#include <QPlainTextEdit>
#include <QRadioButton>
DatabaseImporterDialog::DatabaseImporterDialog( QWidget *parent )
: KAssistantDialog( parent )
, m_importer( 0 )
, m_importerConfig( 0 )
{
setAttribute( Qt::WA_DeleteOnClose );
QWidget::setWindowTitle( i18n( "Import Collection" ) );
KVBox *importerBox = new KVBox( this );
importerBox->setSpacing( KDialog::spacingHint() );
m_configBox = new KVBox( this );
m_configBox->setSpacing( KDialog::spacingHint() );
m_configPage = addPage( m_configBox, i18n("Import configuration") );
m_importer = new SqlBatchImporter( this );
- connect( m_importer, SIGNAL(importSucceeded()), this, SLOT(importSucceeded()) );
- connect( m_importer, SIGNAL(importFailed()), this, SLOT(importFailed()) );
- connect( m_importer, SIGNAL(trackAdded(Meta::TrackPtr)), this, SLOT(importedTrack(Meta::TrackPtr)) );
- connect( m_importer, SIGNAL(trackDiscarded(QString)), this, SLOT(discardedTrack(QString)) );
- connect( m_importer, SIGNAL(trackMatchFound(Meta::TrackPtr,QString)),
- this, SLOT(matchedTrack(Meta::TrackPtr,QString)) );
- connect( m_importer, SIGNAL(trackMatchMultiple(Meta::TrackList,QString)),
- this, SLOT(ambigousTrack(Meta::TrackList,QString)) );
- connect( m_importer, SIGNAL(importError(QString)), this, SLOT(importError(QString)) );
- connect( m_importer, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString)) );
+ connect( m_importer, &SqlBatchImporter::importSucceeded, this, &DatabaseImporterDialog::importSucceeded );
+ connect( m_importer, &SqlBatchImporter::importFailed, this, &DatabaseImporterDialog::importFailed );
+ connect( m_importer, &SqlBatchImporter::trackAdded, this, &DatabaseImporterDialog::importedTrack );
+ connect( m_importer, &SqlBatchImporter::trackDiscarded, this, &DatabaseImporterDialog::discardedTrack );
+ connect( m_importer, &SqlBatchImporter::trackMatchFound, this, &DatabaseImporterDialog::matchedTrack );
+ connect( m_importer, &SqlBatchImporter::trackMatchMultiple, this, &DatabaseImporterDialog::ambigousTrack );
+ connect( m_importer, &SqlBatchImporter::importError, this, &DatabaseImporterDialog::importError );
+ connect( m_importer, &SqlBatchImporter::showMessage, this, &DatabaseImporterDialog::showMessage );
m_importerConfig = m_importer->configWidget( m_configBox );
KVBox *resultBox = new KVBox( this );
resultBox->setSpacing( KDialog::spacingHint() );
m_results = new QPlainTextEdit( resultBox );
m_results->setReadOnly( true );
m_results->setTabChangesFocus( true );
m_resultsPage = addPage( resultBox, i18n("Migrating") );
- connect( this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), SLOT(pageChanged(KPageWidgetItem*,KPageWidgetItem*)) );
+ connect( this, &DatabaseImporterDialog::currentPageChanged, this, &DatabaseImporterDialog::pageChanged );
}
DatabaseImporterDialog::~DatabaseImporterDialog()
{
delete m_importer;
}
void
DatabaseImporterDialog::pageChanged( KPageWidgetItem *current, KPageWidgetItem *before )
{
DEBUG_BLOCK
if( before == m_configPage && current == m_resultsPage )
{
if( m_importer && !m_importer->importing() )
m_importer->startImporting();
QPushButton* user1Button = new QPushButton();
user1Button->setEnabled( false );
return;
}
}
void
DatabaseImporterDialog::importSucceeded()
{
// Special case the 0 import track count as it is really a failure
QString text;
if( !m_importer->importedCount() )
text = i18n( "<b><font color='red'>Failed:</font></b> No tracks were imported" );
else
text = i18np( "<b><font color='green'>Success:</font></b> Imported %1 track",
"<b><font color='green'>Success:</font></b> Imported %1 tracks", m_importer->importedCount() );
m_results->appendHtml( text );
QPushButton* user1Button = new QPushButton();
user1Button->setEnabled( true );
}
void
DatabaseImporterDialog::importFailed()
{
QString text = i18n( "<b><font color='red'>Failed:</font></b> Unable to import statistics" );
m_results->appendHtml( text );
QPushButton* user1Button = new QPushButton();
user1Button->setEnabled( true );
}
void
DatabaseImporterDialog::showMessage( QString message )
{
m_results->appendHtml( message );
}
void
DatabaseImporterDialog::importError( QString error )
{
QString text = i18n( "<b><font color='red'>Error:</font></b> %1", error );
m_results->appendHtml( text );
}
void
DatabaseImporterDialog::importedTrack( Meta::TrackPtr track )
{
if( !track ) return;
QString text;
Meta::ArtistPtr artist = track->artist();
Meta::AlbumPtr album = track->album();
if( !artist || artist->name().isEmpty() )
text = i18nc( "Track has been imported, format: Track",
"Imported <b>%1</b>", track->name() );
else if( !album || album->name().isEmpty() )
text = i18nc( "Track has been imported, format: Artist - Track",
"Imported <b>%1 - %2</b>", artist->name(), track->name() );
else
text = i18nc( "Track has been imported, format: Artist - Track (Album)",
"Imported <b>%1 - %2 (%3)</b>", artist->name(), track->name(), album->name() );
m_results->appendHtml( text );
}
void DatabaseImporterDialog::discardedTrack( QString url )
{
QString text;
text = i18nc( "Track has been discarded, format: Url",
"Discarded <b><font color='gray'>%1</font></b>", url );
m_results->appendHtml( text );
}
void DatabaseImporterDialog::matchedTrack( Meta::TrackPtr track, QString oldUrl )
{
if( !track ) return;
QString text;
Meta::ArtistPtr artist = track->artist();
Meta::AlbumPtr album = track->album();
//TODO: help text; also check wording with imported; unify?
if( !artist || artist->name().isEmpty() )
text = i18nc( "Track has been imported by tags, format: Track, from Url, to Url",
"Imported <b><font color='green'>%1</font></b><br/>&nbsp;&nbsp;from %2<br/>&nbsp;&nbsp;to %3", track->name(), oldUrl, track->prettyUrl() );
else if( !album || album->name().isEmpty() )
text = i18nc( "Track has been imported by tags, format: Artist - Track, from Url, to Url",
"Imported <b><font color='green'>%1 - %2</font></b><br/>&nbsp;&nbsp;from %3<br/>&nbsp;&nbsp;to %4", artist->name(), track->name(), oldUrl, track->prettyUrl() );
else
text = i18nc( "Track has been imported by tags, format: Artist - Track (Album), from Url, to Url",
"Imported <b><font color='green'>%1 - %2 (%3)</font></b><br/>&nbsp;&nbsp;from %4<br/>&nbsp;&nbsp;to %5", artist->name(), track->name(), album->name(), oldUrl, track->prettyUrl() );
m_results->appendHtml( text );
}
void DatabaseImporterDialog::ambigousTrack( Meta::TrackList tracks, QString oldUrl )
{
Q_UNUSED( tracks );
QString text;
// TODO: wording; etc.
text = i18nc( "Track has been matched ambigously, format: Url",
"Multiple ambiguous matches found for <b><font color='red'>%1</font></b>, has been discarded.", oldUrl );
m_results->appendHtml( text );
}
diff --git a/src/dialogs/DiagnosticDialog.cpp b/src/dialogs/DiagnosticDialog.cpp
index 340132158a..5d3b59e632 100644
--- a/src/dialogs/DiagnosticDialog.cpp
+++ b/src/dialogs/DiagnosticDialog.cpp
@@ -1,140 +1,140 @@
/****************************************************************************************
* Copyright (c) 2012 Andrzej J. R. Hunt <andrzej at ahunt.org> *
* Copyright (c) Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DiagnosticDialog.h"
#include "context/ContextView.h"
#include "PluginManager.h"
#include "scripting/scriptmanager/ScriptManager.h"
#include <KAboutData>
#include <QApplication>
#include <KGlobal>
#include <KPluginInfo>
#include <KService>
#include <KServiceTypeTrader>
#include <Plasma/Applet>
#include <phonon/pulsesupport.h>
#include <kdeversion.h>
#include <QClipboard>
#include <QPlainTextEdit>
DiagnosticDialog::DiagnosticDialog( const KAboutData about, QWidget *parent )
: KDialog( parent )
{
const KAboutData *aboutData = &about;
m_textBox = new QPlainTextEdit( generateReport( aboutData ), this );
setPlainCaption( i18nc( "%1 is the program name", "%1 Diagnostics", KAboutData::applicationData().displayName() ) );
setButtons( Close | User1 );
setButtonText( User1, i18n( "Copy to Clipboard" ) );
m_textBox->setReadOnly( true );
setMainWidget( m_textBox );
setInitialSize( QSize( 480, 460 ) );
- connect( this, SIGNAL(user1Clicked()), SLOT(slotCopyToClipboard()) );
- connect( this, SIGNAL(finished()), SLOT(deleteLater()) );
+ connect( this, &DiagnosticDialog::user1Clicked, this, &DiagnosticDialog::slotCopyToClipboard );
+ connect( this, &DiagnosticDialog::finished, this, &DiagnosticDialog::deleteLater );
}
const QString
DiagnosticDialog::generateReport( const KAboutData *aboutData )
{
// Get scripts -- we have to assemble 3 lists into one
KPluginInfo::List aScripts;
const ScriptManager *aScriptManager = ScriptManager::instance();
aScripts.append( aScriptManager->scripts( QLatin1String( "Generic" ) ) );
aScripts.append( aScriptManager->scripts( QLatin1String( "Lyrics" ) ) );
aScripts.append( aScriptManager->scripts( QLatin1String( "Scriptable Service" ) ) );
// Format the data to be readable
QString aScriptString;
foreach( KPluginInfo aInfo, aScripts )
{
if( aInfo.isPluginEnabled() )
aScriptString += " " + aInfo.name() + " (" + aInfo.version() + ")\n";
}
// Get plugins -- we have to assemble a list again.
KPluginInfo::List aPlugins;
const Plugins::PluginManager *aPluginManager = Plugins::PluginManager::instance();
aPlugins.append( aPluginManager->plugins( Plugins::PluginManager::Collection ) );
aPlugins.append( aPluginManager->plugins( Plugins::PluginManager::Service ) );
aPlugins.append( aPluginManager->plugins( Plugins::PluginManager::Importer ) );
QString aPluginString;
foreach( KPluginInfo aInfo, aPlugins )
{
if( aInfo.isPluginEnabled() )
aPluginString += " " + aInfo.name() + " (" + aInfo.version() + ")\n";
}
// Get applets
QString appletString;
/* FIXME: disabled temporarily for KF5 porting
const QStringList appletList = Context::ContextView::self()->currentAppletNames();
foreach( const QString &applet, appletList )
{
// Currently we cannot extract the applet version number this way
appletString += " " + applet + '\n';
}
*/
const KService::Ptr aPhononBackend =
KServiceTypeTrader::self()->preferredService( "PhononBackend" );
const bool hasPulse = Phonon::PulseSupport::getInstance()->isActive();
const QString pulse = hasPulse ? i18nc( "Usage", "Yes" ) : i18nc( "Usage", "No" );
return i18n(
"%1 Diagnostics\n\nGeneral Information:\n"
" %1 Version: %2\n"
" KDE Version: %3\n"
" Qt Version: %4\n"
" Phonon Version: %5\n"
" Phonon Backend: %6 (%7)\n"
" PulseAudio: %8\n\n",
KAboutData::applicationData().displayName(), aboutData->version(), // Amarok
KDE::versionString(), // KDE
qVersion(), // QT
Phonon::phononVersion(), // Phonon
aPhononBackend.data()->name(),
aPhononBackend.data()->property( "X-KDE-PhononBackendInfo-Version", QVariant::String ).toString(), // & Backend
pulse // PulseAudio
) + i18n(
"Enabled Scripts:\n%1\n"
"Enabled Plugins:\n%2\n"
"Enabled Applets:\n%3\n",
aScriptString, aPluginString, appletString
);
}
void
DiagnosticDialog::slotCopyToClipboard() const
{
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText( m_textBox->toPlainText() );
}
diff --git a/src/dialogs/EditFilterDialog.cpp b/src/dialogs/EditFilterDialog.cpp
index 410eb267f8..0c4abc5ecf 100644
--- a/src/dialogs/EditFilterDialog.cpp
+++ b/src/dialogs/EditFilterDialog.cpp
@@ -1,493 +1,493 @@
/****************************************************************************************
* Copyright (c) 2006 Giovanni Venturi <giovanni@kde-it.org> *
* Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "EditFilterDialog"
#include "amarokconfig.h"
#include "ui_EditFilterDialog.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/collections/support/Expression.h"
#include "dialogs/EditFilterDialog.h"
#include "widgets/TokenDropTarget.h"
#include <KGlobal>
#include <KLocale>
#include <klocalizeddate.h>
#include <KMessageBox>
#include <QPushButton>
#define OR_TOKEN Meta::valCustom + 1
#define AND_TOKEN Meta::valCustom + 2
#define AND_TOKEN_CONSTRUCT new Token( i18n( "AND" ), "filename-and-amarok", AND_TOKEN )
#define OR_TOKEN_CONSTRUCT new Token( i18n( "OR" ), "filename-divider", OR_TOKEN )
#define SIMPLE_TEXT_CONSTRUCT new Token( i18n( "Simple text" ), "media-track-edit-amarok", 0 )
EditFilterDialog::EditFilterDialog( QWidget* parent, const QString &text )
: KDialog( parent )
, m_ui( new Ui::EditFilterDialog )
, m_curToken( 0 )
, m_separator( " AND " )
, m_isUpdating()
{
setCaption( i18n( "Edit Filter" ) );
setButtons( KDialog::Reset | KDialog::Ok | KDialog::Cancel );
m_ui->setupUi( mainWidget() );
m_ui->dropTarget->setRowLimit( 1 );
initTokenPool();
m_ui->searchEdit->setText( text );
updateDropTarget( text );
updateAttributeEditor();
- connect( m_ui->mqwAttributeEditor, SIGNAL(changed(MetaQueryWidget::Filter)),
- SLOT(slotAttributeChanged(MetaQueryWidget::Filter)) );
- connect( this, SIGNAL(resetClicked()), SLOT(slotReset()) );
- connect( m_ui->cbInvert, SIGNAL(toggled(bool)),
- SLOT(slotInvert(bool)) );
- connect( m_ui->rbAnd, SIGNAL(toggled(bool)),
- SLOT(slotSeparatorChange()) );
- connect( m_ui->rbOr, SIGNAL(toggled(bool)),
- SLOT(slotSeparatorChange()) );
- connect( m_ui->tpTokenPool, SIGNAL(onDoubleClick(Token*)),
- m_ui->dropTarget, SLOT(insertToken(Token*)) );
- connect( m_ui->dropTarget, SIGNAL(tokenSelected(Token*)),
- SLOT(slotTokenSelected(Token*)) );
- connect( m_ui->dropTarget, SIGNAL(changed()),
- SLOT(updateSearchEdit()) ); // in case someone dragged a token around.
-
- connect( m_ui->searchEdit, SIGNAL(textEdited(QString)),
- SLOT(slotSearchEditChanged(QString)) );
+ connect( m_ui->mqwAttributeEditor, &MetaQueryWidget::changed,
+ this, &EditFilterDialog::slotAttributeChanged );
+ connect( this, &EditFilterDialog::resetClicked, this, &EditFilterDialog::slotReset );
+ connect( m_ui->cbInvert, &QCheckBox::toggled,
+ this, &EditFilterDialog::slotInvert );
+ connect( m_ui->rbAnd, &QCheckBox::toggled,
+ this, &EditFilterDialog::slotSeparatorChange );
+ connect( m_ui->rbOr, &QCheckBox::toggled,
+ this, &EditFilterDialog::slotSeparatorChange );
+ connect( m_ui->tpTokenPool, &TokenPool::onDoubleClick,
+ m_ui->dropTarget, &TokenDropTarget::appendToken );
+ connect( m_ui->dropTarget, &TokenDropTarget::tokenSelected,
+ this, &EditFilterDialog::slotTokenSelected );
+ connect( m_ui->dropTarget, &TokenDropTarget::changed,
+ this, &EditFilterDialog::updateSearchEdit ); // in case someone dragged a token around.
+
+ connect( m_ui->searchEdit, &QLineEdit::textEdited,
+ this, &EditFilterDialog::slotSearchEditChanged );
}
EditFilterDialog::~EditFilterDialog()
{
delete m_ui;
}
void
EditFilterDialog::initTokenPool()
{
m_ui->tpTokenPool->addToken( SIMPLE_TEXT_CONSTRUCT );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valTitle ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valArtist ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valAlbumArtist ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valAlbum ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valGenre ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valComposer ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valComment ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valUrl ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valYear ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valTrackNr ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valDiscNr ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valBpm ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valLength ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valBitrate ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valSamplerate ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valFilesize ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valFormat ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valCreateDate ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valScore ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valRating ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valFirstPlayed ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valLastPlayed ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valPlaycount ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valLabel ) );
m_ui->tpTokenPool->addToken( tokenForField( Meta::valModified ) );
m_ui->tpTokenPool->addToken( OR_TOKEN_CONSTRUCT );
m_ui->tpTokenPool->addToken( AND_TOKEN_CONSTRUCT );
}
Token *
EditFilterDialog::tokenForField( const qint64 field )
{
QString icon = Meta::iconForField( field );
QString text = Meta::i18nForField( field );
return new Token( text, icon, field );
}
EditFilterDialog::Filter &
EditFilterDialog::filterForToken( Token *token )
{
// a new token!
if( !m_filters.contains( token ) ) {
Filter newFilter;
newFilter.filter.setField( token->value() );
newFilter.inverted = false;
m_filters.insert( token, newFilter );
- connect( token, SIGNAL(destroyed(QObject*)),
- this, SLOT(slotTokenDestroyed(QObject*)) );
+ connect( token, &Token::destroyed,
+ this, &EditFilterDialog::slotTokenDestroyed );
}
return m_filters[token];
}
void
EditFilterDialog::slotTokenSelected( Token *token )
{
DEBUG_BLOCK;
if( m_curToken == token )
return; // nothing to do
m_curToken = token;
if( m_curToken && m_curToken->value() > Meta::valCustom ) // OR / AND tokens case
m_curToken = 0;
updateAttributeEditor();
}
void
EditFilterDialog::slotTokenDestroyed( QObject *token )
{
DEBUG_BLOCK
m_filters.take( qobject_cast<Token*>(token) );
if( m_curToken == token )
{
m_curToken = 0;
updateAttributeEditor();
}
updateSearchEdit();
}
void
EditFilterDialog::slotAttributeChanged( const MetaQueryWidget::Filter &newFilter )
{
DEBUG_BLOCK;
if( m_curToken )
m_filters[m_curToken].filter = newFilter;
updateSearchEdit();
}
void
EditFilterDialog::slotInvert( bool checked )
{
if( m_curToken )
m_filters[m_curToken].inverted = checked;
updateSearchEdit();
}
void
EditFilterDialog::slotSeparatorChange()
{
if( m_ui->rbAnd->isChecked() )
m_separator = QString( " AND " );
else
m_separator = QString( " OR " );
updateSearchEdit();
}
void
EditFilterDialog::slotSearchEditChanged( const QString &filterText )
{
updateDropTarget( filterText );
updateAttributeEditor();
}
void
EditFilterDialog::slotReset()
{
m_ui->dropTarget->clear();
m_ui->rbAnd->setChecked( true );
updateAttributeEditor();
updateSearchEdit();
}
void
EditFilterDialog::accept()
{
emit filterChanged( filter() );
KDialog::accept();
}
void
EditFilterDialog::updateAttributeEditor()
{
DEBUG_BLOCK;
if( m_isUpdating )
return;
m_isUpdating = true;
if( m_curToken )
{
Filter &filter = filterForToken( m_curToken );
m_ui->mqwAttributeEditor->setFilter( filter.filter );
m_ui->cbInvert->setChecked( filter.inverted );
}
m_ui->mqwAttributeEditor->setEnabled( ( bool )m_curToken );
m_ui->cbInvert->setEnabled( ( bool )m_curToken );
m_isUpdating = false;
}
void
EditFilterDialog::updateSearchEdit()
{
DEBUG_BLOCK;
if( m_isUpdating )
return;
m_isUpdating = true;
m_ui->searchEdit->setText( filter() );
m_isUpdating = false;
}
void
EditFilterDialog::updateDropTarget( const QString &text )
{
DEBUG_BLOCK;
if( m_isUpdating )
return;
m_isUpdating = true;
m_ui->dropTarget->clear();
// some code duplications, see Collections::semanticDateTimeParser
ParsedExpression parsed = ExpressionParser::parse( text );
bool AND = false; // need an AND token
bool OR = false; // need an OR token
bool isDateAbsolute = false;
foreach( const or_list &orList, parsed )
{
foreach( const expression_element &elem, orList )
{
if( AND )
- m_ui->dropTarget->insertToken( AND_TOKEN_CONSTRUCT );
+ m_ui->dropTarget->appendToken( AND_TOKEN_CONSTRUCT );
else if( OR )
- m_ui->dropTarget->insertToken( OR_TOKEN_CONSTRUCT );
+ m_ui->dropTarget->appendToken( OR_TOKEN_CONSTRUCT );
Filter filter;
filter.filter.setField( !elem.field.isEmpty() ? Meta::fieldForName( elem.field ) : 0 );
if( filter.filter.field() == Meta::valRating )
{
filter.filter.numValue = 2 * elem.text.toFloat();
}
else if( filter.filter.isDate() )
{
QString strTime = elem.text;
// parse date using local settings
KLocalizedDate localizedDate = KLocalizedDate::readDate( strTime, KLocale::ShortFormat );
// parse date using a backup standard independent from local settings
QRegExp shortDateReg("(\\d{1,2})[-.](\\d{1,2})");
QRegExp longDateReg("(\\d{1,2})[-.](\\d{1,2})[-.](\\d{4})");
// NOTE for absolute time specifications numValue is a unix timestamp,
// for relative time specifications numValue is a time difference in seconds 'pointing to the past'
if( localizedDate.isValid() )
{
filter.filter.numValue = QDateTime( localizedDate.date() ).toTime_t();
isDateAbsolute = true;
}
else if( strTime.contains(shortDateReg) )
{
filter.filter.numValue = QDateTime( QDate( QDate::currentDate().year(), shortDateReg.cap(2).toInt(), shortDateReg.cap(1).toInt() ) ).toTime_t();
isDateAbsolute = true;
}
else if( strTime.contains(longDateReg) )
{
filter.filter.numValue = QDateTime( QDate( longDateReg.cap(3).toInt(), longDateReg.cap(2).toInt(), longDateReg.cap(1).toInt() ) ).toTime_t();
isDateAbsolute = true;
}
else
{
// parse a "#m#d" (discoverability == 0, but without a GUI, how to do it?)
int years = 0, months = 0, days = 0, secs = 0;
QString tmp;
for( int i = 0; i < strTime.length(); i++ )
{
QChar c = strTime.at( i );
if( c.isNumber() )
{
tmp += c;
}
else if( c == 'y' )
{
years += tmp.toInt();
tmp.clear();
}
else if( c == 'm' )
{
months += tmp.toInt();
tmp.clear();
}
else if( c == 'w' )
{
days += tmp.toInt() * 7;
tmp.clear();
}
else if( c == 'd' )
{
days += tmp.toInt();
tmp.clear();
}
else if( c == 'h' )
{
secs += tmp.toInt() * 60 * 60;
tmp.clear();
}
else if( c == 'M' )
{
secs += tmp.toInt() * 60;
tmp.clear();
}
else if( c == 's' )
{
secs += tmp.toInt();
tmp.clear();
}
}
filter.filter.numValue = years*365*24*60*60 + months*30*24*60*60 + days*24*60*60 + secs;
isDateAbsolute = false;
}
}
else if( filter.filter.isNumeric() )
{
filter.filter.numValue = elem.text.toInt();
}
if( filter.filter.isDate() )
{
switch( elem.match )
{
case expression_element::Less:
if( isDateAbsolute )
filter.filter.condition = MetaQueryWidget::LessThan;
else
filter.filter.condition = MetaQueryWidget::NewerThan;
break;
case expression_element::More:
if( isDateAbsolute )
filter.filter.condition = MetaQueryWidget::GreaterThan;
else
filter.filter.condition = MetaQueryWidget::OlderThan;
break;
default:
filter.filter.condition = MetaQueryWidget::Equals;
}
}
else if( filter.filter.isNumeric() )
{
switch( elem.match )
{
case expression_element::Equals:
filter.filter.condition = MetaQueryWidget::Equals;
break;
case expression_element::Less:
filter.filter.condition = MetaQueryWidget::LessThan;
break;
case expression_element::More:
filter.filter.condition = MetaQueryWidget::GreaterThan;
break;
case expression_element::Contains:
break;
}
}
else
{
switch( elem.match )
{
case expression_element::Contains:
filter.filter.condition = MetaQueryWidget::Contains;
break;
case expression_element::Equals:
filter.filter.condition = MetaQueryWidget::Equals;
break;
case expression_element::Less:
case expression_element::More:
break;
}
filter.filter.value = elem.text;
}
filter.inverted = elem.negate;
Token *nToken = filter.filter.field()
? tokenForField( filter.filter.field() )
: SIMPLE_TEXT_CONSTRUCT;
m_filters.insert( nToken, filter );
- connect( nToken, SIGNAL(destroyed(QObject*)),
- this, SLOT(slotTokenDestroyed(QObject*)) );
+ connect( nToken, &Token::destroyed,
+ this, &EditFilterDialog::slotTokenDestroyed );
- m_ui->dropTarget->insertToken( nToken );
+ m_ui->dropTarget->appendToken( nToken );
OR = true;
}
OR = false;
AND = true;
}
m_isUpdating = false;
}
QString
EditFilterDialog::filter()
{
QString filterString;
QList < Token *> tokens = m_ui->dropTarget->tokensAtRow();
bool join = false;
foreach( Token *token, tokens )
{
if( token->value() == OR_TOKEN )
{
filterString.append( " OR " );
join = false;
}
else if( token->value() == AND_TOKEN )
{
filterString.append( " AND " );
join = false;
}
else
{
if( join )
filterString.append( m_separator );
Filter &filter = filterForToken( token );
filterString.append( filter.filter.toString( filter.inverted ) );
join = true;
}
}
return filterString;
}
diff --git a/src/dialogs/EqualizerDialog.cpp b/src/dialogs/EqualizerDialog.cpp
index 594c8c6c00..e612a8d79a 100644
--- a/src/dialogs/EqualizerDialog.cpp
+++ b/src/dialogs/EqualizerDialog.cpp
@@ -1,321 +1,322 @@
/****************************************************************************************
* Copyright (c) 2004-2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "EqualizerDialog.h"
#include "amarokconfig.h"
#include "EngineController.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
EqualizerDialog * EqualizerDialog::s_instance = 0;
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, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(restoreOriginalSettings()));
+ 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( QString("+") + mlblText );
eqMinEq->setText( QString("-") + 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->applyEqualizerPreset( AmarokConfig::equalizerMode() - 1 );
+ equalizer->applyEqualizerPresetByIndex( AmarokConfig::equalizerMode() - 1 );
equalizer->setGains( equalizer->gains() );
updateUi();
- connect( equalizer, SIGNAL(presetsChanged(QString)), SLOT(presetsChanged(QString)) );
- connect( equalizer, SIGNAL(gainsChanged(QList<int>)), SLOT(gainsChanged(QList<int>)) );
- connect( equalizer, SIGNAL(presetApplied(int)), SLOT(presetApplied(int)) );
+ 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, SIGNAL(toggled(bool)), SLOT(toggleEqualizer(bool)) );
- connect( eqPresets, SIGNAL(currentIndexChanged(int)), equalizer, SLOT(applyEqualizerPreset(int)) );
- connect( eqPresets, SIGNAL(editTextChanged(QString)), SLOT(updateUi()) );
+ connect( activeCheckBox, &QCheckBox::toggled, this, &EqualizerDialog::toggleEqualizer );
+ connect( eqPresets, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ equalizer, &EqualizerController::applyEqualizerPresetByIndex );
+ connect( eqPresets, &QComboBox::editTextChanged, this, &EqualizerDialog::updateUi );
foreach( QSlider* mSlider, m_bands )
- connect( mSlider, SIGNAL(valueChanged(int)), SLOT(bandsChanged()) );
+ connect( mSlider, &QSlider::valueChanged, this, &EqualizerDialog::bandsChanged );
eqPresetSaveBtn->setIcon( QIcon::fromTheme( "document-save" ) );
- connect( eqPresetSaveBtn, SIGNAL(clicked()), SLOT(savePreset()) );
+ connect( eqPresetSaveBtn, &QAbstractButton::clicked, this, &EqualizerDialog::savePreset );
eqPresetDeleteBtn->setIcon( QIcon::fromTheme( "edit-delete" ) );
- connect( eqPresetDeleteBtn, SIGNAL(clicked()), SLOT(deletePreset()) );
+ connect( eqPresetDeleteBtn, &QAbstractButton::clicked, this, &EqualizerDialog::deletePreset );
eqPresetResetBtn->setIcon( QIcon::fromTheme( "edit-undo" ) );
- connect( eqPresetResetBtn, SIGNAL(clicked()), SLOT(restorePreset()) );
+ connect( eqPresetResetBtn, &QAbstractButton::clicked, this, &EqualizerDialog::restorePreset );
}
EqualizerDialog::~EqualizerDialog()
{ }
void EqualizerDialog::showOnce( QWidget *parent )
{
DEBUG_BLOCK
if( s_instance == 0 )
s_instance = new EqualizerDialog( parent );
s_instance->activateWindow();
s_instance->show();
s_instance->raise();
s_instance->storeOriginalSettings();
}
QList<int>
EqualizerDialog::gains() const
{
QList<int> result;
foreach( QSlider* mSlider, m_bands )
result << mSlider->value();
return result;
}
void
EqualizerDialog::gainsChanged( const QList<int> &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()
{
activeCheckBox->setChecked( m_originalActivated );
int originalPresetIndex = EqualizerPresets::eqGlobalList().indexOf( m_originalPreset );
- The::engineController()->equalizerController()->applyEqualizerPreset( originalPresetIndex );
+ 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->applyEqualizerPreset( -1 );
+ eq->applyEqualizerPresetByIndex( -1 );
else
- eq->applyEqualizerPreset( eqPresets->currentIndex() );
+ eq->applyEqualizerPresetByIndex( eqPresets->currentIndex() );
}
diff --git a/src/dialogs/MusicBrainzTagger.cpp b/src/dialogs/MusicBrainzTagger.cpp
index 0184701434..ac7d1ff2df 100644
--- a/src/dialogs/MusicBrainzTagger.cpp
+++ b/src/dialogs/MusicBrainzTagger.cpp
@@ -1,199 +1,199 @@
/****************************************************************************************
* Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> *
* Copyright (c) 2013 Alberto Villa <avilla@FreeBSD.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MusicBrainzTagDialog"
#include "MusicBrainzTagger.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "musicbrainz/MusicBrainzFinder.h"
#include "musicbrainz/MusicBrainzTagsModel.h"
#include "musicbrainz/MusicBrainzTagsModelDelegate.h"
#ifdef HAVE_LIBOFA
#include "musicbrainz/MusicDNSFinder.h"
#endif
#include "ui_MusicBrainzTagger.h"
#include <QIcon>
#include <QSortFilterProxyModel>
#include <QToolBar>
#include <QToolButton>
MusicBrainzTagger::MusicBrainzTagger( const Meta::TrackList &tracks,
QWidget *parent )
: KDialog( parent )
, ui( new Ui::MusicBrainzTagger() )
{
DEBUG_BLOCK
foreach( Meta::TrackPtr track, tracks )
{
if( !track->playableUrl().toLocalFile().isEmpty() )
m_tracks << track;
}
ui->setupUi( mainWidget() );
restoreDialogSize( Amarok::config( "MusicBrainzTagDialog" ) );
init();
search();
}
MusicBrainzTagger::~MusicBrainzTagger()
{
KConfigGroup group = Amarok::config( "MusicBrainzTagDialog" );
saveDialogSize( group );
delete ui;
}
void
MusicBrainzTagger::init()
{
DEBUG_BLOCK
setButtons( KDialog::None );
setAttribute( Qt::WA_DeleteOnClose );
setMinimumSize( 550, 300 );
m_resultsModel = new MusicBrainzTagsModel( this );
m_resultsModelDelegate = new MusicBrainzTagsModelDelegate( this );
m_resultsProxyModel = new QSortFilterProxyModel( this );
m_resultsProxyModel->setSourceModel( m_resultsModel );
m_resultsProxyModel->setSortRole( MusicBrainzTagsModel::SortRole );
m_resultsProxyModel->setDynamicSortFilter( true );
ui->resultsView->setModel( m_resultsProxyModel );
ui->resultsView->setItemDelegate( m_resultsModelDelegate );
// The column is not important, they all have the same data.
ui->resultsView->sortByColumn( 0, Qt::AscendingOrder );
if( m_tracks.count() > 1 )
{
QToolBar *toolBar = new QToolBar( this );
toolBar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
- QAction *lastAction = toolBar->addAction( QIcon::fromTheme( "tools-wizard" ), i18n( "Choose Best Matches" ), m_resultsModel, SLOT(chooseBestMatches()) );
+ QAction *lastAction = toolBar->addAction( QIcon::fromTheme( "tools-wizard" ), i18n( "Choose Best Matches" ), m_resultsModel, &MusicBrainzTagsModel::chooseBestMatches );
lastAction->setToolTip( i18n( "Use the top result for each undecided track. Alternatively, you can click on <b>Choose Best Matches from This Album</b> in the context menu of a good suggestion; it may give even better results because it prevents mixing different album releases together." ) );
- lastAction = toolBar->addAction( QIcon::fromTheme( "edit-clear" ), i18n( "Clear Choices" ), m_resultsModel, SLOT(clearChoices()) );
+ lastAction = toolBar->addAction( QIcon::fromTheme( "edit-clear" ), i18n( "Clear Choices" ), m_resultsModel, &MusicBrainzTagsModel::clearChoices );
lastAction->setToolTip( i18n( "Clear all choices, even manually made ones." ) );
toolBar->addSeparator();
QToolButton *lastButton = new QToolButton( toolBar );
lastAction = new QAction( i18n( "Collapse Chosen" ), lastButton );
- connect( lastAction, SIGNAL(triggered()),
- ui->resultsView, SLOT(collapseChosen()) );
+ connect( lastAction, &QAction::triggered,
+ ui->resultsView, &MusicBrainzTagsView::collapseChosen );
lastButton->setDefaultAction( lastAction );
lastAction = new QAction( i18n( "Collapse All" ), lastButton );
- connect( lastAction, SIGNAL(triggered()),
- ui->resultsView, SLOT(collapseAll()) );
+ connect( lastAction, &QAction::triggered,
+ ui->resultsView, &MusicBrainzTagsView::collapseAll );
lastButton->addAction( lastAction );
toolBar->addWidget( lastButton );
lastButton = new QToolButton( toolBar );
lastAction = new QAction( i18n( "Expand Unchosen" ), lastButton );
- connect( lastAction, SIGNAL(triggered()),
- ui->resultsView, SLOT(expandUnchosen()) );
+ connect( lastAction, &QAction::triggered,
+ ui->resultsView, &MusicBrainzTagsView::expandUnchosen );
lastButton->setDefaultAction( lastAction );
lastAction = new QAction( i18n( "Expand All" ), lastButton );
- connect( lastAction, SIGNAL(triggered()),
- ui->resultsView, SLOT(expandAll()) );
+ connect( lastAction, &QAction::triggered,
+ ui->resultsView, &MusicBrainzTagsView::expandAll );
lastButton->addAction( lastAction );
toolBar->addWidget( lastButton );
ui->verticalLayout->insertWidget( 0, toolBar );
}
ui->progressBar->hide();
mb_finder = new MusicBrainzFinder( this );
#ifdef HAVE_LIBOFA
mdns_finder = new MusicDNSFinder( this );
- connect( mdns_finder, SIGNAL(trackFound(Meta::TrackPtr,QString)),
- mb_finder, SLOT(lookUpByPUID(Meta::TrackPtr,QString)) );
- connect( mdns_finder, SIGNAL(progressStep()), SLOT(progressStep()) );
- connect( mdns_finder, SIGNAL(done()), this, SLOT(mdnsSearchDone()) );
+ connect( mdns_finder, &MusicDNSFinder::trackFound,
+ mb_finder, &MusicBrainzFinder::lookUpByPUID );
+ connect( mdns_finder, &MusicDNSFinder::progressStep, this, &MusicBrainzTagger::progressStep );
+ connect( mdns_finder, &MusicDNSFinder::done, this, &MusicBrainzTagger::mdnsSearchDone );
#endif
- connect( mb_finder, SIGNAL(done()), SLOT(searchDone()) );
- connect( mb_finder, SIGNAL(trackFound(Meta::TrackPtr,QVariantMap)),
- m_resultsModel, SLOT(addTrack(Meta::TrackPtr,QVariantMap)) );
- connect( mb_finder, SIGNAL(progressStep()), SLOT(progressStep()) );
- connect( ui->pushButton_saveAndClose, SIGNAL(clicked(bool)), SLOT(saveAndExit()) );
- connect( ui->pushButton_cancel, SIGNAL(clicked(bool)), SLOT(reject()) );
+ connect( mb_finder, &MusicBrainzFinder::done, this, &MusicBrainzTagger::searchDone );
+ connect( mb_finder, &MusicBrainzFinder::trackFound,
+ m_resultsModel, &MusicBrainzTagsModel::addTrack );
+ connect( mb_finder, &MusicBrainzFinder::progressStep, this, &MusicBrainzTagger::progressStep );
+ connect( ui->pushButton_saveAndClose, &QAbstractButton::clicked, this, &MusicBrainzTagger::saveAndExit );
+ connect( ui->pushButton_cancel, &QAbstractButton::clicked, this, &MusicBrainzTagger::reject );
}
void
MusicBrainzTagger::search()
{
int barSize = m_tracks.count();
mb_finder->run( m_tracks );
#ifdef HAVE_LIBOFA
barSize *= 2;
mdns_searchDone = false;
mdns_finder->run( m_tracks );
#endif
ui->progressBar->setRange( 0, barSize );
ui->progressBar->setValue( 0 );
ui->horizontalSpacer->changeSize( 0, 0, QSizePolicy::Ignored );
ui->progressBar->show();
}
void
MusicBrainzTagger::saveAndExit()
{
QMap<Meta::TrackPtr, QVariantMap> result = m_resultsModel->chosenItems();
if( !result.isEmpty() )
emit sendResult( result );
accept();
}
void
MusicBrainzTagger::searchDone()
{
DEBUG_BLOCK
#ifdef HAVE_LIBOFA
if( !mdns_searchDone )
return;
#endif
ui->horizontalSpacer->changeSize( 0, 0, QSizePolicy::Expanding );
ui->progressBar->hide();
ui->resultsView->expandAll();
ui->resultsView->header()->resizeSections( QHeaderView::ResizeToContents );
}
#ifdef HAVE_LIBOFA
void
MusicBrainzTagger::mdnsSearchDone()
{
DEBUG_BLOCK
mdns_searchDone = true;
if( !mb_finder->isRunning() )
searchDone();
}
#endif
void
MusicBrainzTagger::progressStep()
{
ui->progressBar->setValue( ui->progressBar->value() + 1 );
}
diff --git a/src/dialogs/OrganizeCollectionDialog.cpp b/src/dialogs/OrganizeCollectionDialog.cpp
index ffeabc9709..2162ca808b 100644
--- a/src/dialogs/OrganizeCollectionDialog.cpp
+++ b/src/dialogs/OrganizeCollectionDialog.cpp
@@ -1,407 +1,408 @@
/****************************************************************************************
* Copyright (c) 2008 Bonne Eggleston <b.eggleston@gmail.com> *
* Copyright (c) 2008 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "OrganizeCollectionDialog"
#include "OrganizeCollectionDialog.h"
#include "amarokconfig.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core-impl/meta/file/File.h"
#include "dialogs/TrackOrganizer.h"
#include "widgets/TokenPool.h"
#include "ui_OrganizeCollectionDialogBase.h"
#include <KColorScheme>
#include <KInputDialog>
#include <QApplication>
#include <QDesktopWidget>
#include <QDir>
#include <QTimer>
// -------------- OrganizeCollectionOptionWidget ------------
OrganizeCollectionOptionWidget::OrganizeCollectionOptionWidget( QWidget *parent )
: QGroupBox( parent )
{
setupUi( this );
- connect( spaceCheck, SIGNAL(toggled(bool)), SIGNAL(optionsChanged()) );
- connect( ignoreTheCheck, SIGNAL(toggled(bool)), SIGNAL(optionsChanged()) );
- connect( vfatCheck, SIGNAL(toggled(bool)), SIGNAL(optionsChanged()) );
- connect( asciiCheck, SIGNAL(toggled(bool)), SIGNAL(optionsChanged()) );
- connect( regexpEdit, SIGNAL(editingFinished()), SIGNAL(optionsChanged()) );
- connect( replaceEdit, SIGNAL(editingFinished()), SIGNAL(optionsChanged()) );
+ connect( spaceCheck, &QCheckBox::toggled, this, &OrganizeCollectionOptionWidget::optionsChanged );
+ connect( ignoreTheCheck, &QCheckBox::toggled, this, &OrganizeCollectionOptionWidget::optionsChanged );
+ connect( vfatCheck, &QCheckBox::toggled, this, &OrganizeCollectionOptionWidget::optionsChanged );
+ connect( asciiCheck, &QCheckBox::toggled, this, &OrganizeCollectionOptionWidget::optionsChanged );
+ connect( regexpEdit, &KLineEdit::editingFinished, this, &OrganizeCollectionOptionWidget::optionsChanged );
+ connect( replaceEdit, &KLineEdit::editingFinished, this, &OrganizeCollectionOptionWidget::optionsChanged );
}
// ------------------------- OrganizeCollectionWidget -------------------
OrganizeCollectionWidget::OrganizeCollectionWidget( QWidget *parent )
: FilenameLayoutWidget( parent )
{
m_configCategory = "OrganizeCollectionDialog";
// TODO: also supported by TrackOrganizer:
// folder theartist thealbumartist rating filesize length
m_tokenPool->addToken( createToken( Title ) );
m_tokenPool->addToken( createToken( Artist ) );
m_tokenPool->addToken( createToken( AlbumArtist ) );
m_tokenPool->addToken( createToken( Album ) );
m_tokenPool->addToken( createToken( Genre ) );
m_tokenPool->addToken( createToken( Composer ) );
m_tokenPool->addToken( createToken( Comment ) );
m_tokenPool->addToken( createToken( Year ) );
m_tokenPool->addToken( createToken( TrackNumber ) );
m_tokenPool->addToken( createToken( DiscNumber ) );
m_tokenPool->addToken( createToken( Folder ) );
m_tokenPool->addToken( createToken( FileType ) );
m_tokenPool->addToken( createToken( Initial ) );
m_tokenPool->addToken( createToken( Slash ) );
m_tokenPool->addToken( createToken( Underscore ) );
m_tokenPool->addToken( createToken( Dash ) );
m_tokenPool->addToken( createToken( Dot ) );
m_tokenPool->addToken( createToken( Space ) );
// show some non-editable tags before and after
// but only if screen size is large enough (BR: 283361)
const QRect screenRect = QApplication::desktop()->screenGeometry();
if( screenRect.width() >= 1024 )
{
m_schemaLineLayout->insertWidget( 0,
createStaticToken( CollectionRoot ), 0 );
m_schemaLineLayout->insertWidget( 1,
createStaticToken( Slash ), 0 );
m_schemaLineLayout->insertWidget( m_schemaLineLayout->count(),
createStaticToken( Dot ) );
m_schemaLineLayout->insertWidget( m_schemaLineLayout->count(),
createStaticToken( FileType ) );
}
m_syntaxLabel->setText( buildFormatTip() );
populateConfiguration();
}
QString
OrganizeCollectionWidget::buildFormatTip() const
{
QMap<QString, QString> args;
args["albumartist"] = i18n( "%1 or %2", QLatin1String("Album Artist, The") , QLatin1String("The Album Artist") );
args["thealbumartist"] = i18n( "The Album Artist" );
args["theartist"] = i18n( "The Artist" );
args["artist"] = i18n( "%1 or %2", QLatin1String("Artist, The") , QLatin1String("The Artist") );
args["initial"] = i18n( "Artist's Initial" );
args["filetype"] = i18n( "File Extension of Source" );
args["track"] = i18n( "Track Number" );
QString tooltip = i18n( "You can use the following tokens:" );
tooltip += "<ul>";
for( QMap<QString, QString>::iterator it = args.begin(); it != args.end(); ++it )
tooltip += QString( "<li>%1 - %%2%" ).arg( it.value(), it.key() );
tooltip += "</ul>";
tooltip += i18n( "If you surround sections of text that contain a token with curly-braces, "
"that section will be hidden if the token is empty." );
return tooltip;
}
OrganizeCollectionDialog::OrganizeCollectionDialog( const Meta::TrackList &tracks,
const QStringList &folders,
const QString &targetExtension,
QWidget *parent,
const char *name,
bool modal,
const QString &caption,
QFlags<KDialog::ButtonCode> buttonMask )
: KDialog( parent )
, ui( new Ui::OrganizeCollectionDialogBase )
, m_conflict( false )
{
Q_UNUSED( name )
setCaption( caption );
setModal( modal );
setButtons( buttonMask );
showButtonSeparator( true );
m_targetFileExtension = targetExtension;
if( tracks.size() > 0 )
m_allTracks = tracks;
KVBox *mainVBox = new KVBox( this );
setMainWidget( mainVBox );
QWidget *mainContainer = new QWidget( mainVBox );
ui->setupUi( mainContainer );
m_trackOrganizer = new TrackOrganizer( m_allTracks, this );
ui->folderCombo->insertItems( 0, folders );
if( ui->folderCombo->contains( AmarokConfig::organizeDirectory() ) )
ui->folderCombo->setCurrentItem( AmarokConfig::organizeDirectory() );
else
ui->folderCombo->setCurrentIndex( 0 ); //TODO possible bug: assumes folder list is not empty.
ui->overwriteCheck->setChecked( AmarokConfig::overwriteFiles() );
ui->optionsWidget->setReplaceSpaces( AmarokConfig::replaceSpace() );
ui->optionsWidget->setPostfixThe( AmarokConfig::ignoreThe() );
ui->optionsWidget->setVfatCompatible( AmarokConfig::vfatCompatible() );
ui->optionsWidget->setAsciiOnly( AmarokConfig::asciiOnly() );
ui->optionsWidget->setRegexpText( AmarokConfig::replacementRegexp() );
ui->optionsWidget->setReplaceText( AmarokConfig::replacementString() );
ui->previewTableWidget->horizontalHeader()->setResizeMode( QHeaderView::ResizeToContents );
ui->conflictLabel->setText("");
QPalette p = ui->conflictLabel->palette();
KColorScheme::adjustForeground( p, KColorScheme::NegativeText ); // TODO this isn't working, the color is still normal
ui->conflictLabel->setPalette( p );
ui->previewTableWidget->sortItems( 0, Qt::AscendingOrder );
// only show the options when the Options button is checked
- connect( ui->optionsButton, SIGNAL(toggled(bool)), ui->organizeCollectionWidget, SLOT(setVisible(bool)) );
- connect( ui->optionsButton, SIGNAL(toggled(bool)), ui->optionsWidget, SLOT(setVisible(bool)) );
+ connect( ui->optionsButton, &QAbstractButton::toggled, ui->organizeCollectionWidget, &OrganizeCollectionWidget::setVisible );
+ connect( ui->optionsButton, &QAbstractButton::toggled, ui->optionsWidget, &OrganizeCollectionOptionWidget::setVisible );
ui->organizeCollectionWidget->hide();
ui->optionsWidget->hide();
- connect( ui->folderCombo, SIGNAL(currentIndexChanged(QString)), SLOT(slotUpdatePreview()) );
- connect( ui->organizeCollectionWidget, SIGNAL(schemeChanged()), SLOT(slotUpdatePreview()) );
- connect( ui->optionsWidget, SIGNAL(optionsChanged()), SLOT(slotUpdatePreview()));
+ connect( ui->folderCombo, QOverload<const QString&>::of(&KComboBox::currentIndexChanged),
+ this, &OrganizeCollectionDialog::slotUpdatePreview );
+ connect( ui->organizeCollectionWidget, &OrganizeCollectionWidget::schemeChanged, this, &OrganizeCollectionDialog::slotUpdatePreview );
+ connect( ui->optionsWidget, &OrganizeCollectionOptionWidget::optionsChanged, this, &OrganizeCollectionDialog::slotUpdatePreview);
// to show the conflict error
- connect( ui->overwriteCheck, SIGNAL(stateChanged(int)), SLOT(slotOverwriteModeChanged()) );
+ connect( ui->overwriteCheck, &QCheckBox::stateChanged, this, &OrganizeCollectionDialog::slotOverwriteModeChanged );
- connect( this, SIGNAL(accepted()), ui->organizeCollectionWidget, SLOT(onAccept()) );
- connect( this, SIGNAL(accepted()), SLOT(slotDialogAccepted()) );
- connect( ui->folderCombo, SIGNAL(currentIndexChanged(QString)),
- SLOT(slotEnableOk(QString)) );
+ connect( this, &OrganizeCollectionDialog::accepted, ui->organizeCollectionWidget, &OrganizeCollectionWidget::onAccept );
+ connect( this, &OrganizeCollectionDialog::accepted, this, &OrganizeCollectionDialog::slotDialogAccepted );
+ connect( ui->folderCombo, QOverload<const QString&>::of(&KComboBox::currentIndexChanged),
+ this, &OrganizeCollectionDialog::slotEnableOk );
slotEnableOk( ui->folderCombo->currentText() );
restoreDialogSize( Amarok::config( "OrganizeCollectionDialog" ) );
- QTimer::singleShot( 0, this, SLOT(slotUpdatePreview()) );
+ QTimer::singleShot( 0, this, &OrganizeCollectionDialog::slotUpdatePreview );
}
OrganizeCollectionDialog::~OrganizeCollectionDialog()
{
KConfigGroup group = Amarok::config( "OrganizeCollectionDialog" );
saveDialogSize( group );
AmarokConfig::setOrganizeDirectory( ui->folderCombo->currentText() );
delete ui;
}
QMap<Meta::TrackPtr, QString>
OrganizeCollectionDialog::getDestinations()
{
return m_trackOrganizer->getDestinations();
}
bool
OrganizeCollectionDialog::overwriteDestinations() const
{
return ui->overwriteCheck->isChecked();
}
QString
OrganizeCollectionDialog::buildFormatString() const
{
if( ui->organizeCollectionWidget->getParsableScheme().simplified().isEmpty() )
return "";
return "%collectionroot%/" + ui->organizeCollectionWidget->getParsableScheme() + ".%filetype%";
}
void
OrganizeCollectionDialog::slotUpdatePreview()
{
QString formatString = buildFormatString();
m_trackOrganizer->setAsciiOnly( ui->optionsWidget->asciiOnly() );
m_trackOrganizer->setFolderPrefix( ui->folderCombo->currentText() );
m_trackOrganizer->setFormatString( formatString );
m_trackOrganizer->setTargetFileExtension( m_targetFileExtension );
m_trackOrganizer->setPostfixThe( ui->optionsWidget->postfixThe() );
m_trackOrganizer->setReplaceSpaces( ui->optionsWidget->replaceSpaces() );
m_trackOrganizer->setReplace( ui->optionsWidget->regexpText(),
ui->optionsWidget->replaceText() );
m_trackOrganizer->setVfatSafe( ui->optionsWidget->vfatCompatible() );
// empty the table, not only its contents
ui->previewTableWidget->clearContents();
ui->previewTableWidget->setRowCount( 0 );
ui->previewTableWidget->setSortingEnabled( false ); // intereferes with inserting
m_trackOrganizer->resetTrackOffset();
m_conflict = false;
setCursor( Qt::BusyCursor );
// be nice do the UI, try not to block for too long
- QTimer::singleShot( 0, this, SLOT(processPreviewPaths()) );
+ QTimer::singleShot( 0, this, &OrganizeCollectionDialog::processPreviewPaths );
}
void
OrganizeCollectionDialog::processPreviewPaths()
{
QStringList originals;
QStringList previews;
QStringList commonOriginalPrefix; // common initial directories
QStringList commonPreviewPrefix; // common initial directories
QMap<Meta::TrackPtr, QString> destinations = m_trackOrganizer->getDestinations();
for( auto it = destinations.constBegin(); it != destinations.constEnd(); ++it )
{
originals << it.key()->prettyUrl();
previews << it.value();
QStringList originalPrefix = originals.last().split( '/' );
originalPrefix.removeLast(); // we never include file name in the common prefix
QStringList previewPrefix = previews.last().split( '/' );
previewPrefix.removeLast();
if( it == destinations.constBegin() )
{
commonOriginalPrefix = originalPrefix;
commonPreviewPrefix = previewPrefix;
} else {
int commonLength = 0;
while( commonOriginalPrefix.size() > commonLength &&
originalPrefix.size() > commonLength &&
commonOriginalPrefix[ commonLength ] == originalPrefix[ commonLength ] )
{
commonLength++;
}
commonOriginalPrefix = commonOriginalPrefix.mid( 0, commonLength );
commonLength = 0;
while( commonPreviewPrefix.size() > commonLength &&
previewPrefix.size() > commonLength &&
commonPreviewPrefix[ commonLength ] == previewPrefix[ commonLength ] )
{
commonLength++;
}
commonPreviewPrefix = commonPreviewPrefix.mid( 0, commonLength );
}
}
QString originalPrefix = commonOriginalPrefix.isEmpty() ? QString() : commonOriginalPrefix.join( "/" ) + '/';
m_previewPrefix = commonPreviewPrefix.isEmpty() ? QString() : commonPreviewPrefix.join( "/" ) + '/';
ui->previewTableWidget->horizontalHeaderItem( 1 )->setText( i18n( "Original: %1", originalPrefix ) );
ui->previewTableWidget->horizontalHeaderItem( 0 )->setText( i18n( "Preview: %1", m_previewPrefix ) );
m_originals.clear();
m_originals.reserve( originals.size() );
m_previews.clear();
m_previews.reserve( previews.size() );
for( int i = 0; i < qMin( originals.size(), previews.size() ); i++ )
{
m_originals << originals.at( i ).mid( originalPrefix.length() );
m_previews << previews.at( i ).mid( m_previewPrefix.length() );
}
- QTimer::singleShot( 0, this, SLOT(previewNextBatch()) );
+ QTimer::singleShot( 0, this, &OrganizeCollectionDialog::previewNextBatch );
}
void
OrganizeCollectionDialog::previewNextBatch()
{
const int batchSize = 100;
QPalette negativePalette = ui->previewTableWidget->palette();
KColorScheme::adjustBackground( negativePalette, KColorScheme::NegativeBackground );
int processed = 0;
while( !m_originals.isEmpty() && !m_previews.isEmpty() )
{
QString originalPath = m_originals.takeFirst();
QString newPath = m_previews.takeFirst();
int newRow = ui->previewTableWidget->rowCount();
ui->previewTableWidget->insertRow( newRow );
// new path preview in the 1st column
QTableWidgetItem *item = new QTableWidgetItem( newPath );
if( QFileInfo( m_previewPrefix + newPath ).exists() )
{
item->setBackgroundColor( negativePalette.color( QPalette::Base ) );
m_conflict = true;
}
ui->previewTableWidget->setItem( newRow, 0, item );
//original in the second column
item = new QTableWidgetItem( originalPath );
ui->previewTableWidget->setItem( newRow, 1, item );
processed++;
if( processed >= batchSize )
{
// yield some room to the other events in the main loop
- QTimer::singleShot( 0, this, SLOT(previewNextBatch()) );
+ QTimer::singleShot( 0, this, &OrganizeCollectionDialog::previewNextBatch );
return;
}
}
// finished
unsetCursor();
ui->previewTableWidget->setSortingEnabled( true );
slotOverwriteModeChanged(); // in fact, m_conflict may have changed
}
void
OrganizeCollectionDialog::slotOverwriteModeChanged()
{
if( m_conflict )
{
if( ui->overwriteCheck->isChecked() )
ui->conflictLabel->setText( i18n( "There is a filename conflict, existing files will be overwritten." ) );
else
ui->conflictLabel->setText( i18n( "There is a filename conflict, existing files will not be changed." ) );
}
else
ui->conflictLabel->setText(""); // we clear the text instead of hiding it to retain the layout spacing
}
void
OrganizeCollectionDialog::slotDialogAccepted()
{
AmarokConfig::setOrganizeDirectory( ui->folderCombo->currentText() );
AmarokConfig::setIgnoreThe( ui->optionsWidget->postfixThe() );
AmarokConfig::setReplaceSpace( ui->optionsWidget->replaceSpaces() );
AmarokConfig::setVfatCompatible( ui->optionsWidget->vfatCompatible() );
AmarokConfig::setAsciiOnly( ui->optionsWidget->asciiOnly() );
AmarokConfig::setReplacementRegexp( ui->optionsWidget->regexpText() );
AmarokConfig::setReplacementString( ui->optionsWidget->replaceText() );
}
//The Ok button should be disabled when there's no collection root selected, and when there is no .%filetype in format string
void
OrganizeCollectionDialog::slotEnableOk( const QString & currentCollectionRoot )
{
if( currentCollectionRoot == 0 )
enableButtonOk( false );
else
enableButtonOk( true );
}
diff --git a/src/dialogs/TagDialog.cpp b/src/dialogs/TagDialog.cpp
index 0ab07bf630..af461a49d5 100644
--- a/src/dialogs/TagDialog.cpp
+++ b/src/dialogs/TagDialog.cpp
@@ -1,1411 +1,1416 @@
/****************************************************************************************
* Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it> *
* Copyright (c) 2005-2006 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2008 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2009 Daniel Dewald <Daniel.Dewald@time-shift.de> *
* Copyright (c) 2009 Pierre Dumuid <pmdumuid@gmail.com> *
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "TagDialog"
#include "TagDialog.h"
#include "MainWindow.h"
#include "SvgHandler.h"
#include "core/collections/QueryMaker.h"
#include "core/interfaces/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 <KLineEdit>
#include <QMenu>
#include <KRun>
#include <KGlobalSettings>
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 )
: KDialog( 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( mainWidget() );
resize( minimumSizeHint() );
initUi();
setCurrentTrack( 0 );
}
TagDialog::TagDialog( Meta::TrackPtr track, QWidget *parent )
: KDialog( parent )
, m_perTrack( true )
, m_currentTrackNum( 0 )
, m_changed( false )
, m_queryMaker( 0 )
, ui( new Ui::TagDialogBase() )
{
DEBUG_BLOCK
addTrack( track );
ui->setupUi( mainWidget() );
resize( minimumSizeHint() );
initUi();
setCurrentTrack( 0 );
- QTimer::singleShot( 0, this, SLOT(show()) );
+ QTimer::singleShot( 0, this, &TagDialog::show );
}
TagDialog::TagDialog( Collections::QueryMaker *qm )
: KDialog( The::mainWindow() )
, m_perTrack( true )
, m_currentTrackNum( 0 )
, m_changed( false )
, m_queryMaker( qm )
, ui( new Ui::TagDialogBase() )
{
DEBUG_BLOCK
ui->setupUi( mainWidget() );
resize( minimumSizeHint() );
qm->setQueryType( Collections::QueryMaker::Track );
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(resultReady(Meta::TrackList)), Qt::QueuedConnection );
- connect( qm, SIGNAL(queryDone()), this, SLOT(queryDone()), Qt::QueuedConnection );
+ 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->kTabWidget->currentIndex() );
if( m_currentTrack && m_currentTrack->album() )
unsubscribeFrom( m_currentTrack->album() );
delete ui;
}
void
TagDialog::metadataChanged( Meta::AlbumPtr album )
{
if( !m_currentTrack || !m_currentTrack->album() )
return;
// If the metadata of the current album has changed, reload the cover
if( album == m_currentTrack->album() )
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::resultReady( const Meta::TrackList &tracks )
+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, SLOT(show()) );
+ QTimer::singleShot( 0, this, &TagDialog::show );
}
else
{
deleteLater();
}
}
void
-TagDialog::resultReady( const Meta::AlbumList &albums )
+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::resultReady( const Meta::ArtistList &artists )
+TagDialog::artistsReady( const Meta::ArtistList &artists )
{
foreach( const Meta::ArtistPtr &artist, artists )
{
if( !artist->name().isEmpty() )
m_artists << artist->name();
}
}
void
-TagDialog::resultReady( const Meta::ComposerList &composers )
+TagDialog::composersReady( const Meta::ComposerList &composers )
{
foreach( const Meta::ComposerPtr &composer, composers )
{
if( !composer->name().isEmpty() )
m_composers << composer->name();
}
}
void
-TagDialog::resultReady( const Meta::GenreList &genres )
+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::resultReady( const Meta::LabelList &labels )
+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();
KDialog::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 KDialog 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() == KDialog::Accepted )
{
dialog.onAccept();
int cur = 0;
QMap<qint64,QString> 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 );
setButtons( KDialog::None );
KConfigGroup config = Amarok::config( "TagDialog" );
ui->kTabWidget->addTab( ui->summaryTab , i18n( "Summary" ) );
ui->kTabWidget->addTab( ui->tagsTab , i18n( "Tags" ) );
ui->kTabWidget->addTab( ui->lyricsTab , i18n( "Lyrics" ) );
ui->kTabWidget->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->kTabWidget->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" ) );
- connect( ui->pushButton_guessTags, SIGNAL(clicked()), SLOT(guessFromFilename()) );
+ 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, SIGNAL(textChanged(QString)), SLOT(checkChanged()) );
- connect( ui->kComboBox_composer, SIGNAL(activated(int)), SLOT(checkChanged()) );
- connect( ui->kComboBox_composer, SIGNAL(editTextChanged(QString)), SLOT(checkChanged()) );
- connect( ui->kComboBox_artist, SIGNAL(activated(int)), SLOT(checkChanged()) );
- connect( ui->kComboBox_artist, SIGNAL(editTextChanged(QString)), SLOT(checkChanged()) );
- connect( ui->kComboBox_album, SIGNAL(activated(int)), SLOT(checkChanged()) );
- connect( ui->kComboBox_album, SIGNAL(editTextChanged(QString)), SLOT(checkChanged()) );
- connect( ui->kComboBox_albumArtist, SIGNAL(activated(int)), SLOT(checkChanged()) );
- connect( ui->kComboBox_albumArtist, SIGNAL(editTextChanged(QString)), SLOT(checkChanged()) );
- connect( ui->kComboBox_genre, SIGNAL(activated(int)), SLOT(checkChanged()) );
- connect( ui->kComboBox_genre, SIGNAL(editTextChanged(QString)), SLOT(checkChanged()) );
- connect( ui->kLineEdit_Bpm, SIGNAL(textChanged(QString)) , SLOT(checkChanged()) );
- connect( ui->ratingWidget, SIGNAL(ratingChanged(int)), SLOT(checkChanged()) );
- connect( ui->qSpinBox_track, SIGNAL(valueChanged(int)), SLOT(checkChanged()) );
- connect( ui->qSpinBox_year, SIGNAL(valueChanged(int)), SLOT(checkChanged()) );
- connect( ui->qSpinBox_score, SIGNAL(valueChanged(int)), SLOT(checkChanged()) );
- connect( ui->qPlainTextEdit_comment,SIGNAL(textChanged()), SLOT(checkChanged()) );
- connect( ui->kRichTextEdit_lyrics, SIGNAL(textChanged()), SLOT(checkChanged()) );
- connect( ui->qSpinBox_discNumber, SIGNAL(valueChanged(int)), SLOT(checkChanged()) );
-
- connect( ui->pushButton_cancel, SIGNAL(clicked()), SLOT(cancelPressed()) );
- connect( ui->pushButton_ok, SIGNAL(clicked()), SLOT(accept()) );
- connect( ui->pushButton_open, SIGNAL(clicked()), SLOT(openPressed()) );
- connect( ui->pushButton_previous, SIGNAL(clicked()), SLOT(previousTrack()) );
- connect( ui->pushButton_next, SIGNAL(clicked()), SLOT(nextTrack()) );
- connect( ui->checkBox_perTrack, SIGNAL(toggled(bool)), SLOT(perTrack(bool)) );
-
- connect( ui->addButton, SIGNAL(clicked()), SLOT(addLabelPressed()) );
- connect( ui->removeButton, SIGNAL(clicked()), SLOT(removeLabelPressed()) );
- connect( ui->kComboBox_label, SIGNAL(activated(int)), SLOT(labelModified()) );
- connect( ui->kComboBox_label, SIGNAL(editTextChanged(QString)), SLOT(labelModified()) );
- connect( ui->kComboBox_label, SIGNAL(returnPressed()), SLOT(addLabelPressed()) );
- connect( ui->kComboBox_label, SIGNAL(returnPressed()), SLOT(checkChanged()) );
- connect( ui->labelsList, SIGNAL(pressed(QModelIndex)), SLOT(labelSelected()) );
+ connect( ui->kLineEdit_title, &KLineEdit::textChanged, this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_composer, QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_composer, &KComboBox::editTextChanged, this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_artist, QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_artist, &KComboBox::editTextChanged, this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_album, QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_album, &KComboBox::editTextChanged, this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_albumArtist, QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_albumArtist, &KComboBox::editTextChanged, this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_genre, QOverload<int>::of(&QComboBox::activated), this, &TagDialog::checkChanged );
+ connect( ui->kComboBox_genre, &KComboBox::editTextChanged, this, &TagDialog::checkChanged );
+ connect( ui->kLineEdit_Bpm, &QLineEdit::textChanged, this, &TagDialog::checkChanged );
+ connect( ui->ratingWidget, QOverload<int>::of(&KRatingWidget::ratingChanged), this, &TagDialog::checkChanged );
+ connect( ui->qSpinBox_track, QOverload<int>::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged );
+ connect( ui->qSpinBox_year, QOverload<int>::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged );
+ connect( ui->qSpinBox_score, QOverload<int>::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<int>::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<int>::of(&QComboBox::activated), this, &TagDialog::labelModified );
+ connect( ui->kComboBox_label, &KComboBox::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, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showCoverMenu(QPoint)) );
+ connect( ui->pixmap_cover, &CoverLabel::customContextMenuRequested, this, &TagDialog::showCoverMenu );
- connect( ui->pushButton_musicbrainz, SIGNAL(clicked()), SLOT(musicbrainzTagger()) );
+ 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_currentTrack && m_currentTrack->album() )
unsubscribeFrom( m_currentTrack->album() );
m_currentTrack = m_tracks.at( num );
m_currentTrackNum = num;
if( m_currentTrack && m_currentTrack->album() )
subscribeTo( m_currentTrack->album() );
setControlsAccessability();
updateButtons();
setTagsToUi();
}
void
-TagDialog::startDataQuery( Collections::QueryMaker::QueryType type, const char *signal,
- const char *slot )
+TagDialog::startDataQuery( Collections::QueryMaker::QueryType type, const QMetaMethod &signal,
+ const QMetaMethod &slot )
{
Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker();
qm->setQueryType( type );
- connect( qm, SIGNAL(queryDone()), SLOT(dataQueryDone()), Qt::QueuedConnection );
- connect( qm, signal, slot, Qt::QueuedConnection );
+ 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,
- SIGNAL(newResultReady(Meta::ArtistList)),
- SLOT(resultReady(Meta::ArtistList)) );
+ QMetaMethod::fromSignal(&Collections::QueryMaker::newArtistsReady),
+ QMetaMethod::fromSignal(&TagDialog::artistsReady) );
startDataQuery( Collections::QueryMaker::Album,
- SIGNAL(newResultReady(Meta::AlbumList)),
- SLOT(resultReady(Meta::AlbumList)) );
+ QMetaMethod::fromSignal(&Collections::QueryMaker::newAlbumsReady),
+ QMetaMethod::fromSignal(&TagDialog::albumsReady) );
startDataQuery( Collections::QueryMaker::Composer,
- SIGNAL(newResultReady(Meta::ComposerList)),
- SLOT(resultReady(Meta::ComposerList)) );
+ QMetaMethod::fromSignal(&Collections::QueryMaker::newComposersReady),
+ QMetaMethod::fromSignal(&TagDialog::composersReady) );
startDataQuery( Collections::QueryMaker::Genre,
- SIGNAL(newResultReady(Meta::GenreList)),
- SLOT(resultReady(Meta::GenreList)) );
+ QMetaMethod::fromSignal(&Collections::QueryMaker::newGenresReady),
+ QMetaMethod::fromSignal(&TagDialog::genresReady) );
startDataQuery( Collections::QueryMaker::Label,
- SIGNAL(newResultReady(Meta::LabelList)),
- SLOT(resultReady(Meta::LabelList)) );
+ 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 )
{
Meta::AlbumPtr album = m_currentTrack->album();
if( !album )
return; // TODO: warning or something?
QAction *displayCoverAction = new DisplayCoverAction( this, album );
QAction *unsetCoverAction = new UnsetCoverAction( this, album );
if( !album->hasImage() )
{
displayCoverAction->setEnabled( false );
unsetCoverAction->setEnabled( false );
}
QMenu *menu = new QMenu( this );
menu->addAction( displayCoverAction );
menu->addAction( new FetchCoverAction( this, album ) );
menu->addAction( new SetCustomCoverAction( this, album ) );
menu->addAction( unsetCoverAction );
menu->exec( ui->pixmap_cover->mapToGlobal(pos) );
}
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( Qt::escape( m_currentTrack->name() ), Qt::ElideRight, len );
QString curTrackPretName = fnt.elidedText( Qt::escape( m_currentTrack->prettyName() ), Qt::ElideRight, len );
if( m_currentTrack->album() )
curTrackAlbName = fnt.elidedText( Qt::escape( m_currentTrack->album()->name() ), Qt::ElideRight, len );
if( m_currentTrack->artist() )
curArtistName = fnt.elidedText( Qt::escape( m_currentTrack->artist()->name() ), Qt::ElideRight, len );
if( m_currentTrack->album() && m_currentTrack->album()->name().isEmpty() )
{
if( !m_currentTrack->name().isEmpty() )
{
if( !m_currentTrack->artist()->name().isEmpty() )
niceTitle = i18n( "<b>%1</b> by <b>%2</b>", curTrackName, curArtistName );
else
niceTitle = i18n( "<b>%1</b>", curTrackName );
}
else
niceTitle = curTrackPretName;
}
else if( m_currentTrack->album() )
niceTitle = i18n( "<b>%1</b> by <b>%2</b> on <b>%3</b>" , curTrackName, curArtistName, curTrackAlbName );
else if( m_currentTrack->artist() )
niceTitle = i18n( "<b>%1</b> by <b>%2</b>" , curTrackName, curArtistName );
else
niceTitle = i18n( "<b>%1</b>" , 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<QString> uiLabels = m_labelModel->labels().toSet();
QSet<QString> 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<QString> 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<QString> oldLabelsSet = oldTags.value( Meta::Field::LABELS ).toStringList().toSet();
QSet<QString> newLabelsSet = newTags.value( Meta::Field::LABELS ).toStringList().toSet();
QSet<QString> labelsToRemove = oldLabelsSet - newLabelsSet;
QSet<QString> labelsToAdd = newLabelsSet - oldLabelsSet;
// apply the differences for each track
foreach( const Meta::TrackPtr &track, m_tracks )
{
QSet<QString> 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_currentTrack->album();
if( !m_perTrack )
{
foreach( Meta::TrackPtr track, m_tracks )
{
if( track->album() != album )
album = 0;
}
}
// -- 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->kTabWidget->setTabEnabled( ui->kTabWidget->indexOf(ui->lyricsTab),
m_perTrack );
ui->kLineEdit_title->setEnabled( m_perTrack && editable );
ui->kLineEdit_title->setClearButtonShown( m_perTrack && editable );
#define enableOrDisable( X ) \
ui->X->setEnabled( editable ); \
qobject_cast<KLineEdit*>(ui->X->lineEdit())->setClearButtonShown( 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->setClearButtonShown( editable );
ui->qPlainTextEdit_comment->setEnabled( editable );
ui->pushButton_guessTags->setEnabled( m_perTrack && editable );
ui->pushButton_musicbrainz->setEnabled( editable );
}
void
TagDialog::saveLabels( Meta::TrackPtr track, const QStringList &labels )
{
if( !track )
return;
QHash<QString, Meta::LabelPtr> labelMap;
foreach( const Meta::LabelPtr &label, track->labels() )
{
labelMap.insert( label->name(), label );
}
// labels to remove
foreach( const QString &label, labelMap.keys().toSet() - labels.toSet() )
{
track->removeLabel( labelMap.value( label ) );
}
// labels to add
foreach( const QString &label, labels.toSet() - labelMap.keys().toSet() )
{
track->addLabel( label );
}
}
void
TagDialog::saveTags()
{
setTagsToTrack();
foreach( Meta::TrackPtr 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....";
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() );
emit lyricsChanged( track->uidUrl() );
}
saveLabels( track, data.value( Meta::Field::LABELS ).toStringList() );
Meta::TrackEditorPtr ec = track->editor();
if( !ec )
{
debug() << "Track" << track->prettyUrl() << "does not have Meta::TrackEditor. Skipping.";
continue;
}
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 emit a collectionUpdated signal if needed
}
}
}
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, SIGNAL(sendResult(QMap<Meta::TrackPtr,QVariantMap>)),
- this, SLOT(musicbrainzTaggerResult(QMap<Meta::TrackPtr,QVariantMap>)) );
+ connect( dialog, &MusicBrainzTagger::sendResult,
+ this, &TagDialog::musicbrainzTaggerResult );
dialog->show();
}
void
TagDialog::musicbrainzTaggerResult( const QMap<Meta::TrackPtr, QVariantMap> 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() );
}
#include "moc_TagDialog.cpp"
diff --git a/src/dialogs/TagDialog.h b/src/dialogs/TagDialog.h
index 319dc7d526..6bfeab2e06 100644
--- a/src/dialogs/TagDialog.h
+++ b/src/dialogs/TagDialog.h
@@ -1,237 +1,237 @@
/****************************************************************************************
* Copyright (c) 2004 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it> *
* Copyright (c) 2005 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_TAGDIALOG_H
#define AMAROK_TAGDIALOG_H
#include <config.h>
#include "amarok_export.h"
#include "playlist/PlaylistItem.h"
#include "LabelListModel.h"
#include "core/meta/Observer.h"
#include "core/collections/MetaQueryMaker.h"
#include <KDialog>
#include <QDateTime>
#include <QLabel>
#include <QListIterator>
#include <QMap>
#include <QSet>
#include <QVariant>
#include <QWidget>
namespace Ui
{
class TagDialogBase;
}
class QComboBox;
class AMAROK_EXPORT TagDialog : public KDialog, public Meta::Observer
{
Q_OBJECT
public:
enum Tabs { SUMMARYTAB, TAGSTAB, LYRICSTAB, LABELSTAB };
explicit TagDialog( const Meta::TrackList &tracks, QWidget *parent = 0 );
explicit TagDialog( Meta::TrackPtr track, QWidget *parent = 0 );
explicit TagDialog( Collections::QueryMaker *qm );
~TagDialog();
// inherited from Meta::Observer
using Observer::metadataChanged;
void metadataChanged( Meta::AlbumPtr album );
Q_SIGNALS:
void lyricsChanged( const QString& );
private Q_SLOTS:
void accept();
void cancelPressed();
void openPressed();
void previousTrack();
void nextTrack();
void perTrack( bool );
void checkChanged();
/**
* removes selected label from list
*/
void removeLabelPressed();
/**
* adds label to list
*/
void addLabelPressed();
void showCoverMenu( const QPoint &pos );
/**
* Shows FileNameLayoutDialog to guess tags from filename
*/
void guessFromFilename();
void musicbrainzTagger();
void musicbrainzTaggerResult( const QMap<Meta::TrackPtr, QVariantMap > result );
/** Safely adds a track to m_tracks.
Ensures that tracks are not added twice.
*/
void addTrack( Meta::TrackPtr &track );
- void resultReady( const Meta::TrackList &tracks );
+ void tracksReady( const Meta::TrackList &tracks );
void queryDone();
- void resultReady( const Meta::AlbumList &albums );
- void resultReady( const Meta::ArtistList &artists );
- void resultReady( const Meta::ComposerList &composers );
- void resultReady( const Meta::GenreList &genres );
+ void albumsReady( const Meta::AlbumList &albums );
+ void artistsReady( const Meta::ArtistList &artists );
+ void composersReady( const Meta::ComposerList &composers );
+ void genresReady( const Meta::GenreList &genres );
/**
* Updates global label list by querying all collections for all existing labels.
*/
- void resultReady( const Meta::LabelList &labels );
+ void labelsReady( const Meta::LabelList &labels );
void dataQueryDone();
/**
* Updates Add label button
*/
void labelModified();
/**
* Updates Remove label button
*/
void labelSelected();
private:
/** Sets some further properties and connects all the signals */
void initUi();
/** Set's the current track to the number.
Will check agains invalid numbers, so the caller does not have to do that.
*/
void setCurrentTrack( int num );
/** Start a query maker for the given query type */
- void startDataQuery( Collections::QueryMaker::QueryType type, const char* signal, const char* slot );
+ void startDataQuery( Collections::QueryMaker::QueryType type, const QMetaMethod &signal, const QMetaMethod &slot );
/** Start queries for artists, albums, composers, genres and labels to fill out the combo boxes */
void startDataQueries();
/** Sets the tags in the UI, cleaning unset tags */
void setTagsToUi( const QVariantMap &tags );
/** Sets the tags in the UI, cleaning unset tags depending on m_perTrack */
void setTagsToUi();
/** Gets the changed tags from the UI */
QVariantMap getTagsFromUi( const QVariantMap &tags ) const;
/** Gets all the needed tags (just the one that we display or edit) from the track */
QVariantMap getTagsFromTrack( const Meta::TrackPtr &track ) const;
/** Gets a summary of all the tags from m_tracks */
QVariantMap getTagsFromMultipleTracks() const;
/** Overwrites all values in the stored tags with the new ones. Tags not in "tags" map are left unchanged. */
void setTagsToTrack( const Meta::TrackPtr &track, const QVariantMap &tags );
/** Overwrites all values in the stored tags with the new ones.
Tags not in "tags" map are left unchanged.
Exception are labels which are not set.
*/
void setTagsToMultipleTracks( QVariantMap tags );
/** Smartly writes back tags data depending on m_perTrack */
void setTagsToTrack();
/** Sets the UI to edit either one or the complete list of tracks.
Don't forget to save the old ui values and set the new tags afterwards.
*/
void setPerTrack( bool isEnabled );
void updateButtons();
void updateCover();
void setControlsAccessability();
/**
* Stores changes to labels for a specific track
* @arg track Track to store the labels to
* @arg labels The new set of labels for the track
*/
void saveLabels( Meta::TrackPtr track, const QStringList &labels );
/** Writes all the tags to all the tracks.
This finally updates the Meta::Tracks
*/
void saveTags();
/**
* Returns "Unknown" if the value is null or not known
* Otherwise returns the string
*/
const QString unknownSafe( const QString &s ) const;
const QString unknownSafe( int i ) const;
const QStringList filenameSchemes();
void selectOrInsertText( const QString &text, QComboBox *comboBox );
QString m_path; // the directory of the current track/tracks
LabelListModel *m_labelModel; //!< Model MVC Class for Track label list
bool m_perTrack;
Meta::TrackList m_tracks;
Meta::TrackPtr m_currentTrack;
int m_currentTrackNum;
/** True if m_storedTags contains changed.
The pushButton_ok will be activated if this one is true and the UI
has further changes
*/
bool m_changed;
/** The tags for the tracks.
If the tags are edited then this structure is updated when switching
between single and multiple mode or when pressing the save button.
*/
QMap<Meta::TrackPtr, QVariantMap > m_storedTags;
// the query maker to get the tracks to be edited
Collections::QueryMaker *m_queryMaker;
QSet<QString> m_artists;
QSet<QString> m_albums;
QSet<QString> m_albumArtists;
QSet<QString> m_composers;
QSet<QString> m_genres;
QSet<QString> m_allLabels; //! all labels known to currently active collections, used for autocompletion
Ui::TagDialogBase *ui;
};
#endif /*AMAROK_TAGDIALOG_H*/
diff --git a/src/dialogs/TagGuesserDialog.cpp b/src/dialogs/TagGuesserDialog.cpp
index 5e27847eed..5a9bcd7d73 100644
--- a/src/dialogs/TagGuesserDialog.cpp
+++ b/src/dialogs/TagGuesserDialog.cpp
@@ -1,449 +1,449 @@
/****************************************************************************************
* Copyright (c) 2008 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Daniel Dewald <Daniel.Dewald@time.shift.de> *
* Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TagGuesserDialog.h"
#include "TagGuesser.h"
#include "../widgets/TokenPool.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "MetaValues.h"
#include "TagsFromFileNameGuesser.h"
#include <QBoxLayout>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
#define album_color Qt::red
#define albumartist_color Qt::blue
#define artist_color Qt::blue
#define comment_color Qt::gray
#define composer_color Qt::magenta
#define genre_color Qt::cyan
#define title_color Qt::green
#define track_color Qt::yellow
#define discnr_color Qt::yellow
#define year_color Qt::darkRed
// -------------- TagGuessOptionWidget ------------
TagGuessOptionWidget::TagGuessOptionWidget( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
m_caseEditRadioButtons << rbAllUpper
<< rbAllLower
<< rbFirstLetter
<< rbTitleCase;
int caseOptions = Amarok::config( "TagGuesser" ).readEntry( "Case options", 4 );
if( !caseOptions )
cbCase->setChecked( false );
else
{
cbCase->setChecked( true );
switch( caseOptions )
{
case 4:
rbAllLower->setChecked( true );
break;
case 3:
rbAllUpper->setChecked( true );
break;
case 2:
rbFirstLetter->setChecked( true );
break;
case 1:
rbTitleCase->setChecked( true );
break;
default:
debug() << "OUCH";
}
}
cbEliminateSpaces->setChecked( Amarok::config( "TagGuesser" ).readEntry( "Eliminate trailing spaces", false ) );
cbReplaceUnderscores->setChecked( Amarok::config( "TagGuesser" ).readEntry( "Replace underscores", false ) );
- connect( cbCase, SIGNAL(toggled(bool)),
- this, SLOT(editStateEnable(bool)) );
- connect( cbCase, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
- connect( rbTitleCase, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
- connect( rbFirstLetter, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
- connect( rbAllLower, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
- connect( rbAllUpper, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
- connect( cbEliminateSpaces, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
- connect( cbReplaceUnderscores, SIGNAL(toggled(bool)),
- this, SIGNAL(changed()) );
+ connect( cbCase, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::editStateEnable );
+ connect( cbCase, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
+ connect( rbTitleCase, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
+ connect( rbFirstLetter, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
+ connect( rbAllLower, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
+ connect( rbAllUpper, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
+ connect( cbEliminateSpaces, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
+ connect( cbReplaceUnderscores, &QCheckBox::toggled,
+ this, &TagGuessOptionWidget::changed );
}
void
TagGuessOptionWidget::editStateEnable( bool checked ) //SLOT
{
foreach( QRadioButton *rb, m_caseEditRadioButtons )
rb->setEnabled( checked );
}
//Returns a code for the configuration.
int
TagGuessOptionWidget::getCaseOptions()
{
//Amarok::config( "TagGuesser" ).readEntry( "Filename schemes", QStringList() );
if( !cbCase->isChecked() )
return 0;
else
{
if( rbAllLower->isChecked() )
return 4;
else if( rbAllUpper->isChecked() )
return 3;
else if( rbFirstLetter->isChecked() )
return 2;
else if( rbTitleCase->isChecked() )
return 1;
else
{
debug() << "OUCH!";
return 0;
}
}
}
//As above
bool
TagGuessOptionWidget::getWhitespaceOptions()
{
return cbEliminateSpaces->isChecked();
}
//As above
bool
TagGuessOptionWidget::getUnderscoreOptions()
{
return cbReplaceUnderscores->isChecked();
}
// ------------------------- TagGuesserWidget -------------------
TagGuesserWidget::TagGuesserWidget( QWidget *parent )
: FilenameLayoutWidget( parent )
{
m_configCategory = "FilenameLayoutWidget";
m_tokenPool->addToken( createToken( Title ) );
m_tokenPool->addToken( createToken( Artist ) );
m_tokenPool->addToken( createToken( AlbumArtist ) );
m_tokenPool->addToken( createToken( Album ) );
m_tokenPool->addToken( createToken( Genre ) );
m_tokenPool->addToken( createToken( Composer ) );
m_tokenPool->addToken( createToken( Comment ) );
m_tokenPool->addToken( createToken( Year ) );
m_tokenPool->addToken( createToken( TrackNumber ) );
m_tokenPool->addToken( createToken( DiscNumber ) );
m_tokenPool->addToken( createToken( Ignore ) );
m_tokenPool->addToken( createToken( Slash ) );
m_tokenPool->addToken( createToken( Underscore ) );
m_tokenPool->addToken( createToken( Dash ) );
m_tokenPool->addToken( createToken( Dot ) );
m_tokenPool->addToken( createToken( Space ) );
m_syntaxLabel->setText( i18nc("Please do not translate the %foo% words as they define a syntax used internally by a parser to describe a filename.",
// xgettext: no-c-format
"The following tokens can be used to define a filename scheme:<br> \
<font color=\"%1\">%track%</font>, <font color=\"%2\">%title%</font>, \
<font color=\"%3\">%artist%</font>, <font color=\"%4\">%composer%</font>, \
<font color=\"%5\">%year%</font>, <font color=\"%6\">%album%</font>, \
<font color=\"%7\">%albumartist%</font>, <font color=\"%8\">%comment%</font>, \
<font color=\"%9\">%genre%</font>, %ignore%."
, QColor( track_color ).name(), QColor( title_color ).name(), QColor( artist_color ).name(), \
QColor( composer_color ).name(), QColor( year_color ).name(), QColor( album_color ).name(), QColor( albumartist_color ).name(), \
QColor( comment_color ).name(), QColor( genre_color ).name() ) );
populateConfiguration();
}
Token*
TagGuesserWidget::createToken(qint64 value) const
{
Token* token = FilenameLayoutWidget::createToken( value );
// return colored tokens.
QColor color = Qt::transparent;
switch( value )
{
case TrackNumber: color = QColor( track_color ); break;
case Title: color = QColor( title_color ); break;
case Artist: color = QColor( artist_color ); break;
case Composer: color = QColor( composer_color ); break;
case Year: color = QColor( year_color ); break;
case Album: color = QColor( album_color ); break;
case AlbumArtist: color = QColor( albumartist_color ); break;
case Comment: color = QColor( comment_color ); break;
case Genre: color = QColor( genre_color );
}
if (color != Qt::transparent)
token->setTextColor( color );
return token;
}
// -------------- TagGuesserDialog ------------
TagGuesserDialog::TagGuesserDialog( const QString &fileName, QWidget *parent )
: QDialog( parent )
, m_fileName( fileName )
{
setWindowTitle( i18n( "Guess Tags from Filename" ) );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QWidget* mainWidget = new QWidget( this );
QBoxLayout* mainLayout = new QVBoxLayout( mainWidget );
setLayout(mainLayout);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &TagGuesserDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &TagGuesserDialog::reject);
m_layoutWidget = new TagGuesserWidget( this );
mainLayout->addWidget( m_layoutWidget );
m_filenamePreview = new QLabel();
m_filenamePreview->setAlignment( Qt::AlignHCenter );
mainLayout->addWidget( m_filenamePreview );
m_optionsWidget = new TagGuessOptionWidget();
mainLayout->addWidget( m_optionsWidget );
- connect( m_layoutWidget, SIGNAL(schemeChanged()),
- this, SLOT(updatePreview()) );
- connect( m_optionsWidget, SIGNAL(changed()),
- this, SLOT(updatePreview()) );
+ connect( m_layoutWidget, &TagGuesserWidget::schemeChanged,
+ this, &TagGuesserDialog::updatePreview );
+ connect( m_optionsWidget, &TagGuessOptionWidget::changed,
+ this, &TagGuesserDialog::updatePreview );
updatePreview();
mainLayout->addWidget(mainWidget);
mainLayout->addWidget(buttonBox);
}
//Sets Filename for Preview
void
TagGuesserDialog::setFileName( const QString& fileName )
{
m_fileName = fileName;
updatePreview();
}
//Stores the configuration when the dialog is accepted.
void
TagGuesserDialog::onAccept() //SLOT
{
m_layoutWidget->onAccept();
Amarok::config( "TagGuesser" ).writeEntry( "Case options", m_optionsWidget->getCaseOptions() );
Amarok::config( "TagGuesser" ).writeEntry( "Eliminate trailing spaces", m_optionsWidget->getWhitespaceOptions() );
Amarok::config( "TagGuesser" ).writeEntry( "Replace underscores", m_optionsWidget->getUnderscoreOptions() );
}
QMap<qint64,QString>
TagGuesserDialog::guessedTags()
{
DEBUG_BLOCK;
QString scheme = m_layoutWidget->getParsableScheme();
QString fileName = getParsableFileName();
if( scheme.isEmpty() )
return QMap<qint64,QString>();
TagGuesser guesser;
guesser.setFilename( fileName );
guesser.setCaseType( m_optionsWidget->getCaseOptions() );
guesser.setConvertUnderscores( m_optionsWidget->getUnderscoreOptions() );
guesser.setCutTrailingSpaces( m_optionsWidget->getWhitespaceOptions() );
guesser.setSchema( scheme );
if( !guesser.guess() )
{
m_filenamePreview->setText( getParsableFileName() );
return QMap<qint64,QString>();
}
return guesser.tags();
}
//Updates the Filename Preview
void
TagGuesserDialog::updatePreview() //SLOT
{
DEBUG_BLOCK;
QMap<qint64,QString> tags = guessedTags();
m_filenamePreview->setText( coloredFileName( tags ) );
QString emptyTagText = i18nc( "Text to represent an empty tag. Braces (<>) are only to clarify emptiness.", "&lt;empty&gt;" );
quint64 fields[] = {
Meta::valAlbum,
Meta::valAlbumArtist,
Meta::valTitle,
Meta::valAlbum,
Meta::valArtist,
Meta::valComposer,
Meta::valGenre,
Meta::valComment,
Meta::valTrackNr,
Meta::valYear,
0};
QLabel *labels[] = {
m_optionsWidget->Album_result,
m_optionsWidget->AlbumArtist_result,
m_optionsWidget->Title_result,
m_optionsWidget->Album_result,
m_optionsWidget->Artist_result,
m_optionsWidget->Composer_result,
m_optionsWidget->Genre_result,
m_optionsWidget->Comment_result,
m_optionsWidget->Track_result,
m_optionsWidget->Year_result,
0};
for( int i = 0; fields[i]; i++ )
{
if( tags.contains( fields[i] ) )
labels[i]->setText( "<font color='" + TagGuesserDialog::fieldColor( fields[i] ) + "'>" + tags[ fields[i] ] + "</font>" );
else
labels[i]->setText( emptyTagText );
}
}
QString
TagGuesserDialog::parsableFileName( const QFileInfo &fileInfo ) const
{
DEBUG_BLOCK;
QString path = fileInfo.absoluteFilePath();
debug() << m_layoutWidget->getParsableScheme() << "; " << path;
int schemaLevels = m_layoutWidget->getParsableScheme().count( '/' );
int pathLevels = path.count( '/' );
// -- cut paths
int pos;
for( pos = 0; pathLevels > schemaLevels && pos < path.length(); pos++ )
if( path[pos] == '/' )
pathLevels--;
// -- cut extension
int dotPos = path.lastIndexOf( '.' );
if( dotPos >= 0 )
dotPos -= pos;
debug() << "parsableFileName schemaLevels:" << schemaLevels << "pathLevels:" << pathLevels << "path:" << path << "pos:" << pos << dotPos << path.mid( pos, dotPos );
return path.mid( pos, dotPos );
}
QString
TagGuesserDialog::getParsableFileName()
{
return parsableFileName( QFileInfo( m_fileName ) );
}
// creates a colored version of the filename
QString
TagGuesserDialog::coloredFileName( QMap<qint64,QString> tags )
{
QString coloredFileName = m_fileName;
foreach( qint64 key, tags.keys() )
{
QString value = tags[key];
// TODO: replace is not the right way to do this.
coloredFileName.replace( value, "<font color=\"" + fieldColor( key ) +
"\">" + value + "</font>", Qt::CaseInsensitive );
}
return coloredFileName;
}
QString
TagGuesserDialog::fieldColor( qint64 field )
{
Qt::GlobalColor color;
switch ( field )
{
case Meta::valAlbum:
color = album_color;
break;
case Meta::valAlbumArtist:
color = albumartist_color;
break;
case Meta::valArtist:
color = artist_color;
break;
case Meta::valComment:
color = comment_color;
break;
case Meta::valComposer:
color = composer_color;
break;
case Meta::valDiscNr:
color = discnr_color;
break;
case Meta::valGenre:
color = genre_color;
break;
case Meta::valTitle:
color = title_color;
break;
case Meta::valTrackNr:
color = track_color;
break;
case Meta::valYear:
color = year_color;
break;
default:
color = Qt::black;
}
return QColor( color ).name();
}
diff --git a/src/dialogs/deletedialog.cpp b/src/dialogs/deletedialog.cpp
index f3c4f9f804..e87e9c9eac 100644
--- a/src/dialogs/deletedialog.cpp
+++ b/src/dialogs/deletedialog.cpp
@@ -1,164 +1,164 @@
/****************************************************************************************
* Copyright (c) 2004 Michael Pyne <michael.pyne@kdemail.net> *
* Copyright (c) 2006 Ian Monroe <ian@monroe.nu> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "deletedialog.h"
#include "core/support/Amarok.h"
#include "App.h"
#include "statusbar/StatusBar.h"
#include <KConfigGroup>
#include <KDialog>
#include <KGlobal>
#include <KIconLoader>
#include <KIO/DeleteJob>
#include <KLocale>
#include <KStandardGuiItem>
#include <QUrl>
//////////////////////////////////////////////////////////////////////////////
// DeleteWidget implementation
//////////////////////////////////////////////////////////////////////////////
DeleteWidget::DeleteWidget(QWidget *parent)
: DeleteDialogBase(parent)
{
KConfigGroup messageGroup(KGlobal::config(), "FileRemover");
bool deleteInstead = messageGroup.readEntry("deleteInsteadOfTrash", false);
slotShouldDelete(deleteInstead);
ddShouldDelete->setChecked(deleteInstead);
}
void DeleteWidget::setFiles(const QList<QUrl> &files)
{
ddFileList->clear();
// ddFileList->insertStringList(files);
for( QList<QUrl>::ConstIterator it = files.constBegin(), end = files.constEnd(); it != end; ++it)
{
if( (*it).isLocalFile() ) //path is nil for non-local
ddFileList->insertItem( (*it).path() );
else
ddFileList->insertItem( (*it).url() );
}
ddNumFiles->setText(i18np("<b>1</b> file selected.", "<b>%1</b> files selected.", files.count()));
}
void DeleteWidget::slotShouldDelete(bool shouldDelete)
{
if(shouldDelete) {
ddDeleteText->setText(i18n("<qt>These items will be <b>permanently "
"deleted</b> from your hard disk.</qt>"));
ddWarningIcon->setPixmap(KIconLoader::global()->loadIcon("dialog-warning",
KIconLoader::Desktop, KIconLoader::SizeLarge));
}
else {
ddDeleteText->setText(i18n("<qt>These items will be moved to the Trash Bin.</qt>"));
ddWarningIcon->setPixmap(KIconLoader::global()->loadIcon("user-trash-full",
KIconLoader::Desktop, KIconLoader::SizeLarge));
}
}
//////////////////////////////////////////////////////////////////////////////
// DeleteDialog implementation
//////////////////////////////////////////////////////////////////////////////
DeleteDialog::DeleteDialog( QWidget *parent, const char *name )
: KDialog( parent ),
m_trashGuiItem(i18n("&Send to Trash"), "user-trash-full")
{
//Swallow, Qt::WStyle_DialogBorder, parent, name,
//true /* modal */, i18n("About to delete selected files"),
// Ok | Cancel, Cancel /* Default */, true /* separator */
setObjectName( name );
setCaption( i18n("About to delete selected files") );
setModal( true );
setButtons( Ok | Cancel );
setDefaultButton( Cancel );
showButtonSeparator( true );
m_widget = new DeleteWidget(this);
m_widget->setObjectName("delete_dialog_widget");
setMainWidget(m_widget);
m_widget->setMinimumSize(400, 300);
setMinimumSize(410, 326);
adjustSize();
slotShouldDelete(shouldDelete());
- connect(m_widget->ddShouldDelete, SIGNAL(toggled(bool)), SLOT(slotShouldDelete(bool)));
+ connect(m_widget->ddShouldDelete, &QCheckBox::toggled, this, &DeleteDialog::slotShouldDelete);
}
bool DeleteDialog::confirmDeleteList(const QList<QUrl>& condemnedFiles)
{
m_widget->setFiles(condemnedFiles);
return exec() == QDialog::Accepted;
}
void DeleteDialog::setFiles(const QList<QUrl> &files)
{
m_widget->setFiles(files);
}
void DeleteDialog::accept()
{
KConfigGroup messageGroup(KGlobal::config(), "FileRemover");
// Save user's preference
messageGroup.writeEntry("deleteInsteadOfTrash", shouldDelete());
messageGroup.sync();
KDialog::accept();
}
void DeleteDialog::slotShouldDelete(bool shouldDelete)
{
setButtonGuiItem(Ok, shouldDelete ? KStandardGuiItem::del() : m_trashGuiItem);
}
bool DeleteDialog::showTrashDialog(QWidget* parent, const QList<QUrl>& files)
{
DeleteDialog dialog(parent);
bool doDelete = dialog.confirmDeleteList(files);
if( doDelete )
{
KIO::Job* job = 0;
bool shouldDelete = dialog.shouldDelete();
if ( ( shouldDelete && (job = KIO::del( files )) ) ||
( job = App::instance()->trashFiles( files ) ) )
{
if(shouldDelete) //amarok::trashFiles already does the progress operation
The::statusBar()->newProgressOperation( job, i18n("Deleting files") );
}
}
return doDelete;
}
// vim: set et ts=4 sw=4:
diff --git a/src/dialogs/deviceconfiguredialog.cpp b/src/dialogs/deviceconfiguredialog.cpp
index 825f8922b3..94b37ba866 100644
--- a/src/dialogs/deviceconfiguredialog.cpp
+++ b/src/dialogs/deviceconfiguredialog.cpp
@@ -1,142 +1,142 @@
/****************************************************************************************
* Copyright (c) 2005 Martin Aumueller <aumuell@reserv.at> *
* Copyright (c) 2006 Jeff Mitchell <kde-dev@emailgoeshere.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "deviceconfiguredialog.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "HintLineEdit.h"
#include "mediabrowser.h"
#include "MediaDevice.h"
#include "core/support/PluginManager.h"
#include "scripting/scriptmanager/ScriptManager.h"
#include <KLocale>
#include <KVBox>
#include <QCheckBox>
#include <QRadioButton>
#include <QLabel>
#include <q3buttongroup.h>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
DeviceConfigureDialog::DeviceConfigureDialog( MediaDevice *device )
: QDialog( Amarok::mainWindow() )
, m_device( device )
{
setWindowTitle( i18n("Select Plugin for %1", m_device->name() ) );
setModal( true );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, SLOT(accept()));
+ connect(buttonBox, &QDialogButtonBox::rejected, this, SLOT(reject()));
// showButtonSeparator( true );
//kapp->setTopWidget( this );
setWindowTitle( i18n( "Configure Media Device" ) );
buttonBox->button(QDialogButtonBox::Apply)->setVisible(false);
KVBox* vbox = new KVBox( this );
mainLayout->addWidget(vbox);
vbox->setSpacing( QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) );
//TODO KF5:PM_DefaultLayoutSpacing is obsolete. Look in QStyle docs for correctly replacing it.
QLabel *connectLabel = 0;
m_connectEdit = 0;
QLabel *disconnectLabel = 0;
m_disconnectEdit = 0;
m_transcodeCheck = 0;
Q3ButtonGroup *transcodeGroup = 0;
m_transcodeAlways = 0;
m_transcodeWhenNecessary = 0;
m_transcodeRemove = 0;
if( device )
{
device->loadConfig();
// pre-connect/post-disconnect (mount/umount)
connectLabel = new QLabel( vbox );
connectLabel->setText( i18n( "Pre-&connect command:" ) );
m_connectEdit = new HintLineEdit( device->m_preconnectcmd, vbox );
m_connectEdit->setHint( i18n( "Example: mount %d" ) );
connectLabel->setBuddy( m_connectEdit );
m_connectEdit->setToolTip( i18n( "Set a command to be run before connecting to your device (e.g. a mount command) here.\n%d is replaced by the device node, %m by the mount point.\nEmpty commands are not executed." ) );
disconnectLabel = new QLabel( vbox );
disconnectLabel->setText( i18n( "Post-&disconnect command:" ) );
m_disconnectEdit = new HintLineEdit( device->m_postdisconnectcmd, vbox );
disconnectLabel->setBuddy( m_disconnectEdit );
m_disconnectEdit->setHint( i18n( "Example: eject %d" ) );
m_disconnectEdit->setToolTip( i18n( "Set a command to be run after disconnecting from your device (e.g. an eject command) here.\n%d is replaced by the device node, %m by the mount point.\nEmpty commands are not executed." ) );
// transcode
m_transcodeCheck = new QCheckBox( vbox );
m_transcodeCheck->setText( i18n( "&Transcode before transferring to device" ) );
m_transcodeCheck->setChecked( device->m_transcode );
transcodeGroup = new Q3VButtonGroup( vbox );
QString format = "mp3";
if( !device->supportedFiletypes().isEmpty() )
format = device->supportedFiletypes().first();
transcodeGroup->setTitle( i18n( "Transcode to preferred format (%1) for device", format ) );
m_transcodeAlways = new QRadioButton( transcodeGroup );
m_transcodeAlways->setText( i18n( "Whenever possible" ) );
m_transcodeAlways->setChecked( device->m_transcodeAlways );
m_transcodeWhenNecessary = new QRadioButton( transcodeGroup );
m_transcodeWhenNecessary->setText( i18n( "When necessary" ) );
m_transcodeWhenNecessary->setChecked( !device->m_transcodeAlways );
- connect( m_transcodeCheck, SIGNAL(toggled(bool)),
+ connect( m_transcodeCheck, &QCheckBox::toggled,
transcodeGroup, SLOT(setEnabled(bool)) );
transcodeGroup->insert( m_transcodeAlways );
transcodeGroup->insert( m_transcodeWhenNecessary );
m_transcodeRemove = new QCheckBox( transcodeGroup );
m_transcodeRemove->setText( i18n( "Remove transcoded files after transfer" ) );
m_transcodeRemove->setChecked( device->m_transcodeRemove );
const ScriptManager *sm = ScriptManager::instance();
m_transcodeCheck->setEnabled( !sm->transcodeScriptRunning().isEmpty() );
transcodeGroup->setEnabled( !sm->transcodeScriptRunning().isEmpty() && device->m_transcode );
if( sm->transcodeScriptRunning().isNull() )
{
m_transcodeCheck->setToolTip( i18n( "For this feature, a script of type \"Transcode\" has to be running" ) );
transcodeGroup->setToolTip( i18n( "For this feature, a script of type \"Transcode\" has to be running" ) );
}
device->addConfigElements( vbox );
}
m_accepted = false;
}
DeviceConfigureDialog::~DeviceConfigureDialog()
{
delete m_connectEdit;
delete m_disconnectEdit;
}
diff --git a/src/dynamic/Bias.cpp b/src/dynamic/Bias.cpp
index 7b5c35c744..e0c846ca6d 100644
--- a/src/dynamic/Bias.cpp
+++ b/src/dynamic/Bias.cpp
@@ -1,514 +1,514 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com> *
* Copyright (c) 2009 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Bias"
#include "Bias.h"
#include "core/support/Debug.h"
#include "dynamic/BiasFactory.h"
#include "dynamic/DynamicModel.h"
#include "dynamic/biases/SearchQueryBias.h"
#include <KLocale>
#include <QPainter>
#include <QBuffer>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
// -------- AbstractBias -------------
Dynamic::AbstractBias::AbstractBias()
{ }
Dynamic::AbstractBias::~AbstractBias()
{
// debug() << "destroying bias" << this;
}
void
Dynamic::AbstractBias::fromXml( QXmlStreamReader *reader )
{
reader->skipCurrentElement();
}
void
Dynamic::AbstractBias::toXml( QXmlStreamWriter *writer ) const
{
Q_UNUSED( writer );
}
Dynamic::BiasPtr
Dynamic::AbstractBias::clone() const
{
QByteArray bytes;
QBuffer buffer( &bytes, 0 );
buffer.open( QIODevice::ReadWrite );
// write the bias
QXmlStreamWriter xmlWriter( &buffer );
xmlWriter.writeStartElement( name() );
toXml( &xmlWriter );
xmlWriter.writeEndElement();
// and read a new list
buffer.seek( 0 );
QXmlStreamReader xmlReader( &buffer );
while( !xmlReader.isStartElement() )
xmlReader.readNext();
return Dynamic::BiasFactory::fromXml( &xmlReader );
}
QString
Dynamic::AbstractBias::sName()
{
return QLatin1String( "abstractBias" );
}
QString
Dynamic::AbstractBias::name() const
{
return Dynamic::AbstractBias::sName();
}
QWidget*
Dynamic::AbstractBias::widget( QWidget* parent )
{
Q_UNUSED( parent );
return 0;
}
void
Dynamic::AbstractBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
{
Q_UNUSED( painter );
Q_UNUSED( rect );
Q_UNUSED( bias );
}
void
Dynamic::AbstractBias::invalidate()
{ }
void
Dynamic::AbstractBias::replace( Dynamic::BiasPtr newBias )
{
emit replaced( BiasPtr(const_cast<Dynamic::AbstractBias*>(this)), newBias );
}
// -------- RandomBias ------
Dynamic::RandomBias::RandomBias()
{ }
Dynamic::RandomBias::~RandomBias()
{ }
QString
Dynamic::RandomBias::sName()
{
return QLatin1String( "randomBias" );
}
QString
Dynamic::RandomBias::name() const
{
return Dynamic::RandomBias::sName();
}
QString
Dynamic::RandomBias::toString() const
{
return i18nc("Random bias representation", "Random tracks");
}
QWidget*
Dynamic::RandomBias::widget( QWidget* parent )
{
Q_UNUSED( parent );
return 0;
}
Dynamic::TrackSet
Dynamic::RandomBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
Q_UNUSED( playlist );
Q_UNUSED( contextCount );
Q_UNUSED( finalCount );
return Dynamic::TrackSet( universe, true );
}
bool
Dynamic::RandomBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
Q_UNUSED( position );
Q_UNUSED( playlist );
Q_UNUSED( contextCount );
return true;
}
// -------- AndBias ------
Dynamic::AndBias::AndBias()
{ }
Dynamic::AndBias::~AndBias()
{ }
void
Dynamic::AndBias::fromXml( QXmlStreamReader *reader )
{
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
Dynamic::BiasPtr bias( Dynamic::BiasFactory::fromXml( reader ) );
if( bias )
{
appendBias( bias );
}
else
{
warning()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
void
Dynamic::AndBias::toXml( QXmlStreamWriter *writer ) const
{
foreach( Dynamic::BiasPtr bias, m_biases )
{
writer->writeStartElement( bias->name() );
bias->toXml( writer );
writer->writeEndElement();
}
}
QString
Dynamic::AndBias::sName()
{
return QLatin1String( "andBias" );
}
QString
Dynamic::AndBias::name() const
{
return Dynamic::AndBias::sName();
}
QString
Dynamic::AndBias::toString() const
{
return i18nc("And bias representation", "Match all");
}
QWidget*
Dynamic::AndBias::widget( QWidget* parent )
{
Q_UNUSED( parent );
return 0;
}
void
Dynamic::AndBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
{
if( m_biases.indexOf( Dynamic::BiasPtr(bias) ) > 0 )
{
painter->drawText( rect.adjusted(2, 0, -2, 0),
Qt::AlignRight,
i18nc("Prefix for AndBias. Shown in front of a bias in the dynamic playlist view", "and" ) );
}
}
Dynamic::TrackSet
Dynamic::AndBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
DEBUG_BLOCK;
debug() << "universe:" << universe.data();
m_tracks = Dynamic::TrackSet( universe, true );
m_outstandingMatches = 0;
foreach( Dynamic::BiasPtr bias, m_biases )
{
Dynamic::TrackSet tracks = bias->matchingTracks( playlist, contextCount, finalCount, universe );
if( tracks.isOutstanding() )
m_outstandingMatches++;
else
m_tracks.intersect( tracks );
// debug() << "AndBias::matchingTracks" << bias->name() << "tracks:"<<tracks.trackCount() << "outstanding?" << tracks.isOutstanding() << "numOUt:" << m_outstandingMatches;
if( m_tracks.isEmpty() )
break;
}
// debug() << "AndBias::matchingTracks end: tracks:"<<m_tracks.trackCount() << "outstanding?" << m_tracks.isOutstanding() << "numOUt:" << m_outstandingMatches;
if( m_outstandingMatches > 0 )
return Dynamic::TrackSet();
else
return m_tracks;
}
bool
Dynamic::AndBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
foreach( Dynamic::BiasPtr bias, m_biases )
{
if( !bias->trackMatches( position, playlist, contextCount ) )
return false;
}
return true;
}
void
Dynamic::AndBias::invalidate()
{
foreach( Dynamic::BiasPtr bias, m_biases )
{
bias->invalidate();
}
m_tracks = TrackSet();
}
void
Dynamic::AndBias::appendBias( Dynamic::BiasPtr bias )
{
bool newInModel = DynamicModel::instance()->index( bias ).isValid();
if (newInModel) {
warning() << "Argh, the old bias "<<bias->toString()<<"is still in a model";
return;
}
BiasPtr thisPtr( this );
bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
if( inModel )
DynamicModel::instance()->beginInsertBias( thisPtr, m_biases.count() );
m_biases.append( bias );
if( inModel )
DynamicModel::instance()->endInsertBias();
- connect( bias.data(), SIGNAL(resultReady(Dynamic::TrackSet)),
- this, SLOT(resultReceived(Dynamic::TrackSet)) );
- connect( bias.data(), SIGNAL(replaced(Dynamic::BiasPtr,Dynamic::BiasPtr)),
- this, SLOT(biasReplaced(Dynamic::BiasPtr,Dynamic::BiasPtr)) );
- connect( bias.data(), SIGNAL(changed(Dynamic::BiasPtr)),
- this, SLOT(biasChanged(Dynamic::BiasPtr)) );
+ connect( bias.data(), &Dynamic::AbstractBias::resultReady,
+ this, &AndBias::resultReceived );
+ connect( bias.data(), &Dynamic::AbstractBias::replaced,
+ this, &AndBias::biasReplaced );
+ connect( bias.data(), &Dynamic::AbstractBias::changed,
+ this, &AndBias::biasChanged );
emit biasAppended( bias );
// creating a shared pointer and destructing it just afterwards would
// also destruct this object.
// so we give the object creating this bias a chance to increment the refcount
emit changed( thisPtr );
}
void
Dynamic::AndBias::moveBias( int from, int to )
{
if( from == to )
return;
if( from < 0 || from >= m_biases.count() )
return;
if( to < 0 || to >= m_biases.count() )
return;
BiasPtr thisPtr( this );
bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
if( inModel )
DynamicModel::instance()->beginMoveBias( thisPtr, from, to );
m_biases.insert( to, m_biases.takeAt( from ) );
if( inModel )
DynamicModel::instance()->endMoveBias();
emit biasMoved( from, to );
emit changed( BiasPtr( this ) );
}
void
Dynamic::AndBias::resultReceived( const Dynamic::TrackSet &tracks )
{
m_tracks.intersect( tracks );
--m_outstandingMatches;
if( m_outstandingMatches < 0 )
warning() << "Received more results than expected.";
else if( m_outstandingMatches == 0 )
emit resultReady( m_tracks );
}
void
Dynamic::AndBias::biasReplaced( Dynamic::BiasPtr oldBias, Dynamic::BiasPtr newBias )
{
DEBUG_BLOCK;
BiasPtr thisPtr( this );
int index = m_biases.indexOf( oldBias );
Q_ASSERT( index >= 0 );
disconnect( oldBias.data(), 0, this, 0 );
bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
if( inModel )
DynamicModel::instance()->beginRemoveBias( thisPtr, index );
m_biases.removeAt( index );
if( inModel )
DynamicModel::instance()->endRemoveBias();
emit biasRemoved( index );
if( newBias )
{
- connect( newBias.data(), SIGNAL(resultReady(Dynamic::TrackSet)),
- this, SLOT(resultReceived(Dynamic::TrackSet)) );
- connect( newBias.data(), SIGNAL(replaced(Dynamic::BiasPtr,Dynamic::BiasPtr)),
- this, SLOT(biasReplaced(Dynamic::BiasPtr,Dynamic::BiasPtr)) );
- connect( newBias.data(), SIGNAL(changed(Dynamic::BiasPtr)),
- this, SIGNAL(changed(Dynamic::BiasPtr)) );
+ connect( newBias.data(), &Dynamic::AbstractBias::resultReady,
+ this, &AndBias::resultReceived );
+ connect( newBias.data(), &Dynamic::AbstractBias::replaced,
+ this, &AndBias::biasReplaced );
+ connect( newBias.data(), &Dynamic::AbstractBias::changed,
+ this, &AndBias::changed );
if( inModel )
DynamicModel::instance()->beginInsertBias( thisPtr, index );
m_biases.insert( index, newBias );
if( inModel )
DynamicModel::instance()->endInsertBias();
// we don't have an bias inserted signal
emit biasAppended( newBias );
emit biasMoved( m_biases.count()-1, index );
}
emit changed( thisPtr );
}
void
Dynamic::AndBias::biasChanged( Dynamic::BiasPtr bias )
{
BiasPtr thisPtr( this );
emit changed( thisPtr );
bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
if( inModel )
DynamicModel::instance()->biasChanged( bias );
}
// -------- OrBias ------
Dynamic::OrBias::OrBias()
: AndBias()
{ }
QString
Dynamic::OrBias::sName()
{
return QLatin1String( "orBias" );
}
QString
Dynamic::OrBias::name() const
{
return Dynamic::OrBias::sName();
}
void
Dynamic::OrBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
{
if( m_biases.indexOf( Dynamic::BiasPtr(bias) ) > 0 )
{
painter->drawText( rect.adjusted(2, 0, -2, 0),
Qt::AlignRight,
i18nc("Prefix for OrBias. Shown in front of a bias in the dynamic playlist view", "or" ) );
}
}
QString
Dynamic::OrBias::toString() const
{
return i18nc("Or bias representation", "Match any");
}
Dynamic::TrackSet
Dynamic::OrBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
m_tracks = Dynamic::TrackSet( universe, false );
m_outstandingMatches = 0;
foreach( Dynamic::BiasPtr bias, m_biases )
{
Dynamic::TrackSet tracks = bias->matchingTracks( playlist, contextCount, finalCount, universe );
if( tracks.isOutstanding() )
m_outstandingMatches++;
else
m_tracks.unite( tracks );
if( m_tracks.isFull() )
break;
}
if( m_outstandingMatches > 0 )
return Dynamic::TrackSet();
else
return m_tracks;
}
bool
Dynamic::OrBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
foreach( Dynamic::BiasPtr bias, m_biases )
{
if( bias->trackMatches( position, playlist, contextCount ) )
return true;
}
return false;
}
void
Dynamic::OrBias::resultReceived( const Dynamic::TrackSet &tracks )
{
m_tracks.unite( tracks );
--m_outstandingMatches;
if( m_outstandingMatches < 0 )
warning() << "Received more results than expected.";
else if( m_outstandingMatches == 0 )
emit resultReady( m_tracks );
}
diff --git a/src/dynamic/BiasFactory.cpp b/src/dynamic/BiasFactory.cpp
index 0a317be7b9..7e991aa4dd 100644
--- a/src/dynamic/BiasFactory.cpp
+++ b/src/dynamic/BiasFactory.cpp
@@ -1,300 +1,300 @@
/****************************************************************************************
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "BiasFactory"
#include "BiasFactory.h"
#include "App.h"
#include "biases/AlbumPlayBias.h"
#include "biases/IfElseBias.h"
#include "biases/PartBias.h"
#include "biases/TagMatchBias.h"
#include "biases/SearchQueryBias.h"
#include "biases/QuizPlayBias.h"
#include "biases/EchoNestBias.h"
#include "core/support/Debug.h"
#include "core/collections/QueryMaker.h"
#include "dynamic/Bias.h"
#include "scripting/scriptengine/exporters/ScriptableBiasExporter.h"
#include <QFormLayout>
#include <QLabel>
#include <QList>
#include <QXmlStreamReader>
Dynamic::BiasPtr
Dynamic::AbstractBiasFactory::createFromXml( QXmlStreamReader *reader )
{
Dynamic::BiasPtr bias( createBias() );
bias->fromXml( reader );
return bias;
}
class RandomBiasFactory : public Dynamic::AbstractBiasFactory
{
QString i18nName() const
{ return i18nc("Name of the random bias", "Random"); }
QString name() const
{ return Dynamic::RandomBias::sName(); }
QString i18nDescription() const
{ return i18nc("Description of the random bias",
"The random bias adds random tracks from the\n"
"whole collection without any bias."); }
Dynamic::BiasPtr createBias()
{ return Dynamic::BiasPtr( new Dynamic::RandomBias() ); }
};
class AndBiasFactory : public Dynamic::AbstractBiasFactory
{
QString i18nName() const
{ return i18nc("Name of the \"And\" bias", "And"); }
QString name() const
{ return Dynamic::AndBias::sName(); }
QString i18nDescription() const
{ return i18nc("Description of the \"And\" bias",
"The \"And\" bias adds tracks that match all\n"
"of the sub biases."); }
Dynamic::BiasPtr createBias()
{ return Dynamic::BiasPtr( new Dynamic::AndBias() ); }
};
class OrBiasFactory : public Dynamic::AbstractBiasFactory
{
QString i18nName() const
{ return i18nc("Name of the \"Or\" bias", "Or"); }
QString name() const
{ return Dynamic::OrBias::sName(); }
QString i18nDescription() const
{ return i18nc("Description of the \"Or\" bias",
"The \"Or\" bias adds tracks that match at\n"
"least one of the sub biases."); }
Dynamic::BiasPtr createBias()
{ return Dynamic::BiasPtr( new Dynamic::OrBias() ); }
};
Dynamic::BiasFactory* Dynamic::BiasFactory::s_instance = 0;
QList<Dynamic::AbstractBiasFactory*> Dynamic::BiasFactory::s_biasFactories = QList<Dynamic::AbstractBiasFactory*>();
Dynamic::BiasFactory*
Dynamic::BiasFactory::instance()
{
if( !s_instance )
{
// --- build in biases
s_biasFactories.append( new Dynamic::SearchQueryBiasFactory() );
s_biasFactories.append( new RandomBiasFactory() );
s_biasFactories.append( new AndBiasFactory() );
s_biasFactories.append( new OrBiasFactory() );
s_biasFactories.append( new Dynamic::PartBiasFactory() );
s_biasFactories.append( new Dynamic::IfElseBiasFactory() );
s_biasFactories.append( new Dynamic::TagMatchBiasFactory() );
s_biasFactories.append( new Dynamic::AlbumPlayBiasFactory() );
s_biasFactories.append( new Dynamic::QuizPlayBiasFactory() );
s_biasFactories.append( new Dynamic::EchoNestBiasFactory() );
s_instance = new BiasFactory( App::instance() );
}
return s_instance;
}
// --------------- ReplacementBias -------------
Dynamic::ReplacementBias::ReplacementBias( const QString &n )
: m_name( n )
{
- connect( BiasFactory::instance(), SIGNAL(changed()), this, SLOT(factoryChanged()) );
+ connect( BiasFactory::instance(), &Dynamic::BiasFactory::changed, this, &ReplacementBias::factoryChanged );
}
Dynamic::ReplacementBias::ReplacementBias( const QString &n, QXmlStreamReader *reader )
: m_name( n )
{
// -- read the original bias data as one block
quint64 start = reader->characterOffset();
reader->skipCurrentElement();
quint64 end = reader->characterOffset();
QIODevice *device = reader->device();
if( device->isSequential() )
{
warning() << "Cannot read xml for bias"<<n<<"from sequential device.";
return;
}
device->seek( start );
m_html = device->read( end - start );
-debug() << "replacement bias for"<<n<<"is"<<m_html;
+ debug() << "replacement bias for"<<n<<"is"<<m_html;
- connect( BiasFactory::instance(), SIGNAL(changed()), this, SLOT(factoryChanged()) );
+ connect( BiasFactory::instance(), &Dynamic::BiasFactory::changed, this, &ReplacementBias::factoryChanged );
}
void
Dynamic::ReplacementBias::toXml( QXmlStreamWriter *writer ) const
{
Q_UNUSED( writer );
writer->writeComment("Replacement"); // we need to force the closing of the bias start tag
writer->device()->write( m_html.left( m_html.size() - m_name.length() - 3 ) );
}
QString
Dynamic::ReplacementBias::sName()
{
return QLatin1String( "replacementBias" );
}
QString
Dynamic::ReplacementBias::name() const
{
return m_name;
}
QString
Dynamic::ReplacementBias::toString() const
{
return i18n( "Replacement for bias %1", m_name );
}
QWidget*
Dynamic::ReplacementBias::widget( QWidget* parent )
{
QLabel *label = new QLabel( i18n( "Replacement for bias %1", m_name ), parent );
return label;
}
void
Dynamic::ReplacementBias::factoryChanged()
{
DEBUG_BLOCK;
// -- search if there is a new factory with my name
foreach( AbstractBiasFactory* factory, BiasFactory::instance()->factories() )
{
if( factory->name() == m_name )
{
debug() << "Found new factory for" << m_name;
// -- replace myself with the new bias
QXmlStreamReader reader( m_html );
Dynamic::BiasPtr newBias( factory->createFromXml( &reader ) );
replace( newBias );
return;
}
}
}
// ------------- BiasFactory --------------
Dynamic::BiasFactory::BiasFactory( QObject *parent )
: QObject( parent )
{ }
Dynamic::BiasFactory::~BiasFactory()
{
qDeleteAll(s_biasFactories);
}
Dynamic::BiasPtr
Dynamic::BiasFactory::fromXml( QXmlStreamReader *reader )
{
QStringRef name = reader->name();
instance(); // ensure that we have an instance with the default factories
foreach( Dynamic::AbstractBiasFactory* fac, s_biasFactories )
{
if( name == fac->name() )
return fac->createFromXml( reader );
}
return Dynamic::BiasPtr( new ReplacementBias( name.toString(), reader ) );
}
Dynamic::BiasPtr
Dynamic::BiasFactory::fromName( const QString &name )
{
instance(); // ensure that we have an instance with the default factories
foreach( Dynamic::AbstractBiasFactory* fac, s_biasFactories )
{
if( name == fac->name() )
return fac->createBias();
}
return Dynamic::BiasPtr( new ReplacementBias( name ) );
}
void
Dynamic::BiasFactory::registerNewBiasFactory( Dynamic::AbstractBiasFactory* factory )
{
instance(); // ensure that we have an instance with the default factories
debug() << "new factory of type:" << factory->name();
if( !s_biasFactories.contains( factory ) )
s_biasFactories.append( factory );
/*
foreach( const QString &name, s_failedMap.keys() )
{
if( name == entry->pluginName() ) // lazy loading!
{
debug() << "found entry loaded without proper custombiasentry. fixing now, with old weight of" << s_failedMap[ name ]->weight() ;
// need to manually set the weight, as we set it on the old widget which is now being thrown away
Dynamic::CustomBiasEntry* cbe = factory->newCustomBiasEntry( s_failedMapXml[ name ] );
s_failedMap[ name ]->setCurrentEntry( cbe );
s_failedMap.remove( name );
s_failedMapXml.remove( name );
}
}
*/
instance()->emitChanged();
}
void
Dynamic::BiasFactory::removeBiasFactory( Dynamic::AbstractBiasFactory* factory )
{
if( s_biasFactories.contains( factory ) )
s_biasFactories.removeAll( factory );
instance()->emitChanged();
}
QList<Dynamic::AbstractBiasFactory*>
Dynamic::BiasFactory::factories()
{
instance(); // ensure that we have an instance with the default factories
return s_biasFactories;
}
void
Dynamic::BiasFactory::emitChanged()
{
emit changed();
}
diff --git a/src/dynamic/BiasSolver.cpp b/src/dynamic/BiasSolver.cpp
index d4d2eddc8c..bd9b7b1f69 100644
--- a/src/dynamic/BiasSolver.cpp
+++ b/src/dynamic/BiasSolver.cpp
@@ -1,355 +1,355 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* Copyright (c) 2010, 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "BiasSolver"
#include "BiasSolver.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Debug.h"
#include "core/collections/QueryMaker.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <ThreadWeaver/Thread>
#include <QHash>
#include <QMutexLocker>
#include <cmath>
/* These number are black magic. The best values can only be obtained through
* exhaustive trial and error or writing another optimization program to
* optimize this optimization program. They are very sensitive. Be careful */
namespace Dynamic
{
class SolverList
{
public:
SolverList( Meta::TrackList trackList,
int contextCount,
BiasPtr bias )
: m_trackList(trackList)
, m_contextCount( contextCount )
, m_bias( bias )
{}
void appendTrack( Meta::TrackPtr track )
{
m_trackList.append( track );
}
void removeTrack()
{
m_trackList.removeLast();
}
SolverList &operator=( const SolverList& x )
{
m_trackList = x.m_trackList;
m_contextCount = x.m_contextCount;
m_bias = x.m_bias;
return *this;
}
Meta::TrackList m_trackList;
int m_contextCount; // the number of tracks belonging to the context
BiasPtr m_bias;
};
BiasSolver::BiasSolver( int n, BiasPtr bias, Meta::TrackList context )
: m_n( n )
, m_bias( bias )
, m_context( context )
, m_abortRequested( false )
, m_currentProgress( 0 )
{
debug() << "CREATING BiasSolver in thread:" << QThread::currentThreadId() << "to get"<<n<<"tracks with"<<context.count()<<"context";
m_allowDuplicates = AmarokConfig::dynamicDuplicates();
getTrackCollection();
- connect( m_bias.data(), SIGNAL(resultReady(Dynamic::TrackSet)),
- this, SLOT(biasResultReady(Dynamic::TrackSet)) );
+ connect( m_bias.data(), &Dynamic::AbstractBias::resultReady,
+ this, &BiasSolver::biasResultReady );
}
BiasSolver::~BiasSolver()
{
debug() << "DESTROYING BiasSolver in thread:" << QThread::currentThreadId();
emit endProgressOperation( this );
}
void
BiasSolver::requestAbort()
{
m_abortRequested = true;
emit endProgressOperation( this );
}
bool
BiasSolver::success() const
{
return !m_abortRequested;
}
void
BiasSolver::setAutoDelete( bool autoDelete )
{
if( autoDelete )
{
if( isFinished() )
deleteLater();
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(deleteLater()) );
+ connect( this, &BiasSolver::done, this, &BiasSolver::deleteLater );
}
else
{
- disconnect( this, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(deleteLater()) );
+ disconnect( this, &BiasSolver::done, this, &BiasSolver::deleteLater );
}
}
void
BiasSolver::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread )
{
Q_UNUSED(self);
Q_UNUSED(thread);
DEBUG_BLOCK
debug() << "BiasSolver::run in thread:" << QThread::currentThreadId();
m_startTime = QDateTime::currentDateTime();
// wait until we get the track collection
{
QMutexLocker locker( &m_collectionResultsMutex );
if( !m_trackCollection )
{
debug() << "waiting for collection results";
m_collectionResultsReady.wait( &m_collectionResultsMutex );
}
debug() << "collection has" << m_trackCollection->count()<<"uids";
}
debug() << "generating playlist";
SolverList list( m_context, m_context.count(), m_bias );
addTracks( &list );
debug() << "found solution"<<list.m_trackList.count()<<"time"<< m_startTime.msecsTo( QDateTime::currentDateTime() );
m_solution = list.m_trackList.mid( m_context.count() );
// setFinished( true );
setStatus(Status_Success);
}
void
BiasSolver::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
BiasSolver::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
BiasSolver::addTracks( SolverList *list )
{
bool firstTrack = ( list->m_trackList.count() == list->m_contextCount );
if( m_abortRequested )
return;
updateProgress( list );
if( list->m_trackList.count() >= list->m_contextCount + m_n )
return; // we have all tracks
TrackSet set = matchingTracks( list->m_trackList );
if( !m_allowDuplicates )
set = withoutDuplicate( list->m_trackList.count(), list->m_trackList, set );
if( set.trackCount() == 0 )
return; // no candidates
// debug() << "addTracks at"<<list->m_trackList.count()<<"candidates:"<<set.trackCount()<<"time"<< m_startTime.msecsTo( QDateTime::currentDateTime() );
for( int tries = 0; tries < 5 || firstTrack ; tries++ )
{
if( m_abortRequested )
return;
list->appendTrack( getRandomTrack( set ) );
addTracks( list ); // add another track recursively
if( list->m_trackList.count() >= list->m_contextCount + m_n )
return; // we have all tracks
// if time is up just try to fill the list as much as possible not cleaning up
if( m_startTime.msecsTo( QDateTime::currentDateTime() ) > MAX_TIME_MS )
return;
list->removeTrack();
}
}
Meta::TrackList
BiasSolver::solution()
{
return m_solution;
}
Meta::TrackPtr
BiasSolver::getRandomTrack( const TrackSet& subset ) const
{
if( subset.trackCount() == 0 )
return Meta::TrackPtr();
Meta::TrackPtr track;
// this is really dumb, but we sometimes end up with uids that don't point to anything
int giveup = 50;
while( giveup-- && !track )
track = trackForUid( subset.getRandomTrack() );
if( !track )
error() << "track is 0 in BiasSolver::getRandomTrack()";
return track;
}
Meta::TrackPtr
BiasSolver::trackForUid( const QString& uid ) const
{
const QUrl url( uid );
Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url );
if( !track )
warning() << "trackForUid returned no track for "<<uid;
return track;
}
// ---- getting the matchingTracks ----
void
BiasSolver::biasResultReady( const TrackSet &set )
{
QMutexLocker locker( &m_biasResultsMutex );
m_tracks = set;
m_biasResultsReady.wakeAll();
}
TrackSet
BiasSolver::matchingTracks( const Meta::TrackList& playlist ) const
{
QMutexLocker locker( &m_biasResultsMutex );
m_tracks = m_bias->matchingTracks( playlist,
m_context.count(), m_context.count() + m_n,
m_trackCollection );
if( m_tracks.isOutstanding() )
m_biasResultsReady.wait( &m_biasResultsMutex );
// debug() << "BiasSolver::matchingTracks returns"<<m_tracks.trackCount()<<"of"<<m_trackCollection->count()<<"tracks.";
return m_tracks;
}
Dynamic::TrackSet
BiasSolver::withoutDuplicate( int position, const Meta::TrackList& playlist,
const Dynamic::TrackSet& oldSet )
{
Dynamic::TrackSet result = Dynamic::TrackSet( oldSet );
for( int i = 0; i < playlist.count(); i++ )
if( i != position && playlist[i] )
result.subtract( playlist[i] );
return result;
}
// ---- getting the TrackCollection ----
void
BiasSolver::trackCollectionResultsReady( QStringList uids )
{
m_collectionUids.append( uids );
}
void
BiasSolver::trackCollectionDone()
{
QMutexLocker locker( &m_collectionResultsMutex );
m_trackCollection = TrackCollectionPtr( new TrackCollection( m_collectionUids ) );
m_collectionUids.clear();
m_collectionResultsReady.wakeAll();
}
void
BiasSolver::getTrackCollection()
{
// get all the unique ids from the collection manager
Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker();
qm->setQueryType( Collections::QueryMaker::Custom );
qm->addReturnValue( Meta::valUniqueId );
qm->setAutoDelete( true );
- connect( qm, SIGNAL(newResultReady(QStringList)),
- this, SLOT(trackCollectionResultsReady(QStringList)) );
- connect( qm, SIGNAL(queryDone()),
- this, SLOT(trackCollectionDone()) );
+ connect( qm, &Collections::QueryMaker::newResultReady,
+ this, &BiasSolver::trackCollectionResultsReady );
+ connect( qm, &Collections::QueryMaker::queryDone,
+ this, &BiasSolver::trackCollectionDone );
qm->run();
}
void
BiasSolver::updateProgress( const SolverList* list )
{
if( m_n <= 0 )
return;
int progress = (int)(100.0 * (double)( list->m_trackList.count() - list->m_contextCount ) / m_n );
while( m_currentProgress < progress )
{
m_currentProgress++;
emit incrementProgress();
}
}
}
diff --git a/src/dynamic/BiasedPlaylist.cpp b/src/dynamic/BiasedPlaylist.cpp
index 4d235a2a33..26cf7d6f39 100644
--- a/src/dynamic/BiasedPlaylist.cpp
+++ b/src/dynamic/BiasedPlaylist.cpp
@@ -1,219 +1,219 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "BiasedPlaylist"
#include "BiasedPlaylist.h"
#include "App.h"
#include "amarokconfig.h"
#include "core/collections/Collection.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "dynamic/BiasSolver.h"
#include "dynamic/BiasFactory.h"
#include "dynamic/DynamicModel.h"
#include "playlist/PlaylistModelStack.h" // for The::playlist
#include <QThread>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
Dynamic::BiasedPlaylist::BiasedPlaylist( QObject *parent )
: DynamicPlaylist( parent )
, m_bias( 0 )
, m_solver( 0 )
{
m_title = i18nc( "Title for a default dynamic playlist. The default playlist only returns random tracks.", "Random" );
BiasPtr biasPtr( BiasPtr( new Dynamic::RandomBias() ) );
biasReplaced( BiasPtr(), biasPtr );
}
Dynamic::BiasedPlaylist::BiasedPlaylist( QXmlStreamReader *reader, QObject *parent )
: DynamicPlaylist( parent )
, m_bias( 0 )
, m_solver( 0 )
{
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
QStringRef name = reader->name();
if( name == "title" )
m_title = reader->readElementText(QXmlStreamReader::SkipChildElements);
else
{
BiasPtr biasPtr( Dynamic::BiasFactory::fromXml( reader ) );
if( biasPtr )
{
biasReplaced( BiasPtr(), biasPtr );
}
else
{
debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
Dynamic::BiasedPlaylist::~BiasedPlaylist()
{
requestAbort();
}
void
Dynamic::BiasedPlaylist::toXml( QXmlStreamWriter *writer ) const
{
writer->writeTextElement( "title", m_title );
writer->writeStartElement( m_bias->name() );
m_bias->toXml( writer );
writer->writeEndElement();
}
void
Dynamic::BiasedPlaylist::requestAbort()
{
DEBUG_BLOCK
if( m_solver ) {
m_solver->setAutoDelete( true );
m_solver->requestAbort();
m_solver = 0;
}
}
void
Dynamic::BiasedPlaylist::startSolver( int numRequested )
{
DEBUG_BLOCK
debug() << "BiasedPlaylist in:" << QThread::currentThreadId();
if( !m_solver )
{
debug() << "assigning new m_solver";
m_solver = new BiasSolver( numRequested, m_bias, getContext() );
- connect( m_solver, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(solverFinished()) );
+ connect( m_solver, &BiasSolver::done, this, &BiasedPlaylist::solverFinished );
Amarok::Components::logger()->newProgressOperation( m_solver,
i18n( "Generating playlist..." ), 100,
this, SLOT(requestAbort()) );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(m_solver) );
debug() << "called prepareToRun";
}
else
debug() << "solver already running!";
}
void
Dynamic::BiasedPlaylist::biasChanged()
{
emit changed( this );
bool inModel = DynamicModel::instance()->index( this ).isValid();
if( inModel )
DynamicModel::instance()->biasChanged( m_bias );
}
void
Dynamic::BiasedPlaylist::biasReplaced( Dynamic::BiasPtr oldBias, Dynamic::BiasPtr newBias )
{
if( oldBias && !newBias ) // don't move the last bias away from this playlist without replacement
return;
bool inModel = DynamicModel::instance()->index( this ).isValid();
if( m_bias )
{
disconnect( m_bias.data(), 0, this, 0 );
if( inModel )
Dynamic::DynamicModel::instance()->beginRemoveBias( this );
m_bias = 0;
if( inModel )
Dynamic::DynamicModel::instance()->endRemoveBias();
}
if( inModel )
Dynamic::DynamicModel::instance()->beginInsertBias( this );
m_bias = newBias;
if( inModel )
Dynamic::DynamicModel::instance()->endInsertBias();
- connect( m_bias.data(), SIGNAL(changed(Dynamic::BiasPtr)),
- this, SLOT(biasChanged()) );
- connect( m_bias.data(), SIGNAL(replaced(Dynamic::BiasPtr,Dynamic::BiasPtr)),
- this, SLOT(biasReplaced(Dynamic::BiasPtr,Dynamic::BiasPtr)) );
+ connect( m_bias.data(), &AbstractBias::changed,
+ this, &BiasedPlaylist::biasChanged );
+ connect( m_bias.data(), &AbstractBias::replaced,
+ this, &BiasedPlaylist::biasReplaced );
if( oldBias ) // don't emit a changed during construction
biasChanged();
}
void
Dynamic::BiasedPlaylist::requestTracks( int n )
{
if( n > 0 )
startSolver( n + 1 ); // we request an additional track so that we don't end up in a position that e.g. does have no "similar" track.
}
Dynamic::BiasPtr
Dynamic::BiasedPlaylist::bias() const
{
return m_bias;
}
void
Dynamic::BiasedPlaylist::solverFinished()
{
DEBUG_BLOCK
if( m_solver != sender() )
return; // maybe an old solver... aborted solvers should autodelete
Meta::TrackList list = m_solver->solution();
if( list.count() > 0 )
{
// remove the additional requested track
if( list.count() > 1 )
list.removeLast();
emit tracksReady( list );
}
m_solver->deleteLater();
m_solver = 0;
}
Meta::TrackList
Dynamic::BiasedPlaylist::getContext()
{
Meta::TrackList context = The::playlist()->tracks();
return context;
}
diff --git a/src/dynamic/biases/AlbumPlayBias.cpp b/src/dynamic/biases/AlbumPlayBias.cpp
index 646c553abf..28b6da15b2 100644
--- a/src/dynamic/biases/AlbumPlayBias.cpp
+++ b/src/dynamic/biases/AlbumPlayBias.cpp
@@ -1,300 +1,300 @@
/****************************************************************************************
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AlbumPlayBias"
#include "AlbumPlayBias.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "dynamic/TrackSet.h"
#include <KLocale>
#include <QComboBox>
#include <QFormLayout>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
QString
Dynamic::AlbumPlayBiasFactory::i18nName() const
{ return i18nc("Name of the \"AlbumPlay\" bias", "Album play"); }
QString
Dynamic::AlbumPlayBiasFactory::name() const
{ return Dynamic::AlbumPlayBias::sName(); }
QString
Dynamic::AlbumPlayBiasFactory::i18nDescription() const
{ return i18nc("Description of the \"AlbumPlay\" bias",
"The \"AlbumPlay\" bias adds tracks that belong to one album."); }
Dynamic::BiasPtr
Dynamic::AlbumPlayBiasFactory::createBias()
{ return Dynamic::BiasPtr( new Dynamic::AlbumPlayBias() ); }
Dynamic::AlbumPlayBias::AlbumPlayBias()
: m_follow( DirectlyFollow )
{ }
void
Dynamic::AlbumPlayBias::fromXml( QXmlStreamReader *reader )
{
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
QStringRef name = reader->name();
if( name == "follow" )
m_follow = followForName( reader->readElementText(QXmlStreamReader::SkipChildElements) );
else
{
debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
void
Dynamic::AlbumPlayBias::toXml( QXmlStreamWriter *writer ) const
{
writer->writeTextElement( "follow", nameForFollow( m_follow ) );
}
QString
Dynamic::AlbumPlayBias::sName()
{
return QLatin1String( "albumPlayBias" );
}
QString
Dynamic::AlbumPlayBias::name() const
{
return Dynamic::AlbumPlayBias::sName();
}
QString
Dynamic::AlbumPlayBias::toString() const
{
switch( m_follow )
{
case DirectlyFollow:
return i18nc("AlbumPlay bias representation",
"The next track from the album");
case Follow:
return i18nc("AlbumPlay bias representation",
"Any later track from the album");
case DontCare:
return i18nc("AlbumPlay bias representation",
"Tracks from the same album");
}
return QString();
}
QWidget*
Dynamic::AlbumPlayBias::widget( QWidget* parent )
{
QComboBox *combo = new QComboBox( parent );
combo->addItem( i18n( "Track directly follows previous track in album" ),
nameForFollow( DirectlyFollow ) );
combo->addItem( i18n( "Track comes after previous track in album" ),
nameForFollow( Follow ) );
combo->addItem( i18n( "Track is in the same album as previous track" ),
nameForFollow( DontCare ) );
switch( m_follow )
{
case DirectlyFollow: combo->setCurrentIndex(0); break;
case Follow: combo->setCurrentIndex(1); break;
case DontCare: combo->setCurrentIndex(2); break;
}
- connect( combo, SIGNAL(currentIndexChanged(int)),
- this, SLOT(selectionChanged(int)) );
+ connect( combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &AlbumPlayBias::selectionChanged );
return combo;
}
Dynamic::TrackSet
Dynamic::AlbumPlayBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
Q_UNUSED( contextCount );
Q_UNUSED( finalCount );
if( playlist.isEmpty() ) // no track means we can't find any tracks in the same album
return Dynamic::TrackSet( universe, false );
Meta::TrackPtr track = playlist.last();
Meta::AlbumPtr album = track->album();
if( !album ) // no album means we can't find any tracks in the same album
return Dynamic::TrackSet( universe, false );
Meta::TrackList albumTracks = album->tracks();
if( ( albumTracks.count() <= 1 ) || // the album has only one track (or even less) so there can't be any other tracks in the same album
( m_follow != DontCare && sameTrack( track, albumTracks.last() ) ) ) // track is the last one and we want to find a later one.
return Dynamic::TrackSet( universe, false );
// we assume that the album tracks are sorted by cd and track number which
// is at least true for the SqlCollection
TrackSet result( universe, false );
if( m_follow == DirectlyFollow )
{
for( int i = 1; i < albumTracks.count(); i++ )
if( sameTrack( albumTracks[i-1], track ) )
result.unite( albumTracks[i] );
}
else if( m_follow == Follow )
{
bool found = false;
for( int i = 0; i < albumTracks.count(); i++ )
{
if( found )
result.unite( albumTracks[i] );
if( sameTrack( albumTracks[i], track ) )
found = true;
}
}
else if( m_follow == DontCare )
{
for( int i = 0; i < albumTracks.count(); i++ )
{
if( !sameTrack( albumTracks[i], track ) )
result.unite( albumTracks[i] );
}
}
return result;
}
bool
Dynamic::AlbumPlayBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
Q_UNUSED( contextCount );
if( position <= 0 || playlist.count() <= position )
return true;
Meta::TrackPtr track = playlist[position-1];
Meta::AlbumPtr album = track->album();
Meta::TrackPtr currentTrack = playlist[position];
Meta::AlbumPtr currentAlbum = currentTrack->album();
if( !album || album->tracks().isEmpty() )
return false;
Meta::TrackList albumTracks = album->tracks();
if( sameTrack( track, albumTracks.last() ) && m_follow != DontCare )
return false;
// we assume that the album tracks are sorted by cd and track number which
// is at least true for the SqlCollection
if( m_follow == DirectlyFollow )
{
for( int i = 1; i < albumTracks.count(); i++ )
if( sameTrack( albumTracks[i-1], track ) )
return sameTrack( albumTracks[i], currentTrack );
return false;
}
else if( m_follow == Follow )
{
bool found = false;
for( int i = 0; i < albumTracks.count(); i++ )
{
if( found && sameTrack( albumTracks[i], currentTrack ) )
return true;
if( sameTrack( albumTracks[i], track ) )
found = true;
}
return false;
}
else if( m_follow == DontCare )
{
return album == currentAlbum;
}
return false;
}
Dynamic::AlbumPlayBias::FollowType
Dynamic::AlbumPlayBias::follow() const
{
return m_follow;
}
void
Dynamic::AlbumPlayBias::setFollow( Dynamic::AlbumPlayBias::FollowType value )
{
m_follow = value;
invalidate();
emit changed( BiasPtr(this) );
}
void
Dynamic::AlbumPlayBias::selectionChanged( int which )
{
if( QComboBox *box = qobject_cast<QComboBox*>(sender()) )
setFollow( followForName( box->itemData( which ).toString() ) );
}
QString
Dynamic::AlbumPlayBias::nameForFollow( Dynamic::AlbumPlayBias::FollowType match )
{
switch( match )
{
case Dynamic::AlbumPlayBias::DirectlyFollow: return "directlyFollow";
case Dynamic::AlbumPlayBias::Follow: return "follow";
case Dynamic::AlbumPlayBias::DontCare: return "dontCare";
}
return QString();
}
Dynamic::AlbumPlayBias::FollowType
Dynamic::AlbumPlayBias::followForName( const QString &name )
{
if( name == "directlyFollow" ) return DirectlyFollow;
else if( name == "follow" ) return Follow;
else if( name == "dontCare" ) return DontCare;
else return DontCare;
}
bool
Dynamic::AlbumPlayBias::sameTrack( Meta::TrackPtr track1, Meta::TrackPtr track2 ) const
{
// We compare items which may be MetaProxy::Track or Meta::Track. For the
// same underlying track, MetaProxy::Track == Meta;:Track will be true, but
// Meta::Track == MetaProxy::Track false. Check both ways, and if either
// returns true, it's the same track.
return ( *track1 == *track2 ) || ( *track2 == *track1 );
}
diff --git a/src/dynamic/biases/EchoNestBias.cpp b/src/dynamic/biases/EchoNestBias.cpp
index 4202c3ec1b..348bcaf9ed 100644
--- a/src/dynamic/biases/EchoNestBias.cpp
+++ b/src/dynamic/biases/EchoNestBias.cpp
@@ -1,540 +1,540 @@
/****************************************************************************************
* Copyright (c) 2009 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2010, 2011, 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "EchoNestBias"
#include "EchoNestBias.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <KStandardDirs>
#include <KIO/Job>
#include <klocalizedstring.h>
#include <QDomDocument>
#include <QDomNode>
#include <QFile>
#include <QLabel>
#include <QPixmap>
#include <QRadioButton>
#include <QTimer>
#include <QVBoxLayout>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
QString
Dynamic::EchoNestBiasFactory::i18nName() const
{ return i18nc("Name of the \"EchoNest\" bias", "EchoNest similar artist"); }
QString
Dynamic::EchoNestBiasFactory::name() const
{ return Dynamic::EchoNestBias::sName(); }
QString
Dynamic::EchoNestBiasFactory::i18nDescription() const
{ return i18nc("Description of the \"EchoNest\" bias",
"The \"EchoNest\" bias looks up tracks on echo nest and only adds similar tracks."); }
Dynamic::BiasPtr
Dynamic::EchoNestBiasFactory::createBias()
{ return Dynamic::BiasPtr( new Dynamic::EchoNestBias() ); }
// ----- EchoNestBias --------
Dynamic::EchoNestBias::EchoNestBias()
: SimpleMatchBias()
, m_artistSuggestedQuery( 0 )
, m_match( PreviousTrack )
, m_mutex( QMutex::Recursive )
{
loadDataFromFile();
}
Dynamic::EchoNestBias::~EchoNestBias()
{
// TODO: kill all running queries
}
void
Dynamic::EchoNestBias::fromXml( QXmlStreamReader *reader )
{
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
QStringRef name = reader->name();
if( name == "match" )
m_match = matchForName( reader->readElementText(QXmlStreamReader::SkipChildElements) );
else
{
debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
void
Dynamic::EchoNestBias::toXml( QXmlStreamWriter *writer ) const
{
writer->writeTextElement( "match", nameForMatch( m_match ) );
}
QString
Dynamic::EchoNestBias::sName()
{
return QLatin1String( "echoNestBias" );
}
QString
Dynamic::EchoNestBias::name() const
{
return Dynamic::EchoNestBias::sName();
}
QString
Dynamic::EchoNestBias::toString() const
{
switch( m_match )
{
case PreviousTrack:
return i18nc("EchoNest bias representation",
"Similar to the previous artist (as reported by EchoNest)");
case Playlist:
return i18nc("EchoNest bias representation",
"Similar to any artist in the current playlist (as reported by EchoNest)");
}
return QString();
}
QWidget*
Dynamic::EchoNestBias::widget( QWidget* parent )
{
QWidget *widget = new QWidget( parent );
QVBoxLayout *layout = new QVBoxLayout( widget );
QLabel *imageLabel = new QLabel();
imageLabel->setPixmap( QPixmap( KStandardDirs::locate( "data", "amarok/images/echonest.png" ) ) );
QLabel *label = new QLabel( i18n( "<a href=\"http://the.echonest.com/\">the echonest</a> thinks the artist is similar to" ) );
QRadioButton *rb1 = new QRadioButton( i18n( "the previous track's artist" ) );
QRadioButton *rb2 = new QRadioButton( i18n( "one of the artist in the current playlist" ) );
rb1->setChecked( m_match == PreviousTrack );
rb2->setChecked( m_match == Playlist );
- connect( rb2, SIGNAL(toggled(bool)),
- this, SLOT(setMatchTypePlaylist(bool)) );
+ connect( rb2, &QRadioButton::toggled,
+ this, &Dynamic::EchoNestBias::setMatchTypePlaylist );
layout->addWidget( imageLabel );
layout->addWidget( label );
layout->addWidget( rb1 );
layout->addWidget( rb2 );
return widget;
}
Dynamic::TrackSet
Dynamic::EchoNestBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
Q_UNUSED( contextCount );
Q_UNUSED( finalCount );
// collect the artist
QStringList artists = currentArtists( playlist.count() - 1, playlist );
if( artists.isEmpty() )
return Dynamic::TrackSet( universe, true );
{
QMutexLocker locker( &m_mutex );
QString key = tracksMapKey( artists );
// debug() << "searching in cache for"<<key
// <<"have tracks"<<m_tracksMap.contains( key )
// <<"have artists"<<m_similarArtistMap.contains( key );
if( m_tracksMap.contains( key ) )
return m_tracksMap.value( key );
}
m_tracks = Dynamic::TrackSet( universe, false );
m_currentArtists = artists;
QTimer::singleShot(0,
const_cast<EchoNestBias*>(this),
- SLOT(newQuery())); // create the new query from my parent thread
+ &EchoNestBias::newQuery); // create the new query from my parent thread
return Dynamic::TrackSet();
}
bool
Dynamic::EchoNestBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
Q_UNUSED( contextCount );
// collect the artist
QStringList artists = currentArtists( position, playlist );
if( artists.isEmpty() )
return true;
// the artist of this track
if( position < 0 || position >= playlist.count() )
return false;
Meta::TrackPtr track = playlist[position];
Meta::ArtistPtr artist = track->artist();
if( !artist || artist->name().isEmpty() )
return false;
{
QMutexLocker locker( &m_mutex );
QString key = tracksMapKey( artists );
if( m_similarArtistMap.contains( key ) )
return m_similarArtistMap.value( key ).contains( artist->name() );
}
debug() << "didn't have artist suggestions saved for this artist:" << artist->name();
return false;
}
void
Dynamic::EchoNestBias::invalidate()
{
SimpleMatchBias::invalidate();
m_tracksMap.clear();
}
void
Dynamic::EchoNestBias::newQuery()
{
// - get the similar artists
QStringList similar;
{
QMutexLocker locker( &m_mutex );
QString key = tracksMapKey( m_currentArtists );
if( m_similarArtistMap.contains( key ) )
{
similar = m_similarArtistMap.value( key );
debug() << "got similar artists:" << similar.join(", ");
}
else
{
newSimilarArtistQuery();
return; // not yet ready to do construct a query maker
}
}
// ok, I need a new query maker
m_qm.reset( CollectionManager::instance()->queryMaker() );
// - construct the query
m_qm->beginOr();
foreach( const QString &artistName, similar )
{
m_qm->addFilter( Meta::valArtist, artistName, true, true );
}
m_qm->endAndOr();
m_qm->setQueryType( Collections::QueryMaker::Custom );
m_qm->addReturnValue( Meta::valUniqueId );
- connect( m_qm.data(), SIGNAL(newResultReady(QStringList)),
- this, SLOT(updateReady(QStringList)) );
- connect( m_qm.data(), SIGNAL(queryDone()),
- this, SLOT(updateFinished()) );
+ connect( m_qm.data(), &Collections::QueryMaker::newResultReady,
+ this, &EchoNestBias::updateReady );
+ connect( m_qm.data(), &Collections::QueryMaker::queryDone,
+ this, &EchoNestBias::updateFinished );
// - run the query
m_qm.data()->run();
}
void
Dynamic::EchoNestBias::newSimilarArtistQuery()
{
QMultiMap< QString, QString > params;
// -- start the query
params.insert( "results", "30" );
params.insert( "name", m_currentArtists.join(", ") );
m_artistSuggestedQuery = KIO::storedGet( createUrl( "artist/similar", params ), KIO::NoReload, KIO::HideProgressInfo );
- connect( m_artistSuggestedQuery, SIGNAL(result(KJob*)),
- this, SLOT(similarArtistQueryDone(KJob*)) );
+ connect( m_artistSuggestedQuery, &KJob::result,
+ this, &EchoNestBias::similarArtistQueryDone );
}
void
Dynamic::EchoNestBias::similarArtistQueryDone( KJob* job ) // slot
{
job->deleteLater();
if( job != m_artistSuggestedQuery )
{
debug() << "job was deleted from under us...wtf! blame the gerbils.";
m_tracks.reset( false );
emit resultReady( m_tracks );
return;
}
QDomDocument doc;
if( !doc.setContent( m_artistSuggestedQuery->data() ) )
{
debug() << "got invalid XML from EchoNest::get_similar!";
m_tracks.reset( false );
emit resultReady( m_tracks );
return;
}
// -- decode the result
QDomNodeList artists = doc.elementsByTagName( "artist" );
if( artists.isEmpty() )
{
debug() << "Got no similar artists! Bailing!";
m_tracks.reset( false );
emit resultReady( m_tracks );
return;
}
QStringList similarArtists;
for( int i = 0; i < artists.count(); i++ )
{
similarArtists.append( artists.at(i).firstChildElement( "name" ).text() );
}
// -- commit the result
{
QMutexLocker locker( &m_mutex );
QString key = tracksMapKey( m_currentArtists );
m_similarArtistMap.insert( key, similarArtists );
saveDataToFile();
}
newQuery();
}
void
Dynamic::EchoNestBias::updateFinished()
{
// -- store away the result for future reference
QString key = tracksMapKey( m_currentArtists );
m_tracksMap.insert( key, m_tracks );
debug() << "saving found similar tracks to key:" << key;
SimpleMatchBias::updateFinished();
}
QStringList
Dynamic::EchoNestBias::currentArtists( int position, const Meta::TrackList& playlist ) const
{
QStringList result;
if( m_match == PreviousTrack )
{
if( position >= 0 && position < playlist.count() )
{
Meta::ArtistPtr artist = playlist[ position ]->artist();
if( artist && !artist->name().isEmpty() )
result.append( artist->name() );
}
}
else if( m_match == Playlist )
{
for( int i=0; i < position && i < playlist.count(); i++ )
{
Meta::ArtistPtr artist = playlist[i]->artist();
if( artist && !artist->name().isEmpty() )
result.append( artist->name() );
}
}
return result;
}
// this method shamelessly inspired by liblastfm/src/ws/ws.cpp
QUrl Dynamic::EchoNestBias::createUrl( QString method, QMultiMap< QString, QString > params )
{
params.insert( "api_key", "DD9P0OV9OYFH1LCAE" );
params.insert( "format", "xml" );
QUrl url;
url.setScheme( "http" );
url.setHost( "developer.echonest.com" );
url.setPath( "/api/v4/" + method );
// take care of the ID possibility manually
// Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually
QMapIterator<QString, QString> i( params );
while ( i.hasNext() ) {
i.next();
QByteArray const key = QUrl::toPercentEncoding( i.key() );
QByteArray const value = QUrl::toPercentEncoding( i.value() );
url.addEncodedQueryItem( key, value );
}
return url;
}
void
Dynamic::EchoNestBias::saveDataToFile() const
{
QFile file( Amarok::saveLocation() + "dynamic_echonest_similar.xml" );
if( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
return;
QXmlStreamWriter writer( &file );
writer.setAutoFormatting( true );
writer.writeStartDocument();
writer.writeStartElement( QLatin1String("echonestSimilar") );
// -- write the similar artists
foreach( const QString& key, m_similarArtistMap.keys() )
{
writer.writeStartElement( QLatin1String("similarArtist") );
writer.writeTextElement( QLatin1String("artist"), key );
foreach( const QString& name, m_similarArtistMap.value( key ) )
{
writer.writeTextElement( QLatin1String("similar"), name );
}
writer.writeEndElement();
}
writer.writeEndElement();
writer.writeEndDocument();
}
void
Dynamic::EchoNestBias::readSimilarArtists( QXmlStreamReader *reader )
{
QString key;
QList<QString> artists;
while (!reader->atEnd()) {
reader->readNext();
QStringRef name = reader->name();
if( reader->isStartElement() )
{
if( name == QLatin1String("artist") )
key = reader->readElementText(QXmlStreamReader::SkipChildElements);
else if( name == QLatin1String("similar") )
artists.append( reader->readElementText(QXmlStreamReader::SkipChildElements) );
else
reader->skipCurrentElement();
}
else if( reader->isEndElement() )
{
break;
}
}
m_similarArtistMap.insert( key, artists );
}
void
Dynamic::EchoNestBias::loadDataFromFile()
{
m_similarArtistMap.clear();
QFile file( Amarok::saveLocation() + "dynamic_echonest_similar.xml" );
if( !file.exists() ||
!file.open( QIODevice::ReadOnly ) )
return;
QXmlStreamReader reader( &file );
while (!reader.atEnd()) {
reader.readNext();
QStringRef name = reader.name();
if( reader.isStartElement() )
{
if( name == QLatin1String("lastfmSimilar") )
{
; // just recurse into the element
}
else if( name == QLatin1String("similarArtist") )
{
readSimilarArtists( &reader );
}
else
{
reader.skipCurrentElement();
}
}
else if( reader.isEndElement() )
{
break;
}
}
}
Dynamic::EchoNestBias::MatchType
Dynamic::EchoNestBias::match() const
{ return m_match; }
void
Dynamic::EchoNestBias::setMatch( Dynamic::EchoNestBias::MatchType value )
{
m_match = value;
invalidate();
emit changed( BiasPtr(this) );
}
void
Dynamic::EchoNestBias::setMatchTypePlaylist( bool playlist )
{
setMatch( playlist ? Playlist : PreviousTrack );
}
QString
Dynamic::EchoNestBias::nameForMatch( Dynamic::EchoNestBias::MatchType match )
{
switch( match )
{
case Dynamic::EchoNestBias::PreviousTrack: return "previous";
case Dynamic::EchoNestBias::Playlist: return "playlist";
}
return QString();
}
Dynamic::EchoNestBias::MatchType
Dynamic::EchoNestBias::matchForName( const QString &name )
{
if( name == "previous" ) return PreviousTrack;
else if( name == "playlist" ) return Playlist;
else return PreviousTrack;
}
QString
Dynamic::EchoNestBias::tracksMapKey( QStringList artists )
{
return artists.join("|");
}
diff --git a/src/dynamic/biases/PartBias.cpp b/src/dynamic/biases/PartBias.cpp
index 00b8d4fb8c..85f9d0ec86 100644
--- a/src/dynamic/biases/PartBias.cpp
+++ b/src/dynamic/biases/PartBias.cpp
@@ -1,632 +1,632 @@
/****************************************************************************************
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PartBias"
#include "PartBias.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "widgets/SliderWidget.h"
#include <KLocale>
#include <KRandom>
#include <QtGlobal> // for qRound
#include <QApplication>
#include <QGridLayout>
#include <QLabel>
#include <QSlider>
#include <QStyle>
#include <QStyleOption>
#include <QWidget>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
QString
Dynamic::PartBiasFactory::i18nName() const
{ return i18nc("Name of the \"Part\" bias", "Partition"); }
QString
Dynamic::PartBiasFactory::name() const
{ return Dynamic::PartBias::sName(); }
QString
Dynamic::PartBiasFactory::i18nDescription() const
{ return i18nc("Description of the \"Part\" bias",
"The \"Part\" bias fills parts of the playlist from different sub-biases."); }
Dynamic::BiasPtr
Dynamic::PartBiasFactory::createBias()
{ return Dynamic::BiasPtr( new Dynamic::PartBias() ); }
/* Note:
We use the Ford-Fulkerson method to compute the maximum match between the bias
and the tracks.
The MatchState class will do this matching and keep track of all the needed
data.
We are not building up the full graph and we don't even compute in advance
all the edges.
*/
/** This is the helper object to calculate the maximum match.
For the sake of the algorithm we are using every sub-bias as a source with
a capacity depending on it's weight.
Every track in the playlist is a drain with capacity 1.
*/
class MatchState
{
public:
/** Creates the matching
@param ignoreTrack a track number that should be ignored for matching. -1 if no track should be ignored.
*/
MatchState( const Dynamic::PartBias *bias,
const Meta::TrackList& playlist,
int contextCount, int finalCount )
: m_bias( bias )
, m_playlist( playlist )
, m_contextCount( contextCount )
, m_sourceCount( bias->weights().count() )
, m_drainCount( finalCount - contextCount )
, m_edges( m_sourceCount * m_drainCount, false )
, m_edgesUsed( m_sourceCount * m_drainCount, false )
, m_sourceCapacity( m_sourceCount )
, m_sourceFlow( m_sourceCount )
, m_drainFlow( m_drainCount )
, m_drainSource( m_drainCount )
{
QList<qreal> weights = m_bias->weights();
int assignedDrainCount = 0;
for( int source = 0; source < m_sourceCount-1; source++ )
{
m_sourceCapacity[source] = qRound( weights[source] * m_drainCount );
assignedDrainCount += m_sourceCapacity[source];
// debug() << "MatchState: bias"<<m_bias->biases()[source]->name()<<"should match"<<m_sourceCapacity[source]<<"of"<< m_drainCount << "tracks.";
}
// the last bias get's all the rest
if( m_sourceCount > 0 )
m_sourceCapacity[m_sourceCount - 1] = m_drainCount - assignedDrainCount;
compute();
}
void compute()
{
// -- initialize the values
for( int source = m_sourceCount-1; source >= 0; --source )
m_sourceFlow[source] = 0;
for( int drain = m_drainCount-1; drain >= 0; --drain )
{
m_drainFlow[drain] = 0;
m_drainSource[drain] = -1;
}
// -- get all the edges
Dynamic::BiasList biases = m_bias->biases();
for( int source = m_sourceCount-1; source >= 0; --source )
for( int drain = m_drainCount-1; drain >= 0; --drain )
{
m_edgesUsed[ source * m_drainCount + drain ] = false;
if( drain + m_contextCount >= m_playlist.count() )
continue;
m_edges[ source * m_drainCount + drain ] =
biases[source]->trackMatches( drain + m_contextCount,
m_playlist,
m_contextCount );
// debug() << "edge:" << source << "x" << drain << ":" << m_edges[ source * m_drainCount + drain ];
}
// find a source whose capacity is not full
for( int source = m_sourceCount-1; source >= 0; --source )
{
if( m_sourceFlow[source] >= m_sourceCapacity[source] )
continue;
for( int drain = 0; drain < m_drainCount; drain++ )
{
if( !m_edges[ source * m_drainCount + drain ] )
continue;
if( m_drainFlow[drain] < 1 )
{
// direct connections source to drain
// make a new connection
m_sourceFlow[source]++;
m_drainFlow[drain]++;
m_drainSource[drain] = source;
m_edgesUsed[ source * m_drainCount + drain ] = true;
}
else
{
// indirect connections source to drain to source to drain
// or in other words: Check if we can re-order another source
// to get a connection for this source
int source2 = m_drainSource[drain];
for( int drain2 = m_drainCount-1; drain2 >= 0; --drain2 )
{
if( m_drainFlow[drain2] > 0 )
continue;
if( !m_edgesUsed[ source2 * m_drainCount + drain ] )
continue;
if( !m_edges[ source2 * m_drainCount + drain2 ] )
continue;
// revert the old connection
m_sourceFlow[source2]--;
m_drainFlow[drain]--;
m_edgesUsed[ source2 * m_drainCount + drain ] = false;
// make two new connections
m_sourceFlow[source]++;
m_drainFlow[drain]++;
m_drainSource[drain] = source;
m_edgesUsed[ source * m_drainCount + drain ] = true;
m_sourceFlow[source2]++;
m_drainFlow[drain2]++;
m_drainSource[drain2] = source2;
m_edgesUsed[ source2 * m_drainCount + drain2 ] = true;
break;
}
}
// finished with this source?
if( m_sourceFlow[source] >= m_sourceCapacity[source] )
break;
}
}
}
const Dynamic::PartBias* const m_bias;
const Meta::TrackList& m_playlist;
int m_contextCount;
int m_sourceCount;
int m_drainCount;
QBitArray m_edges;
QBitArray m_edgesUsed;
QVector<int> m_sourceCapacity;
QVector<int> m_sourceFlow;
QVector<int> m_drainFlow;
QVector<int> m_drainSource; // the source currently used by the drain
};
// -------- PartBiasWidget -----------
Dynamic::PartBiasWidget::PartBiasWidget( Dynamic::PartBias* bias, QWidget* parent )
: QWidget( parent )
, m_inSignal( false )
, m_bias( bias )
{
- connect( bias, SIGNAL(biasAppended(Dynamic::BiasPtr)),
- this, SLOT(biasAppended(Dynamic::BiasPtr)) );
+ connect( bias, &PartBias::biasAppended,
+ this, &PartBiasWidget::biasAppended );
- connect( bias, SIGNAL(biasRemoved(int)),
- this, SLOT(biasRemoved(int)) );
+ connect( bias, &PartBias::biasRemoved,
+ this, &PartBiasWidget::biasRemoved );
- connect( bias, SIGNAL(biasMoved(int,int)),
- this, SLOT(biasMoved(int,int)) );
+ connect( bias, &PartBias::biasMoved,
+ this, &PartBiasWidget::biasMoved );
- connect( bias, SIGNAL(weightsChanged()),
- this, SLOT(biasWeightsChanged()) );
+ connect( bias, &PartBias::weightsChanged,
+ this, &PartBiasWidget::biasWeightsChanged );
m_layout = new QGridLayout( this );
// -- add all sub-bias widgets
foreach( Dynamic::BiasPtr bias, m_bias->biases() )
{
biasAppended( bias );
}
}
void
Dynamic::PartBiasWidget::biasAppended( Dynamic::BiasPtr bias )
{
int index = m_bias->biases().indexOf( bias );
Amarok::Slider* slider = 0;
slider = new Amarok::Slider( Qt::Horizontal, 100 );
slider->setValue( m_bias->weights()[ m_bias->biases().indexOf( bias ) ] * 100.0 );
slider->setToolTip( i18n( "This controls what portion of the playlist should match the criteria" ) );
- connect( slider, SIGNAL(valueChanged(int)), SLOT(sliderValueChanged(int)) );
+ connect( slider, &Amarok::Slider::valueChanged, this, &PartBiasWidget::sliderValueChanged );
QLabel* label = new QLabel( bias->toString() );
m_sliders.append( slider );
m_widgets.append( label );
// -- add the widget (with slider)
m_layout->addWidget( slider, index, 0 );
m_layout->addWidget( label, index, 1 );
}
void
Dynamic::PartBiasWidget::biasRemoved( int pos )
{
m_layout->takeAt( pos * 2 );
m_layout->takeAt( pos * 2 );
m_sliders.takeAt( pos )->deleteLater();
m_widgets.takeAt( pos )->deleteLater();
}
void
Dynamic::PartBiasWidget::biasMoved( int from, int to )
{
QSlider* slider = m_sliders.takeAt( from );
m_sliders.insert( to, slider );
QWidget* widget = m_widgets.takeAt( from );
m_widgets.insert( to, widget );
// -- move the item in the layout
// TODO
/*
m_layout->insertWidget( to * 2, slider );
m_layout->insertWidget( to * 2 + 1, widget );
*/
}
void
Dynamic::PartBiasWidget::sliderValueChanged( int val )
{
DEBUG_BLOCK;
// protect agains recursion
if( m_inSignal )
return;
for( int i = 0; i < m_sliders.count(); i++ )
{
if( m_sliders.at(i) == sender() )
m_bias->changeBiasWeight( i, qreal(val) / 100.0 );
}
}
void
Dynamic::PartBiasWidget::biasWeightsChanged()
{
DEBUG_BLOCK;
// protect agains recursion
if( m_inSignal )
return;
m_inSignal = true;
QList<qreal> weights = m_bias->weights();
for( int i = 0; i < weights.count() && i < m_sliders.count(); i++ )
m_sliders.at(i)->setValue( weights.at(i) * 100.0 );
m_inSignal = false;
}
// ----------- PartBias ----------------
Dynamic::PartBias::PartBias()
: AndBias()
{
// add weights for already existing biases
for( int i = 0; i < biases().count(); i++ )
m_weights.append( 1.0 / biases().count() );
}
void
Dynamic::PartBias::fromXml( QXmlStreamReader *reader )
{
QList<qreal> weights; // we have to add all biases before we can set their weights
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
float weight = reader->attributes().value( "weight" ).toString().toFloat();
Dynamic::BiasPtr bias( Dynamic::BiasFactory::fromXml( reader ) );
if( bias )
{
appendBias( bias );
weights.append( weight );
}
else
{
warning()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
m_weights = weights;
}
void
Dynamic::PartBias::toXml( QXmlStreamWriter *writer ) const
{
for( int i = 0; i < m_biases.count(); i++ )
{
writer->writeStartElement( m_biases[i]->name() );
writer->writeAttribute( "weight", QString::number(m_weights[i]) );
m_biases[i]->toXml( writer );
writer->writeEndElement();
}
}
QString
Dynamic::PartBias::sName()
{
return QLatin1String( "partBias" );
}
QString
Dynamic::PartBias::name() const
{
return Dynamic::PartBias::sName();
}
QString
Dynamic::PartBias::toString() const
{
return i18nc("Part bias representation", "Partition");
}
QWidget*
Dynamic::PartBias::widget( QWidget* parent )
{
return new Dynamic::PartBiasWidget( this, parent );
}
void
Dynamic::PartBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
{
int index = m_biases.indexOf( Dynamic::BiasPtr(bias) );
if( index < 0 )
return;
QStyleOptionProgressBar progressBarOption;
progressBarOption.rect = rect.adjusted( 2, 2, -2, -2 );
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.progress = m_weights[index] * 100.0;
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
}
QList<qreal>
Dynamic::PartBias::weights() const
{
return m_weights;
}
Dynamic::TrackSet
Dynamic::PartBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
DEBUG_BLOCK;
// store the parameters in case we need to request additional matching tracks later
m_playlist = playlist;
m_contextCount = contextCount;
m_finalCount = finalCount;
m_universe = universe;
m_tracks = Dynamic::TrackSet();
m_matchingTracks.resize( m_biases.length() );
// get the matching tracks from all sub-biases
for( int i = 0; i < m_biases.length(); ++i )
m_matchingTracks[i] = m_biases[i]->matchingTracks( playlist, contextCount, finalCount, universe );
updateResults();
return m_tracks;
}
void
Dynamic::PartBias::updateResults() const
{
DEBUG_BLOCK;
// -- first check if we have valid tracks from all sub-biases
foreach( const Dynamic::TrackSet &tracks, m_matchingTracks )
if( tracks.isOutstanding() )
return;
// -- determine the current matching
MatchState state( this, m_playlist, m_contextCount, m_finalCount );
debug()<<"compute matching tracks for"<<m_finalCount<<"pc"<<m_playlist.count()<<"context:"<<m_contextCount;
// -- add all the tracks from one bias that has not fulfilled their capacity
// biases still missing more tracks are picked more often
// this prevents the problem that biases with only a few tracks always add their tracks
// last
int missingCapacity = 0;
for( int source = 0; source < state.m_sourceCount; source++ )
{
if( state.m_sourceFlow[source] < state.m_sourceCapacity[source] &&
m_matchingTracks[source].trackCount() > 0 )
missingCapacity += state.m_sourceCapacity[source] - state.m_sourceFlow[source];
}
m_tracks = Dynamic::TrackSet( m_universe, false );
// if we have some biases under-represented
if( missingCapacity > 0 )
{
int random = KRandom::random() % missingCapacity;
for( int source = 0; source < state.m_sourceCount; source++ )
{
// debug() << "PartBias::matchingTracks: biase"<<m_biases[source]->toString()<<"matches"<<state.m_sourceFlow[source]<<"out of"<<state.m_sourceCapacity[source]<<"tracks.";
if( state.m_sourceFlow[source] < state.m_sourceCapacity[source] &&
m_matchingTracks[source].trackCount() > 0 )
random -= state.m_sourceCapacity[source] - state.m_sourceFlow[source];
if( random < 0 )
{
m_tracks.unite( m_matchingTracks[source] );
break;
}
}
}
// else pick a random one.
}
void
Dynamic::PartBias::resultReceived( const Dynamic::TrackSet &tracks )
{
int index = m_biases.indexOf(Dynamic::BiasPtr(qobject_cast<Dynamic::AbstractBias*>(sender())));
if( index < 0 ) {
warning() << "Got results from a bias that I don't have.";
return;
}
if( !m_tracks.isOutstanding() ) {
warning() << "currently in resultReceived but we already have a solution";
return;
}
m_matchingTracks[index] = tracks;
updateResults();
if( !m_tracks.isOutstanding() )
emit resultReady( m_tracks );
}
bool
Dynamic::PartBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
MatchState state( this, playlist, contextCount, playlist.count() );
return ( state.m_drainFlow[position - contextCount] >= 0 );
}
void
Dynamic::PartBias::appendBias( Dynamic::BiasPtr bias )
{
DEBUG_BLOCK;
m_weights.append( qreal(0.0) );
changeBiasWeight( 0, m_weights.at(0) ); // fix the weights to 1.0 again.
AndBias::appendBias( bias );
}
void
Dynamic::PartBias::moveBias( int from, int to )
{
DEBUG_BLOCK;
m_weights.insert( to, m_weights.takeAt( from ) );
AndBias::moveBias( from, to );
}
void
Dynamic::PartBias::changeBiasWeight( int biasNum, qreal value )
{
DEBUG_BLOCK;
Q_ASSERT( biasNum >= 0 && biasNum < m_weights.count() );
// the weights should sum up to 1.0
// -- only one weight. that is easy
if( m_weights.count() == 1 )
{
if( m_weights.at(0) != 1.0 )
{
m_weights[0] = 1.0;
emit weightsChanged();
}
return;
}
// -- more than one. we have to modify the remaining.
m_weights[biasNum] = qBound( qreal( 0.0 ), value, qreal( 1.0 ) );
// - sum up all the weights
qreal sum = 0.0;
foreach( qreal v, m_weights )
sum += v;
// -- we are always using the first value to balance it out if possible
if( biasNum != 0 )
{
qreal oldV = m_weights.at(0);
qreal newV = qBound<qreal>( qreal( 0.0 ), 1.0 - (sum - oldV), qreal( 1.0 ) );
m_weights[0] = newV;
sum = sum - oldV + newV;
}
// modify all the remaining value
if( sum != 1.0 )
{
if( sum - m_weights.at(biasNum) == 0.0 )
{
// in this case the entry has all the weight.
// distribute the remainder to the other weights
for( int i = 0; i < m_weights.count(); i++ )
if( i != biasNum )
m_weights[i] = sum / (m_weights.count() - 1);
}
else
{
// in this case we have some remaining weights. use a factor
qreal factor = (1.0 - m_weights.at(biasNum)) / (sum - m_weights.at(biasNum));
for( int i = 0; i < m_weights.count(); i++ )
if( i != biasNum )
m_weights[i] *= factor;
}
}
for( int i = 0; i < m_weights.count(); i++ )
debug() << "Weight"<<i<<":"<<m_weights[i];
emit weightsChanged();
emit changed( BiasPtr( this ) );
}
void
Dynamic::PartBias::biasReplaced( Dynamic::BiasPtr oldBias, Dynamic::BiasPtr newBias )
{
DEBUG_BLOCK;
int index = m_biases.indexOf( oldBias );
if( !newBias )
{
m_weights.takeAt( index );
if( !m_weights.isEmpty() )
changeBiasWeight( 0, m_weights.at(0) ); // fix the weights to 1.0 again.
}
AndBias::biasReplaced( oldBias, newBias );
}
diff --git a/src/dynamic/biases/QuizPlayBias.cpp b/src/dynamic/biases/QuizPlayBias.cpp
index 28263811a1..27b0afb75c 100644
--- a/src/dynamic/biases/QuizPlayBias.cpp
+++ b/src/dynamic/biases/QuizPlayBias.cpp
@@ -1,360 +1,360 @@
/****************************************************************************************
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "QuizPlayBias"
#include "QuizPlayBias.h"
#include "core/collections/Collection.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "dynamic/TrackSet.h"
#include <QLabel>
#include <QComboBox>
#include <QVBoxLayout>
#include <QTimer>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <KLocale>
QString
Dynamic::QuizPlayBiasFactory::i18nName() const
{ return i18nc("Name of the \"QuizPlay\" bias", "Quiz play"); }
QString
Dynamic::QuizPlayBiasFactory::name() const
{ return Dynamic::QuizPlayBias::sName(); }
QString
Dynamic::QuizPlayBiasFactory::i18nDescription() const
{ return i18nc("Description of the \"QuizPlay\" bias",
"The \"QuizPlay\" bias adds tracks that start\n"
"with a character the last track ended with."); }
Dynamic::BiasPtr
Dynamic::QuizPlayBiasFactory::createBias()
{ return Dynamic::BiasPtr( new Dynamic::QuizPlayBias() ); }
// Note: this whole bias does not work correctly for right-to-left languages.
Dynamic::QuizPlayBias::QuizPlayBias()
: SimpleMatchBias()
, m_follow( TitleToTitle )
{ }
void
Dynamic::QuizPlayBias::fromXml( QXmlStreamReader *reader )
{
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
QStringRef name = reader->name();
if( name == "follow" )
m_follow = followForName( reader->readElementText(QXmlStreamReader::SkipChildElements) );
else
{
debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
void
Dynamic::QuizPlayBias::toXml( QXmlStreamWriter *writer ) const
{
writer->writeTextElement( "follow", nameForFollow( m_follow ) );
}
QString
Dynamic::QuizPlayBias::sName()
{
return QLatin1String( "quizPlayBias" );
}
QString
Dynamic::QuizPlayBias::name() const
{
return Dynamic::QuizPlayBias::sName();
}
QString
Dynamic::QuizPlayBias::toString() const
{
switch( m_follow )
{
case TitleToTitle:
return i18nc("QuizPlay bias representation",
"Tracks whose title start with a\n"
"character the last track ended with");
case ArtistToArtist:
return i18nc("QuizPlay bias representation",
"Tracks whose artist name start\n"
"with a character the last track ended with");
case AlbumToAlbum:
return i18nc("QuizPlay bias representation",
"Tracks whose album name start\n"
"with a character the last track ended with");
}
return QString();
}
QWidget*
Dynamic::QuizPlayBias::widget( QWidget* parent )
{
QWidget *widget = new QWidget( parent );
QVBoxLayout *layout = new QVBoxLayout( widget );
QLabel *label = new QLabel( i18n( "Last character of the previous song is\n"
"the first character of the next song" ) );
layout->addWidget( label );
QComboBox *combo = new QComboBox();
combo->addItem( i18n( "of the track title (Title quiz)" ),
nameForFollow( TitleToTitle ) );
combo->addItem( i18n( "of the artist (Artist quiz)" ),
nameForFollow( ArtistToArtist ) );
combo->addItem( i18n( "of the album name (Album quiz)" ),
nameForFollow( AlbumToAlbum ) );
switch( m_follow )
{
case TitleToTitle: combo->setCurrentIndex(0); break;
case ArtistToArtist: combo->setCurrentIndex(1); break;
case AlbumToAlbum: combo->setCurrentIndex(2); break;
}
- connect( combo, SIGNAL(currentIndexChanged(int)),
- this, SLOT(selectionChanged(int)) );
+ connect( combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &QuizPlayBias::selectionChanged );
layout->addWidget( combo );
return widget;
}
Dynamic::TrackSet
Dynamic::QuizPlayBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
Q_UNUSED( contextCount );
Q_UNUSED( finalCount );
if( playlist.isEmpty() )
return Dynamic::TrackSet( universe, true );
// determine the last character we need to quiz
Meta::TrackPtr lastTrack = playlist.last();
Meta::DataPtr lastData;
if( m_follow == TitleToTitle )
lastData = Meta::DataPtr::staticCast<Meta::Track>(lastTrack);
else if( m_follow == ArtistToArtist )
lastData = Meta::DataPtr::staticCast<Meta::Artist>(lastTrack->artist());
else if( m_follow == AlbumToAlbum )
lastData = Meta::DataPtr::staticCast<Meta::Album>(lastTrack->album());
if( !lastData || lastData->name().isEmpty() )
{
// debug() << "QuizPlay: no data for"<<lastTrack->name();
return Dynamic::TrackSet( universe, true );
}
m_currentCharacter = lastChar(lastData->name()).toLower();
// debug() << "QuizPlay: data for"<<lastTrack->name()<<"is"<<m_currentCharacter;
// -- look if we already buffered it
if( m_characterTrackMap.contains( m_currentCharacter ) )
return m_characterTrackMap.value( m_currentCharacter );
// -- start a new query
m_tracks = Dynamic::TrackSet( universe, false );
QTimer::singleShot(0,
const_cast<QuizPlayBias*>(this),
- SLOT(newQuery())); // create the new query from my parent thread
+ &QuizPlayBias::newQuery); // create the new query from my parent thread
return Dynamic::TrackSet();
}
bool
Dynamic::QuizPlayBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
Q_UNUSED( contextCount );
if( position <= 0 || position >= playlist.count())
return true;
// -- determine the last character we need to quiz
Meta::TrackPtr lastTrack = playlist[position-1];
Meta::DataPtr lastData;
if( m_follow == TitleToTitle )
lastData = Meta::DataPtr::staticCast<Meta::Track>(lastTrack);
else if( m_follow == ArtistToArtist )
lastData = Meta::DataPtr::staticCast<Meta::Artist>(lastTrack->artist());
else if( m_follow == AlbumToAlbum )
lastData = Meta::DataPtr::staticCast<Meta::Album>(lastTrack->album());
if( !lastData || lastData->name().isEmpty() )
return true;
// -- determine the first character
Meta::TrackPtr track = playlist[position];
Meta::DataPtr data;
if( m_follow == TitleToTitle )
data = Meta::DataPtr::staticCast<Meta::Track>(track);
else if( m_follow == ArtistToArtist )
data = Meta::DataPtr::staticCast<Meta::Artist>(track->artist());
else if( m_follow == AlbumToAlbum )
data = Meta::DataPtr::staticCast<Meta::Album>(track->album());
if( !data || data->name().isEmpty() )
return false;
// -- now compare
QString lastName = lastData->name();
QString name = data->name();
return lastChar( lastName ).toLower() == name[0].toLower();
}
Dynamic::QuizPlayBias::FollowType
Dynamic::QuizPlayBias::follow() const
{
return m_follow;
}
void
Dynamic::QuizPlayBias::setFollow( Dynamic::QuizPlayBias::FollowType value )
{
m_follow = value;
invalidate();
emit changed( BiasPtr(this) );
}
void
Dynamic::QuizPlayBias::updateFinished()
{
m_characterTrackMap.insert( m_currentCharacter, m_tracks );
SimpleMatchBias::updateFinished();
}
void
Dynamic::QuizPlayBias::invalidate()
{
m_characterTrackMap.clear();
SimpleMatchBias::invalidate();
}
void
Dynamic::QuizPlayBias::selectionChanged( int which )
{
if( QComboBox *box = qobject_cast<QComboBox*>(sender()) )
setFollow( followForName( box->itemData( which ).toString() ) );
}
void
Dynamic::QuizPlayBias::newQuery()
{
// ok, I need a new query maker
m_qm.reset( CollectionManager::instance()->queryMaker() );
uint field = 0;
switch( m_follow )
{
case Dynamic::QuizPlayBias::TitleToTitle: field = Meta::valTitle; break;
case Dynamic::QuizPlayBias::ArtistToArtist: field = Meta::valArtist; break;
case Dynamic::QuizPlayBias::AlbumToAlbum: field = Meta::valAlbum; break;
}
m_qm->addFilter( field, QString(m_currentCharacter), true, false );
m_qm->setQueryType( Collections::QueryMaker::Custom );
m_qm->addReturnValue( Meta::valUniqueId );
- connect( m_qm.data(), SIGNAL(newResultReady(QStringList)),
- this, SLOT(updateReady(QStringList)) );
- connect( m_qm.data(), SIGNAL(queryDone()),
- this, SLOT(updateFinished()) );
+ connect( m_qm.data(), &Collections::QueryMaker::newResultReady,
+ this, &QuizPlayBias::updateReady );
+ connect( m_qm.data(), &Collections::QueryMaker::queryDone,
+ this, &QuizPlayBias::updateFinished );
m_qm.data()->run();
}
QChar
Dynamic::QuizPlayBias::lastChar( const QString &str )
{
int endIndex = str.length();
int index;
index = str.indexOf( '[' );
if( index > 0 && index < endIndex )
endIndex = index;
index = str.indexOf( '(' );
if( index > 0 && index < endIndex )
endIndex = index;
index = str.indexOf( QLatin1String(" cd"), Qt::CaseInsensitive );
if( index > 0 && index < endIndex )
endIndex = index;
while( endIndex > 0 &&
(str[ endIndex - 1 ].isSpace() || str[ endIndex - 1 ].isPunct()) )
endIndex--;
if( endIndex > 0 )
return str[ endIndex - 1 ];
return QChar();
}
QString
Dynamic::QuizPlayBias::nameForFollow( Dynamic::QuizPlayBias::FollowType match )
{
switch( match )
{
case Dynamic::QuizPlayBias::TitleToTitle: return "titleQuiz";
case Dynamic::QuizPlayBias::ArtistToArtist: return "artistQuiz";
case Dynamic::QuizPlayBias::AlbumToAlbum: return "albumQuiz";
}
return QString();
}
Dynamic::QuizPlayBias::FollowType
Dynamic::QuizPlayBias::followForName( const QString &name )
{
if( name == "titleQuiz" ) return TitleToTitle;
else if( name == "artistQuiz" ) return ArtistToArtist;
else if( name == "albumQuiz" ) return AlbumToAlbum;
else return TitleToTitle;
}
diff --git a/src/dynamic/biases/SearchQueryBias.cpp b/src/dynamic/biases/SearchQueryBias.cpp
index 35e09e1a17..17cbe971c3 100644
--- a/src/dynamic/biases/SearchQueryBias.cpp
+++ b/src/dynamic/biases/SearchQueryBias.cpp
@@ -1,172 +1,172 @@
/****************************************************************************************
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "SearchQueryBias"
#include "SearchQueryBias.h"
#include "core/collections/Collection.h"
#include "core/collections/QueryMaker.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/collections/support/TextualQueryFilter.h"
#include "dynamic/TrackSet.h"
#include <KLineEdit>
#include <QLabel>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
QString
Dynamic::SearchQueryBiasFactory::i18nName() const
{ return i18nc("Name of the \"SearchQuery\" bias", "Search"); }
QString
Dynamic::SearchQueryBiasFactory::name() const
{ return Dynamic::SearchQueryBias::sName(); }
QString
Dynamic::SearchQueryBiasFactory::i18nDescription() const
{ return i18nc("Description of the \"SearchQuery\" bias",
"The \"SearchQuery\" bias adds tracks that are\n"
"found by a search query. It uses the same search\n"
"query as the collection browser."); }
Dynamic::BiasPtr
Dynamic::SearchQueryBiasFactory::createBias()
{ return Dynamic::BiasPtr( new Dynamic::SearchQueryBias() ); }
// ----- SearchQueryBias --------
Dynamic::SearchQueryBias::SearchQueryBias( QString filter )
: SimpleMatchBias()
, m_filter( filter )
{ }
void
Dynamic::SearchQueryBias::fromXml( QXmlStreamReader *reader )
{
DEBUG_BLOCK;
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
QStringRef name = reader->name();
if( name == "filter" )
m_filter = reader->readElementText(QXmlStreamReader::SkipChildElements);
else
{
debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
void
Dynamic::SearchQueryBias::toXml( QXmlStreamWriter *writer ) const
{
writer->writeTextElement( "filter", m_filter );
}
QString
Dynamic::SearchQueryBias::sName()
{
return QLatin1String( "searchQueryBias" );
}
QString
Dynamic::SearchQueryBias::name() const
{
return Dynamic::SearchQueryBias::sName();
}
QString
Dynamic::SearchQueryBias::toString() const
{
if( m_filter.isEmpty() )
return i18nc("Random bias representation",
"Random tracks");
else
return i18nc("SearchQuery bias representation",
"Search for: %1", m_filter );
}
QWidget*
Dynamic::SearchQueryBias::widget( QWidget* parent )
{
QWidget *widget = new QWidget( parent );
QVBoxLayout *layout = new QVBoxLayout( widget );
KLineEdit *edit = new KLineEdit( m_filter );
layout->addWidget( edit );
- connect( edit, SIGNAL(textChanged(QString)),
- this, SLOT(setFilter(QString)) );
+ connect( edit, &KLineEdit::textChanged,
+ this, &SearchQueryBias::setFilter );
return widget;
}
QString
Dynamic::SearchQueryBias::filter() const
{
return m_filter;
}
void
Dynamic::SearchQueryBias::setFilter( const QString &filter )
{
DEBUG_BLOCK;
if( filter == m_filter )
return;
m_filter = filter;
invalidate();
emit changed( BiasPtr(this) );
}
void
Dynamic::SearchQueryBias::newQuery()
{
DEBUG_BLOCK;
// ok, I need a new query maker
m_qm.reset( CollectionManager::instance()->queryMaker() );
Collections::addTextualFilter( m_qm.data(), m_filter );
m_qm->setQueryType( Collections::QueryMaker::Custom );
m_qm->addReturnValue( Meta::valUniqueId );
- connect( m_qm.data(), SIGNAL(newResultReady(QStringList)),
- this, SLOT(updateReady(QStringList)), Qt::QueuedConnection );
- connect( m_qm.data(), SIGNAL(queryDone()),
- this, SLOT(updateFinished()), Qt::QueuedConnection );
+ connect( m_qm.data(), &Collections::QueryMaker::newResultReady,
+ this, &SearchQueryBias::updateReady, Qt::QueuedConnection );
+ connect( m_qm.data(), &Collections::QueryMaker::queryDone,
+ this, &SearchQueryBias::updateFinished, Qt::QueuedConnection );
m_qm.data()->run();
}
diff --git a/src/dynamic/biases/TagMatchBias.cpp b/src/dynamic/biases/TagMatchBias.cpp
index d9f377489e..037999a0d0 100644
--- a/src/dynamic/biases/TagMatchBias.cpp
+++ b/src/dynamic/biases/TagMatchBias.cpp
@@ -1,522 +1,522 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com> *
* Copyright (c) 2009 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "TagMatchBias"
#include "TagMatchBias.h"
#include "core/collections/Collection.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "dynamic/TrackSet.h"
#include <QDateTime>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QTimer>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
QString
Dynamic::TagMatchBiasFactory::i18nName() const
{ return i18nc("Name of the \"TagMatch\" bias", "Match meta tag"); }
QString
Dynamic::TagMatchBiasFactory::name() const
{ return Dynamic::TagMatchBias::sName(); }
QString
Dynamic::TagMatchBiasFactory::i18nDescription() const
{ return i18nc("Description of the \"TagMatch\" bias",
"The \"TagMatch\" bias adds tracks that\n"
"fulfill a specific condition."); }
Dynamic::BiasPtr
Dynamic::TagMatchBiasFactory::createBias()
{ return Dynamic::BiasPtr( new Dynamic::TagMatchBias() ); }
// ----- SimpleMatchBias --------
Dynamic::SimpleMatchBias::SimpleMatchBias()
: m_invert( false )
{ }
void
Dynamic::SimpleMatchBias::fromXml( QXmlStreamReader *reader )
{
m_invert = reader->attributes().value( "invert" ).toString().toInt();
}
void
Dynamic::SimpleMatchBias::toXml( QXmlStreamWriter *writer ) const
{
if( m_invert )
writer->writeAttribute("invert", "1");
}
bool
Dynamic::SimpleMatchBias::isInvert() const
{
return m_invert;
}
void
Dynamic::SimpleMatchBias::setInvert( bool value )
{
DEBUG_BLOCK;
if( value == m_invert )
return;
m_invert = value;
// setting "invert" does not invalidate the search results
emit changed( BiasPtr(this) );
}
Dynamic::TrackSet
Dynamic::SimpleMatchBias::matchingTracks( const Meta::TrackList& playlist,
int contextCount, int finalCount,
Dynamic::TrackCollectionPtr universe ) const
{
Q_UNUSED( playlist );
Q_UNUSED( contextCount );
Q_UNUSED( finalCount );
if( tracksValid() )
return m_tracks;
m_tracks = Dynamic::TrackSet( universe, m_invert );
QTimer::singleShot(0,
const_cast<SimpleMatchBias*>(this),
- SLOT(newQuery())); // create the new query from my parent thread
+ &SimpleMatchBias::newQuery); // create the new query from my parent thread
return Dynamic::TrackSet();
}
void
Dynamic::SimpleMatchBias::updateReady( QStringList uids )
{
if( m_invert )
m_tracks.subtract( uids );
else
m_tracks.unite( uids );
}
void
Dynamic::SimpleMatchBias::updateFinished()
{
m_tracksTime = QDateTime::currentDateTime();
m_qm.reset();
debug() << "SimpleMatchBias::"<<name()<<"updateFinished"<<m_tracks.trackCount();
emit resultReady( m_tracks );
}
bool
Dynamic::SimpleMatchBias::tracksValid() const
{
return m_tracksTime.isValid() &&
m_tracksTime.secsTo(QDateTime::currentDateTime()) < 60 * 3;
}
bool
Dynamic::SimpleMatchBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
Q_UNUSED( contextCount );
if( tracksValid() )
return m_tracks.contains( playlist.at(position) );
return true; // we should have already received the tracks before some-one calls trackMatches
}
void
Dynamic::SimpleMatchBias::invalidate()
{
m_tracksTime = QDateTime();
m_tracks = TrackSet();
// TODO: need to finish a running query
m_qm.reset();
}
// ---------- TagMatchBias --------
Dynamic::TagMatchBiasWidget::TagMatchBiasWidget( Dynamic::TagMatchBias* bias,
QWidget* parent )
: QWidget( parent )
, m_bias( bias )
{
QVBoxLayout *layout = new QVBoxLayout( this );
QHBoxLayout *invertLayout = new QHBoxLayout();
m_invertBox = new QCheckBox();
QLabel *label = new QLabel( i18n("Invert condition") );
label->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
label->setBuddy( m_invertBox );
invertLayout->addWidget( m_invertBox, 0 );
invertLayout->addWidget( label, 1 );
layout->addLayout(invertLayout);
m_queryWidget = new MetaQueryWidget();
layout->addWidget( m_queryWidget );
syncControlsToBias();
- connect( m_invertBox, SIGNAL(toggled(bool)),
- SLOT(syncBiasToControls()));
- connect( m_queryWidget, SIGNAL(changed(MetaQueryWidget::Filter)),
- SLOT(syncBiasToControls()));
+ connect( m_invertBox, &QCheckBox::toggled,
+ this, &TagMatchBiasWidget::syncBiasToControls );
+ connect( m_queryWidget, &MetaQueryWidget::changed,
+ this, &TagMatchBiasWidget::syncBiasToControls );
}
void
Dynamic::TagMatchBiasWidget::syncControlsToBias()
{
m_queryWidget->setFilter( m_bias->filter() );
m_invertBox->setChecked( m_bias->isInvert() );
}
void
Dynamic::TagMatchBiasWidget::syncBiasToControls()
{
m_bias->setFilter( m_queryWidget->filter() );
m_bias->setInvert( m_invertBox->isChecked() );
}
// ----- TagMatchBias --------
Dynamic::TagMatchBias::TagMatchBias()
: SimpleMatchBias()
{ }
void
Dynamic::TagMatchBias::fromXml( QXmlStreamReader *reader )
{
SimpleMatchBias::fromXml( reader );
while (!reader->atEnd()) {
reader->readNext();
if( reader->isStartElement() )
{
QStringRef name = reader->name();
if( name == "field" )
m_filter.setField( Meta::fieldForPlaylistName( reader->readElementText(QXmlStreamReader::SkipChildElements) ) );
else if( name == "numValue" )
m_filter.numValue = reader->readElementText(QXmlStreamReader::SkipChildElements).toUInt();
else if( name == "numValue2" )
m_filter.numValue2 = reader->readElementText(QXmlStreamReader::SkipChildElements).toUInt();
else if( name == "value" )
m_filter.value = reader->readElementText(QXmlStreamReader::SkipChildElements);
else if( name == "condition" )
m_filter.condition = conditionForName( reader->readElementText(QXmlStreamReader::SkipChildElements) );
else
{
debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
reader->skipCurrentElement();
}
}
else if( reader->isEndElement() )
{
break;
}
}
}
void
Dynamic::TagMatchBias::toXml( QXmlStreamWriter *writer ) const
{
SimpleMatchBias::toXml( writer );
writer->writeTextElement( "field", Meta::playlistNameForField( m_filter.field() ) );
if( m_filter.isNumeric() )
{
writer->writeTextElement( "numValue", QString::number( m_filter.numValue ) );
writer->writeTextElement( "numValue2", QString::number( m_filter.numValue2 ) );
}
else
{
writer->writeTextElement( "value", m_filter.value );
}
writer->writeTextElement( "condition", nameForCondition( m_filter.condition ) );
}
QString
Dynamic::TagMatchBias::sName()
{
return QLatin1String( "tagMatchBias" );
}
QString
Dynamic::TagMatchBias::name() const
{
return Dynamic::TagMatchBias::sName();
}
QString
Dynamic::TagMatchBias::toString() const
{
if( isInvert() )
return i18nc("Inverted condition in tag match bias",
"Not %1", m_filter.toString() );
else
return m_filter.toString();
}
QWidget*
Dynamic::TagMatchBias::widget( QWidget* parent )
{
return new Dynamic::TagMatchBiasWidget( this, parent );
}
bool
Dynamic::TagMatchBias::trackMatches( int position,
const Meta::TrackList& playlist,
int contextCount ) const
{
Q_UNUSED( contextCount );
if( tracksValid() )
return m_tracks.contains( playlist.at(position) );
else
return matches( playlist.at(position) );
}
MetaQueryWidget::Filter
Dynamic::TagMatchBias::filter() const
{
return m_filter;
}
void
Dynamic::TagMatchBias::setFilter( const MetaQueryWidget::Filter &filter)
{
DEBUG_BLOCK;
m_filter = filter;
invalidate();
emit changed( BiasPtr(this) );
}
void
Dynamic::TagMatchBias::newQuery()
{
DEBUG_BLOCK;
// ok, I need a new query maker
m_qm.reset( CollectionManager::instance()->queryMaker() );
// -- set the querymaker
if( m_filter.isDate() )
{
switch( m_filter.condition )
{
case MetaQueryWidget::LessThan:
case MetaQueryWidget::Equals:
case MetaQueryWidget::GreaterThan:
m_qm->addNumberFilter( m_filter.field(), m_filter.numValue,
(Collections::QueryMaker::NumberComparison)m_filter.condition );
break;
case MetaQueryWidget::Between:
m_qm->beginAnd();
m_qm->addNumberFilter( m_filter.field(), qMin(m_filter.numValue, m_filter.numValue2)-1,
Collections::QueryMaker::GreaterThan );
m_qm->addNumberFilter( m_filter.field(), qMax(m_filter.numValue, m_filter.numValue2)+1,
Collections::QueryMaker::LessThan );
m_qm->endAndOr();
break;
case MetaQueryWidget::OlderThan:
m_qm->addNumberFilter( m_filter.field(), QDateTime::currentDateTime().toTime_t() - m_filter.numValue,
Collections::QueryMaker::LessThan );
break;
default:
;
}
}
else if( m_filter.isNumeric() )
{
switch( m_filter.condition )
{
case MetaQueryWidget::LessThan:
case MetaQueryWidget::Equals:
case MetaQueryWidget::GreaterThan:
m_qm->addNumberFilter( m_filter.field(), m_filter.numValue,
(Collections::QueryMaker::NumberComparison)m_filter.condition );
break;
case MetaQueryWidget::Between:
m_qm->beginAnd();
m_qm->addNumberFilter( m_filter.field(), qMin(m_filter.numValue, m_filter.numValue2)-1,
Collections::QueryMaker::GreaterThan );
m_qm->addNumberFilter( m_filter.field(), qMax(m_filter.numValue, m_filter.numValue2)+1,
Collections::QueryMaker::LessThan );
m_qm->endAndOr();
break;
default:
;
}
}
else
{
switch( m_filter.condition )
{
case MetaQueryWidget::Equals:
m_qm->addFilter( m_filter.field(), m_filter.value, true, true );
break;
case MetaQueryWidget::Contains:
if( m_filter.field() == 0 )
{
// simple search
// TODO: split different words and make separate searches
m_qm->beginOr();
m_qm->addFilter( Meta::valArtist, m_filter.value );
m_qm->addFilter( Meta::valTitle, m_filter.value );
m_qm->addFilter( Meta::valAlbum, m_filter.value );
m_qm->addFilter( Meta::valGenre, m_filter.value );
m_qm->addFilter( Meta::valUrl, m_filter.value );
m_qm->addFilter( Meta::valComment, m_filter.value );
m_qm->addFilter( Meta::valLabel, m_filter.value );
m_qm->endAndOr();
}
else
{
m_qm->addFilter( m_filter.field(), m_filter.value );
}
break;
default:
;
}
}
m_qm->setQueryType( Collections::QueryMaker::Custom );
m_qm->addReturnValue( Meta::valUniqueId );
- connect( m_qm.data(), SIGNAL(newResultReady(QStringList)),
- this, SLOT(updateReady(QStringList)), Qt::QueuedConnection );
- connect( m_qm.data(), SIGNAL(queryDone()),
- this, SLOT(updateFinished()), Qt::QueuedConnection );
+ connect( m_qm.data(), &Collections::QueryMaker::newResultReady,
+ this, &TagMatchBias::updateReady, Qt::QueuedConnection );
+ connect( m_qm.data(), &Collections::QueryMaker::queryDone,
+ this, &TagMatchBias::updateFinished, Qt::QueuedConnection );
m_qm.data()->run();
}
QString
Dynamic::TagMatchBias::nameForCondition( MetaQueryWidget::FilterCondition cond )
{
switch( cond )
{
case MetaQueryWidget::Equals: return "equals";
case MetaQueryWidget::GreaterThan: return "greater";
case MetaQueryWidget::LessThan: return "less";
case MetaQueryWidget::Between: return "between";
case MetaQueryWidget::OlderThan: return "older";
case MetaQueryWidget::Contains: return "contains";
default:
;// the other conditions are only for the advanced playlist generator
}
return QString();
}
MetaQueryWidget::FilterCondition
Dynamic::TagMatchBias::conditionForName( const QString &name )
{
if( name == "equals" ) return MetaQueryWidget::Equals;
else if( name == "greater" ) return MetaQueryWidget::GreaterThan;
else if( name == "less" ) return MetaQueryWidget::LessThan;
else if( name == "between" ) return MetaQueryWidget::Between;
else if( name == "older" ) return MetaQueryWidget::OlderThan;
else if( name == "contains" ) return MetaQueryWidget::Contains;
else return MetaQueryWidget::Equals;
}
bool
Dynamic::TagMatchBias::matches( const Meta::TrackPtr &track ) const
{
QVariant value = Meta::valueForField( m_filter.field(), track );
bool result = false;
if( m_filter.isDate() )
{
switch( m_filter.condition )
{
case MetaQueryWidget::LessThan:
result = value.toLongLong() < m_filter.numValue;
break;
case MetaQueryWidget::Equals:
result = value.toLongLong() == m_filter.numValue;
break;
case MetaQueryWidget::GreaterThan:
result = value.toLongLong() > m_filter.numValue;
break;
case MetaQueryWidget::Between:
result = value.toLongLong() > m_filter.numValue &&
value.toLongLong() < m_filter.numValue2;
break;
case MetaQueryWidget::OlderThan:
result = value.toLongLong() < m_filter.numValue + QDateTime::currentDateTime().toTime_t();
break;
default:
;
}
}
else if( m_filter.isNumeric() )
{
switch( m_filter.condition )
{
case MetaQueryWidget::LessThan:
result = value.toLongLong() < m_filter.numValue;
break;
case MetaQueryWidget::Equals:
result = value.toLongLong() == m_filter.numValue;
break;
case MetaQueryWidget::GreaterThan:
result = value.toLongLong() > m_filter.numValue;
break;
case MetaQueryWidget::Between:
result = value.toLongLong() > m_filter.numValue &&
value.toLongLong() < m_filter.numValue2;
break;
default:
;
}
}
else
{
switch( m_filter.condition )
{
case MetaQueryWidget::Equals:
result = value.toString() == m_filter.value;
break;
case MetaQueryWidget::Contains:
result = value.toString().contains( m_filter.value, Qt::CaseInsensitive );
break;
default:
;
}
}
if( m_invert )
return !result;
else
return result;
}
diff --git a/src/importers/ImporterManager.cpp b/src/importers/ImporterManager.cpp
index 677b3efd1b..a09cc5ea06 100644
--- a/src/importers/ImporterManager.cpp
+++ b/src/importers/ImporterManager.cpp
@@ -1,165 +1,165 @@
/****************************************************************************************
* Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ImporterManager.h"
#include "ImporterProvider.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "statsyncing/Config.h"
#include "statsyncing/Controller.h"
#include <KConfigGroup>
#include <QStringList>
#include <QVariant>
namespace StatSyncing
{
ImporterManager::ImporterManager( QObject *parent, const QVariantList &args )
: ProviderFactory( parent, args )
{
}
ImporterManager::~ImporterManager()
{
}
void
ImporterManager::init()
{
m_info = pluginInfo();
foreach( const QString &providerId, managerConfig().groupList() )
{
KConfigGroup group = providerConfig( providerId );
QVariantMap config;
foreach( const QString &key, group.keyList() )
config.insert( key, group.readEntry( key, QVariant( QString() ) ) );
ProviderPtr provider = createProvider( config );
m_providers.insert( provider->id(), provider );
}
if( Controller *controller = Amarok::Components::statSyncingController() )
if( Config *config = controller->config() )
- connect( config, SIGNAL(providerForgotten(QString)),
- SLOT(slotProviderForgotten(QString)) );
+ connect( config, &Config::providerForgotten,
+ this, &ImporterManager::slotProviderForgotten );
m_initialized = true;
}
ProviderConfigWidget*
ImporterManager::createConfigWidget()
{
return configWidget( QVariantMap() );
}
ProviderPtr
ImporterManager::createProvider( QVariantMap config )
{
Controller *controller = Amarok::Components::statSyncingController();
// First, get rid of the old provider instance. Note: the StatSyncing::Config
// remembers the provider by the id, even when it's unregistered. After this
// block, old instance should be destroyed, its destructor called.
if( config.contains( "uid" ) )
{
const QString providerId = config.value( "uid" ).toString();
if( m_providers.contains( providerId ) )
{
ProviderPtr oldProvider = m_providers.take( providerId );
if( controller )
controller->unregisterProvider( oldProvider );
}
}
// Create a concrete provider using the config. The QueuedConnection in connect()
// is important, because on reconfigure we *destroy* the old provider instance
ImporterProviderPtr provider = newInstance( config );
if( !provider )
{
warning() << __PRETTY_FUNCTION__ << "created provider is null!";
return provider;
}
- connect( provider.data(), SIGNAL(reconfigurationRequested(QVariantMap)),
- SLOT(createProvider(QVariantMap)), Qt::QueuedConnection);
+ connect( provider.data(), &StatSyncing::ImporterProvider::reconfigurationRequested,
+ this, &ImporterManager::createProvider, Qt::QueuedConnection);
m_providers.insert( provider->id(), provider );
// Register the provider
if( controller )
{
controller->registerProvider( provider );
// Set provider to offline
if( Config *config = controller->config() )
{
config->updateProvider( provider->id(), provider->prettyName(),
provider->icon(), /*online*/ false );
config->save();
}
}
// Save the settings
KConfigGroup group = providerConfig( provider );
group.deleteGroup();
foreach( const QString &key, provider->m_config.keys() )
group.writeEntry( key, provider->m_config.value( key ) );
group.sync();
return provider;
}
KConfigGroup
ImporterManager::managerConfig() const
{
return Amarok::config( "Importers" ).group( type() );
}
KConfigGroup
ImporterManager::providerConfig( const QString &providerId ) const
{
return managerConfig().group( providerId );
}
KConfigGroup
ImporterManager::providerConfig( const ProviderPtr &provider ) const
{
return providerConfig( provider->id() );
}
void
ImporterManager::slotProviderForgotten( const QString &providerId )
{
// Check if the provider is managed by this ImporterManager
if( !m_providers.contains( providerId ) )
return;
ProviderPtr provider = m_providers.take( providerId );
if( Controller *controller = Amarok::Components::statSyncingController() )
controller->unregisterProvider( provider );
// Remove configuration
KConfigGroup group = providerConfig( providerId );
group.deleteGroup();
group.sync();
}
} // namespace StatSyncing
diff --git a/src/importers/amarok/AmarokEmbeddedSqlConnection.cpp b/src/importers/amarok/AmarokEmbeddedSqlConnection.cpp
index fdefa81618..7b0cdf5cc1 100644
--- a/src/importers/amarok/AmarokEmbeddedSqlConnection.cpp
+++ b/src/importers/amarok/AmarokEmbeddedSqlConnection.cpp
@@ -1,180 +1,180 @@
/****************************************************************************************
* Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokEmbeddedSqlConnection.h"
#include "core/support/Debug.h"
#include <ThreadWeaver/Thread>
#include <QEventLoop>
#include <QFileSystemWatcher>
#include <QMutexLocker>
#include <QStringList>
#include <QTemporaryFile>
using namespace StatSyncing;
AmarokEmbeddedSqlConnection::AmarokEmbeddedSqlConnection( const QFileInfo &mysqld,
const QDir &datadir )
: ImporterSqlConnection()
, m_mysqld( mysqld )
, m_datadir( datadir )
{
- connect( &m_shutdownTimer, SIGNAL(timeout()), SLOT(stopServer()) );
+ connect( &m_shutdownTimer, &QTimer::timeout, SLOT(stopServer()) );
m_shutdownTimer.setSingleShot( true );
}
AmarokEmbeddedSqlConnection::~AmarokEmbeddedSqlConnection()
{
if( isTransaction() )
rollback();
stopServer();
}
QSqlDatabase
AmarokEmbeddedSqlConnection::connection()
{
Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
QMutexLocker lock( &m_srvMutex );
// The server's already running; only refresh its shutdown timer
if( m_srv.state() == QProcess::Running )
{
m_shutdownTimer.start( SERVER_SHUTDOWN_AFTER );
return QSqlDatabase::database( m_connectionName );
}
QTemporaryFile pidFile( QDir::temp().filePath( "amarok_importer-XXXXXX.pid" ) );
QTemporaryFile socket( QDir::temp().filePath( "amarok_importer-XXXXXX.socket" ) );
pidFile.open();
socket.open();
// Get random port in range 3307 - 65535
const int port = ( qrand() % ( 65536 - 3307 ) ) + 3307;
QSqlDatabase::removeDatabase( m_connectionName );
QSqlDatabase db = QSqlDatabase::addDatabase( "QMYSQL", m_connectionName );
db.setDatabaseName ( "amarok" );
db.setHostName ( "localhost" );
db.setUserName ( "root" );
db.setPassword ( "" );
db.setPort ( port );
db.setConnectOptions( "UNIX_SOCKET=" + QFileInfo( socket ).absoluteFilePath() );
if( startServer( port, QFileInfo( socket ).absoluteFilePath(),
QFileInfo( pidFile ).absoluteFilePath() ) )
{
// Give tempfiles ownership over to mysqld
pidFile.setAutoRemove( false );
socket.setAutoRemove( false );
m_shutdownTimer.start( SERVER_SHUTDOWN_AFTER );
}
db.open();
return db;
}
bool
AmarokEmbeddedSqlConnection::startServer( const int port, const QString &socketPath,
const QString &pidPath )
{
DEBUG_BLOCK
Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
if( !m_mysqld.isExecutable() )
{
warning() << __PRETTY_FUNCTION__ << m_mysqld.absoluteFilePath()
<< "is not executable";
return false;
}
if( !m_datadir.isReadable() )
{
warning() << __PRETTY_FUNCTION__ << m_datadir.absolutePath() << "is not readable";
return false;
}
QEventLoop loop;
QFileSystemWatcher watcher;
QTimer timer;
// Set conditions on which we stop waiting for the startup
connect( &timer, SIGNAL(timeout()),
&loop, SLOT(quit()), Qt::QueuedConnection );
connect( &watcher, SIGNAL(fileChanged(QString)),
&loop, SLOT(quit()), Qt::QueuedConnection );
connect( &m_srv, SIGNAL(error(QProcess::ProcessError)),
&loop, SLOT(quit()), Qt::QueuedConnection );
// Important: we use modification of pidfile as a cue that the server is ready
// This is consistent with behavior of mysqld startup scripts
watcher.addPath( pidPath );
timer.start( SERVER_START_TIMEOUT );
const QStringList args = QStringList()
<< "--no-defaults"
<< "--port=" + QString::number( port )
<< "--datadir=" + m_datadir.absolutePath()
<< "--default-storage-engine=MyISAM"
<< "--skip-grant-tables"
<< "--myisam-recover-options=FORCE"
<< "--key-buffer-size=16777216"
<< "--character-set-server=utf8"
<< "--collation-server=utf8_bin"
<< "--skip-innodb"
<< "--bind-address=localhost"
<< "--socket=" + socketPath
<< "--pid-file=" + pidPath;
m_srv.start( m_mysqld.absoluteFilePath(), args );
debug() << __PRETTY_FUNCTION__ << m_mysqld.absoluteFilePath() + " " + args.join(" ");
// Wait for any of the startup conditions to be true
loop.exec();
if( m_srv.state() != QProcess::Running )
{
warning() << __PRETTY_FUNCTION__ << "error starting server application:"
<< m_srv.errorString();
return false;
}
return true;
}
void
AmarokEmbeddedSqlConnection::stopServer()
{
DEBUG_BLOCK
Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
QMutexLocker lock( &m_srvMutex );
if( isTransaction() || m_srv.state() == QProcess::NotRunning )
return;
m_shutdownTimer.stop();
QSqlDatabase::removeDatabase( m_connectionName );
m_srv.terminate();
if( !m_srv.waitForFinished() )
{
m_srv.kill();
m_srv.waitForFinished();
}
}
diff --git a/src/mac/GrowlInterface.cpp b/src/mac/GrowlInterface.cpp
index 7eb931f1e2..60be98eee1 100644
--- a/src/mac/GrowlInterface.cpp
+++ b/src/mac/GrowlInterface.cpp
@@ -1,75 +1,75 @@
/****************************************************************************************
* Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "GrowlInterface.h"
#include "amarokconfig.h"
#include "App.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h" // for secToPrettyTime
#include "SvgHandler.h"
#include "TrayIcon.h"
GrowlInterface::GrowlInterface( QString appName ) :
m_appName( appName )
{
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
- this, SLOT(show(Meta::TrackPtr)) );
+ connect( engine, &EngineController::trackChanged,
+ this, &GrowlInterface::show );
}
void
GrowlInterface::show( Meta::TrackPtr track )
{
DEBUG_BLOCK
QString text;
if( !track || track->playableUrl().isEmpty() )
text = i18n( "No track playing" );
else
{
text = track->prettyName();
if( track->artist() && !track->artist()->prettyName().isEmpty() )
text = track->artist()->prettyName() + " - " + text;
if( track->album() && !track->album()->prettyName().isEmpty() )
text += "\n (" + track->album()->prettyName() + ") ";
else
text += '\n';
if( track->length() > 0 )
text += Meta::msToPrettyTime( track->length() );
}
if( text.isEmpty() )
text = track->playableUrl().fileName();
if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that.
text = text.mid( 2 );
if( text.isEmpty() ) //still
text = i18n("No information available for this track");
if( App::instance()->trayIcon() )
{
if( track && track->album() )
{
App::instance()->trayIcon()->setIconByPixmap( The::svgHandler()->imageWithBorder( track->album(), 100, 5 ) );
}
App::instance()->trayIcon()->showMessage( "Amarok", text, QString() );
}
}
diff --git a/src/mac/MacSystemNotify.mm b/src/mac/MacSystemNotify.mm
index c634b82a2c..8e28ee33e9 100644
--- a/src/mac/MacSystemNotify.mm
+++ b/src/mac/MacSystemNotify.mm
@@ -1,112 +1,112 @@
/****************************************************************************************
* Copyright (c) 2014 Daniel Meltzer <parallelgrapefruit@gmail.com *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MacSystemNotify.h"
#include "amarokconfig.h"
#include "App.h"
#include "CoverManager/CoverCache.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h" // for secToPrettyTime
#include "SvgHandler.h"
#include "TrayIcon.h"
#import <Foundation/NSUserNotification.h>
#import <ApplicationServices/ApplicationServices.h>
namespace {
void SendNotifactionCenterMessage(NSString* title, NSString* subtitle, NSString *informativeText, NSImage *image)
{
NSUserNotificationCenter* center =
[NSUserNotificationCenter defaultUserNotificationCenter];
NSUserNotification *notification =
[[NSUserNotification alloc] init];
[center removeAllDeliveredNotifications]; // Clear the previous one before sending another
[notification setTitle: title];
[notification setSubtitle: subtitle];
[notification setInformativeText: informativeText];
[notification setContentImage: image];
[center deliverNotification: notification];
[notification release];
}
}
OSXNotify::OSXNotify(QString appName): QObject()
, m_appName( appName )
{
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
- this, SLOT(show(Meta::TrackPtr)) );
+ connect( engine, &EngineController::trackChanged,
+ this, &OSXNotify::show );
}
void
OSXNotify::show( Meta::TrackPtr track )
{
DEBUG_BLOCK
QString text;
QString albumString;
QString artistString;
QPixmap albumImage;
if( !track || track->playableUrl().isEmpty() )
text = i18n( "No track playing" );
else
{
text = track->prettyName();
if( track->artist() && !track->artist()->prettyName().isEmpty() )
artistString = track->artist()->prettyName();
if( track->album() && !track->album()->prettyName().isEmpty() )
albumString = track->album()->prettyName();
if( track->length() > 0 )
{
text += " (";
text += Meta::msToPrettyTime( track->length() );
text += ')';
}
if( text.isEmpty() )
text = track->playableUrl().fileName();
albumImage = The::coverCache()->getCover( track->album(), 100 );
}
if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that.
text = text.mid( 2 );
if( text.isEmpty() ) //still
text = i18n("No information available for this track");
NSImage *nImage = 0;
if( !albumImage.isNull() )
{
CGImageRef cgImage = albumImage.toMacCGImageRef();
nImage = [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];
CFRelease(cgImage);
}
if( artistString.isEmpty() )
artistString = i18n( "Unknown" );
if( albumString.isEmpty() )
albumString = i18n( "Unknown" );
NSString *title =[[NSString alloc] initWithUTF8String: artistString.toUtf8().constData() ];
NSString *subTitle = [[NSString alloc] initWithUTF8String: albumString.toUtf8().constData() ];
NSString *songTitle = [[NSString alloc] initWithUTF8String: text.toUtf8().constData() ];
SendNotifactionCenterMessage( songTitle, title, subTitle, nImage );
}
diff --git a/src/main.cpp b/src/main.cpp
index c7e235b158..fc30e8b17d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,349 +1,349 @@
/****************************************************************************************
* Copyright (c) 2002 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "App.h"
#include "aboutdialog/OcsData.h"
#include <KAboutData>
#include <KCmdLineArgs>
#include <KDebug>
#include <KDBusService>
#include <KLocalizedString>
#include <qglobal.h>
#ifdef Q_WS_X11
#include <X11/Xlib.h>
#endif
#include <csignal>
#include <QCommandLineParser>
//#define AMAROK_USE_DRKONQI
#ifdef Q_OS_WIN
AMAROK_EXPORT OcsData ocsData;
#endif
int main( int argc, char *argv[] )
{
App app(argc, argv);
app.setApplicationDisplayName(i18n("Amarok"));
QCoreApplication::setApplicationName("amarok");
QCoreApplication::setOrganizationDomain("kde.org");
QCoreApplication::setApplicationVersion(AMAROK_VERSION);
KAboutData aboutData( "amarok",
ki18n( "Amarok" ).toString(),
AMAROK_VERSION,
ki18n( "The audio player for KDE" ).toString(),
KAboutLicense::GPL,
ki18n( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2013, The Amarok Development Squad" ).toString(),
ki18n( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es, #amarok.fr\n\nFeedback:\namarok@kde.org\n\n(Build Date: %1)" ).subs( __DATE__ ).toString(),
( "http://amarok.kde.org" ) );
//------------ About data ----------------------
//Currently active Authors
extern OcsData ocsData;
aboutData.addAuthor( ki18n("Bart 'Where are my toothpicks' Cerneels").toString(),
ki18n("Developer (Stecchino)").toString(), "bart.cerneels@kde.org", "http://commonideas.blogspot.com" );
ocsData.addAuthor( "Stecchino", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Edward \"Hades\" Toroshchin").toString(),
ki18n("Developer (dr_lepper)").toString(), "edward.hades@gmail.com" );
ocsData.addAuthor( "hadeschief", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Mark Kretschmann" ).toString(),
ki18n("Project founder (markey)").toString(), "kretschmann@kde.org", "https://plus.google.com/102602725322221030250/posts" );
ocsData.addAuthor( "MarkKretschmann", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Matěj Laitl").toString(),
ki18n("iPod collection rewrite & more (strohel)").toString(), "matej@laitl.cz", "http://strohel.blogspot.com/" );
ocsData.addAuthor( "strohel", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Myriam Schweingruber").toString(), ki18n("Rokymoter, Bug triaging (Mamarok)").toString(), "myriam@kde.org", "http://blogs.fsfe.org/myriam" );
ocsData.addAuthor( "Mamarok", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Ralf 'SalsaMaster' Engels").toString(),
ki18n("Developer (rengels)").toString(), "ralf.engels@nokia.com" );
ocsData.addAuthor( QString(), aboutData.authors().last() );
aboutData.addAuthor( ki18n("Patrick von Reth").toString(), ki18n("Windows build (TheOneRing)").toString(),
"patrick.vonreth@gmail.com" );
ocsData.addAuthor( QString(), aboutData.authors().last() );
aboutData.addAuthor( ki18n("Rick W. Chen").toString(),
ki18n("Developer (stuffcorpse)").toString(), "stuffcorpse@archlinux.us" );
ocsData.addAuthor( "stuffcorpse", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Sam Lade").toString(), ki18n("Developer (Sentynel)").toString(),
"sam@sentynel.com" );
ocsData.addAuthor( "Sentynel", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Sven Krohlas").toString(), ki18n("Rokymoter, Developer (sven423)").toString(), "sven@asbest-online.de" );
ocsData.addAuthor( "krohlas", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Téo Mrnjavac").toString(),
ki18n("Developer (Teo`)").toString(), "teo@kde.org", "http://teom.wordpress.com/" );
ocsData.addAuthor( "teom", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Valorie Zimmerman").toString(),
ki18n("Rokymoter, Handbook (valorie)").toString(), "valorie@kde.org" );
ocsData.addAuthor( "valorie", aboutData.authors().last() );
//Inactive authors
/* This list should contain people who still hold major copyright on the current code
* For instance: does not include authors of 1.4 who have not contributed to 2.x */
aboutData.addAuthor( ki18n("<i>Inactive authors</i>").toString(),
ki18n("Amarok authorship is not a hobby, it's a lifestyle. "
"But when people move on we want to keep respecting "
"them by mentioning them here:").toString(), "" );
ocsData.addAuthor( "%%category%%", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Ian 'The Beard' Monroe").toString(), ki18n("Developer (eean)").toString(), "ian@monroe.nu" );
ocsData.addAuthor( "eean", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Jeff 'IROKSOHARD' Mitchell").toString(), ki18n("Developer (jefferai)").toString(), "mitchell@kde.org" );
ocsData.addAuthor( "jefferai", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Leo Franchi").toString(), ki18n("Developer (lfranchi)").toString(), "lfranchi@kde.org" );
ocsData.addAuthor( "lfranchi", aboutData.authors().last() );
aboutData.addAuthor( ki18n("Lydia 'is wrong(TM)' Pintscher").toString(), ki18n("Release Vixen (Nightrose)").toString(), "lydia@kde.org" );
ocsData.addAuthor( "nightrose", aboutData.authors().last() );
aboutData.addCredit( ki18n("Max Howell").toString(), ki18n("Developer, Vision").toString(), "max.howell@methylblue.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addAuthor( ki18n("Maximilian Kossick").toString(), ki18n("Developer (maxx_k)").toString(), "maximilian.kossick@gmail.com" );
ocsData.addAuthor( QString(), aboutData.authors().last() );
aboutData.addAuthor( ki18n("Nikolaj Hald 'Also very hot' Nielsen").toString(), ki18n("Developer (nhn)").toString(), "nhn@kde.org" );
ocsData.addAuthor( "nhnFreespirit", aboutData.authors().last() );
aboutData.addCredit( ki18n("Seb 'Surfin' down under' Ruiz").toString(), ki18n("Developer (sebr)").toString(), "ruiz@kde.org" );
ocsData.addCredit( "seb", aboutData.credits().last() );
//Contributors
aboutData.addCredit( ki18n("Alejandro Wainzinger").toString(), ki18n("Developer (xevix)").toString(), "aikawarazuni@gmail.com" );
ocsData.addCredit( "xevix", aboutData.credits().last() );
aboutData.addCredit( ki18n("Alex Merry").toString(), ki18n("Developer, Replay Gain support").toString(), "kde@randomguy3.me.uk" );
ocsData.addCredit( "randomguy3", aboutData.credits().last() );
aboutData.addCredit( ki18n("Casey Link").toString(), ki18n("MP3tunes integration").toString(), "unnamedrambler@gmail.com" );
ocsData.addCredit( "Ramblurr", aboutData.credits().last() );
aboutData.addCredit( ki18n("Casper van Donderen").toString(), ki18n("Windows porting").toString(), "casper.vandonderen@gmail.com" );
ocsData.addCredit( "cvandonderen", aboutData.credits().last() );
aboutData.addCredit( ki18n("Christie Harris").toString(), ki18n("Rokymoter (dangle)").toString(), "dangle.baby@gmail.com" );
ocsData.addCredit( "dangle", aboutData.credits().last() );
aboutData.addCredit( ki18n("Dan Leinir Turthra Jensen").toString(), ki18n("Usability").toString(), "admin@leinir.dk" );
ocsData.addCredit( "leinir", aboutData.credits().last() );
aboutData.addCredit( ki18n("Dan 'Hey, it compiled...' Meltzer").toString(), ki18n("Developer (hydrogen)").toString(), "parallelgrapefruit@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Daniel Caleb Jones").toString(), ki18n("Biased playlists").toString(), "danielcjones@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Daniel Dewald").toString(), ki18n("Tag Guesser, Labels, Spectrum Analyzer").toString(), "Daniel.Dewald@time-shift.de" );
ocsData.addCredit( "TheCrasher", aboutData.credits().last() );
aboutData.addCredit( ki18n("Daniel Winter").toString(), ki18n("Nepomuk integration").toString(), "dw@danielwinter.de" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Frank Meerkötter").toString(), ki18n("Podcast improvements").toString(), "frank@meerkoetter.org" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Greg Meyer").toString(), ki18n("Live CD, Bug squashing (oggb4mp3)").toString(), "greg@gkmweb.com" );
ocsData.addCredit( "oggb4mp3", aboutData.credits().last() );
aboutData.addCredit( ki18n("Harald Sitter").toString(), ki18n("Phonon, Lord-President of KDE Multimedia (apachelogger)").toString(), "harald.sitter@kdemail.net" );
ocsData.addCredit( "apachelogger", aboutData.credits().last() );
aboutData.addCredit( ki18n("John Atkinson").toString(), ki18n("Assorted patches").toString(), "john@fauxnetic.co.uk" );
ocsData.addCredit( "fauxnetic", aboutData.credits().last() );
aboutData.addCredit( ki18n("Kenneth Wesley Wimer II").toString(), ki18n("Icons").toString(), "kwwii@bootsplash.org" );
ocsData.addCredit( "kwwii", aboutData.credits().last() );
aboutData.addCredit( ki18n("Kevin Funk").toString(), ki18n("Developer, Website theme (KRF)").toString(), "krf@electrostorm.net" );
ocsData.addCredit( "krf", aboutData.credits().last() );
aboutData.addCredit( ki18n("Kuba Serafinowski").toString(), ki18n("Rokymoter").toString(), "zizzfizzix@gmail.com" );
ocsData.addCredit( "zizzfizzix", aboutData.credits().last() );
aboutData.addCredit( ki18n("Lee Olson").toString(), ki18n("Artwork").toString(), "leetolson@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Ljubomir Simin").toString(), ki18n("Rokymoter (ljubomir)").toString(), "ljubomir.simin@gmail.com" );
ocsData.addCredit( "ljubomir", aboutData.credits().last() );
aboutData.addCredit( ki18n("Lucas Gomes").toString(), ki18n("Developer (MaskMaster)").toString(), "x8lucas8x@gmail.com" );
ocsData.addCredit( "x8lucas8x", aboutData.credits().last() );
aboutData.addCredit( ki18n("Mathias Panzenböck").toString(), ki18n("Podcast improvements").toString(), "grosser.meister.morti@gmx.net" );
ocsData.addCredit( "panzi", aboutData.credits().last() );
aboutData.addCredit( ki18n("Mikko Caldara").toString(), ki18n("Bug triaging and sanitizing").toString(), "mikko.cal@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Nikhil Marathe").toString(), ki18n("UPnP support and patches (nsm)").toString(), "nsm.nikhil@gmail.com" );
ocsData.addCredit( "nikhilm", aboutData.credits().last() );
aboutData.addCredit( ki18n("Nuno Pinheiro").toString(), ki18n("Artwork").toString(), "nuno@oxygen-icons.org" );
ocsData.addCredit( "nunopinheirokde", aboutData.credits().last() );
aboutData.addCredit( ki18n("Olivier Bédard").toString(), ki18n("Website hosting").toString(), "paleo@pwsp.net" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Pasi Lalinaho").toString(), ki18n("Rokymoter (emunkki)").toString(), "pasi@getamarok.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Peter Zhou Lei").toString(), ki18n("Scripting interface").toString(), "peterzhoulei@gmail.com" );
ocsData.addCredit( "peterzl", aboutData.credits().last() );
aboutData.addCredit( ki18n("Phalgun Guduthur").toString(), ki18n("Nepomuk Collection (phalgun)").toString(), "me@phalgun.in" );
ocsData.addCredit( "phalgun", aboutData.credits().last() );
aboutData.addCredit( ki18n("Scott Wheeler").toString(), ki18n("TagLib & ktrm code").toString(), "wheeler@kde.org" );
ocsData.addCredit( "wheels", aboutData.credits().last() );
aboutData.addCredit( ki18n("Shane King").toString(), ki18n("Patches & Windows porting (shakes)").toString(), "kde@dontletsstart.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Simon Esneault").toString(), ki18n("Photos & Videos applets, Context View").toString(), "simon.esneault@gmail.com" );
ocsData.addCredit( "Takahani", aboutData.credits().last() );
aboutData.addCredit( ki18n("Soren Harward").toString(), ki18n("Developer, Automated Playlist Generator").toString(), "stharward@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Thomas Lübking").toString(), ki18n("Developer").toString(), "thomas.luebking@web.de" );
ocsData.addCredit( "thomas12777", aboutData.credits().last() );
aboutData.addCredit( ki18n("Valentin Rouet").toString(), ki18n("Developer").toString(), "v.rouet@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Wade Olson").toString(), ki18n("Splash screen artist").toString(), "wade@corefunction.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("William Viana Soares").toString(), ki18n("Context view").toString(), "vianasw@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
//Former Contributors
aboutData.addCredit( ki18n("Former contributors").toString(), ki18n("People listed below have contributed to Amarok in the past. Thank you!").toString(), "" );
ocsData.addCredit( "%%category%%", aboutData.credits().last() );
aboutData.addCredit( ki18n("Adam Pigg").toString(), ki18n("Analyzers, patches, shoutcast").toString(), "adam@piggz.co.uk" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Adeodato Simó").toString(), ki18n("Patches").toString(), "asp16@alu.ua.es" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Alexandre Oliveira").toString(), ki18n("Developer").toString(), "aleprj@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Andreas Mair").toString(), ki18n("MySQL support").toString(), "am_ml@linogate.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Andrew de Quincey").toString(), ki18n("Postgresql support").toString(), "adq_dvb@lidskialf.net" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Andrew Turner").toString(), ki18n("Patches").toString(), "andrewturner512@googlemail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Andy Kelk").toString(), ki18n("MTP and Rio Karma media devices, patches").toString(), "andy@mopoke.co.uk" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Christian Muehlhaeuser").toString(), ki18n("Developer").toString(), "chris@chris.de" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Derek Nelson").toString(), ki18n("Graphics, splash-screen").toString(), "admrla@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Enrico Ros").toString(), ki18n("Analyzers, Context Browser and systray eye-candy").toString(), "eros.kde@email.it" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Frederik Holljen").toString(), ki18n("Developer").toString(), "fh@ez.no" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Gábor Lehel").toString(), ki18n("Developer").toString(), "illissius@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Gérard Dürrmeyer").toString(), ki18n("Icons and image work").toString(), "gerard@randomtree.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Giovanni Venturi").toString(), ki18n("Dialog to filter the collection titles").toString(), "giovanni@ksniffer.org" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Jarkko Lehti").toString(), ki18n("Tester, IRC channel operator, whipping").toString(), "grue@iki.fi" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Jocke Andersson").toString(), ki18n("Rokymoter, bug fixer (Firetech)").toString(), "ajocke@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Marco Gulino").toString(), ki18n("Konqueror Sidebar, some DCOP methods").toString(), "marco@kmobiletools.org" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Martin Aumueller").toString(), ki18n("Developer").toString(), "aumuell@reserv.at" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Melchior Franz").toString(), ki18n("FHT routine, bugfixes").toString(), "mfranz@kde.org" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Michael Pyne").toString(), ki18n("K3b export code").toString(), "michael.pyne@kdemail.net" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Mike Diehl").toString(), ki18n("Developer").toString(), "madpenguin8@yahoo.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Paul Cifarelli").toString(), ki18n("Developer").toString(), "paul@cifarelli.net" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Peter C. Ndikuwera").toString(), ki18n("Bugfixes, PostgreSQL support").toString(), "pndiku@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Pierpaolo Panfilo").toString(), ki18n("Developer").toString(), "pippo_dp@libero.it" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Reigo Reinmets").toString(), ki18n("Wikipedia support, patches").toString(), "xatax@hot.ee" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Roman Becker").toString(), ki18n("Former Amarok logo, former splash screen, former icons").toString(), "roman@formmorf.de" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Sami Nieminen").toString(), ki18n("Audioscrobbler support").toString(), "sami.nieminen@iki.fi" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Stanislav Karchebny").toString(), ki18n("Developer").toString(), "berkus@madfire.net" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Stefan Bogner").toString(), ki18n("Loads of stuff").toString(), "bochi@online.ms" );
ocsData.addCredit( QString(), aboutData.credits().last() );
aboutData.addCredit( ki18n("Tomasz Dudzik").toString(), ki18n("Splash screen").toString(), "madsheytan@gmail.com" );
ocsData.addCredit( QString(), aboutData.credits().last() );
//Donors:
//Last update: 2012/11/07, post Roktober 2012
ocsData.addDonor( "ayleph", KAboutPerson( ki18n( "Andrew Browning" ).toString() ) );
ocsData.addDonor( QString(), KAboutPerson( ki18n( "Chris Wales" ).toString() ) );
ocsData.addDonor( QString(), KAboutPerson( ki18n( "ZImin Stanislav" ).toString() ) );
KAboutData::setApplicationData(aboutData);
// Command line parser
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
aboutData.setupCommandLine(&parser);
app.initCliArgs(&parser);
parser.process(app);
aboutData.processCommandLine(&parser);
KDBusService::StartupOptions startOptions = parser.isSet( "multipleinstances" ) ? KDBusService::Multiple
: KDBusService::Unique ;
// register the app to dbus
KDBusService dbusService( startOptions );
- QObject::connect(&dbusService, SIGNAL(activateRequested(QStringList,QString)),
- &app, SLOT(activateRequested(QStringList,QString)));
+ QObject::connect(&dbusService, &KDBusService::activateRequested,
+ &app, &App::activateRequested);
const bool debugColorsEnabled = !parser.isSet( "coloroff" );
const bool debugEnabled = parser.isSet( "debug" );
Debug::setDebugEnabled( debugEnabled );
Debug::setColoredDebug( debugColorsEnabled );
if ( parser.isSet( "debug-audio" ) ) {
qputenv( "PHONON_DEBUG", QByteArray( "3" ) );
qputenv( "PHONON_BACKEND_DEBUG", QByteArray( "3" ) );
qputenv( "PHONON_PULSEAUDIO_DEBUG", QByteArray( "3" ) );
}
#pragma message("PORT KF5: This *if* hould be moved to activateRequested() slot")
if( !dbusService.isRegistered() ) {
QList<QByteArray> instanceOptions;
instanceOptions << "previous" << "play" << "play-pause" << "stop" << "next"
<< "append" << "queue" << "load";
// Check if an option for a running instance is set
bool isSet = false;
for( int i = 0; i < instanceOptions.size(); ++i )
if( parser.isSet( instanceOptions[ i ] ) )
isSet = true;
if ( !isSet )
fprintf( stderr, "Amarok is already running!\n" );
return 0;
}
// Rewrite default SIGINT and SIGTERM handlers
// to make amarok save current playlists during forced
// application termination (logout, Ctr+C in console etc.)
signal( SIGINT, &QCoreApplication::exit );
signal( SIGTERM, &QCoreApplication::exit );
// This call is needed to prevent a crash on exit with Phonon-VLC and LibPulse
#ifdef Q_WS_X11
XInitThreads();
#endif
app.continueInit();
return app.exec();
}
diff --git a/src/moodbar/MoodbarManager.cpp b/src/moodbar/MoodbarManager.cpp
index 0925173286..ff527297ed 100644
--- a/src/moodbar/MoodbarManager.cpp
+++ b/src/moodbar/MoodbarManager.cpp
@@ -1,530 +1,530 @@
/****************************************************************************************
* Copyright (c) 2005 Gav Wood <gav@kde.org> *
* Copyright (c) 2006 Joseph Rabinoff <rabinoff@post.harvard.edu> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MoodbarManager"
/*
The mood file loading and rendering code is based on the Amarok 1.4 moodbar implementation
by Gav Wood and Joseph Rabinoff, ported to Qt 4 with only a few modifications by me.
The moodbar generator seems to be running just fine on modern systems if gstreamer is
installed, but it could none the less do with a major update, perhaps to use Phonon or
even porting to qtscript so it could be run, as needed, by Amarok.
- Nikolaj
*/
#include "MoodbarManager.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "PaletteHandler.h"
#include <QFile>
#include <QFileInfo>
#include <QPainter>
#define NUM_HUES 12
namespace The
{
static MoodbarManager* s_MoodbarManager_instance = 0;
MoodbarManager* moodbarManager()
{
if( !s_MoodbarManager_instance )
s_MoodbarManager_instance = new MoodbarManager();
return s_MoodbarManager_instance;
}
}
MoodbarManager::MoodbarManager()
: m_cache( new KImageCache( "Amarok-moodbars", 10 * 1024 ) )
, m_lastPaintMode( 0 )
{
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(paletteChanged(QPalette)) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &MoodbarManager::paletteChanged );
}
MoodbarManager::~MoodbarManager()
{}
bool MoodbarManager::hasMoodbar( Meta::TrackPtr track )
{
//check if we already checked this track:
if ( m_hasMoodMap.contains( track ) )
{
//debug() << "Cached value, returning: " << m_hasMoodMap.value( track );
return m_hasMoodMap.value( track );
}
QUrl trackUrl = track->playableUrl();
//only supports local files for now.
if ( !trackUrl.isLocalFile() )
{
debug() << "non local file, no moodbar...";
m_hasMoodMap.insert( track, false );
return false;
}
//do we already have a moodFile path for this track?
QString moodFilePath;
if ( m_moodFileMap.contains( track ) )
moodFilePath = m_moodFileMap.value( track );
else
{
//Now, lets see if there is a mood file that matches the track filename
moodFilePath = moodPath( trackUrl.path() );
}
debug() << "file path: " << trackUrl.path();
debug() << "mood file path: " << moodFilePath;
if( !QFile::exists( moodFilePath ) )
{
debug() << "no such file";
//for fun, try without the leading '.'
QFileInfo fInfo( moodFilePath );
QString testName = fInfo.fileName();
testName.remove( 0, 1 );
moodFilePath.replace( fInfo.fileName(), testName );
debug() << "trying : " << moodFilePath;
if( !QFile::exists( moodFilePath ) )
{
debug() << "no luck removing the leading '.' either...";
m_hasMoodMap.insert( track, false );
return false;
}
debug() << "whoops, missing leading '.', so mood file path: " << moodFilePath;
}
//it is a local file with a matching .mood file. Good enough for now!
m_moodFileMap.insert( track, moodFilePath );
m_hasMoodMap.insert( track, true );
return true;
}
QPixmap MoodbarManager::getMoodbar( Meta::TrackPtr track, int width, int height, bool rtl )
{
//if we have already marked this track as
//not having a moodbar, don't even bother...
if ( m_hasMoodMap.contains( track ) )
if( !m_hasMoodMap.value( track ) )
return QPixmap();
//first of all... Check if rendering settings have changed. If
//so, clear data and pixmap caches.
if( m_lastPaintMode != AmarokConfig::moodbarPaintStyle() )
{
m_lastPaintMode = AmarokConfig::moodbarPaintStyle();
m_cache->clear();
m_moodDataMap.clear();
emit moodbarStyleChanged();
}
//Do we already have this pixmap cached?
const QString pixmapKey = QString( "mood:%1-%2x%3%4" ).arg( track->uidUrl(), QString::number( width ),
QString::number( height ), QString( rtl?"r":"" ) );
QPixmap moodbar;
if( m_cache->findPixmap( pixmapKey, &moodbar ) )
return moodbar;
//No? Ok, then create it reusing as much info as possible
MoodbarColorList data;
if ( m_moodDataMap.contains( track ) )
data = m_moodDataMap.value( track );
else
{
QString moodFilePath;
if ( m_moodFileMap.contains( track ) )
moodFilePath = m_moodFileMap.value( track );
else
moodFilePath = moodPath( track->playableUrl().path() );
data = readMoodFile( QUrl::fromUserInput(moodFilePath) );
if ( data.size() > 10 )
m_moodDataMap.insert( track, data );
else
{
//likely a corrupt file, so mark this track as not having a moodbar
m_hasMoodMap.insert( track, false );
}
}
//assume that the readMoodFile function emits the proper error...
if ( data.size() < 10 )
return moodbar;
moodbar = drawMoodbar( data, width, height, rtl );
m_cache->insertPixmap( pixmapKey, moodbar );
return moodbar;
}
MoodbarColorList MoodbarManager::readMoodFile( const QUrl &moodFileUrl )
{
DEBUG_BLOCK
MoodbarColorList data;
const QString path = moodFileUrl.path();
if( path.isEmpty() )
return data;
debug() << "Trying to read " << path;
QFile moodFile( path );
if( !moodFile.open( QIODevice::ReadOnly ) )
return data;
int r, g, b, samples = moodFile.size() / 3;
debug() << "File" << path << "opened. Proceeding to read contents... s=" << samples;
// This would be bad.
if( samples == 0 )
{
debug() << "Filex " << moodFile.fileName() << "is corrupted, removing";
//TODO: notify the user somehow
//moodFile.remove();
return data;
}
int huedist[360]; // For alterMood
int modalHue[NUM_HUES]; // For m_hueSort
int h, s, v;
memset( modalHue, 0, sizeof( modalHue ) );
memset( huedist, 0, sizeof( huedist ) );
// Read the file, keeping track of some histograms
for( int i = 0; i < samples; ++i )
{
char rChar, gChar, bChar;
moodFile.getChar( &rChar );
moodFile.getChar( &gChar );
moodFile.getChar( &bChar );
r = qAbs( (int) rChar );
g = qAbs( (int) gChar );
b = qAbs( (int) bChar );
data.append( QColor( qBound( 0, r, 255 ),
qBound( 0, g, 255 ),
qBound( 0, b, 255 ) ) );
// Make a histogram of hues
data.last().getHsv( &h, &s, &v );
modalHue[qBound( 0, h * NUM_HUES / 360, NUM_HUES - 1 )] += v;
if( h < 0 ) h = 0; else h = h % 360;
huedist[h]++;
}
// Make moodier -- copied straight from Gav Wood's code
// Here's an explanation of the algorithm:
//
// The "input" hue for each bar is mapped to a hue between
// rangeStart and (rangeStart + rangeDelta). The mapping is
// determined by the hue histogram, huedist[], which is calculated
// above by putting each sample into one of 360 hue bins. The
// mapping is such that if your histogram is concentrated on a few
// hues that are close together, then these hues are separated,
// and the space between spikes in the hue histogram is
// compressed. Here we consider a hue value to be a "spike" in
// the hue histogram if the number of samples in that bin is
// greater than the threshold variable.
//
// As an example, suppose we have 100 samples, and that
// threshold = 10 rangeStart = 0 rangeDelta = 288
// Suppose that we have 10 samples at each of 99,100,101, and 200.
// Suppose that there are 20 samples < 99, 20 between 102 and 199,
// and 20 above 201, with no spikes. There will be five hues in
// the output, at hues 0, 72, 144, 216, and 288, containing the
// following number of samples:
// 0: 20 + 10 = 30 (range 0 - 99 )
// 72: 10 (range 100 - 100)
// 144: 10 (range 101 - 101)
// 216: 10 + 20 = 30 (range 102 - 200)
// 288: 20 (range 201 - 359)
// The hues are now much more evenly distributed.
//
// After the hue redistribution is calculated, the saturation and
// value are scaled by sat and val, respectively, which are percentage
// values.
moodFile.close();
const int paintStyle = AmarokConfig::moodbarPaintStyle();
{
MoodbarColorList modifiedData;
// Explanation of the parameters:
//
// threshold: A hue value is considered to be a "spike" in the
// histogram if it's above this value. Setting this value
// higher will tend to make the hue distribution more uniform
//
// rangeStart, rangeDelta: output hues will be more or less
// evenly spaced between rangeStart and (rangeStart + rangeDelta)
//
// sat, val: the saturation and value are scaled by these integral
// percentage values
int threshold, rangeStart, rangeDelta, sat, val;
int total = 0;
memset( modalHue, 0, sizeof( modalHue ) ); // Recalculate this
switch( paintStyle )
{
case Angry: // Angry
threshold = samples / 360 * 9;
rangeStart = 45;
rangeDelta = -45;
sat = 200;
val = 100;
break;
case Frozen: // Frozen
threshold = samples / 360 * 1;
rangeStart = 140;
rangeDelta = 160;
sat = 50;
val = 100;
break;
case Happy: // Happy
threshold = samples / 360 * 2;
rangeStart = 0;
rangeDelta = 359;
sat = 150;
val = 250;
break;
case Normal: // old "normal" mode, don't change moodfile's RGB values
threshold = samples / 360 * 3;
rangeStart = 0;
rangeDelta = 359;
sat = 100;
val = 100;
break;
case SystemColours:
default: // Default (system colours)
threshold = samples / 360 * 3;
rangeStart = The::paletteHandler()->highlightColor().hsvHue();
rangeStart = (rangeStart - 20 + 360) % 360;
rangeDelta = 20;
sat = The::paletteHandler()->highlightColor().hsvSaturation();
val = The::paletteHandler()->highlightColor().value() / 2;
}
//debug() << "ReadMood: Applying filter t=" << threshold
// << ", rS=" << rangeStart << ", rD=" << rangeDelta
// << ", s=" << sat << "%, v=" << val << "%" << endl;
// On average, huedist[i] = samples / 360. This counts the
// number of samples over the threshold, which is usually
// 1, 2, 9, etc. times the average samples in each bin.
// The total determines how many output hues there are,
// evenly spaced between rangeStart and rangeStart + rangeDelta.
for( int i = 0; i < 360; i++ )
if( huedist[i] > threshold )
total++;
if( total < 360 && total > 0 )
{
// Remap the hue values to be between rangeStart and
// rangeStart + rangeDelta. Every time we see an input hue
// above the threshold, increment the output hue by
// (1/total) * rangeDelta.
for( int i = 0, n = 0; i < 360; i++ )
huedist[i] = ( ( huedist[i] > threshold ? n++ : n )
* rangeDelta / total + rangeStart ) % 360;
// Now huedist is a hue mapper: huedist[h] is the new hue value
// for a bar with hue h
foreach( QColor color, data )
{
color.getHsv( &h, &s, &v );
h = h < 0 ? 0 : h % 360;
color.setHsv( qBound( 0, huedist[h], 359 ),
qBound( 0, s * sat / 100, 255 ),
qBound( 0, v * val / 100, 255 ) );
modalHue[qBound( 0, huedist[h] * NUM_HUES / 360, NUM_HUES - 1 )] += ( v * val / 100 );
modifiedData.append( color );
}
}
return modifiedData;
}
// Calculate m_hueSort. This is a 3-digit number in base NUM_HUES,
// where the most significant digit is the first strongest hue, the
// second digit is the second strongest hue, and the third digit
// is the third strongest. This code was written by Gav Wood.
/*
m_hueSort = 0;
mx = 0;
for( int i = 1; i < NUM_HUES; i++ )
if( modalHue[i] > modalHue[mx] )
mx = i;
m_hueSort = mx * NUM_HUES * NUM_HUES;
modalHue[mx] = 0;
mx = 0;
for( int i = 1; i < NUM_HUES; i++ )
if( modalHue[i] > modalHue[mx] )
mx = i;
m_hueSort += mx * NUM_HUES;
modalHue[mx] = 0;
mx = 0;
for( int i = 1; i < NUM_HUES; i++ )
if( modalHue[i] > modalHue[mx] )
mx = i;
m_hueSort += mx;
*/
//debug() << "All done.";
return data;
}
QPixmap MoodbarManager::drawMoodbar( const MoodbarColorList &data, int width, int height, bool rtl )
{
// First average the moodbar samples that will go into each
// vertical bar on the screen.
if( data.size() == 0 ) // Play it safe -- see below
return QPixmap();
MoodbarColorList screenColors;
QColor bar;
float r, g, b;
int h, s, v;
for( int i = 0; i < width; i++ )
{
r = 0.f; g = 0.f; b = 0.f;
// data.size() needs to be at least 1 for this not to crash!
uint start = i * data.size() / width;
uint end = (i + 1) * data.size() / width;
if( start == end )
end = start + 1;
for( uint j = start; j < end; j++ )
{
r += data[j].red();
g += data[j].green();
b += data[j].blue();
}
uint n = end - start;
bar = QColor( int( r / float( n ) ),
int( g / float( n ) ),
int( b / float( n ) ) );
// Snap to the HSV values for later
bar.getHsv(&h, &s, &v);
bar.setHsv(h, s, v);
screenColors.append( bar );
}
// Paint the bars. This is Gav's painting code -- it breaks up the
// monotony of solid-color vertical bars by playing with the saturation
// and value.
QPixmap pixmap = QPixmap( width, height );
QPainter paint( &pixmap );
for( int x = 0; x < width; x++ )
{
screenColors[x].getHsv( &h, &s, &v );
for( int y = 0; y <= height / 2; y++ )
{
float coeff = float( y ) / float( height / 2 );
float coeff2 = 1.f - ( ( 1.f - coeff ) * ( 1.f - coeff ) );
coeff = 1.f - ( 1.f - coeff ) / 2.f;
coeff2 = 1.f - ( 1.f - coeff2 ) / 2.f;
QColor hsvColor;
hsvColor.setHsv( h,
qBound( 0, int( float( s ) * coeff ), 255 ),
qBound( 0, int( 255.f - (255.f - float( v ) ) * coeff2 ), 255 ) ) ;
paint.setPen( hsvColor );
paint.drawPoint( x, y );
paint.drawPoint( x, height - 1 - y );
}
}
paint.end();
if ( rtl )
pixmap = QPixmap::fromImage( pixmap.toImage().mirrored( true, false ) );
return pixmap;
}
QString MoodbarManager::moodPath( const QString &trackPath ) const
{
QStringList parts = trackPath.split( '.' );
parts.takeLast();
parts.append( "mood" );
QString moodPath = parts.join( "." );
//now prepend the filename with .
const QFileInfo fileInfo( moodPath );
const QString fileName = fileInfo.fileName();
return moodPath.replace( fileName, '.' + fileName );
}
void MoodbarManager::paletteChanged( const QPalette &palette )
{
Q_UNUSED( palette )
const int paintStyle = AmarokConfig::moodbarPaintStyle();
if( paintStyle == 0 ) // system default colour
{
m_cache->clear();
m_moodDataMap.clear();
}
}
diff --git a/src/musicbrainz/MusicBrainzFinder.cpp b/src/musicbrainz/MusicBrainzFinder.cpp
index c196a3df85..ad4dbd11b1 100644
--- a/src/musicbrainz/MusicBrainzFinder.cpp
+++ b/src/musicbrainz/MusicBrainzFinder.cpp
@@ -1,656 +1,656 @@
/****************************************************************************************
* Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> *
* Copyright (c) 2013 Alberto Villa <avilla@FreeBSD.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MusicBrainzFinder"
#include "MusicBrainzFinder.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Debug.h"
#include "MusicBrainzMeta.h"
#include "TagsFromFileNameGuesser.h"
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/Job>
#include <QAuthenticator>
#include <QNetworkAccessManager>
#include <QTimer>
/*
* Levenshtein distance algorithm implementation carefully pirated from Wikibooks
* (http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance)
* and modified (a bit) to return similarity measure instead of distance.
*/
float
similarity( const QString &s1, const QString &s2 )
{
const size_t len1 = s1.length(), len2 = s2.length();
QVector<uint> col( len2 + 1 ), prevCol( len2 + 1 );
for( uint i = 0; i <= len2; i++ )
prevCol[i] = i;
for( uint i = 0; i < len1; i++ )
{
col[0] = i + 1;
for( uint j = 0; j < len2; j++ )
col[j + 1] = qMin( qMin( 1 + col[j], 1 + prevCol[1 + j] ),
prevCol[j] + ( s1[i] == s2[j] ? 0 : 1 ) );
col.swap( prevCol );
}
return 1.0 - ( float )prevCol[len2] / ( len1 + len2 );
}
MusicBrainzFinder::MusicBrainzFinder( QObject *parent, const QString &host,
const int port, const QString &pathPrefix,
const QString &username, const QString &password )
: QObject( parent )
, mb_host( host )
, mb_port( port )
, mb_pathPrefix( pathPrefix )
, mb_username( username )
, mb_password( password )
{
DEBUG_BLOCK
debug() << "Initiating MusicBrainz search:";
debug() << "\thost:\t\t" << mb_host;
debug() << "\tport:\t\t" << mb_port;
debug() << "\tpath prefix:\t" << mb_pathPrefix;
debug() << "\tusername:\t" << mb_username;
debug() << "\tpassword:\t" << mb_password;
net = The::networkAccessManager();
m_timer = new QTimer( this );
m_timer->setInterval( 1000 );
- connect( net, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
- SLOT(gotAuthenticationRequest(QNetworkReply*,QAuthenticator*)) );
- connect( net, SIGNAL(finished(QNetworkReply*)),
- SLOT(gotReply(QNetworkReply*)) );
- connect( m_timer, SIGNAL(timeout()), SLOT(sendNewRequest()) );
+ connect( net, &QNetworkAccessManager::authenticationRequired,
+ this, &MusicBrainzFinder::gotAuthenticationRequest );
+ connect( net, &QNetworkAccessManager::finished,
+ this, &MusicBrainzFinder::gotReply );
+ connect( m_timer, &QTimer::timeout, this, &MusicBrainzFinder::sendNewRequest );
}
bool
MusicBrainzFinder::isRunning() const
{
return !( m_requests.isEmpty() && m_replies.isEmpty() &&
m_parsers.isEmpty() ) || m_timer->isActive();
}
void
MusicBrainzFinder::run( const Meta::TrackList &tracks )
{
foreach( const Meta::TrackPtr &track, tracks )
m_requests.append( qMakePair( track, compileTrackRequest( track ) ) );
m_timer->start();
}
void
MusicBrainzFinder::lookUpByPUID( const Meta::TrackPtr &track, const QString &puid )
{
m_requests.append( qMakePair( track, compilePUIDRequest( puid ) ) );
if( !m_timer->isActive() )
m_timer->start();
}
void
MusicBrainzFinder::sendNewRequest()
{
DEBUG_BLOCK
if( m_requests.isEmpty() )
{
checkDone();
return;
}
QPair<Meta::TrackPtr, QNetworkRequest> req = m_requests.takeFirst();
QNetworkReply *reply = net->get( req.second );
m_replies.insert( reply, req.first );
- connect( reply, SIGNAL(error(QNetworkReply::NetworkError)),
- this, SLOT(gotReplyError(QNetworkReply::NetworkError)) );
+ connect( reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
+ this, &MusicBrainzFinder::gotReplyError );
debug() << "Request sent:" << req.second.url().toString();
}
void
MusicBrainzFinder::gotAuthenticationRequest( const QNetworkReply *reply, QAuthenticator *authenticator )
{
if( reply->url().host() == mb_host )
{
authenticator->setUser( mb_username );
authenticator->setPassword( mb_password );
}
}
void
MusicBrainzFinder::gotReplyError( QNetworkReply::NetworkError code )
{
DEBUG_BLOCK
QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
if( !reply )
return;
if( !m_replies.contains( reply ) || code == QNetworkReply::NoError )
return;
debug() << "Error occurred during network request:" << reply->errorString();
- disconnect( reply, SIGNAL(error(QNetworkReply::NetworkError)),
- this, SLOT(gotReplyError(QNetworkReply::NetworkError)) );
+ disconnect( reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
+ this, &MusicBrainzFinder::gotReplyError );
// Send an empty result to populate the tagger.
sendTrack( m_replies.value( reply ), QVariantMap() );
m_replies.remove( reply );
reply->deleteLater();
checkDone();
}
void
MusicBrainzFinder::gotReply( QNetworkReply *reply )
{
DEBUG_BLOCK
if( m_replies.contains( reply ) )
{
if( reply->error() == QNetworkReply::NoError )
{
QString document( reply->readAll() );
MusicBrainzXmlParser *parser = new MusicBrainzXmlParser( document );
m_parsers.insert( parser, m_replies.value( reply ) );
- connect( parser, SIGNAL(done(ThreadWeaver::JobPointer)),
- SLOT(parsingDone(ThreadWeaver::JobPointer)) );
+ connect( parser, &MusicBrainzXmlParser::done,
+ this, &MusicBrainzFinder::parsingDone );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(parser) );
}
else
/*
* Send an empty result to populate the tagger. In theory, this part should
* never be reachable, but you never know.
*/
sendTrack( m_replies.value( reply ), QVariantMap() );
}
m_replies.remove( reply );
reply->deleteLater();
checkDone();
}
void
MusicBrainzFinder::parsingDone( ThreadWeaver::JobPointer _parser )
{
DEBUG_BLOCK
MusicBrainzXmlParser *parser = dynamic_cast<MusicBrainzXmlParser*>( _parser.data() );
- disconnect( parser, SIGNAL(done(ThreadWeaver::JobPointer)),
- this, SLOT(parsingDone(ThreadWeaver::JobPointer)) );
+ disconnect( parser, &MusicBrainzXmlParser::done,
+ this, &MusicBrainzFinder::parsingDone );
if( m_parsers.contains( parser ) && !m_parsers.value( parser ).isNull() )
{
// When m_parsers.value( parser ) is not empty, we've been parsing tracks.
Meta::TrackPtr trackPtr = m_parsers.value( parser );
bool found = false;
emit progressStep();
if( parser->type() == MusicBrainzXmlParser::TrackList &&
!parser->tracks.isEmpty() )
{
QVariantMap metadata = m_parsedMetadata.value( trackPtr );
QString scoreType = MusicBrainz::MUSICBRAINZ;
// Maximum allowed error in track length (seconds).
qlonglong lengthTolerance = 30;
// If there is no parsed metadata, a fingerprint lookup was done.
if( !m_parsedMetadata.contains( trackPtr ) )
{
scoreType = MusicBrainz::MUSICDNS;
lengthTolerance = 10;
}
lengthTolerance *= 1000;
foreach( QVariantMap track, parser->tracks.values() )
{
#define SIMILARITY( k ) similarity( metadata.value( k ).toString().toLower(), \
track.value( k ).toString().toLower() )
if( track.value( Meta::Field::SCORE ).toInt() < 50 )
continue;
QString title = track.value( Meta::Field::TITLE ).toString();
qlonglong length = track.value( Meta::Field::LENGTH ).toLongLong();
float score = 0;
int maxScore = 0;
/*
* We don't check for the entry to exist because a result without an
* artist deserves a bad score.
*/
if( metadata.contains( Meta::Field::ARTIST ) )
{
score += 6.0 * SIMILARITY( Meta::Field::ARTIST );
maxScore += 6;
}
if( track.contains( MusicBrainz::RELEASELIST ) )
{
// We try to send as many tracks as are the related releases.
foreach( const QString &releaseID,
track.value( MusicBrainz::RELEASELIST ).toStringList() )
{
/*
* The album artist could be parsed and inserted here, but since
* we have to parse each release group (only once), it's better to
* do it later, as we don't need it to calculate the score anyway.
* The release date has to be fetched in the second round
* (actually, it's the real reason behind the second round), as we
* want it to be the first release date of the release group:
* http://tickets.musicbrainz.org/browse/SEARCH-218
*/
QVariantMap release = parser->releases.value( releaseID );
float releaseScore = score;
int maxReleaseScore = maxScore;
track.insert( MusicBrainz::RELEASEID, releaseID );
track.insert( MusicBrainz::RELEASEGROUPID,
release.value( MusicBrainz::RELEASEGROUPID ) );
track.insert( Meta::Field::ALBUM,
release.value( Meta::Field::TITLE ) );
if( metadata.contains( Meta::Field::ALBUM ) )
{
releaseScore += 12.0 * SIMILARITY( Meta::Field::ALBUM );
maxReleaseScore += 12;
}
int trackCount = release.value( MusicBrainz::TRACKCOUNT ).toInt();
if( trackCount > 0 )
track.insert( MusicBrainz::TRACKCOUNT, trackCount );
else
track.remove( MusicBrainz::TRACKCOUNT );
/*
* A track can appear more than once in a release (on different
* discs, or in different versions), but we're going to send it
* multiple times only if it has different properties per
* iteration (yes, if the properties below are defined, at least
* some of them must be different by design). Otherwise, it would
* result in duplicated entries (which is bad for several
* reasons).
*/
foreach( const QVariant &info,
track.value( MusicBrainz::TRACKINFO ).toMap().value( releaseID ).toList() )
{
QVariantMap trackInfo = info.toMap();
float currentReleaseScore = releaseScore;
int maxCurrentReleaseScore = maxReleaseScore;
/*
* Track title and length can be different on different
* releases.
*/
QString currentTitle = trackInfo.value( Meta::Field::TITLE ).toString();
if( currentTitle.isEmpty() )
currentTitle = title;
track.insert( Meta::Field::TITLE, currentTitle );
// Same logic as for the artist tag above.
if( metadata.contains( Meta::Field::TITLE ) )
{
currentReleaseScore += 22.0 * SIMILARITY( Meta::Field::TITLE );
maxCurrentReleaseScore += 22;
}
qlonglong currentLength = trackInfo.value( Meta::Field::LENGTH ).toLongLong();
if( currentLength <= 0 )
currentLength = length;
if( currentLength > 0 )
track.insert( Meta::Field::LENGTH, currentLength );
else
track.remove( Meta::Field::LENGTH );
if( track.contains( Meta::Field::LENGTH ) )
{
currentReleaseScore += 8.0 * ( 1.0 - float( qMin( qAbs( trackPtr->length() -
track.value( Meta::Field::LENGTH ).toLongLong() ),
lengthTolerance ) ) / lengthTolerance );
maxCurrentReleaseScore += 8;
}
int currentDiscNumber = trackInfo.value( Meta::Field::DISCNUMBER ).toInt();
if( currentDiscNumber > 0 )
track.insert( Meta::Field::DISCNUMBER, currentDiscNumber );
else
track.remove( Meta::Field::DISCNUMBER );
if( metadata.contains( Meta::Field::DISCNUMBER ) &&
track.contains( Meta::Field::DISCNUMBER ) )
{
currentReleaseScore += ( metadata.value( Meta::Field::DISCNUMBER ).toInt() ==
track.value( Meta::Field::DISCNUMBER ).toInt() )? 6 : 0;
maxCurrentReleaseScore += 6;
}
else if( metadata.value( Meta::Field::DISCNUMBER ).toInt() !=
track.value( Meta::Field::DISCNUMBER ).toInt() )
/*
* Always prefer results with matching disc number,
* even when empty.
*/
currentReleaseScore -= 0.1;
int currentTrackNumber = trackInfo.value( Meta::Field::TRACKNUMBER ).toInt();
if( currentTrackNumber > 0 )
track.insert( Meta::Field::TRACKNUMBER, currentTrackNumber );
else
track.remove( Meta::Field::TRACKNUMBER );
if( metadata.contains( Meta::Field::TRACKNUMBER ) &&
track.contains( Meta::Field::TRACKNUMBER ) )
{
currentReleaseScore += ( metadata.value( Meta::Field::TRACKNUMBER ).toInt() ==
track.value( Meta::Field::TRACKNUMBER ).toInt() )? 6 : 0;
maxCurrentReleaseScore += 6;
}
else if( metadata.value( Meta::Field::TRACKNUMBER ).toInt() !=
track.value( Meta::Field::TRACKNUMBER ).toInt() )
/*
* Always prefer results with matching track number,
* even when empty.
*/
currentReleaseScore -= 0.1;
if( maxCurrentReleaseScore <= 0 )
continue;
float sim = currentReleaseScore / maxCurrentReleaseScore;
if( sim > MusicBrainz::MINSIMILARITY )
{
found = true;
track.insert( scoreType, sim );
sendTrack( trackPtr, track );
}
}
}
}
else
{
// A track without releases has been found (not too rare).
if( metadata.contains( Meta::Field::TITLE ) )
{
score += 22.0 * SIMILARITY( Meta::Field::TITLE );
maxScore += 22;
}
if( track.contains( Meta::Field::LENGTH ) )
{
score += 8.0 * ( 1.0 - float( qMin( qAbs( trackPtr->length() -
track.value( Meta::Field::LENGTH ).toLongLong() ),
lengthTolerance ) ) / lengthTolerance );
maxScore += 8;
}
if( maxScore <= 0 )
continue;
float sim = score / maxScore;
if( sim > MusicBrainz::MINSIMILARITY )
{
found = true;
track.insert( scoreType, sim );
sendTrack( trackPtr, track );
}
}
#undef SIMILARITY
}
m_parsedMetadata.remove( trackPtr );
}
else if( parser->type() != MusicBrainzXmlParser::TrackList )
debug() << "Invalid parsing result.";
/*
* Sending an empty result is important: it creates a disabled entry in the tagger
* to show that the track was not found (otherwise, it would pass unnoticed).
*/
if( !found )
sendTrack( trackPtr, QVariantMap() );
}
else if( parser->type() == MusicBrainzXmlParser::ReleaseGroup &&
!parser->releaseGroups.isEmpty() )
{
// Cache the release group and flush the queue of tracks.
QString releaseGroupID = parser->releaseGroups.keys().first();
mb_releaseGroups.insert( releaseGroupID,
parser->releaseGroups.value( releaseGroupID ) );
foreach( const TrackInfo &trackInfo, mb_queuedTracks.value( releaseGroupID ) )
sendTrack( trackInfo.first, trackInfo.second );
mb_queuedTracks.remove( releaseGroupID );
}
m_parsers.remove( parser );
parser->deleteLater();
checkDone();
}
void
MusicBrainzFinder::sendTrack( const Meta::TrackPtr &track, QVariantMap tags )
{
if( !tags.isEmpty() )
{
if( tags.contains( MusicBrainz::RELEASEGROUPID ) )
{
QString releaseGroupID = tags.value( MusicBrainz::RELEASEGROUPID ).toString();
if( mb_releaseGroups.contains( releaseGroupID ) )
{
QVariantMap releaseGroup = mb_releaseGroups.value( releaseGroupID );
if( releaseGroup.contains( Meta::Field::ARTIST ) )
tags.insert( Meta::Field::ALBUMARTIST,
releaseGroup.value( Meta::Field::ARTIST ) );
else if( tags.contains( Meta::Field::ARTIST ) )
tags.insert( Meta::Field::ALBUMARTIST,
tags.value( Meta::Field::ARTIST ) );
if( releaseGroup.contains( Meta::Field::YEAR ) )
tags.insert( Meta::Field::YEAR,
releaseGroup.value( Meta::Field::YEAR ) );
}
else
{
/*
* The tags reference a release group we don't know yet. Queue the track
* and fetch information about the release group.
*/
if( !mb_queuedTracks.contains( releaseGroupID ) )
{
QList<TrackInfo> trackList;
trackList.append( qMakePair( track, tags ) );
mb_queuedTracks.insert( releaseGroupID, trackList );
m_requests.prepend( qMakePair( Meta::TrackPtr(),
compileReleaseGroupRequest( releaseGroupID ) ) );
}
else
mb_queuedTracks[releaseGroupID].append( qMakePair( track, tags ) );
return;
}
}
// Clean metadata from unused fields.
tags.remove( Meta::Field::LENGTH );
tags.remove( Meta::Field::SCORE );
tags.remove( MusicBrainz::RELEASELIST );
tags.remove( MusicBrainz::TRACKINFO );
}
emit trackFound( track, tags );
}
void
MusicBrainzFinder::checkDone()
{
if( m_requests.isEmpty() && m_replies.isEmpty() && m_parsers.isEmpty() )
{
/*
* Empty the queue of tracks waiting for release group requests. If the requests
* fail (hint: network failure), remeaining queued tracks will silently disappear.
* Sending an empty result makes the user aware of the fact that the track will
* not be tagged.
*/
foreach( const QList<TrackInfo> &trackInfoList,
mb_queuedTracks.values() )
foreach( const TrackInfo &trackInfo, trackInfoList )
sendTrack( trackInfo.first, QVariantMap() );
debug() << "There is no queued request. Stopping timer.";
m_timer->stop();
emit done();
}
}
QVariantMap
MusicBrainzFinder::guessMetadata( const Meta::TrackPtr &track ) const
{
DEBUG_BLOCK
debug() << "Trying to guess metadata from filename:" << track->playableUrl().fileName();
QVariantMap metadata;
if( ( track->artist().isNull() || track->artist()->name().isEmpty() ) &&
( track->album().isNull() || track->album()->name().isEmpty() ) )
{
Meta::FieldHash tags = Meta::Tag::TagGuesser::guessTags( track->playableUrl().fileName() );
foreach( const quint64 &key, tags.keys() )
{
switch( key )
{
case Meta::valAlbum:
metadata.insert( Meta::Field::ALBUM, tags[key] );
break;
case Meta::valAlbumArtist:
metadata.insert( Meta::Field::ALBUMARTIST, tags[key] );
break;
case Meta::valArtist:
metadata.insert( Meta::Field::ARTIST, tags[key] );
break;
case Meta::valDiscNr:
metadata.insert( Meta::Field::DISCNUMBER, tags[key] );
break;
case Meta::valTitle:
metadata.insert( Meta::Field::TITLE, tags[key] );
break;
case Meta::valTrackNr:
metadata.insert( Meta::Field::TRACKNUMBER, tags[key] );
break;
}
}
}
else
metadata.insert( Meta::Field::TITLE, track->name() );
if( !track->album().isNull() && !track->album()->name().isEmpty() )
metadata.insert( Meta::Field::ALBUM, track->album()->name() );
if( !track->artist().isNull() && !track->artist()->name().isEmpty() )
metadata.insert( Meta::Field::ARTIST, track->artist()->name() );
if( track->discNumber() > 0 )
metadata.insert( Meta::Field::DISCNUMBER, track->discNumber() );
if( track->trackNumber() > 0 )
metadata.insert( Meta::Field::TRACKNUMBER, track->trackNumber() );
debug() << "Guessed track info:";
foreach( const QString &tag, metadata.keys() )
debug() << '\t' << tag << ":\t" << metadata.value( tag ).toString();
return metadata;
}
QNetworkRequest
MusicBrainzFinder::compileTrackRequest( const Meta::TrackPtr &track )
{
QString query;
QVariantMap metadata = guessMetadata( track );
// These characters are not considered in the query, and some of them can break it.
QRegExp unsafe( "[.,:;!?()\\[\\]{}\"]" );
// http://lucene.apache.org/core/old_versioned_docs/versions/3_4_0/queryparsersyntax.html#Escaping Special Characters
QRegExp special( "([+\\-!(){}\\[\\]\\^\"~*?:\\\\]|&&|\\|\\|)" );
QString escape( "\\\\1" );
// We use fuzzy search to bypass typos and small mistakes.
QRegExp endOfWord( "([a-zA-Z0-9])(\\s|$)" );
QString fuzzy( "\\1~\\2" );
/*
* The query results in:
* ("track~ title~"^20 track~ title~) AND artist:("artist~ name~"^2 artist~ name~) AND release:("album~ name~"^7 album~ name~)
* Phrases inside quotes are searched as is (and they're given precedence with the ^N
* - where N was found during tests), with the ~ having absolutely no effect (so we
* don't bother removing it). Words outside quotes have a OR logic: this can throw in
* some bad results, but helps a lot with very bad tagged tracks.
* We might be tempted to search also by qdur (quantized duration), but this has
* proved to exclude lots of good results.
*/
#define VALUE( k ) metadata.value( k ).toString().remove( unsafe ).replace( special, escape ).replace( endOfWord, fuzzy )
if( metadata.contains( Meta::Field::TITLE ) )
query += QString( "(\"%1\"^20 %1)" ).arg( VALUE( Meta::Field::TITLE ) );
if( metadata.contains( Meta::Field::ARTIST ) )
query += QString( " AND artist:(\"%1\"^2 %1)" ).arg( VALUE( Meta::Field::ARTIST ) );
if( metadata.contains( Meta::Field::ALBUM ) )
query += QString( " AND release:(\"%1\"^7 %1)" ).arg( VALUE( Meta::Field::ALBUM ) );
#undef VALUE
m_parsedMetadata.insert( track, metadata );
QUrl url;
url.setPath( mb_pathPrefix + "/recording" );
url.addQueryItem( "limit", "10" );
url.addQueryItem( "query", query );
return compileRequest( url );
}
QNetworkRequest
MusicBrainzFinder::compilePUIDRequest( const QString &puid )
{
QUrl url;
url.setPath( mb_pathPrefix + "/recording" );
url.addQueryItem( "query", "puid:" + puid );
return compileRequest( url );
}
QNetworkRequest
MusicBrainzFinder::compileReleaseGroupRequest( const QString &releaseGroupID )
{
QUrl url;
url.setPath( mb_pathPrefix + "/release-group/" + releaseGroupID );
url.addQueryItem( "inc", "artists" );
return compileRequest( url );
}
QNetworkRequest
MusicBrainzFinder::compileRequest( QUrl &url )
{
url.setScheme( "http" );
url.setHost( mb_host );
url.setPort( mb_port );
QNetworkRequest req( url );
req.setRawHeader( "Accept", "application/xml");
req.setRawHeader( "Connection", "Keep-Alive" );
req.setRawHeader( "User-Agent" , "Amarok" );
if( !m_timer->isActive() )
m_timer->start();
return req;
}
diff --git a/src/musicbrainz/MusicBrainzTagsView.cpp b/src/musicbrainz/MusicBrainzTagsView.cpp
index 0c826fbb80..4c7efb8496 100644
--- a/src/musicbrainz/MusicBrainzTagsView.cpp
+++ b/src/musicbrainz/MusicBrainzTagsView.cpp
@@ -1,237 +1,237 @@
/****************************************************************************************
* Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> *
* Copyright (c) 2013 Alberto Villa <avilla@FreeBSD.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MusicBrainzTagsView"
#include "MusicBrainzTagsView.h"
#include "core/support/Debug.h"
#include "MusicBrainzTagsModel.h"
#include <KActionMenu>
#include <KLocalizedString>
#include <KStandardDirs>
#include <QContextMenuEvent>
#include <QDesktopServices>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QUrl>
MusicBrainzTagsView::MusicBrainzTagsView( QWidget *parent )
: QTreeView( parent )
{
m_artistIcon = QIcon::fromTheme( KStandardDirs::locate( "data", "amarok/images/mb_aicon.png" ) );
m_releaseIcon = QIcon::fromTheme( KStandardDirs::locate( "data", "amarok/images/mb_licon.png" ) );
m_trackIcon = QIcon::fromTheme( KStandardDirs::locate( "data", "amarok/images/mb_ticon.png" ) );
}
MusicBrainzTagsModel *
MusicBrainzTagsView::sourceModel() const
{
QSortFilterProxyModel *model = qobject_cast<QSortFilterProxyModel *>( this->model() );
if( !model )
return 0;
MusicBrainzTagsModel *sourceModel = qobject_cast<MusicBrainzTagsModel *>( model->sourceModel() );
return sourceModel;
}
void
MusicBrainzTagsView::contextMenuEvent( QContextMenuEvent *event )
{
QModelIndex index = indexAt( event->pos() );
if( !index.isValid() || !index.internalPointer() )
{
event->ignore();
return;
}
QAbstractItemModel *model = this->model();
if( !model )
return;
if( ~index.flags() & Qt::ItemIsUserCheckable )
{
event->ignore();
return;
}
QMenu *menu = new QMenu( this );
QList<QAction *> actions;
if( model->rowCount() > 1 && !index.data( MusicBrainzTagsModel::ReleasesRole ).isNull() )
{
QAction *action = new QAction( QIcon::fromTheme( "filename-album-amarok" ),
i18n( "Choose Best Matches from This Album" ), menu );
- connect( action, SIGNAL(triggered()), SLOT(chooseBestMatchesFromRelease()) );
+ connect( action, &QAction::triggered, this, &MusicBrainzTagsView::chooseBestMatchesFromRelease );
menu->addAction( action );
menu->addSeparator();
}
QVariantMap artists;
if( !index.data( MusicBrainzTagsModel::ArtistsRole ).toList().isEmpty() )
artists = index.data( MusicBrainzTagsModel::ArtistsRole ).toList().first().toMap();
if( !artists.isEmpty() )
{
KActionMenu *action = new KActionMenu( m_artistIcon, i18n( "Go to Artist Page" ), menu );
if( artists.size() > 1 )
{
foreach( const QVariant &id, artists.keys() )
{
QAction *subAction = new QAction( artists.value( id.toString() ).toString(), action );
subAction->setData( id );
- connect( subAction, SIGNAL(triggered()), SLOT(openArtistPage()) );
+ connect( subAction, &QAction::triggered, this, &MusicBrainzTagsView::openArtistPage );
action->addAction( subAction );
}
}
else
{
action->setData( artists.keys().first() );
- connect( action, SIGNAL(triggered()), SLOT(openArtistPage()) );
+ connect( action, &QAction::triggered, this, &MusicBrainzTagsView::openArtistPage );
}
actions << action;
}
if( !index.data( MusicBrainzTagsModel::ReleasesRole ).toList().isEmpty() )
{
QAction *action = new QAction( m_releaseIcon, i18n( "Go to Album Page" ), menu );
- connect( action, SIGNAL(triggered()), SLOT(openReleasePage()) );
+ connect( action, &QAction::triggered, this, &MusicBrainzTagsView::openReleasePage );
actions << action;
}
if( !index.data( MusicBrainzTagsModel::TracksRole ).toList().isEmpty() )
{
QAction *action = new QAction( m_trackIcon, i18n( "Go to Track Page" ), menu );
- connect( action, SIGNAL(triggered()), SLOT(openTrackPage()) );
+ connect( action, &QAction::triggered, this, &MusicBrainzTagsView::openTrackPage );
actions << action;
}
if( actions.isEmpty() )
{
delete menu;
event->ignore();
return;
}
menu->addActions( actions );
menu->exec( event->globalPos() );
event->accept();
}
void
MusicBrainzTagsView::collapseChosen()
{
QAbstractItemModel *model = this->model();
if( !model )
return;
for( int i = 0; i < model->rowCount(); i++ )
{
QModelIndex index = model->index( i, 0 );
if( index.isValid() &&
index.data( MusicBrainzTagsModel::ChosenStateRole ) == MusicBrainzTagsModel::Chosen )
collapse( index );
}
}
void
MusicBrainzTagsView::expandUnchosen()
{
QAbstractItemModel *model = this->model();
if( !model )
return;
for( int i = 0; i < model->rowCount(); i++ )
{
QModelIndex index = model->index( i, 0 );
if( index.isValid() &&
index.data( MusicBrainzTagsModel::ChosenStateRole ) == MusicBrainzTagsModel::Unchosen )
expand( index );
}
}
void
MusicBrainzTagsView::chooseBestMatchesFromRelease() const
{
QModelIndex index = selectedIndexes().first();
if( !index.isValid() || !index.internalPointer() )
return;
MusicBrainzTagsModel *sourceModel = this->sourceModel();
if( !sourceModel )
return;
QStringList releases = index.data( MusicBrainzTagsModel::ReleasesRole ).toStringList();
if( releases.isEmpty() )
return;
sourceModel->chooseBestMatchesFromRelease( releases );
}
void
MusicBrainzTagsView::openArtistPage() const
{
QModelIndex index = selectedIndexes().first();
if( !index.isValid() || !index.internalPointer() )
return;
QAction *action = qobject_cast<QAction *>( sender() );
if( !action )
return;
QString artistID = action->data().toString();
if( artistID.isEmpty() )
return;
QString url = QString( "http://musicbrainz.org/artist/%1.html" ).arg( artistID );
QDesktopServices::openUrl( QUrl::fromUserInput(url) );
}
void
MusicBrainzTagsView::openReleasePage() const
{
QModelIndex index = selectedIndexes().first();
if( !index.isValid() || !index.internalPointer() )
return;
QString releaseID = index.data( MusicBrainzTagsModel::ReleasesRole ).toStringList().first();
if( releaseID.isEmpty() )
return;
QString url = QString( "http://musicbrainz.org/release/%1.html" ).arg( releaseID );
QDesktopServices::openUrl( QUrl::fromUserInput(url) );
}
void
MusicBrainzTagsView::openTrackPage() const
{
QModelIndex index = selectedIndexes().first();
if( !index.isValid() || !index.internalPointer() )
return;
QString trackID = index.data( MusicBrainzTagsModel::TracksRole ).toStringList().first();
if( trackID.isEmpty() )
return;
QString url = QString( "http://musicbrainz.org/recording/%1.html" ).arg( trackID );
QDesktopServices::openUrl( QUrl::fromUserInput(url) );
}
diff --git a/src/musicbrainz/MusicDNSFinder.cpp b/src/musicbrainz/MusicDNSFinder.cpp
index 01d7df7167..d2da27bb7e 100644
--- a/src/musicbrainz/MusicDNSFinder.cpp
+++ b/src/musicbrainz/MusicDNSFinder.cpp
@@ -1,221 +1,221 @@
/****************************************************************************************
* Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MusicDNSFinder"
#include "MusicDNSFinder.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include <ThreadWeaver/Queue>
#include <QNetworkAccessManager>
MusicDNSFinder::MusicDNSFinder( QObject *parent,
const QString &host, const int port, const QString &pathPrefix,
const QString &clietnId, const QString &clientVersion )
: QObject( parent )
, mdns_host( host )
, mdns_port( port )
, mdns_pathPrefix( pathPrefix )
, mdns_clientId( clietnId )
, mdns_clientVersion( clientVersion )
{
DEBUG_BLOCK
debug() << "Initiating MusicDNS search:";
debug() << "\tHost:\t\t" << mdns_host;
debug() << "\tPort:\t\t" << mdns_port;
debug() << "\tPath Prefix:\t" << mdns_pathPrefix;
debug() << "\tClient ID:\t" << mdns_clientId;
debug() << "\tClient version:\t" << mdns_clientVersion;
net = The::networkAccessManager();
_timer = new QTimer( this );
_timer->setInterval( 1000 );
decodingComplete = false;
- connect( net, SIGNAL(finished(QNetworkReply*)), SLOT(gotReply(QNetworkReply*)) );
- connect( _timer, SIGNAL(timeout()), SLOT(sendNewRequest()) );
+ connect( net, &NetworkAccessManagerProxy::finished, this, &MusicDNSFinder::gotReply );
+ connect( _timer, &QTimer::timeout, this, &MusicDNSFinder::sendNewRequest );
}
void
MusicDNSFinder::run( const Meta::TrackList &tracks )
{
MusicDNSAudioDecoder *decoder = new MusicDNSAudioDecoder( tracks );
- connect( decoder, SIGNAL(trackDecoded(Meta::TrackPtr,QString)),
- SLOT(trackDecoded(Meta::TrackPtr,QString)) );
- connect( decoder, SIGNAL(done(ThreadWeaver::JobPointer)),
- SLOT(decodingDone(ThreadWeaver::JobPointer)) );
+ connect( decoder, &MusicDNSAudioDecoder::trackDecoded,
+ this, &MusicDNSFinder::trackDecoded );
+ connect( decoder, &MusicDNSAudioDecoder::done,
+ this, &MusicDNSFinder::decodingDone );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(decoder) );
_timer->start();
}
void MusicDNSFinder::sendNewRequest()
{
DEBUG_BLOCK
if( m_requests.isEmpty() )
{
checkDone();
return;
}
QPair < Meta::TrackPtr, QNetworkRequest > req = m_requests.takeFirst();
QNetworkReply *reply = net->get( req.second );
m_replyes.insert( reply, req.first );
- connect( reply, SIGNAL(error(QNetworkReply::NetworkError)),
- this, SLOT(replyError(QNetworkReply::NetworkError)) );
+ connect( reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
+ this, &MusicDNSFinder::replyError );
debug() << "Request sent: " << req.second.url().toString();
}
void
MusicDNSFinder::gotReply( QNetworkReply *reply )
{
DEBUG_BLOCK
if( reply->error() == QNetworkReply::NoError && m_replyes.contains( reply ) )
{
QString document( reply->readAll() );
MusicDNSXmlParser *parser = new MusicDNSXmlParser( document );
if( !m_replyes.value( reply ).isNull() )
m_parsers.insert( parser, m_replyes.value( reply ) );
- connect( parser, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(parsingDone(ThreadWeaver::JobPointer)) );
+ connect( parser, &MusicDNSXmlParser::done, this, &MusicDNSFinder::parsingDone );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(parser) );
}
m_replyes.remove( reply );
reply->deleteLater();
checkDone();
}
void
MusicDNSFinder::replyError( QNetworkReply::NetworkError code )
{
DEBUG_BLOCK
QNetworkReply *reply = qobject_cast< QNetworkReply * >( sender() );
if( !reply )
return;
if( !m_replyes.contains( reply ) || code == QNetworkReply::NoError )
return;
- disconnect( reply, SIGNAL(error(QNetworkReply::NetworkError)),
- this, SLOT(replyError(QNetworkReply::NetworkError)) );
+ disconnect( reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
+ this, &MusicDNSFinder::replyError );
debug() << "Error occurred during network request: " << reply->errorString();
m_replyes.remove( reply );
reply->deleteLater();
checkDone();
}
void
MusicDNSFinder::parsingDone( ThreadWeaver::JobPointer _parser )
{
DEBUG_BLOCK
MusicDNSXmlParser *parser = dynamic_cast< MusicDNSXmlParser * >( _parser.data() );
- disconnect( parser, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(parsingDone(ThreadWeaver::JobPointer)) );
+ disconnect( parser, &MusicDNSXmlParser::done, this, &MusicDNSFinder::parsingDone );
if( m_parsers.contains( parser ) )
{
bool found = false;
foreach( QString PUID, parser->puid() )
if( PUID != "00000000-0000-0000-0000-000000000000" )
{
found = true;
emit trackFound( m_parsers.value( parser ), PUID );
break;
}
if( !found )
emit progressStep();
m_parsers.remove( parser );
}
parser->deleteLater();
checkDone();
}
void
MusicDNSFinder::trackDecoded( const Meta::TrackPtr track, const QString fingerprint )
{
DEBUG_BLOCK
if( fingerprint.isEmpty() )
return;
m_requests.append( qMakePair( track, compileRequest( fingerprint, track ) ) );
}
void
MusicDNSFinder::decodingDone( ThreadWeaver::JobPointer _decoder )
{
DEBUG_BLOCK
MusicDNSAudioDecoder *decoder = dynamic_cast<MusicDNSAudioDecoder*>(_decoder.data());
- disconnect( decoder, SIGNAL(trackDecoded(Meta::TrackPtr,QString)),
- this, SLOT(trackDecoded(Meta::TrackPtr,QString)) );
- disconnect( decoder, SIGNAL(done(ThreadWeaver::JobPointer)),
- this, SLOT(decodingDone(ThreadWeaver::JobPointer)) );
+ disconnect( decoder, &MusicDNSAudioDecoder::trackDecoded,
+ this, &MusicDNSFinder::trackDecoded );
+ disconnect( decoder, &MusicDNSAudioDecoder::done,
+ this, &MusicDNSFinder::decodingDone );
decoder->deleteLater();
decodingComplete = true;
checkDone();
}
void
MusicDNSFinder::checkDone()
{
if( m_parsers.isEmpty() && m_requests.isEmpty() && m_replyes.isEmpty() && decodingComplete )
{
debug() << "There is no any queued requests. Stopping timer.";
_timer->stop();
emit done();
}
}
QNetworkRequest
MusicDNSFinder::compileRequest( const QString &fingerprint, const Meta::TrackPtr track )
{
QUrl url;
url.setScheme( "http" );
url.setHost( mdns_host );
url.setPort( mdns_port );
url.setPath( mdns_pathPrefix+"/track/" );
url.addQueryItem( "gnr", "" );
url.addQueryItem( "art", track->artist().isNull()?"":track->artist()->name() );
url.addQueryItem( "rmd", "0" );
url.addQueryItem( "cid", mdns_clientId );
url.addQueryItem( "alb", track->album().isNull()?"":track->album()->name() );
url.addQueryItem( "fmt", "" );
url.addQueryItem( "brt", QString::number( track->bitrate() ) );
url.addQueryItem( "cvr", mdns_clientVersion );
url.addQueryItem( "fpt", fingerprint );
url.addQueryItem( "ttl", track->name().isNull()?track->playableUrl().fileName().remove(
QRegExp( "^.*(\\.+(?:\\w{2,5}))$" ) ):track->name() );
url.addQueryItem( "tnm", "" );
url.addQueryItem( "lkt", "" );
url.addQueryItem( "dur", QString::number( track->length() ) );
url.addQueryItem( "yrr", "" );
QNetworkRequest req( url );
req.setRawHeader( "User-Agent" , "Amarok" );
req.setRawHeader( "Connection", "Keep-Alive" );
if( !_timer->isActive() )
_timer->start();
return req;
}
diff --git a/src/network/NetworkAccessManagerProxy.cpp b/src/network/NetworkAccessManagerProxy.cpp
index 26de156821..b5306f1d33 100644
--- a/src/network/NetworkAccessManagerProxy.cpp
+++ b/src/network/NetworkAccessManagerProxy.cpp
@@ -1,317 +1,324 @@
/****************************************************************************************
* Copyright (c) 2007 Trolltech ASA <copyright@trolltech.com> *
* Copyright (c) 2008 Urs Wolfer <uwolfer@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "NetworkAccessManagerProxy"
#include "NetworkAccessManagerProxy.h"
#ifdef DEBUG_BUILD_TYPE
#include "NetworkAccessViewer.h"
#endif // DEBUG_BUILD_TYPE
#include "core/support/Debug.h"
#include "Version.h"
#include <KProtocolManager>
#include <QMetaMethod>
#include <QNetworkReply>
#include <QWeakPointer>
NetworkAccessManagerProxy *NetworkAccessManagerProxy::s_instance = 0;
NetworkAccessManagerProxy *NetworkAccessManagerProxy::instance()
{
if( s_instance == 0 )
s_instance = new NetworkAccessManagerProxy();
return s_instance;
}
void NetworkAccessManagerProxy::destroy()
{
if( s_instance )
{
delete s_instance;
s_instance = 0;
}
}
class NetworkAccessManagerProxy::NetworkAccessManagerProxyPrivate
{
public:
NetworkAccessManagerProxyPrivate( NetworkAccessManagerProxy *parent )
: userAgent( QString( "Amarok/" ) + AMAROK_VERSION )
#ifdef DEBUG_BUILD_TYPE
, viewer( 0 )
#endif // DEBUG_BUILD_TYPE
, q_ptr( parent )
{}
~NetworkAccessManagerProxyPrivate() {}
void _replyFinished()
{
Q_Q( NetworkAccessManagerProxy );
QNetworkReply *reply = static_cast<QNetworkReply*>( q->sender() );
QUrl url = reply->request().url();
QList<CallBackData*> callbacks = urlMap.values( url );
urlMap.remove( url );
QByteArray data = reply->readAll();
data.detach(); // detach so the bytes are not deleted before methods are invoked
foreach( const CallBackData *cb, callbacks )
{
// There may have been a redirect.
QUrl redirectUrl = q->getRedirectUrl( reply );
// Check if there's no redirect.
if( redirectUrl.isEmpty() )
{
QByteArray sig = QMetaObject::normalizedSignature( cb->method );
sig.remove( 0, 1 ); // remove first char, which is the member code (see qobjectdefs.h)
// and let Qt's meta object system handle the rest.
if( cb->receiver )
{
bool success( false );
const QMetaObject *mo = cb->receiver.data()->metaObject();
int methodIndex = mo->indexOfSlot( sig );
if( methodIndex != -1 )
{
Error err = { reply->error(), reply->errorString() };
QMetaMethod method = mo->method( methodIndex );
success = method.invoke( cb->receiver.data(),
cb->type,
Q_ARG( QUrl, reply->request().url() ),
Q_ARG( QByteArray, data ),
Q_ARG( NetworkAccessManagerProxy::Error, err ) );
}
if( !success )
{
debug() << QString( "Failed to invoke method %1 of %2" )
.arg( QString(sig) ).arg( mo->className() );
}
}
}
else
{
debug() << "the server is redirecting the request to: " << redirectUrl;
// Let's try to fetch the data again, but this time from the new url.
QNetworkReply *newReply = q->getData( redirectUrl, cb->receiver.data(), cb->method, cb->type );
- emit q->requestRedirected( url, redirectUrl );
- emit q->requestRedirected( reply, newReply );
+ emit q->requestRedirectedUrl( url, redirectUrl );
+ emit q->requestRedirectedReply( reply, newReply );
}
}
qDeleteAll( callbacks );
reply->deleteLater();
}
class CallBackData
{
public:
CallBackData( QObject *rec, QNetworkReply *rep, const char *met, Qt::ConnectionType t )
: receiver( rec )
, reply( rep )
, method( met )
, type( t )
{}
~CallBackData()
{
if( reply )
reply.data()->deleteLater();
}
QWeakPointer<QObject> receiver;
QWeakPointer<QNetworkReply> reply;
const char *method;
Qt::ConnectionType type;
};
QMultiHash<QUrl, CallBackData*> urlMap;
QString userAgent;
#ifdef DEBUG_BUILD_TYPE
NetworkAccessViewer *viewer;
#endif // DEBUG_BUILD_TYPE
private:
NetworkAccessManagerProxy *const q_ptr;
Q_DECLARE_PUBLIC( NetworkAccessManagerProxy )
};
NetworkAccessManagerProxy::NetworkAccessManagerProxy( QObject *parent )
: KIO::Integration::AccessManager( parent )
, d( new NetworkAccessManagerProxyPrivate( this ) )
{
setCache(0); // disable QtWebKit cache to just use KIO one..
qRegisterMetaType<NetworkAccessManagerProxy::Error>();
}
NetworkAccessManagerProxy::~NetworkAccessManagerProxy()
{
delete d;
s_instance = 0;
}
#ifdef DEBUG_BUILD_TYPE
NetworkAccessViewer *
NetworkAccessManagerProxy::networkAccessViewer()
{
return d->viewer;
}
void
NetworkAccessManagerProxy::setNetworkAccessViewer( NetworkAccessViewer *viewer )
{
if( viewer )
{
if( d->viewer )
delete d->viewer;
d->viewer = viewer;
}
}
#endif // DEBUG_BUILD_TYPE
QNetworkReply *
NetworkAccessManagerProxy::getData( const QUrl &url, QObject *receiver, const char *method,
Qt::ConnectionType type )
{
if( !url.isValid() )
{
const QMetaObject *mo = receiver->metaObject();
debug() << QString( "Error: URL '%1' is invalid (from %2)" ).arg( url.url() ).arg( mo->className() );
return 0;
}
QNetworkReply *r = get( QNetworkRequest(url) );
typedef NetworkAccessManagerProxyPrivate::CallBackData PrivateCallBackData;
PrivateCallBackData *cbm = new PrivateCallBackData( receiver, r, method, type );
d->urlMap.insert( url, cbm );
- connect( r, SIGNAL(finished()), this, SLOT(_replyFinished()), type );
+ connect( r, &QNetworkReply::finished, this, &NetworkAccessManagerProxy::replyFinished, type );
return r;
}
int
NetworkAccessManagerProxy::abortGet( const QList<QUrl> &urls )
{
int removed = 0;
const QSet<QUrl> &urlSet = urls.toSet();
foreach( const QUrl &url, urlSet )
removed += abortGet( url );
return removed;
}
int
NetworkAccessManagerProxy::abortGet( const QUrl &url )
{
if( !d->urlMap.contains(url) )
return 0;
qDeleteAll( d->urlMap.values( url ) );
int removed = d->urlMap.remove( url );
return removed;
}
QUrl
NetworkAccessManagerProxy::getRedirectUrl( QNetworkReply *reply )
{
QUrl targetUrl;
// Get the original URL.
QUrl originalUrl = reply->request().url();
// Get the redirect attribute.
QVariant redirectAttribute = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
// Get the redirect URL from the attribute.
QUrl redirectUrl = QUrl( redirectAttribute.toUrl() );
// If the redirect URL is valid and if it differs from the original
// URL then we return the redirect URL. Otherwise an empty URL will
// be returned.
if( !redirectUrl.isEmpty() && redirectUrl != originalUrl )
{
targetUrl = redirectUrl;
}
return targetUrl;
}
void
NetworkAccessManagerProxy::slotError( QObject *obj )
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>( obj );
if( !reply )
return;
QUrl url = reply->request().url();
d->urlMap.remove( url );
reply->deleteLater();
}
QNetworkReply *
NetworkAccessManagerProxy::createRequest( Operation op, const QNetworkRequest &req, QIODevice *outgoingData )
{
QNetworkRequest request = req;
request.setAttribute( QNetworkRequest::HttpPipeliningAllowedAttribute, true );
if ( request.hasRawHeader( "User-Agent" ) )
request.setRawHeader( "User-Agent", d->userAgent.toLocal8Bit() + ' ' + request.rawHeader( "User-Agent" ) );
else
request.setRawHeader( "User-Agent", d->userAgent.toLocal8Bit() );
KIO::CacheControl cc = KProtocolManager::cacheControl();
switch (cc)
{
case KIO::CC_CacheOnly: // Fail request if not in cache.
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
break;
case KIO::CC_Refresh: // Always validate cached entry with remote site.
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork);
break;
case KIO::CC_Reload: // Always fetch from remote site
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
break;
case KIO::CC_Cache: // Use cached entry if available.
case KIO::CC_Verify: // Validate cached entry with remote site if expired.
default:
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
break;
}
QNetworkReply *reply = KIO::Integration::AccessManager::createRequest( op, request, outgoingData );
#ifdef DEBUG_BUILD_TYPE
if( d->viewer )
d->viewer->addRequest( op, request, outgoingData, reply );
#endif // DEBUG_BUILD_TYPE
return reply;
}
+void
+NetworkAccessManagerProxy::replyFinished()
+{
+ d->_replyFinished();
+}
+
+
namespace The
{
NetworkAccessManagerProxy *networkAccessManager()
{
return NetworkAccessManagerProxy::instance();
}
}
#include "moc_NetworkAccessManagerProxy.cpp"
diff --git a/src/network/NetworkAccessManagerProxy.h b/src/network/NetworkAccessManagerProxy.h
index 8a2e80a23e..8c60c247b6 100644
--- a/src/network/NetworkAccessManagerProxy.h
+++ b/src/network/NetworkAccessManagerProxy.h
@@ -1,112 +1,112 @@
/****************************************************************************************
* Copyright (c) 2008 Urs Wolfer <uwolfer@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_NETWORKACCESSMANAGERPROXY
#define AMAROK_NETWORKACCESSMANAGERPROXY
#include "amarok_export.h"
#include <config.h>
#include <KIO/AccessManager>
#include <QUrl>
#include <QNetworkReply>
class NetworkAccessManagerProxy;
#ifdef DEBUG_BUILD_TYPE
class NetworkAccessViewer;
#endif // DEBUG_BUILD_TYPE
namespace The
{
AMAROK_EXPORT NetworkAccessManagerProxy *networkAccessManager();
}
class AMAROK_EXPORT NetworkAccessManagerProxy : public KIO::Integration::AccessManager
{
Q_OBJECT
public:
static NetworkAccessManagerProxy *instance();
static void destroy();
virtual ~NetworkAccessManagerProxy();
struct Error
{
QNetworkReply::NetworkError code;
QString description;
};
/**
* Gets the contents of the target @p url. It is a convenience wrapper
* around QNetworkAccessManager::get() where the user supplies a
* slot @p method to be called when the content is retrieved.
* NOTE: On redirects requestRedirected is emitted.
*
* @param url the url to get the content from.
* @param receiver the receiver object to call @p method on.
* @param method the method to call when content is retrieved.
* @param type the #Qt::ConnectionType used for calling the @p method.
* @return a QNetworkReply object for custom monitoring.
*/
QNetworkReply *getData( const QUrl &url, QObject *receiver, const char *method,
Qt::ConnectionType type = Qt::AutoConnection );
int abortGet( const QUrl &url );
int abortGet( const QList<QUrl> &urls );
/**
* Gets the URL to which a server redirects the request.
* An empty QUrl will be returned if the request was not redirected.
*
* @param reply The QNetworkReply which contains all information about
* the reply from the server.
*
* @return The URL to which the server redirected the request or an empty
* URL if there was no redirect.
*/
QUrl getRedirectUrl( QNetworkReply *reply );
#ifdef DEBUG_BUILD_TYPE
NetworkAccessViewer *networkAccessViewer();
void setNetworkAccessViewer( NetworkAccessViewer *viewer );
#endif // DEBUG_BUILD_TYPE
Q_SIGNALS:
- void requestRedirected( const QUrl &sourceUrl, const QUrl &targetUrl );
- void requestRedirected( QNetworkReply* oldReply, QNetworkReply *newReply );
+ void requestRedirectedUrl( const QUrl &sourceUrl, const QUrl &targetUrl );
+ void requestRedirectedReply( QNetworkReply* oldReply, QNetworkReply *newReply );
public Q_SLOTS:
void slotError( QObject *reply );
protected:
virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &req,
QIODevice *outgoingData = 0);
private:
NetworkAccessManagerProxy( QObject *parent = 0 );
+ void replyFinished();
static NetworkAccessManagerProxy *s_instance;
class NetworkAccessManagerProxyPrivate;
NetworkAccessManagerProxyPrivate* const d;
friend class NetworkAccessManagerProxyPrivate;
Q_DISABLE_COPY( NetworkAccessManagerProxy )
- Q_PRIVATE_SLOT( d, void _replyFinished() )
};
Q_DECLARE_METATYPE( NetworkAccessManagerProxy::Error )
#endif // AMAROK_NETWORKACCESSMANAGERPROXY
diff --git a/src/network/NetworkAccessViewer.cpp b/src/network/NetworkAccessViewer.cpp
index 327a2048d9..38f50ed038 100644
--- a/src/network/NetworkAccessViewer.cpp
+++ b/src/network/NetworkAccessViewer.cpp
@@ -1,173 +1,175 @@
/***************************************************************************
* Copyright (C) 2009, 2010 by Richard J. Moore <rich@kde.org> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "NetworkAccessViewer.h"
#include "core/support/Debug.h"
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSignalMapper>
NetworkAccessViewer::NetworkAccessViewer( QWidget *parent )
: QObject( parent )
{
dialog = new QDialog( parent );
networkRequestsDialog = new Ui::NetworkRequestsDialog;
networkRequestsDialog->setupUi(dialog);
mapper = new QSignalMapper(this);
- connect( mapper, SIGNAL(mapped(QObject*)), SLOT(requestFinished(QObject*)) );
+ connect( mapper, QOverload<QObject*>::of(&QSignalMapper::mapped),
+ this, &NetworkAccessViewer::requestFinished );
- connect( networkRequestsDialog->requestList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(showItemDetails(QTreeWidgetItem*)) );
- connect( networkRequestsDialog->clearButton, SIGNAL(clicked()), SLOT(clear()) );
+ connect( networkRequestsDialog->requestList, &QTreeWidget::currentItemChanged, this, &NetworkAccessViewer::showItemDetails );
+ connect( networkRequestsDialog->clearButton, &QPushButton::clicked, this, &NetworkAccessViewer::clear );
}
NetworkAccessViewer::~NetworkAccessViewer()
{
delete networkRequestsDialog;
}
void NetworkAccessViewer::addRequest( QNetworkAccessManager::Operation op, const QNetworkRequest&req, QIODevice *outgoingData, QNetworkReply *reply )
{
Q_UNUSED( outgoingData )
// Add to list of requests
QStringList cols;
switch( op ) {
case QNetworkAccessManager::HeadOperation:
cols << QString::fromLatin1("HEAD");
break;
case QNetworkAccessManager::GetOperation:
cols << QString::fromLatin1("GET");
break;
case QNetworkAccessManager::PutOperation:
cols << QString::fromLatin1("PUT");
break;
case QNetworkAccessManager::PostOperation:
cols << QString::fromLatin1("POST");
break;
default:
qWarning() << "Unknown network operation";
}
cols << req.url().toString();
cols << tr("Pending");
QTreeWidgetItem *item = new QTreeWidgetItem( cols );
networkRequestsDialog->requestList->addTopLevelItem( item );
networkRequestsDialog->requestList->scrollToItem( item );
// Add to maps
requestMap.insert( reply, req );
itemMap.insert( reply, item );
itemRequestMap.insert( item, req );
mapper->setMapping( reply, reply );
- connect( reply, SIGNAL(finished()), mapper, SLOT(map()) );
+ connect( reply, &QNetworkReply::finished,
+ mapper, QOverload<>::of(&QSignalMapper::map) );
}
void NetworkAccessViewer::show()
{
dialog->show();
}
void NetworkAccessViewer::hide()
{
dialog->hide();
}
void NetworkAccessViewer::clear()
{
requestMap.clear();
itemMap.clear();
itemReplyMap.clear();
itemRequestMap.clear();
networkRequestsDialog->requestList->clear();
networkRequestsDialog->requestDetails->clear();
networkRequestsDialog->responseDetails->clear();
}
void NetworkAccessViewer::requestFinished( QObject *replyObject )
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>( replyObject );
if( !reply )
{
warning() << __PRETTY_FUNCTION__ << "Failed to downcast reply";
return;
}
QTreeWidgetItem *item = itemMap.value( reply );
if( !item )
{
warning() << __PRETTY_FUNCTION__ << "null item";
return;
}
// Record the reply headers
QList<QByteArray> headerValues;
foreach( const QByteArray &header, reply->rawHeaderList() ) {
headerValues += reply->rawHeader( header );
}
QPair< QList<QByteArray>, QList<QByteArray> > replyHeaders;
replyHeaders.first = reply->rawHeaderList();
replyHeaders.second = headerValues;
itemReplyMap[item] = replyHeaders;
// Display the request
int status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
QString reason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
item->setText( 2, tr("%1 %2").arg(status).arg(reason) );
QString length = reply->header( QNetworkRequest::ContentLengthHeader ).toString();
item->setText( 3, length );
QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
item->setText( 4, contentType );
if ( status == 302 ) {
QUrl target = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl();
item->setText( 5, tr("Redirect: %1").arg( target.toString() ) );
}
}
void NetworkAccessViewer::showItemDetails( QTreeWidgetItem *item )
{
// Show request details
QTreeWidget *reqTree = networkRequestsDialog->requestDetails;
reqTree->clear();
QNetworkRequest req = itemRequestMap[item];
QByteArray header;
foreach( header, req.rawHeaderList() ) {
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText( 0, QString::fromLatin1( header ) );
item->setText( 1, QString::fromLatin1( req.rawHeader( header ) ) );
reqTree->addTopLevelItem( item );
}
// Show reply headers
QTreeWidget *respTree = networkRequestsDialog->responseDetails;
respTree->clear();
QPair< QList<QByteArray>, QList<QByteArray> > replyHeaders = itemReplyMap[item];
for ( int i = 0; i < replyHeaders.first.count(); i++ ) {
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText( 0, QString::fromLatin1( replyHeaders.first[i] ) );
item->setText( 1, QString::fromLatin1( replyHeaders.second[i] ) );
respTree->addTopLevelItem( item );
}
}
diff --git a/src/playback/DelayedDoers.cpp b/src/playback/DelayedDoers.cpp
index 3ced64e5d2..79a705d257 100644
--- a/src/playback/DelayedDoers.cpp
+++ b/src/playback/DelayedDoers.cpp
@@ -1,92 +1,93 @@
/***************************************************************************************
* Copyright (c) 2013 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DelayedDoers.h"
#include "core/support/Debug.h"
#include <Phonon/MediaController>
#include <Phonon/MediaObject>
DelayedDoer::DelayedDoer( Phonon::MediaObject *mediaObject,
const QSet<Phonon::State> &applicableStates )
: m_mediaObject( mediaObject )
, m_applicableStates( applicableStates )
{
Q_ASSERT( mediaObject );
- connect( mediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)),
- SLOT(slotStateChanged(Phonon::State)) );
- connect( mediaObject, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
+ connect( mediaObject, &Phonon::MediaObject::stateChanged,
+ this, &DelayedDoer::slotStateChanged );
+ connect( mediaObject, &Phonon::MediaObject::destroyed,
+ this, &DelayedDoer::deleteLater );
}
void
DelayedDoer::slotStateChanged( Phonon::State newState )
{
if( m_applicableStates.contains( newState ) )
{
// don't let be called twice, deleteLater() may fire really LATER
disconnect( m_mediaObject, 0, this, 0 );
performAction();
deleteLater();
}
else
debug() << __PRETTY_FUNCTION__ << "newState" << newState << "not applicable, waiting...";
}
DelayedSeeker::DelayedSeeker( Phonon::MediaObject *mediaObject, qint64 seekTo, bool startPaused )
: DelayedDoer( mediaObject, QSet<Phonon::State>() << Phonon::PlayingState
<< Phonon::BufferingState
<< Phonon::PausedState )
, m_seekTo( seekTo )
, m_startPaused( startPaused )
{
}
void
DelayedSeeker::performAction()
{
m_mediaObject->seek( m_seekTo );
emit trackPositionChanged( m_seekTo, /* userSeek */ true );
if( !m_startPaused )
m_mediaObject->play();
}
DelayedTrackChanger::DelayedTrackChanger( Phonon::MediaObject *mediaObject,
Phonon::MediaController *mediaController,
int trackNumber, qint64 seekTo, bool startPaused )
: DelayedSeeker( mediaObject, seekTo, startPaused )
, m_mediaController( mediaController )
, m_trackNumber( trackNumber )
{
Q_ASSERT( mediaController );
- connect( mediaController, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
+ connect( mediaController, &QObject::destroyed, this, &QObject::deleteLater );
Q_ASSERT( trackNumber > 0 );
}
void
DelayedTrackChanger::performAction()
{
m_mediaController->setCurrentTitle( m_trackNumber );
if( m_seekTo )
{
m_mediaObject->seek( m_seekTo );
emit trackPositionChanged( m_seekTo, /* userSeek */ true );
}
if( !m_startPaused )
m_mediaObject->play();
}
diff --git a/src/playback/EqualizerController.cpp b/src/playback/EqualizerController.cpp
index 6d97f1f012..3cb5b517f4 100644
--- a/src/playback/EqualizerController.cpp
+++ b/src/playback/EqualizerController.cpp
@@ -1,264 +1,264 @@
/****************************************************************************************
* Copyright (c) 2004 Frederik Holljen <fh@ez.no> *
* Copyright (c) 2004,2005 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2004-2013 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2006,2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "EqualizerController"
#include "playback/EqualizerController.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include "equalizer/EqualizerPresets.h"
#include <KLocalizedString>
#include <Phonon/BackendCapabilities>
#include <Phonon/EffectParameter>
EqualizerController::EqualizerController( QObject *object )
: QObject( object )
{}
EqualizerController::~EqualizerController()
{}
//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC
//////////////////////////////////////////////////////////////////////////////////////////
void
EqualizerController::initialize( const Phonon::Path &path )
{
DEBUG_BLOCK
m_path = path;
delete m_equalizer.data();
using namespace Phonon;
// Add an equalizer effect if available
QList<EffectDescription> effects = BackendCapabilities::availableAudioEffects();
QRegExp equalizerRegExp( QString( "equalizer.*%1.*bands" ).arg( s_equalizerBandsNum ),
Qt::CaseInsensitive );
foreach( const EffectDescription &description, effects )
{
if( !description.name().contains( equalizerRegExp ) )
continue;
QScopedPointer<Effect> equalizer( new Effect( description, this ) );
int parameterCount = equalizer->parameters().count();
if( parameterCount == s_equalizerBandsNum || parameterCount == s_equalizerBandsNum + 1 )
{
debug() << "Established Phonon equalizer effect with" << parameterCount
<< "parameters.";
m_equalizer = equalizer.take(); // accept the effect
eqUpdate();
break;
}
else
{
QStringList paramNames;
foreach( const EffectParameter &param, equalizer->parameters() )
paramNames << param.name();
warning() << "Phonon equalizer effect" << description.name() << "with description"
<< description.description() << "has" << parameterCount << "parameters ("
<< paramNames << ") - which is unexpected. Trying other effects.";
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC
//////////////////////////////////////////////////////////////////////////////////////////
bool
EqualizerController::isEqSupported() const
{
// If effect was created it means we have equalizer support
return m_equalizer;
}
double
EqualizerController::eqMaxGain() const
{
if( !m_equalizer )
return 100;
QList<Phonon::EffectParameter> equalizerParameters = m_equalizer.data()->parameters();
if( equalizerParameters.isEmpty() )
return 100.0;
double mScale;
mScale = ( qAbs(equalizerParameters.at(0).maximumValue().toDouble() )
+ qAbs( equalizerParameters.at(0).minimumValue().toDouble() ) );
mScale /= 2.0;
return mScale;
}
QStringList
EqualizerController::eqBandsFreq() const
{
// This will extract the bands frequency values from effect parameter name
// as long as they follow the rules:
// eq-preamp parameter will contain 'pre-amp' string
// bands parameters are described using schema 'xxxHz'
QStringList bandFrequencies;
if( !m_equalizer )
return bandFrequencies;
QList<Phonon::EffectParameter> equalizerParameters = m_equalizer.data()->parameters();
if( equalizerParameters.isEmpty() )
return bandFrequencies;
QRegExp rx( "\\d+(?=Hz)" );
foreach( const Phonon::EffectParameter &mParam, equalizerParameters )
{
if( mParam.name().contains( rx ) )
{
if( rx.cap( 0 ).toInt() < 1000 )
bandFrequencies << i18n( "%0\nHz" ).arg( rx.cap( 0 ) );
else
bandFrequencies << i18n( "%0\nkHz" ).arg( QString::number( rx.cap( 0 ).toInt()/1000 ) );
}
else
bandFrequencies << mParam.name();
}
return bandFrequencies;
}
void
EqualizerController::eqUpdate()
{
DEBUG_BLOCK
// if equalizer not present simply return
if( !m_equalizer )
return;
// check if equalizer should be disabled ??
QList<int> equalizerParametersCfg;
if( AmarokConfig::equalizerMode() <= 0 )
{
// Remove effect from path
if( m_path.effects().indexOf( m_equalizer.data() ) != -1 )
m_path.removeEffect( m_equalizer.data() );
}
else
{
// Set equalizer parameter according to the gains from settings
QList<Phonon::EffectParameter> equalizerParameters = m_equalizer.data()->parameters();
equalizerParametersCfg = AmarokConfig::equalizerGains();
QListIterator<int> equalizerParametersIt( equalizerParametersCfg );
double scaledVal; // Scaled value to set from universal -100 - 100 range to plugin scale
// Checking if preamp is present in equalizer parameters
if( equalizerParameters.size() == s_equalizerBandsNum )
{
// If pre-amp is not present then skip the first element of equalizer gain
if( equalizerParametersIt.hasNext() )
equalizerParametersIt.next();
}
foreach( const Phonon::EffectParameter &mParam, equalizerParameters )
{
scaledVal = equalizerParametersIt.hasNext() ? equalizerParametersIt.next() : 0;
scaledVal *= qAbs(mParam.maximumValue().toDouble() )
+ qAbs( mParam.minimumValue().toDouble() );
scaledVal /= 200.0;
m_equalizer.data()->setParameterValue( mParam, scaledVal );
}
// Insert effect into path if needed
if( m_path.effects().indexOf( m_equalizer.data() ) == -1 )
{
if( !m_path.effects().isEmpty() )
{
m_path.insertEffect( m_equalizer.data(), m_path.effects().first() );
}
else
{
m_path.insertEffect( m_equalizer.data() );
}
}
}
emit gainsChanged( equalizerParametersCfg );
}
QString
EqualizerController::equalizerPreset() const
{
int index = AmarokConfig::equalizerMode() - 1;
if( index > 0 )
return EqualizerPresets::eqGlobalList()[index];
else
return QString("");
}
void
-EqualizerController::applyEqualizerPreset( int index )
+EqualizerController::applyEqualizerPresetByIndex( int index )
{
if( index > -1 )
{
AmarokConfig::setEqualizerMode( index + 1 );
AmarokConfig::setEqualizerGains( EqualizerPresets::eqCfgGetPresetVal( EqualizerPresets::eqGlobalTranslatedList().value( index ) ) );
}
else
AmarokConfig::setEqualizerMode( 0 );
eqUpdate();
emit presetApplied( index );
}
void
-EqualizerController::applyEqualizerPreset( const QString &name )
+EqualizerController::applyEqualizerPresetByName( const QString &name )
{
DEBUG_BLOCK
int index = EqualizerPresets::eqGlobalTranslatedList().indexOf( name );
- return applyEqualizerPreset( index > 0 ? index : 0 );
+ return applyEqualizerPresetByIndex( index > 0 ? index : 0 );
}
void
EqualizerController::savePreset( const QString &name, const QList<int> &gains )
{
EqualizerPresets::eqCfgSetPresetVal( name, gains );
emit presetsChanged( name );
}
bool
EqualizerController::deletePreset( const QString &name )
{
if( !EqualizerPresets::eqCfgDeletePreset( name ) )
return false;
emit presetsChanged( name );
return true;
}
void
EqualizerController::setGains( const QList<int> &gains )
{
AmarokConfig::setEqualizerGains( gains );
eqUpdate();
}
QList<int>
EqualizerController::gains() const
{
return AmarokConfig::equalizerGains();
}
bool
EqualizerController::enabled()
{
return AmarokConfig::equalizerMode() > 0;
}
diff --git a/src/playback/EqualizerController.h b/src/playback/EqualizerController.h
index f31b1e125b..3bfe8d53b7 100644
--- a/src/playback/EqualizerController.h
+++ b/src/playback/EqualizerController.h
@@ -1,122 +1,122 @@
/****************************************************************************************
* Copyright (c) 2004 Frederik Holljen <fh@ez.no> *
* Copyright (c) 2004,2005 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2004-2013 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
* Copyright (c) 2009 Artur Szymiec <artur.szymiec@gmail.com> *
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_EQUALIZERCONTROLLER_H
#define AMAROK_EQUALIZERCONTROLLER_H
#include "amarok_export.h"
#include <QWeakPointer>
#include <Phonon/Path>
#include <Phonon/Effect>
static const int s_equalizerBandsNum = 10; // Number of equalizer parameters excluding Preamp
class AMAROK_EXPORT EqualizerController : public QObject
{
Q_OBJECT
public:
EqualizerController( QObject *object );
~EqualizerController();
void initialize( const Phonon::Path &path );
/**
* Phonon equalizer support is required for Amarok to enable equalizer
* this method return whatever phonon support equalizer effect.
*
* @return @c true if the phonon support equalizer effect, @c false otherwise
*/
bool isEqSupported() const;
/**
* Equalizer implementation for different backends may have different
* gain scale. To properly display it we need to get a scale from effect
*
* @return maximum gain value for equalizer parameters.
*/
double eqMaxGain() const;
/**
* Equalizer implementation for different backends may have different
* frequency bands. For proper display this will try to extract frequency values
* from effect parameters info.
*
* @return QStringList with band labels (form xxx Hz or xxx kHz).
*/
QStringList eqBandsFreq() const;
/**
* @return the name of the equalizer preset being currently used.
*/
QString equalizerPreset() const;
/**
* Changes equaliser preset to preset @param name if it exists.
*/
- void applyEqualizerPreset( const QString &name );
+ void applyEqualizerPresetByName( const QString &name );
QList<int> gains() const;
void setGains( const QList<int> &gains );
void savePreset( const QString &name, const QList<int> &gains );
bool deletePreset( const QString &name );
bool enabled();
public Q_SLOTS:
/**
* Update equalizer status - enabled,disabled,set values
*/
void eqUpdate();
/**
* Change equalizer to preset with index @param index in the global equalizer list.
* Pass -1 to disable.
*/
- void applyEqualizerPreset( int index );
+ void applyEqualizerPresetByIndex( int index );
Q_SIGNALS:
/**
* Emitted when preset with index @param idnex is applied or the equalizer is disabled.
* index is <0 when disabled.
*/
void presetApplied( int index );
/**
* Emitted when the current gains are changed.
*/
void gainsChanged( QList<int> gains );
/**
* Emitted when preset @param name is added, removed or modified.
*/
void presetsChanged( QString name );
private:
QWeakPointer<Phonon::Effect> m_equalizer;
Phonon::Path m_path;
};
#endif
diff --git a/src/playback/Fadeouter.cpp b/src/playback/Fadeouter.cpp
index 74b535e1a6..b62efe33fe 100644
--- a/src/playback/Fadeouter.cpp
+++ b/src/playback/Fadeouter.cpp
@@ -1,61 +1,61 @@
/****************************************************************************************
* Copyright (c) 2013 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* **************************************************************************************/
#include "Fadeouter.h"
#include <Phonon/MediaObject>
#include <Phonon/VolumeFaderEffect>
#include <QTimer>
static const int safetyDelay = 300; // in ms
Fadeouter::Fadeouter( const QWeakPointer<Phonon::MediaObject> &media,
const QWeakPointer<Phonon::VolumeFaderEffect> &fader,
int fadeOutLength )
: QObject( fader.data() )
, m_fader( fader )
{
Q_ASSERT( media );
Q_ASSERT( fader );
Q_ASSERT( fadeOutLength > 0 );
m_fader.data()->fadeOut( fadeOutLength );
// add a bit of a second so that the effect is not cut even if there are some delays
- QTimer::singleShot( fadeOutLength + safetyDelay, this, SLOT(slotFinalizeFadeout()) );
+ QTimer::singleShot( fadeOutLength + safetyDelay, this, &Fadeouter::slotFinalizeFadeout );
// in case a new track starts playing before the fadeout ends, we skip
// slotFinalizeFadeout() and go directly to destructor, which resets fader volume
- connect( media.data(), SIGNAL(currentSourceChanged(Phonon::MediaSource)), SLOT(deleteLater()) );
+ connect( media.data(), &Phonon::MediaObject::currentSourceChanged, this, &QObject::deleteLater );
// no point in having dangling Fadeouters
- connect( media.data(), SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) );
+ connect( media.data(), &QObject::destroyed, this, &QObject::deleteLater );
}
Fadeouter::~Fadeouter()
{
if( m_fader )
// warning: phonon-gstreamer bug 313551 (still present in 4.6.2) prevents
// following call to succeed if a fade is still in progress
m_fader.data()->fadeIn( safetyDelay ); // fade-in, just in case, be nice to ears
}
void
Fadeouter::slotFinalizeFadeout()
{
emit fadeoutFinished();
deleteLater();
}
diff --git a/src/playback/PowerManager.cpp b/src/playback/PowerManager.cpp
index 3533955cd6..397e880dc3 100644
--- a/src/playback/PowerManager.cpp
+++ b/src/playback/PowerManager.cpp
@@ -1,90 +1,90 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PowerManager"
#include "PowerManager.h"
#include "amarokconfig.h"
#include "App.h"
#include "EngineController.h"
#include <Solid/PowerManagement>
#include <KLocalizedString>
#include <QAction>
PowerManager::PowerManager( EngineController *engine )
: QObject( engine )
, m_inhibitionCookie( -1 )
{
- connect( engine, SIGNAL(stopped(qint64,qint64)), this, SLOT(slotNotPlaying()) );
- connect( engine, SIGNAL(paused()), this, SLOT(slotNotPlaying()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)), this, SLOT(slotPlaying()) );
- connect( App::instance(), SIGNAL(settingsChanged()), SLOT(slotSettingsChanged()) );
- connect( Solid::PowerManagement::notifier(), SIGNAL(resumingFromSuspend()),
- this, SLOT(slotResumingFromSuspend()) );
+ connect( engine, &EngineController::stopped, this, &PowerManager::slotNotPlaying );
+ connect( engine, &EngineController::paused, this, &PowerManager::slotNotPlaying );
+ connect( engine, &EngineController::trackPlaying, this, &PowerManager::slotPlaying );
+ connect( App::instance(), &App::settingsChanged, this, &PowerManager::slotSettingsChanged );
+ connect( Solid::PowerManagement::notifier(), &Solid::PowerManagement::Notifier::resumingFromSuspend,
+ this, &PowerManager::slotResumingFromSuspend );
}
PowerManager::~PowerManager()
{
stopInhibitingSuspend();
}
void
PowerManager::slotNotPlaying()
{
stopInhibitingSuspend();
}
void
PowerManager::slotPlaying()
{
if( AmarokConfig::inhibitSuspend() )
startInhibitingSuspend();
}
void
PowerManager::slotResumingFromSuspend()
{
if( AmarokConfig::pauseOnSuspend() && The::engineController()->isPlaying() )
The::engineController()->playPause();
}
void
PowerManager::slotSettingsChanged()
{
if( AmarokConfig::inhibitSuspend() && The::engineController()->isPlaying() )
startInhibitingSuspend();
else
stopInhibitingSuspend();
}
void
PowerManager::startInhibitingSuspend()
{
if( m_inhibitionCookie == -1 )
m_inhibitionCookie = Solid::PowerManagement::beginSuppressingSleep( i18n( "Amarok is currently playing a track" ) );
}
void
PowerManager::stopInhibitingSuspend()
{
if( m_inhibitionCookie != -1 )
{
Solid::PowerManagement::stopSuppressingSleep( m_inhibitionCookie );
m_inhibitionCookie = -1;
}
}
diff --git a/src/playlist/PlaylistActions.cpp b/src/playlist/PlaylistActions.cpp
index 9c11be0dd7..d832e182a4 100644
--- a/src/playlist/PlaylistActions.cpp
+++ b/src/playlist/PlaylistActions.cpp
@@ -1,531 +1,531 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2007-2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::Actions"
#include "PlaylistActions.h"
#include "EngineController.h"
#include "MainWindow.h"
#include "amarokconfig.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core/interfaces/Logger.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "dynamic/DynamicModel.h"
#include "navigators/DynamicTrackNavigator.h"
#include "navigators/RandomAlbumNavigator.h"
#include "navigators/RandomTrackNavigator.h"
#include "navigators/RepeatAlbumNavigator.h"
#include "navigators/RepeatTrackNavigator.h"
#include "navigators/StandardTrackNavigator.h"
#include "navigators/FavoredRandomTrackNavigator.h"
#include "playlist/PlaylistController.h"
#include "playlist/PlaylistDock.h"
#include "playlist/PlaylistModelStack.h"
#include "playlist/PlaylistRestorer.h"
#include "playlistmanager/PlaylistManager.h"
#include <KStandardDirs>
#include <typeinfo>
Playlist::Actions* Playlist::Actions::s_instance = 0;
Playlist::Actions* Playlist::Actions::instance()
{
if( !s_instance )
{
s_instance = new Actions();
s_instance->init(); // prevent infinite recursion by using the playlist actions only after setting the instance.
}
return s_instance;
}
void
Playlist::Actions::destroy()
{
delete s_instance;
s_instance = 0;
}
Playlist::Actions::Actions()
: QObject()
, m_nextTrackCandidate( 0 )
, m_stopAfterPlayingTrackId( 0 )
, m_navigator( 0 )
, m_waitingForNextTrack( false )
{
EngineController *engine = The::engineController();
if( engine ) // test cases might create a playlist without having an EngineController
{
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(slotTrackPlaying(Meta::TrackPtr)) );
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(slotPlayingStopped(qint64,qint64)) );
+ connect( engine, &EngineController::trackPlaying,
+ this, &Playlist::Actions::slotTrackPlaying );
+ connect( engine, &EngineController::stopped,
+ this, &Playlist::Actions::slotPlayingStopped );
}
}
Playlist::Actions::~Actions()
{
delete m_navigator;
}
void
Playlist::Actions::init()
{
playlistModeChanged(); // sets m_navigator.
restoreDefaultPlaylist();
}
Meta::TrackPtr
Playlist::Actions::likelyNextTrack()
{
return The::playlist()->trackForId( m_navigator->likelyNextTrack() );
}
Meta::TrackPtr
Playlist::Actions::likelyPrevTrack()
{
return The::playlist()->trackForId( m_navigator->likelyLastTrack() );
}
void
Playlist::Actions::requestNextTrack()
{
DEBUG_BLOCK
if ( m_nextTrackCandidate != 0 )
return;
m_nextTrackCandidate = m_navigator->requestNextTrack();
if( m_nextTrackCandidate == 0 )
return;
if( willStopAfterTrack( ModelStack::instance()->bottom()->activeId() ) )
// Tell playlist what track to play after users hits Play again:
The::playlist()->setActiveId( m_nextTrackCandidate );
else
play( m_nextTrackCandidate, false );
}
void
Playlist::Actions::requestUserNextTrack()
{
m_nextTrackCandidate = m_navigator->requestUserNextTrack();
play( m_nextTrackCandidate );
}
void
Playlist::Actions::requestPrevTrack()
{
m_nextTrackCandidate = m_navigator->requestLastTrack();
play( m_nextTrackCandidate );
}
void
Playlist::Actions::requestTrack( quint64 id )
{
m_nextTrackCandidate = id;
}
void
Playlist::Actions::stopAfterPlayingTrack( quint64 id )
{
if( id == quint64( -1 ) )
id = ModelStack::instance()->bottom()->activeId(); // 0 is fine
if( id != m_stopAfterPlayingTrackId )
{
m_stopAfterPlayingTrackId = id;
repaintPlaylist(); // to get the visual change
}
}
bool
Playlist::Actions::willStopAfterTrack( const quint64 id ) const
{
return m_stopAfterPlayingTrackId && m_stopAfterPlayingTrackId == id;
}
void
Playlist::Actions::play()
{
DEBUG_BLOCK
if ( m_nextTrackCandidate == 0 )
{
m_nextTrackCandidate = The::playlist()->activeId();
// the queue has priority, and requestNextTrack() respects the queue.
// this is a bit of a hack because we "know" that all navigators will look at the queue first.
if ( !m_nextTrackCandidate || !m_navigator->queue().isEmpty() )
m_nextTrackCandidate = m_navigator->requestNextTrack();
}
play( m_nextTrackCandidate );
}
void
Playlist::Actions::play( const QModelIndex &index )
{
DEBUG_BLOCK
if( index.isValid() )
{
m_nextTrackCandidate = index.data( UniqueIdRole ).value<quint64>();
play( m_nextTrackCandidate );
}
}
void
Playlist::Actions::play( const int row )
{
DEBUG_BLOCK
m_nextTrackCandidate = The::playlist()->idAt( row );
play( m_nextTrackCandidate );
}
void
Playlist::Actions::play( const quint64 trackid, bool now )
{
DEBUG_BLOCK
Meta::TrackPtr track = The::playlist()->trackForId( trackid );
if ( track )
{
if ( now )
The::engineController()->play( track );
else
The::engineController()->setNextTrack( track );
}
else
{
warning() << "Invalid trackid" << trackid;
}
}
void
Playlist::Actions::next()
{
DEBUG_BLOCK
requestUserNextTrack();
}
void
Playlist::Actions::back()
{
DEBUG_BLOCK
requestPrevTrack();
}
void
Playlist::Actions::enableDynamicMode( bool enable )
{
if( AmarokConfig::dynamicMode() == enable )
return;
AmarokConfig::setDynamicMode( enable );
// TODO: turn off other incompatible modes
// TODO: should we restore the state of other modes?
AmarokConfig::self()->writeConfig();
Playlist::Dock *dock = The::mainWindow()->playlistDock();
Playlist::SortWidget *sorting = dock ? dock->sortWidget() : 0;
if( sorting )
sorting->trimToLevel();
playlistModeChanged();
/* append upcoming tracks to satisfy user's with about number of upcoming tracks.
* Needs to be _after_ playlistModeChanged() because before calling it the old
* m_navigator still reigns. */
if( enable )
normalizeDynamicPlaylist();
}
void
Playlist::Actions::playlistModeChanged()
{
DEBUG_BLOCK
QQueue<quint64> currentQueue;
if ( m_navigator )
{
//HACK: Migrate the queue to the new navigator
//TODO: The queue really should not be maintained by the navigators in this way
// but should be handled by a separate and persistant object.
currentQueue = m_navigator->queue();
m_navigator->deleteLater();
}
debug() << "Dynamic mode: " << AmarokConfig::dynamicMode();
if ( AmarokConfig::dynamicMode() )
{
m_navigator = new DynamicTrackNavigator();
emit navigatorChanged();
return;
}
m_navigator = 0;
switch( AmarokConfig::trackProgression() )
{
case AmarokConfig::EnumTrackProgression::RepeatTrack:
m_navigator = new RepeatTrackNavigator();
break;
case AmarokConfig::EnumTrackProgression::RepeatAlbum:
m_navigator = new RepeatAlbumNavigator();
break;
case AmarokConfig::EnumTrackProgression::RandomTrack:
switch( AmarokConfig::favorTracks() )
{
case AmarokConfig::EnumFavorTracks::HigherScores:
case AmarokConfig::EnumFavorTracks::HigherRatings:
case AmarokConfig::EnumFavorTracks::LessRecentlyPlayed:
m_navigator = new FavoredRandomTrackNavigator();
break;
case AmarokConfig::EnumFavorTracks::Off:
default:
m_navigator = new RandomTrackNavigator();
break;
}
break;
case AmarokConfig::EnumTrackProgression::RandomAlbum:
m_navigator = new RandomAlbumNavigator();
break;
//repeat playlist, standard, only queue and fallback are all the normal navigator.
case AmarokConfig::EnumTrackProgression::RepeatPlaylist:
case AmarokConfig::EnumTrackProgression::OnlyQueue:
case AmarokConfig::EnumTrackProgression::Normal:
default:
m_navigator = new StandardTrackNavigator();
break;
}
m_navigator->queueIds( currentQueue );
emit navigatorChanged();
}
void
Playlist::Actions::repopulateDynamicPlaylist()
{
DEBUG_BLOCK
if ( typeid( *m_navigator ) == typeid( DynamicTrackNavigator ) )
{
static_cast<DynamicTrackNavigator*>(m_navigator)->repopulate();
}
}
void
Playlist::Actions::shuffle()
{
QList<int> fromRows, toRows;
{
const int rowCount = The::playlist()->qaim()->rowCount();
fromRows.reserve( rowCount );
QMultiMap<int, int> shuffleToRows;
for( int row = 0; row < rowCount; ++row )
{
fromRows.append( row );
shuffleToRows.insert( qrand(), row );
}
toRows = shuffleToRows.values();
}
The::playlistController()->reorderRows( fromRows, toRows );
}
int
Playlist::Actions::queuePosition( quint64 id )
{
return m_navigator->queuePosition( id );
}
QQueue<quint64>
Playlist::Actions::queue()
{
return m_navigator->queue();
}
bool
Playlist::Actions::queueMoveUp( quint64 id )
{
const bool ret = m_navigator->queueMoveUp( id );
if ( ret )
Playlist::ModelStack::instance()->bottom()->emitQueueChanged();
return ret;
}
bool
Playlist::Actions::queueMoveDown( quint64 id )
{
const bool ret = m_navigator->queueMoveDown( id );
if ( ret )
Playlist::ModelStack::instance()->bottom()->emitQueueChanged();
return ret;
}
void
Playlist::Actions::dequeue( quint64 id )
{
m_navigator->dequeueId( id ); // has no return value, *shrug*
Playlist::ModelStack::instance()->bottom()->emitQueueChanged();
return;
}
void
Playlist::Actions::queue( const QList<int> &rows )
{
QList<quint64> ids;
foreach( int row, rows )
ids << The::playlist()->idAt( row );
queue( ids );
}
void
Playlist::Actions::queue( const QList<quint64> &ids )
{
m_navigator->queueIds( ids );
if ( !ids.isEmpty() )
Playlist::ModelStack::instance()->bottom()->emitQueueChanged();
}
void
Playlist::Actions::dequeue( const QList<int> &rows )
{
DEBUG_BLOCK
foreach( int row, rows )
{
quint64 id = The::playlist()->idAt( row );
m_navigator->dequeueId( id );
}
if ( !rows.isEmpty() )
Playlist::ModelStack::instance()->bottom()->emitQueueChanged();
}
void
Playlist::Actions::slotTrackPlaying( Meta::TrackPtr engineTrack )
{
DEBUG_BLOCK
if ( engineTrack )
{
Meta::TrackPtr candidateTrack = The::playlist()->trackForId( m_nextTrackCandidate ); // May be 0.
if ( engineTrack == candidateTrack )
{ // The engine is playing what we planned: everything is OK.
The::playlist()->setActiveId( m_nextTrackCandidate );
}
else
{
warning() << "engineNewTrackPlaying:" << engineTrack->prettyName() << "does not match what the playlist controller thought it should be";
if ( The::playlist()->activeTrack() != engineTrack )
{
// this will set active row to -1 if the track isn't in the playlist at all
int row = The::playlist()->firstRowForTrack( engineTrack );
if( row != -1 )
The::playlist()->setActiveRow( row );
else
The::playlist()->setActiveRow( AmarokConfig::lastPlaying() );
}
//else
// Engine and playlist are in sync even though we didn't plan it; do nothing
}
}
else
warning() << "engineNewTrackPlaying: not really a track";
m_nextTrackCandidate = 0;
}
void
Playlist::Actions::slotPlayingStopped( qint64 finalPosition, qint64 trackLength )
{
DEBUG_BLOCK;
stopAfterPlayingTrack( 0 ); // reset possible "Stop after playing track";
// we have to determine if we reached the end of the playlist.
// in such a case there would be no new track and the current one
// played until the end.
// else this must be a result of StopAfterCurrent or the user stopped
if( m_nextTrackCandidate || finalPosition < trackLength )
return;
debug() << "nothing more to play...";
// no more stuff to play. make sure to reset the active track so that pressing play
// will start at the top of the playlist (or whereever the navigator wants to start)
// instead of just replaying the last track
The::playlist()->setActiveRow( -1 );
// we also need to mark all tracks as unplayed or some navigators might be unhappy
The::playlist()->setAllUnplayed();
}
void
Playlist::Actions::normalizeDynamicPlaylist()
{
if ( typeid( *m_navigator ) == typeid( DynamicTrackNavigator ) )
{
static_cast<DynamicTrackNavigator*>(m_navigator)->appendUpcoming();
}
}
void
Playlist::Actions::repaintPlaylist()
{
The::mainWindow()->playlistDock()->currentView()->update();
}
void
Playlist::Actions::restoreDefaultPlaylist()
{
DEBUG_BLOCK
// The PlaylistManager needs to be loaded or podcast episodes and other
// non-collection Tracks will not be loaded correctly.
The::playlistManager();
Playlist::Restorer *restorer = new Playlist::Restorer();
restorer->restore( QUrl::fromLocalFile(Amarok::defaultPlaylistPath()) );
- connect( restorer, SIGNAL(restoreFinished()), restorer, SLOT(deleteLater()) );
+ connect( restorer, &Playlist::Restorer::restoreFinished, restorer, &QObject::deleteLater );
}
namespace The
{
AMAROK_EXPORT Playlist::Actions* playlistActions() { return Playlist::Actions::instance(); }
}
diff --git a/src/playlist/PlaylistBreadcrumbItem.cpp b/src/playlist/PlaylistBreadcrumbItem.cpp
index 6fb277fb85..ec229a935c 100644
--- a/src/playlist/PlaylistBreadcrumbItem.cpp
+++ b/src/playlist/PlaylistBreadcrumbItem.cpp
@@ -1,155 +1,155 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistBreadcrumbItem.h"
#include "PlaylistSortWidget.h"
#include <QIcon>
#include <KLocale>
namespace Playlist
{
BreadcrumbItemMenu::BreadcrumbItemMenu( Column currentColumn, QWidget *parent )
: QMenu( parent )
{
for( Column col = Column( 0 ); col != NUM_COLUMNS; col = Column( col + 1 ) )
{
if( !isSortableColumn( col ) || currentColumn == col )
continue;
QAction *action = addAction( QIcon::fromTheme( iconName( col ) ),
QString( columnName( col ) ) );
action->setData( internalColumnName( col ) );
}
addSeparator();
QAction *shuffleAction = addAction( QIcon::fromTheme( "media-playlist-shuffle" ),
QString( i18n( "Shuffle" ) ) );
shuffleAction->setData( QString( "Shuffle" ) );
- connect( this, SIGNAL(triggered(QAction*)), this, SLOT(actionTriggered(QAction*)) );
+ connect( this, &BreadcrumbItemMenu::triggered, this, &BreadcrumbItemMenu::actionTriggered );
}
BreadcrumbItemMenu::~BreadcrumbItemMenu()
{}
void
BreadcrumbItemMenu::actionTriggered( QAction *action )
{
const QString actionName( action->data().toString() );
if( actionName == "Shuffle" )
emit shuffleActionClicked();
else
emit actionClicked( actionName );
}
/////// BreadcrumbItem methods begin here
BreadcrumbItem::BreadcrumbItem( BreadcrumbLevel *level, QWidget *parent )
: KHBox( parent )
, m_name( level->name() )
, m_prettyName( level->prettyName() )
{
// Let's set up the "siblings" button first...
m_menuButton = new BreadcrumbItemMenuButton( this );
m_menu = new BreadcrumbItemMenu( columnForName( m_name ), this );
// Disable used levels
QStringList usedBreadcrumbLevels = qobject_cast< SortWidget * >( parent )->levels();
foreach( QAction *action, m_menu->actions() )
if( usedBreadcrumbLevels.contains( action->data().toString() ) )
action->setEnabled( false );
m_menuButton->setMenu( m_menu );
m_menu->setContentsMargins( /*offset*/ 6, 1, 1, 2 );
// And then the main breadcrumb button...
m_mainButton = new BreadcrumbItemSortButton( level->icon(), level->prettyName(), this );
- connect( m_mainButton, SIGNAL(clicked()), this, SIGNAL(clicked()) );
- connect( m_mainButton, SIGNAL(arrowToggled(Qt::SortOrder)), this, SIGNAL(orderInverted()) );
- connect( m_mainButton, SIGNAL(sizePolicyChanged()), this, SLOT(updateSizePolicy()) );
+ connect( m_mainButton, &BreadcrumbItemSortButton::clicked, this, &BreadcrumbItem::clicked );
+ connect( m_mainButton, &BreadcrumbItemSortButton::arrowToggled, this, &BreadcrumbItem::orderInverted );
+ connect( m_mainButton, &BreadcrumbItemSortButton::sizePolicyChanged, this, &BreadcrumbItem::updateSizePolicy );
m_menu->hide();
updateSizePolicy();
}
BreadcrumbItem::~BreadcrumbItem()
{}
QString
BreadcrumbItem::name() const
{
return m_name;
}
Qt::SortOrder
BreadcrumbItem::sortOrder() const
{
return m_mainButton->orderState();
}
void
BreadcrumbItem::invertOrder()
{
m_mainButton->invertOrder();
}
void
BreadcrumbItem::updateSizePolicy()
{
setSizePolicy( m_mainButton->sizePolicy() );
}
const BreadcrumbItemMenu*
BreadcrumbItem::menu()
{
return m_menu;
}
/////// BreadcrumbAddMenuButton methods begin here
BreadcrumbAddMenuButton::BreadcrumbAddMenuButton( QWidget *parent )
: BreadcrumbItemMenuButton( parent )
{
setToolTip( i18n( "Add a sorting level to the playlist." ) );
//FIXME: the menu should have the same margins as other Playlist::Breadcrumb and
// BrowserBreadcrumb menus.
m_menu = new BreadcrumbItemMenu( PlaceHolder, this );
setMenu( m_menu );
}
BreadcrumbAddMenuButton::~BreadcrumbAddMenuButton()
{}
const BreadcrumbItemMenu*
BreadcrumbAddMenuButton::menu()
{
return m_menu;
}
void
BreadcrumbAddMenuButton::updateMenu( const QStringList &usedBreadcrumbLevels )
{
// Enable unused, disable used levels
foreach( QAction *action, m_menu->actions() )
action->setEnabled( !usedBreadcrumbLevels.contains( action->data().toString() ) );
}
} //namespace Playlist
diff --git a/src/playlist/PlaylistController.cpp b/src/playlist/PlaylistController.cpp
index 5dce25da9a..d6b8442b48 100644
--- a/src/playlist/PlaylistController.cpp
+++ b/src/playlist/PlaylistController.cpp
@@ -1,650 +1,650 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2007-2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 John Atkinson <john@fauxnetic.co.uk> *
* Copyright (c) 2009,2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::Controller"
// WORKAROUND for QTBUG-25960. Required for Qt versions < 4.8.5 in combination with libc++.
#define QT_NO_STL 1
#include <qiterator.h>
#undef QT_NO_STL
#include "PlaylistController.h"
#include "EngineController.h"
#include "amarokconfig.h"
#include "core/collections/QueryMaker.h"
#include "core/support/Debug.h"
#include "core-impl/meta/cue/CueFileSupport.h"
#include "core-impl/meta/file/File.h"
#include "core-impl/meta/multi/MultiTrack.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "core-impl/support/TrackLoader.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "playlistmanager/PlaylistManager.h"
#include <QAction>
#include <algorithm>
#include <typeinfo>
using namespace Playlist;
namespace The
{
AMAROK_EXPORT Controller* playlistController()
{
return Controller::instance();
}
}
Controller* Controller::s_instance = 0;
Controller*
Controller::instance()
{
if( s_instance == 0 )
s_instance = new Controller();
return s_instance;
}
void
Controller::destroy()
{
if( s_instance )
{
delete s_instance;
s_instance = 0;
}
}
Controller::Controller()
: QObject()
, m_undoStack( new QUndoStack( this ) )
{
DEBUG_BLOCK
//As a rule, when talking to the playlist one should always use the topmost model, as
//Playlist::ModelStack::instance->top() or simply The::playlist().
//This is an exception, because we handle the presence of tracks in the bottom model,
//so we get a pointer to the bottom model and use it with great care.
// TODO: get these values only when we really need them to loosen up the
// coupling between Controller and Model
m_bottomModel = ModelStack::instance()->bottom();
m_topModel = The::playlist();
m_undoStack->setUndoLimit( 20 );
- connect( m_undoStack, SIGNAL(canRedoChanged(bool)), this, SIGNAL(canRedoChanged(bool)) );
- connect( m_undoStack, SIGNAL(canUndoChanged(bool)), this, SIGNAL(canUndoChanged(bool)) );
+ connect( m_undoStack, &QUndoStack::canRedoChanged, this, &Controller::canRedoChanged );
+ connect( m_undoStack, &QUndoStack::canUndoChanged, this, &Controller::canUndoChanged );
}
Controller::~Controller() {}
void
Controller::insertOptioned( Meta::TrackPtr track, AddOptions options )
{
if( !track )
return;
Meta::TrackList list;
list.append( track );
insertOptioned( list, options );
}
void
Controller::insertOptioned( Meta::TrackList list, AddOptions options )
{
DEBUG_BLOCK
/* Note: don't use (options & flag) here to test whether flag is present in options.
* We have compound flags and for example (Queue & DirectPlay) == Queue, which
* evaluates to true, which isn't usually what you want.
*
* Use (options & flag == flag) instead, or rather QFlag's convenience method:
* options.testFlag( flag )
*/
if( list.isEmpty() )
return;
QString actionName = i18nc( "name of the action in undo stack", "Add tracks to playlist" );
if( options.testFlag( Queue ) )
actionName = i18nc( "name of the action in undo stack", "Queue tracks" );
if( options.testFlag( Replace ) )
actionName = i18nc( "name of the action in undo stack", "Replace playlist" );
m_undoStack->beginMacro( actionName );
if( options.testFlag( Replace ) )
{
emit replacingPlaylist(); //make sure that we clear filters
clear();
//make sure that we turn off dynamic mode.
Amarok::actionCollection()->action( "disable_dynamic" )->trigger();
}
int bottomModelRowCount = m_bottomModel->qaim()->rowCount();
int bottomModelInsertRow;
if( options.testFlag( Queue ) )
{
// queue is a list of playlist item ids
QQueue<quint64> queue = Actions::instance()->queue();
int activeRow = m_bottomModel->activeRow();
if( options.testFlag( PrependToQueue ) )
{
if( activeRow >= 0 )
bottomModelInsertRow = activeRow + 1; // right after active track
else if( !queue.isEmpty() )
bottomModelInsertRow = m_bottomModel->rowForId( queue.first() ); // prepend to queue
else
bottomModelInsertRow = bottomModelRowCount; // fallback: append to end
}
else // append to queue
{
if( !queue.isEmpty() )
bottomModelInsertRow = m_bottomModel->rowForId( queue.last() ) + 1; // after queue
else if( activeRow >= 0 )
bottomModelInsertRow = activeRow + 1; // after active track
else
bottomModelInsertRow = bottomModelRowCount; // fallback: append to end
}
}
else
bottomModelInsertRow = bottomModelRowCount;
// this guy does the thing:
insertionHelper( bottomModelInsertRow, list );
if( options.testFlag( Queue ) )
{
// Construct list of rows to be queued
QList<quint64> ids;
for( int bottomModelRow = bottomModelInsertRow;
bottomModelRow < bottomModelInsertRow + list.size(); bottomModelRow++ )
{
ids << m_bottomModel->idAt( bottomModelRow );
}
if( options.testFlag( PrependToQueue ) ) // PrependToQueue implies Queue
{
// append current queue to new queue and remove it
foreach( const quint64 id, Actions::instance()->queue() )
{
Actions::instance()->dequeue( id );
ids << id;
}
}
Actions::instance()->queue( ids );
}
m_undoStack->endMacro();
bool startPlaying = false;
EngineController *engine = The::engineController();
if( options.testFlag( DirectPlay ) ) // implies PrependToQueue
startPlaying = true;
else if( options.testFlag( Playlist::StartPlayIfConfigured )
&& AmarokConfig::startPlayingOnAdd() && engine && !engine->isPlaying() )
{
// if nothing is in the queue, queue the first item we have added so that the call
// to ->requestUserNextTrack() pops it. The queueing is therefore invisible to
// user. Else we start playing the queue.
if( Actions::instance()->queue().isEmpty() )
Actions::instance()->queue( QList<quint64>() << m_bottomModel->idAt( bottomModelInsertRow ) );
startPlaying = true;
}
if( startPlaying )
Actions::instance()->requestUserNextTrack(); // desired track will be first in queue
emit changed();
}
void
Controller::insertOptioned( Playlists::PlaylistPtr playlist, AddOptions options )
{
insertOptioned( Playlists::PlaylistList() << playlist, options );
}
void
Controller::insertOptioned( Playlists::PlaylistList list, AddOptions options )
{
TrackLoader::Flags flags;
// if we are going to play, we need full metadata (playable tracks)
if( options.testFlag( DirectPlay ) || ( options.testFlag( Playlist::StartPlayIfConfigured )
&& AmarokConfig::startPlayingOnAdd() ) )
{
flags |= TrackLoader::FullMetadataRequired;
}
if( options.testFlag( Playlist::RemotePlaylistsAreStreams ) )
flags |= TrackLoader::RemotePlaylistsAreStreams;
TrackLoader *loader = new TrackLoader( flags ); // auto-deletes itself
loader->setProperty( "options", QVariant::fromValue<AddOptions>( options ) );
- connect( loader, SIGNAL(finished(Meta::TrackList)),
- SLOT(slotLoaderWithOptionsFinished(Meta::TrackList)) );
+ connect( loader, &TrackLoader::finished,
+ this, &Controller::slotLoaderWithOptionsFinished );
loader->init( list );
}
void
Controller::insertOptioned( const QUrl &url, AddOptions options )
{
insertOptioned( QList<QUrl>() << url, options );
}
void
Controller::insertOptioned( QList<QUrl> &urls, AddOptions options )
{
TrackLoader::Flags flags;
// if we are going to play, we need full metadata (playable tracks)
if( options.testFlag( DirectPlay ) || ( options.testFlag( Playlist::StartPlayIfConfigured )
&& AmarokConfig::startPlayingOnAdd() ) )
{
flags |= TrackLoader::FullMetadataRequired;
}
if( options.testFlag( Playlist::RemotePlaylistsAreStreams ) )
flags |= TrackLoader::RemotePlaylistsAreStreams;
TrackLoader *loader = new TrackLoader( flags ); // auto-deletes itself
loader->setProperty( "options", QVariant::fromValue<AddOptions>( options ) );
- connect( loader, SIGNAL(finished(Meta::TrackList)),
- SLOT(slotLoaderWithOptionsFinished(Meta::TrackList)) );
+ connect( loader, &TrackLoader::finished,
+ this, &Controller::slotLoaderWithOptionsFinished );
loader->init( urls );
}
void
Controller::insertTrack( int topModelRow, Meta::TrackPtr track )
{
if( !track )
return;
insertTracks( topModelRow, Meta::TrackList() << track );
}
void
Controller::insertTracks( int topModelRow, Meta::TrackList tl )
{
insertionHelper( insertionTopRowToBottom( topModelRow ), tl );
}
void
Controller::insertPlaylist( int topModelRow, Playlists::PlaylistPtr playlist )
{
insertPlaylists( topModelRow, Playlists::PlaylistList() << playlist );
}
void
Controller::insertPlaylists( int topModelRow, Playlists::PlaylistList playlists )
{
TrackLoader *loader = new TrackLoader(); // auto-deletes itself
loader->setProperty( "topModelRow", QVariant( topModelRow ) );
- connect( loader, SIGNAL(finished(Meta::TrackList)),
- SLOT(slotLoaderWithRowFinished(Meta::TrackList)) );
+ connect( loader, &TrackLoader::finished,
+ this, &Controller::slotLoaderWithRowFinished );
loader->init( playlists );
}
void
Controller::insertUrls( int topModelRow, QList<QUrl> &urls )
{
TrackLoader *loader = new TrackLoader(); // auto-deletes itself
loader->setProperty( "topModelRow", QVariant( topModelRow ) );
- connect( loader, SIGNAL(finished(Meta::TrackList)),
- SLOT(slotLoaderWithRowFinished(Meta::TrackList)) );
+ connect( loader, &TrackLoader::finished,
+ this, &Controller::slotLoaderWithRowFinished );
loader->init( urls );
}
void
Controller::removeRow( int topModelRow )
{
DEBUG_BLOCK
removeRows( topModelRow, 1 );
}
void
Controller::removeRows( int topModelRow, int count )
{
DEBUG_BLOCK
QList<int> rl;
for( int i = 0; i < count; ++i )
rl.append( topModelRow++ );
removeRows( rl );
}
void
Controller::removeRows( QList<int>& topModelRows )
{
DEBUG_BLOCK
RemoveCmdList bottomModelCmds;
foreach( int topModelRow, topModelRows )
{
if( m_topModel->rowExists( topModelRow ) )
{
Meta::TrackPtr track = m_topModel->trackAt( topModelRow ); // For "undo".
int bottomModelRow = m_topModel->rowToBottomModel( topModelRow );
bottomModelCmds.append( RemoveCmd( track, bottomModelRow ) );
}
else
warning() << "Received command to remove non-existent row. This should NEVER happen. row=" << topModelRow;
}
if( bottomModelCmds.size() > 0 )
m_undoStack->push( new RemoveTracksCmd( 0, bottomModelCmds ) );
emit changed();
}
void
Controller::removeDeadAndDuplicates()
{
DEBUG_BLOCK
QSet<Meta::TrackPtr> uniqueTracks = m_topModel->tracks().toSet();
QList<int> topModelRowsToRemove;
foreach( Meta::TrackPtr unique, uniqueTracks )
{
QList<int> trackRows = m_topModel->allRowsForTrack( unique ).toList();
if( unique->playableUrl().isLocalFile() && !QFile::exists( unique->playableUrl().path() ) )
{
// Track is Dead
// TODO: Check remote files as well
topModelRowsToRemove << trackRows;
}
else if( trackRows.size() > 1 )
{
// Track is Duplicated
// Remove all rows except the first
for( QList<int>::const_iterator it = ++trackRows.constBegin(); it != trackRows.constEnd(); ++it )
topModelRowsToRemove.push_back( *it );
}
}
if( !topModelRowsToRemove.empty() )
{
m_undoStack->beginMacro( "Remove dead and duplicate entries" ); // TODO: Internationalize?
removeRows( topModelRowsToRemove );
m_undoStack->endMacro();
}
}
void
Controller::moveRow( int from, int to )
{
DEBUG_BLOCK
if( ModelStack::instance()->sortProxy()->isSorted() )
return;
if( from == to )
return;
QList<int> source;
QList<int> target;
source.append( from );
source.append( to );
// shift all the rows in between
if( from < to )
{
for( int i = from + 1; i <= to; i++ )
{
source.append( i );
target.append( i - 1 );
}
}
else
{
for( int i = from - 1; i >= to; i-- )
{
source.append( i );
target.append( i + 1 );
}
}
reorderRows( source, target );
}
int
Controller::moveRows( QList<int>& from, int to )
{
DEBUG_BLOCK
if( from.size() <= 0 )
return to;
qSort( from.begin(), from.end() );
if( ModelStack::instance()->sortProxy()->isSorted() )
return from.first();
to = ( to == qBound( 0, to, m_topModel->qaim()->rowCount() ) ) ? to : m_topModel->qaim()->rowCount();
from.erase( std::unique( from.begin(), from.end() ), from.end() );
int min = qMin( to, from.first() );
int max = qMax( to, from.last() );
QList<int> source;
QList<int> target;
for( int i = min; i <= max; i++ )
{
if( i >= m_topModel->qaim()->rowCount() )
break; // we are likely moving below the last element, to an index that really does not exist, and thus should not be moved up.
source.append( i );
target.append( i );
}
int originalTo = to;
foreach ( int f, from )
{
if( f < originalTo )
to--; // since we are moving an item down in the list, this item will no longer count towards the target row
source.removeOne( f );
}
// We iterate through the items in reverse order, as this allows us to keep the target row constant
// (remember that the item that was originally on the target row is pushed down)
QList<int>::const_iterator f_iter = from.constEnd();
while( f_iter != from.constBegin() )
{
--f_iter;
source.insert( ( to - min ), *f_iter );
}
reorderRows( source, target );
return to;
}
void
Controller::reorderRows( const QList<int> &from, const QList<int> &to )
{
DEBUG_BLOCK
if( from.size() != to.size() )
return;
// validity check: each item should appear exactly once in both lists
{
QSet<int> fromItems( from.toSet() );
QSet<int> toItems( to.toSet() );
if( fromItems.size() != from.size() || toItems.size() != to.size() || fromItems != toItems )
{
error() << "row move lists malformed:";
error() << from;
error() << to;
return;
}
}
MoveCmdList bottomModelCmds;
for( int i = 0; i < from.size(); i++ )
{
debug() << "moving rows:" << from.at( i ) << "->" << to.at( i );
if( ( from.at( i ) >= 0 ) && ( from.at( i ) < m_topModel->qaim()->rowCount() ) )
if( from.at( i ) != to.at( i ) )
bottomModelCmds.append( MoveCmd( m_topModel->rowToBottomModel( from.at( i ) ), m_topModel->rowToBottomModel( to.at( i ) ) ) );
}
if( bottomModelCmds.size() > 0 )
m_undoStack->push( new MoveTracksCmd( 0, bottomModelCmds ) );
emit changed();
}
void
Controller::undo()
{
DEBUG_BLOCK
m_undoStack->undo();
emit changed();
}
void
Controller::redo()
{
DEBUG_BLOCK
m_undoStack->redo();
emit changed();
}
void
Controller::clear()
{
DEBUG_BLOCK
removeRows( 0, ModelStack::instance()->bottom()->qaim()->rowCount() );
emit changed();
}
/**************************************************
* Private Functions
**************************************************/
void
Controller::slotLoaderWithOptionsFinished( const Meta::TrackList &tracks )
{
QObject *loader = sender();
if( !loader )
{
error() << __PRETTY_FUNCTION__ << "must be connected to TrackLoader";
return;
}
QVariant options = loader->property( "options" );
if( !options.canConvert<AddOptions>() )
{
error() << __PRETTY_FUNCTION__ << "loader property 'options' is not valid";
return;
}
if( !tracks.isEmpty() )
insertOptioned( tracks, options.value<AddOptions>() );
}
void
Controller::slotLoaderWithRowFinished( const Meta::TrackList &tracks )
{
QObject *loader = sender();
if( !loader )
{
error() << __PRETTY_FUNCTION__ << "must be connected to TrackLoader";
return;
}
QVariant topModelRow = loader->property( "topModelRow" );
if( !topModelRow.isValid() || topModelRow.type() != QVariant::Int )
{
error() << __PRETTY_FUNCTION__ << "loader property 'topModelRow' is not a valid integer";
return;
}
if( !tracks.isEmpty() )
insertTracks( topModelRow.toInt(), tracks );
}
int
Controller::insertionTopRowToBottom( int topModelRow )
{
if( ( topModelRow < 0 ) || ( topModelRow > m_topModel->qaim()->rowCount() ) )
{
error() << "Row number invalid, using bottom:" << topModelRow;
topModelRow = m_topModel->qaim()->rowCount(); // Failsafe: append.
}
if( ModelStack::instance()->sortProxy()->isSorted() )
// if the playlist is sorted there's no point in placing the added tracks at any
// specific point in relation to another track, so we just append them.
return m_bottomModel->qaim()->rowCount();
else
return m_topModel->rowToBottomModel( topModelRow );
}
void
Controller::insertionHelper( int bottomModelRow, Meta::TrackList& tl )
{
//expand any tracks that are actually playlists into multisource tracks
//and any tracks with an associated cue file
Meta::TrackList modifiedList;
QMutableListIterator<Meta::TrackPtr> i( tl );
while( i.hasNext() )
{
i.next();
Meta::TrackPtr track = i.value();
if( !track )
{
/*ignore*/
}
else if( typeid( *track.data() ) == typeid( MetaFile::Track ) )
{
QUrl cuesheet = MetaCue::CueFileSupport::locateCueSheet( track->playableUrl() );
if( !cuesheet.isEmpty() )
{
MetaCue::CueFileItemMap cueMap = MetaCue::CueFileSupport::loadCueFile( cuesheet, track );
if( !cueMap.isEmpty() )
{
Meta::TrackList cueTracks = MetaCue::CueFileSupport::generateTimeCodeTracks( track, cueMap );
if( !cueTracks.isEmpty() )
modifiedList << cueTracks;
else
modifiedList << track;
}
else
modifiedList << track;
}
else
modifiedList << track;
}
else
{
modifiedList << track;
}
}
InsertCmdList bottomModelCmds;
foreach( Meta::TrackPtr t, modifiedList )
bottomModelCmds.append( InsertCmd( t, bottomModelRow++ ) );
if( bottomModelCmds.size() > 0 )
m_undoStack->push( new InsertTracksCmd( 0, bottomModelCmds ) );
emit changed();
}
diff --git a/src/playlist/PlaylistDock.cpp b/src/playlist/PlaylistDock.cpp
index d3203718c8..abf97e71b8 100644
--- a/src/playlist/PlaylistDock.cpp
+++ b/src/playlist/PlaylistDock.cpp
@@ -1,374 +1,370 @@
/****************************************************************************************
* Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2009 Leo Franchi <lfranchi@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "PlaylistDock"
#include "PlaylistDock.h"
#include "ActionClasses.h"
#include "App.h"
#include "MainWindow.h"
#include "PaletteHandler.h"
#include "amarokconfig.h"
#include "amarokurls/AmarokUrl.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistController.h"
#include "playlist/PlaylistDefines.h"
#include "playlist/PlaylistInfoWidget.h"
#include "playlist/PlaylistModelStack.h"
#include "playlist/PlaylistQueueEditor.h"
#include "playlist/PlaylistToolBar.h"
#include "playlist/ProgressiveSearchWidget.h"
#include "playlist/layouts/LayoutManager.h"
#include "playlist/navigators/NavigatorConfigAction.h"
#include "playlistmanager/PlaylistManager.h"
#include "core-impl/playlists/providers/user/UserPlaylistProvider.h"
#include "widgets/HorizontalDivider.h"
#include <KActionMenu>
#include <KStandardDirs>
#include <KToolBarSpacerAction>
#include <KVBox>
#include <QLabel>
#include <QToolBar>
#include <QHBoxLayout>
static const QString s_dynMode( "dynamic_mode" );
static const QString s_repopulate( "repopulate" );
static const QString s_turnOff( "turn_off" );
Playlist::Dock::Dock( QWidget* parent )
: AmarokDockWidget( i18n( "&Playlist" ), parent )
, m_barBox( 0 )
{
setObjectName( "Playlist dock" );
setAllowedAreas( Qt::AllDockWidgetAreas );
}
Playlist::PrettyListView *
Playlist::Dock::currentView()
{
ensurePolish();
return m_playlistView;
}
Playlist::SortWidget *
Playlist::Dock::sortWidget()
{
ensurePolish();
return m_sortWidget;
}
Playlist::ProgressiveSearchWidget *
Playlist::Dock::searchWidget()
{
ensurePolish();
return m_searchWidget;
}
void
Playlist::Dock::polish()
{
m_mainWidget = new KVBox( this );
setWidget( m_mainWidget );
m_mainWidget->setContentsMargins( 0, 0, 0, 0 );
m_mainWidget->setFrameShape( QFrame::NoFrame );
m_mainWidget->setMinimumWidth( 200 );
m_mainWidget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
m_mainWidget->setFocus( Qt::ActiveWindowFocusReason );
m_sortWidget = new Playlist::SortWidget( m_mainWidget );
new HorizontalDivider( m_mainWidget );
m_searchWidget = new Playlist::ProgressiveSearchWidget( m_mainWidget );
// show visual indication of dynamic playlists being enabled
- connect( The::playlistActions(), SIGNAL(navigatorChanged()),
- SLOT(showDynamicHint()) );
+ connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
+ this, &Playlist::Dock::showDynamicHint );
m_dynamicHintWidget = new QLabel( i18n( "<a href='%1'>Dynamic Mode</a> Enabled. "
"<a href='%2'>Repopulate</a> | <a href='%3'>Turn off</a>", s_dynMode,
s_repopulate, s_turnOff ), m_mainWidget );
m_dynamicHintWidget->setAlignment( Qt::AlignCenter );
m_dynamicHintWidget->setTextInteractionFlags( Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse );
m_dynamicHintWidget->setMinimumSize( 1, 1 ); // so that it doesn't prevent playlist from shrinking
- connect( m_dynamicHintWidget, SIGNAL(linkActivated(QString)), SLOT(slotDynamicHintLinkActivated(QString)) );
+ connect( m_dynamicHintWidget, &QLabel::linkActivated, this, &Dock::slotDynamicHintLinkActivated );
QFont dynamicHintWidgetFont = m_dynamicHintWidget->font();
dynamicHintWidgetFont.setPointSize( dynamicHintWidgetFont.pointSize() + 1 );
m_dynamicHintWidget->setFont( dynamicHintWidgetFont );
showDynamicHint();
paletteChanged( App::instance()->palette() );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)),
- SLOT(paletteChanged(QPalette)) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette,
+ this, &Playlist::Dock::paletteChanged );
QWidget * layoutHolder = new QWidget( m_mainWidget );
QVBoxLayout* mainPlaylistlayout = new QVBoxLayout( layoutHolder );
mainPlaylistlayout->setContentsMargins( 0, 0, 0, 0 );
m_playlistView = new PrettyListView();
m_playlistView->show();
- connect( m_searchWidget, SIGNAL(filterChanged(QString,int,bool)),
- m_playlistView, SLOT(find(QString,int,bool)) );
- connect( m_searchWidget, SIGNAL(next(QString,int)),
- m_playlistView, SLOT(findNext(QString,int)) );
- connect( m_searchWidget, SIGNAL(previous(QString,int)),
- m_playlistView, SLOT(findPrevious(QString,int)) );
- connect( m_searchWidget, SIGNAL(filterCleared()),
- m_playlistView, SLOT(clearSearchTerm()) );
- connect( m_searchWidget, SIGNAL(showOnlyMatches(bool)),
- m_playlistView, SLOT(showOnlyMatches(bool)) );
- connect( m_searchWidget, SIGNAL(activateFilterResult()),
- m_playlistView, SLOT(playFirstSelected()) );
- connect( m_searchWidget, SIGNAL(downPressed()), m_playlistView, SLOT(downOneTrack()) );
- connect( m_searchWidget, SIGNAL(upPressed()), m_playlistView, SLOT(upOneTrack()) );
-
- connect( The::mainWindow(), SIGNAL(switchQueueStateShortcut()),
- m_playlistView, SLOT(switchQueueState()) );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::filterChanged,
+ m_playlistView, &Playlist::PrettyListView::find );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::next,
+ m_playlistView, &Playlist::PrettyListView::findNext );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::previous,
+ m_playlistView, &Playlist::PrettyListView::findPrevious );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::filterCleared,
+ m_playlistView, &Playlist::PrettyListView::clearSearchTerm );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::showOnlyMatches,
+ m_playlistView, &Playlist::PrettyListView::showOnlyMatches );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::activateFilterResult,
+ m_playlistView, &Playlist::PrettyListView::playFirstSelected );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::downPressed, m_playlistView, &Playlist::PrettyListView::downOneTrack );
+ connect( m_searchWidget, &Playlist::ProgressiveSearchWidget::upPressed, m_playlistView, &Playlist::PrettyListView::upOneTrack );
+
+ connect( The::mainWindow(), &MainWindow::switchQueueStateShortcut,
+ m_playlistView, &Playlist::PrettyListView::switchQueueState );
KConfigGroup searchConfig = Amarok::config("Playlist Search");
m_playlistView->showOnlyMatches( searchConfig.readEntry( "ShowOnlyMatches", false ) );
- connect( m_playlistView, SIGNAL(found()), m_searchWidget, SLOT(match()) );
- connect( m_playlistView, SIGNAL(notFound()), m_searchWidget, SLOT(noMatch()) );
+ connect( m_playlistView, &Playlist::PrettyListView::found, m_searchWidget, &Playlist::ProgressiveSearchWidget::match );
+ connect( m_playlistView, &Playlist::PrettyListView::notFound, m_searchWidget, &Playlist::ProgressiveSearchWidget::noMatch );
- connect( LayoutManager::instance(), SIGNAL(activeLayoutChanged()),
- m_playlistView, SLOT(reset()) );
+ connect( LayoutManager::instance(), &LayoutManager::activeLayoutChanged,
+ m_playlistView, &Playlist::PrettyListView::reset );
mainPlaylistlayout->setSpacing( 0 );
mainPlaylistlayout->addWidget( m_playlistView );
ModelStack::instance(); //This also creates the Controller.
{ // START: Playlist toolbar
// action toolbar
m_barBox = new KHBox( m_mainWidget );
m_barBox->setObjectName( "PlaylistBarBox" );
m_barBox->setContentsMargins( 0, 0, 4, 0 );
m_barBox->setFixedHeight( 36 );
// Use QToolBar instead of KToolBar, see bug 228390
Playlist::ToolBar *plBar = new Playlist::ToolBar( m_barBox );
plBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
plBar->setMovable( false );
QActionGroup *playlistActions = new QActionGroup( m_mainWidget );
playlistActions->addAction( Amarok::actionCollection()->action( "playlist_clear" ) );
m_savePlaylistMenu = new KActionMenu( QIcon::fromTheme( "document-save-amarok" ),
i18n("&Save Current Playlist"), m_mainWidget );
m_savePlaylistMenu->addAction( Amarok::actionCollection()->action( "playlist_export" ) );
m_saveActions = new KActionCollection( m_mainWidget );
- connect( m_savePlaylistMenu, SIGNAL(triggered(bool)),
- SLOT(slotSaveCurrentPlaylist()) );
+ connect( m_savePlaylistMenu, &KActionMenu::triggered,
+ this, &Dock::slotSaveCurrentPlaylist );
foreach( Playlists::PlaylistProvider *provider, The::playlistManager()->providersForCategory(
PlaylistManager::UserPlaylist ) )
{
playlistProviderAdded( provider, PlaylistManager::UserPlaylist );
}
- connect( The::playlistManager(),
- SIGNAL(providerAdded(Playlists::PlaylistProvider*,int)),
- SLOT(playlistProviderAdded(Playlists::PlaylistProvider*,int))
- );
- connect( The::playlistManager(),
- SIGNAL(providerRemoved(Playlists::PlaylistProvider*,int)),
- SLOT(playlistProviderRemoved(Playlists::PlaylistProvider*,int))
- );
+ connect( The::playlistManager(), &PlaylistManager::providerAdded,
+ this, &Dock::playlistProviderAdded );
+ connect( The::playlistManager(), &PlaylistManager::providerRemoved,
+ this, &Dock::playlistProviderRemoved );
playlistActions->addAction( m_savePlaylistMenu );
playlistActions->addAction( Amarok::actionCollection()->action( "playlist_undo" ) );
//redo action can be accessed from menu > Playlist
playlistActions->addAction( Amarok::actionCollection()->action( "show_active_track" ) );
plBar->addCollapsibleActions( playlistActions );
NavigatorConfigAction *navigatorConfig = new NavigatorConfigAction( m_mainWidget );
plBar->addAction( navigatorConfig );
QToolButton *toolButton =
qobject_cast<QToolButton *>(plBar->widgetForAction( navigatorConfig ) );
if( toolButton )
toolButton->setPopupMode( QToolButton::InstantPopup );
plBar->addAction( new KToolBarSpacerAction( m_mainWidget ) );
// label widget
new PlaylistInfoWidget( m_barBox );
} // END Playlist Toolbar
//set correct colors
paletteChanged( QApplication::palette() );
// If it is active, clear the search filter before replacing the playlist. Fixes Bug #200709.
- connect( The::playlistController(), SIGNAL(replacingPlaylist()),
- SLOT(clearFilterIfActive()) );
+ connect( The::playlistController(), &Playlist::Controller::replacingPlaylist,
+ this, &Playlist::Dock::clearFilterIfActive );
}
QSize
Playlist::Dock::sizeHint() const
{
return QSize( static_cast<QWidget*>( parent() )->size().width() / 4 , 300 );
}
void
Playlist::Dock::paletteChanged( const QPalette &palette )
{
const QString backgroundColor = PaletteHandler::highlightColor().name();
const QString textColor = palette.color( QPalette::Active, QPalette::HighlightedText ).name();
const QString linkColor = palette.color( QPalette::Active, QPalette::Link ).name();
const QString ridgeColor = palette.color( QPalette::Active, QPalette::Window ).name();
QString hintStyle( "QLabel { background-color: %1; color: %2; border-radius: 3px; } "
"a { color: %3; }" );
hintStyle = hintStyle.arg( backgroundColor, textColor, linkColor );
QString barStyle( "QFrame#PlaylistBarBox { border: 1px ridge %1; background-color: %2; "
" color: %3; border-radius: 3px; } "
"QLabel { color: %4; }" );
barStyle = barStyle.arg( ridgeColor, backgroundColor, textColor );
m_dynamicHintWidget->setStyleSheet( hintStyle );
if( m_barBox )
m_barBox->setStyleSheet( barStyle );
}
void
Playlist::Dock::playlistProviderAdded( Playlists::PlaylistProvider *provider, int category )
{
if( category != PlaylistManager::UserPlaylist )
return;
debug() << "Adding provider: " << provider->prettyName();
Playlists::UserPlaylistProvider *userProvider =
dynamic_cast<Playlists::UserPlaylistProvider *>(provider);
if( userProvider == 0 )
return;
QAction *action = new QAction( userProvider->icon(),
i18n("&Save playlist to \"%1\"", provider->prettyName() ),
this );
action->setData( QVariant::fromValue(
QWeakPointer<Playlists::UserPlaylistProvider>( userProvider ) ) );
m_saveActions->addAction( QString::number( (qlonglong) userProvider ), action );
// insert the playlist provider actions before "export"
QAction* exportAction = Amarok::actionCollection()->action( "playlist_export" );
m_savePlaylistMenu->insertAction( exportAction, action );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveCurrentPlaylist()) );
+ connect( action, &QAction::triggered, this, &Playlist::Dock::slotSaveCurrentPlaylist );
}
void
Playlist::Dock::playlistProviderRemoved( Playlists::PlaylistProvider *provider, int category )
{
if( category != PlaylistManager::UserPlaylist )
return;
QAction *action = m_saveActions->action( QString::number( (qlonglong) provider ) );
if( action )
m_savePlaylistMenu->removeAction( action );
else
warning() << __PRETTY_FUNCTION__ << ": no save action for provider" << provider->prettyName();
}
void
Playlist::Dock::slotSaveCurrentPlaylist()
{
DEBUG_BLOCK
QAction *action = qobject_cast<QAction *>( QObject::sender() );
if( action == 0 )
return;
QWeakPointer<Playlists::UserPlaylistProvider> pointer =
action->data().value< QWeakPointer<Playlists::UserPlaylistProvider> >();
Playlists::UserPlaylistProvider* provider = pointer.data();
const Meta::TrackList tracks = The::playlist()->tracks();
The::playlistManager()->save( tracks, Amarok::generatePlaylistName( tracks ), provider );
}
void
Playlist::Dock::slotEditQueue()
{
if( m_playlistQueueEditor ) {
m_playlistQueueEditor.data()->raise();
return;
}
m_playlistQueueEditor = new PlaylistQueueEditor;
m_playlistQueueEditor.data()->setAttribute( Qt::WA_DeleteOnClose );
m_playlistQueueEditor.data()->show();
}
void
Playlist::Dock::showActiveTrack()
{
ensurePolish();
m_playlistView->scrollToActiveTrack();
}
void
Playlist::Dock::editTrackInfo()
{
m_playlistView->editTrackInformation();
}
void
Playlist::Dock::showDynamicHint() // slot
{
DEBUG_BLOCK
if( AmarokConfig::dynamicMode() )
m_dynamicHintWidget->show();
else
m_dynamicHintWidget->hide();
}
void
Playlist::Dock::clearFilterIfActive() // slot
{
DEBUG_BLOCK
KConfigGroup config = Amarok::config( "Playlist Search" );
bool filterActive = config.readEntry( "ShowOnlyMatches", true );
if( filterActive )
m_searchWidget->slotFilterClear();
}
void
Playlist::Dock::slotDynamicHintLinkActivated( const QString &href )
{
if( href == s_dynMode )
AmarokUrl( "amarok://navigate/playlists/dynamic category" ).run();
else if( href == s_repopulate )
The::playlistActions()->repopulateDynamicPlaylist();
else if( href == s_turnOff )
The::playlistActions()->enableDynamicMode( false );
}
diff --git a/src/playlist/PlaylistDock.h b/src/playlist/PlaylistDock.h
index 7fdccc4dc3..8862d66586 100644
--- a/src/playlist/PlaylistDock.h
+++ b/src/playlist/PlaylistDock.h
@@ -1,94 +1,94 @@
/****************************************************************************************
* Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_PLAYLISTWIDGET_H
#define AMAROK_PLAYLISTWIDGET_H
#include "PlaylistSortWidget.h"
#include "view/listview/PrettyListView.h"
#include "widgets/AmarokDockWidget.h"
#include "core-impl/playlists/providers/user/UserPlaylistProvider.h"
#include <QWeakPointer>
class KActionCollection;
class KActionMenu;
class KVBox;
class QLabel;
class QWidget;
class PlaylistQueueEditor;
namespace Playlists {
class PlaylistProvider;
class UserPlaylistProvider;
}
namespace Playlist
{
class ProgressiveSearchWidget;
class Dock : public AmarokDockWidget
{
Q_OBJECT
public:
Dock( QWidget* parent );
PrettyListView *currentView();
SortWidget *sortWidget();
ProgressiveSearchWidget *searchWidget();
void showActiveTrack();
void editTrackInfo();
void polish();
public Q_SLOTS:
void clearFilterIfActive();
+ void slotEditQueue();
protected:
QSize sizeHint() const;
private Q_SLOTS:
/** show or hide the dynamic playlist mode indicator */
void showDynamicHint();
void paletteChanged( const QPalette& palette );
void playlistProviderAdded( Playlists::PlaylistProvider *provider, int category );
void playlistProviderRemoved( Playlists::PlaylistProvider *provider, int category );
void slotSaveCurrentPlaylist();
- void slotEditQueue();
void slotDynamicHintLinkActivated( const QString &href );
private:
KActionMenu *m_savePlaylistMenu;
KActionCollection *m_saveActions;
QWeakPointer<PlaylistQueueEditor> m_playlistQueueEditor;
PrettyListView *m_playlistView;
ProgressiveSearchWidget *m_searchWidget;
SortWidget *m_sortWidget;
QLabel *m_dynamicHintWidget;
KVBox *m_mainWidget;
QFrame *m_barBox;
};
}
Q_DECLARE_METATYPE( QWeakPointer<Playlists::UserPlaylistProvider> )
#endif
diff --git a/src/playlist/PlaylistInfoWidget.cpp b/src/playlist/PlaylistInfoWidget.cpp
index 701769bd95..58a364c6c1 100644
--- a/src/playlist/PlaylistInfoWidget.cpp
+++ b/src/playlist/PlaylistInfoWidget.cpp
@@ -1,129 +1,125 @@
/****************************************************************************************
* Copyright (c) 2011 Kevin Funk <krf@electrostorm.net> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistInfoWidget.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "QEvent"
#include "QHelpEvent"
#include "QToolTip"
PlaylistInfoWidget::PlaylistInfoWidget( QWidget *parent )
: QLabel( parent )
{
- connect( Playlist::ModelStack::instance()->bottom(),
- SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- SLOT(updateTotalPlaylistLength()) );
+ connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::dataChanged,
+ this, &PlaylistInfoWidget::updateTotalPlaylistLength );
// Ignore The::playlist() layoutChanged: rows moving around does not change the total playlist length.
- connect( Playlist::ModelStack::instance()->bottom(), SIGNAL(modelReset()),
- SLOT(updateTotalPlaylistLength()) );
- connect( Playlist::ModelStack::instance()->bottom(),
- SIGNAL(rowsInserted(QModelIndex,int,int)),
- SLOT(updateTotalPlaylistLength()) );
- connect( Playlist::ModelStack::instance()->bottom(),
- SIGNAL(rowsRemoved(QModelIndex,int,int)),
- SLOT(updateTotalPlaylistLength()) );
+ connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::modelReset,
+ this, &PlaylistInfoWidget::updateTotalPlaylistLength );
+ connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::rowsInserted,
+ this, &PlaylistInfoWidget::updateTotalPlaylistLength );
+ connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::rowsRemoved,
+ this, &PlaylistInfoWidget::updateTotalPlaylistLength );
updateTotalPlaylistLength();
}
PlaylistInfoWidget::~PlaylistInfoWidget()
-{
-}
+{}
void
PlaylistInfoWidget::updateTotalPlaylistLength() //SLOT
{
const quint64 totalLength = The::playlist()->totalLength();
const int trackCount = The::playlist()->qaim()->rowCount();
if( totalLength > 0 && trackCount > 0 )
{
const QString prettyTotalLength = Meta::msToPrettyTime( totalLength );
setText( i18ncp( "%1 is number of tracks, %2 is time",
"%1 track (%2)", "%1 tracks (%2)",
trackCount, prettyTotalLength ) );
}
else if( ( totalLength == 0 ) && ( trackCount > 0 ) )
{
setText( i18ncp( "%1 is number of tracks", "%1 track", "%1 tracks", trackCount ) );
}
else // Total Length will not be > 0 if trackCount is 0, so we can ignore it
{
setText( i18n( "No tracks" ) );
}
}
bool
PlaylistInfoWidget::event( QEvent *event )
{
if( event->type() == QEvent::ToolTip ) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
const quint64 totalLength = The::playlist()->totalLength();
const int trackCount = The::playlist()->qaim()->rowCount();
if( totalLength == 0 || trackCount == 0 )
{
QToolTip::hideText();
event->ignore();
}
else
{
// - determine the queued tracks
quint64 queuedTotalLength( 0 );
quint64 queuedTotalSize( 0 );
int queuedCount( 0 );
QQueue<quint64> queue = The::playlistActions()->queue();
foreach( quint64 id, queue )
{
Meta::TrackPtr track = The::playlist()->trackForId( id );
queuedTotalLength += track->length();
queuedTotalSize += track->filesize();
++queuedCount;
}
// - set the tooltip
const quint64 totalSize = The::playlist()->totalSize();
const QString prettyTotalSize = Meta::prettyFilesize( totalSize );
const QString prettyQueuedTotalLength = Meta::msToPrettyTime( queuedTotalLength );
const QString prettyQueuedTotalSize = Meta::prettyFilesize( queuedTotalSize );
QString tooltipLabel;
if( queuedCount > 0 && queuedTotalLength > 0 )
{
tooltipLabel = i18n( "Total playlist size: %1", prettyTotalSize ) + '\n'
+ i18n( "Queue size: %1", prettyQueuedTotalSize ) + '\n'
+ i18n( "Queue length: %1", prettyQueuedTotalLength );
}
else
{
tooltipLabel = i18n( "Total playlist size: %1", prettyTotalSize );
}
QToolTip::showText( helpEvent->globalPos(), tooltipLabel );
}
return true;
}
return QWidget::event(event);
}
diff --git a/src/playlist/PlaylistModel.cpp b/src/playlist/PlaylistModel.cpp
index 370538878a..1ba5ad139b 100644
--- a/src/playlist/PlaylistModel.cpp
+++ b/src/playlist/PlaylistModel.cpp
@@ -1,1273 +1,1273 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2007-2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> *
* Copyright (c) 2010 Dennis Francis <dennisfrancis.in@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::Model"
#include "PlaylistModel.h"
#include "core/support/Amarok.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "AmarokMimeData.h"
#include "core/capabilities/ReadLabelCapability.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "core/capabilities/MultiSourceCapability.h"
#include "core/capabilities/SourceInfoCapability.h"
#include "core/collections/Collection.h"
#include "core/meta/Statistics.h"
#include "core/meta/support/MetaUtility.h"
#include "PlaylistDefines.h"
#include "PlaylistActions.h"
#include "PlaylistController.h"
#include "PlaylistItem.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "core-impl/support/TrackLoader.h"
#include "playlist/UndoCommands.h"
#include <KGlobal>
#include <KIconLoader>
#include <KLocale>
#include <QUrl>
#include <QAction>
#include <QTimer>
#include <QDate>
#include <QStringList>
#include <QTextDocument>
#include <typeinfo>
#define TOOLTIP_STATIC_LINEBREAK 50
bool Playlist::Model::s_tooltipColumns[NUM_COLUMNS];
bool Playlist::Model::s_showToolTip;
// ------- helper functions for the tooltip
static bool
fitsInOneLineHTML(const QString& text)
{
// The size of the normal, standard line
const int lnSize = TOOLTIP_STATIC_LINEBREAK;
return (text.size() <= lnSize);
}
static QString
breakLongLinesHTML( const QString &origText )
{
// filter-out HTML tags..
QString text( origText );
text.replace( '&', "&amp;" ); // needs to be first, obviously
text.replace( '<', "&lt;" );
text.replace( '>', "&gt;" );
// Now let's break up long lines so that the tooltip doesn't become hideously large
if( fitsInOneLineHTML( text ) )
// If the text is not too long, return it as it is
return text;
else
{
const int lnSize = TOOLTIP_STATIC_LINEBREAK;
QString textInLines;
QStringList words = text.trimmed().split(' ');
int lineLength = 0;
while(words.size() > 0)
{
QString word = words.first();
// Let's check if the next word makes the current line too long.
if (lineLength + word.size() + 1 > lnSize)
{
if (lineLength > 0)
{
textInLines += "<br/>";
}
lineLength = 0;
// Let's check if the next word is not too long for the new line to contain
// If it is, cut it
while (word.size() > lnSize)
{
QString wordPart = word;
wordPart.resize(lnSize);
word.remove(0,lnSize);
textInLines += wordPart + "<br/>";
}
}
textInLines += word + ' ';
lineLength += word.size() + 1;
words.removeFirst();
}
return textInLines.trimmed();
}
}
/**
* Prepares a row for the playlist tooltips consisting of an icon representing
* an mp3 tag and its value
* @param column The colunm used to display the icon
* @param value The QString value to be shown
* @return The line to be shown or an empty QString if the value is null
*/
static QString
HTMLLine( const Playlist::Column& column, const QString& value, bool force = false )
{
if( !value.isEmpty() || force )
{
QString line;
line += "<tr><td align=\"right\">";
line += "<img src=\""+KIconLoader::global()->iconPath( Playlist::iconName( column ), -16)+"\" />";
line += "</td><td align=\"left\">";
line += breakLongLinesHTML( value );
line += "</td></tr>";
return line;
}
else
return QString();
}
/**
* Prepares a row for the playlist tooltips consisting of an icon representing
* an mp3 tag and its value
* @param column The colunm used to display the icon
* @param value The integer value to be shown
* @return The line to be shown or an empty QString if the value is 0
*/
static QString
HTMLLine( const Playlist::Column& column, const int value, bool force = false )
{
// there is currenly no numeric meta-data that would have sense if it were negative.
// also, zero denotes not available, unknow etc; don't show these unless forced.
if( value > 0 || force )
{
return HTMLLine( column, QString::number( value ) );
}
else
return QString();
}
Playlist::Model::Model( QObject *parent )
: QAbstractListModel( parent )
, m_activeRow( -1 )
, m_totalLength( 0 )
, m_totalSize( 0 )
, m_setStateOfItem_batchMinRow( -1 )
, m_saveStateTimer( new QTimer(this) )
{
DEBUG_BLOCK
m_saveStateTimer->setInterval( 5000 );
m_saveStateTimer->setSingleShot( true );
- connect( m_saveStateTimer, SIGNAL(timeout()),
- this, SLOT(saveState()) );
- connect( this, SIGNAL(modelReset()),
- this, SLOT(queueSaveState()) );
- connect( this, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- this, SLOT(queueSaveState()) );
- connect( this, SIGNAL(rowsInserted(QModelIndex,int,int)),
- this, SLOT(queueSaveState()) );
- connect( this, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
- this, SLOT(queueSaveState()) );
- connect( this, SIGNAL(rowsRemoved(QModelIndex,int,int)),
- this, SLOT(queueSaveState()) );
+ connect( m_saveStateTimer, &QTimer::timeout,
+ this, &Playlist::Model::saveState );
+ connect( this, &Playlist::Model::modelReset,
+ this, &Playlist::Model::queueSaveState );
+ connect( this, &Playlist::Model::dataChanged,
+ this, &Playlist::Model::queueSaveState );
+ connect( this, &Playlist::Model::rowsInserted,
+ this, &Playlist::Model::queueSaveState );
+ connect( this, &Playlist::Model::rowsMoved,
+ this, &Playlist::Model::queueSaveState );
+ connect( this, &Playlist::Model::rowsRemoved,
+ this, &Playlist::Model::queueSaveState );
}
Playlist::Model::~Model()
{
DEBUG_BLOCK
// Save current playlist
exportPlaylist( Amarok::defaultPlaylistPath() );
qDeleteAll( m_items );
}
void
Playlist::Model::saveState()
{
exportPlaylist( Amarok::defaultPlaylistPath() );
}
void
Playlist::Model::queueSaveState()
{
if ( !m_saveStateTimer->isActive() )
m_saveStateTimer->start();
}
void
Playlist::Model::insertTracksFromTrackLoader( const Meta::TrackList &tracks )
{
QObject *loader = sender();
if( !sender() )
{
warning() << __PRETTY_FUNCTION__ << "can only be connected to TrackLoader";
return;
}
int insertRow = loader->property( "beginRow" ).toInt();
Controller::instance()->insertTracks( insertRow, tracks );
}
QVariant
Playlist::Model::headerData( int section, Qt::Orientation orientation, int role ) const
{
Q_UNUSED( orientation );
if ( role != Qt::DisplayRole )
return QVariant();
return columnName( static_cast<Playlist::Column>( section ) );
}
void
Playlist::Model::setTooltipColumns( bool columns[] )
{
for( int i=0; i<Playlist::NUM_COLUMNS; ++i )
s_tooltipColumns[i] = columns[i];
}
void
Playlist::Model::enableToolTip( bool enable )
{
s_showToolTip = enable;
}
QString
Playlist::Model::tooltipFor( Meta::TrackPtr track ) const
{
QString text;
KLocale *locale = KGlobal::locale();
// get the shared pointers now to be thread safe
Meta::ArtistPtr artist = track->artist();
Meta::AlbumPtr album = track->album();
Meta::ArtistPtr albumArtist = album ? album->albumArtist() : Meta::ArtistPtr();
Meta::GenrePtr genre = track->genre();
Meta::ComposerPtr composer = track->composer();
Meta::YearPtr year = track->year();
Meta::StatisticsPtr statistics = track->statistics();
if( !track->isPlayable() )
text += i18n( "<b>Note:</b> This track is not playable.<br>%1", track->notPlayableReason() );
if( s_tooltipColumns[Playlist::Title] )
text += HTMLLine( Playlist::Title, track->name() );
if( s_tooltipColumns[Playlist::Artist] && artist )
text += HTMLLine( Playlist::Artist, artist->name() );
// only show albumArtist when different from artist (it should suffice to compare pointers)
if( s_tooltipColumns[Playlist::AlbumArtist] && albumArtist && albumArtist != artist )
text += HTMLLine( Playlist::AlbumArtist, albumArtist->name() );
if( s_tooltipColumns[Playlist::Album] && album )
text += HTMLLine( Playlist::Album, album->name() );
if( s_tooltipColumns[Playlist::DiscNumber] )
text += HTMLLine( Playlist::DiscNumber, track->discNumber() );
if( s_tooltipColumns[Playlist::TrackNumber] )
text += HTMLLine( Playlist::TrackNumber, track->trackNumber() );
if( s_tooltipColumns[Playlist::Composer] && composer )
text += HTMLLine( Playlist::Composer, composer->name() );
if( s_tooltipColumns[Playlist::Genre] && genre )
text += HTMLLine( Playlist::Genre, genre->name() );
if( s_tooltipColumns[Playlist::Year] && year && year->year() > 0 )
text += HTMLLine( Playlist::Year, year->year() );
if( s_tooltipColumns[Playlist::Bpm] )
text += HTMLLine( Playlist::Bpm, track->bpm() );
if( s_tooltipColumns[Playlist::Comment]) {
if ( !(fitsInOneLineHTML( track->comment() ) ) )
text += HTMLLine( Playlist::Comment, i18n( "(...)" ) );
else
text += HTMLLine( Playlist::Comment, track->comment() );
}
if( s_tooltipColumns[Playlist::Labels] && !track->labels().empty() )
{
QStringList labels;
foreach( Meta::LabelPtr label, track->labels() )
{
if( label )
labels << label->name();
}
text += HTMLLine( Playlist::Labels, labels.join( QString(", ") ) );
}
if( s_tooltipColumns[Playlist::Score] )
text += HTMLLine( Playlist::Score, statistics->score() );
if( s_tooltipColumns[Playlist::Rating] )
text += HTMLLine( Playlist::Rating, QString::number( statistics->rating()/2.0 ) );
if( s_tooltipColumns[Playlist::PlayCount] )
text += HTMLLine( Playlist::PlayCount, statistics->playCount(), true );
if( s_tooltipColumns[Playlist::LastPlayed] && statistics->lastPlayed().isValid() )
text += HTMLLine( Playlist::LastPlayed, locale->formatDateTime( statistics->lastPlayed() ) );
if( s_tooltipColumns[Playlist::Bitrate] && track->bitrate() )
text += HTMLLine( Playlist::Bitrate, i18nc( "%1: bitrate", "%1 kbps", track->bitrate() ) );
if( text.isEmpty() )
text = QString( i18n( "No extra information available" ) );
else
text = QString("<table>"+ text +"</table>");
return text;
}
QVariant
Playlist::Model::data( const QModelIndex& index, int role ) const
{
int row = index.row();
if ( !index.isValid() || !rowExists( row ) )
return QVariant();
if ( role == UniqueIdRole )
return QVariant( idAt( row ) );
else if ( role == ActiveTrackRole )
return ( row == m_activeRow );
else if ( role == TrackRole && m_items.at( row )->track() )
return QVariant::fromValue( m_items.at( row )->track() );
else if ( role == StateRole )
return m_items.at( row )->state();
else if ( role == QueuePositionRole )
return Actions::instance()->queuePosition( idAt( row ) ) + 1;
else if ( role == InCollectionRole )
return m_items.at( row )->track()->inCollection();
else if ( role == MultiSourceRole )
return m_items.at( row )->track()->has<Capabilities::MultiSourceCapability>();
else if ( role == StopAfterTrackRole )
return Actions::instance()->willStopAfterTrack( idAt( row ) );
else if ( role == Qt::ToolTipRole )
{
Meta::TrackPtr track = m_items.at( row )->track();
if( s_showToolTip )
return tooltipFor( track );
else if( !track->isPlayable() )
return i18n( "<b>Note:</b> This track is not playable.<br>%1", track->notPlayableReason() );
}
else if ( role == Qt::DisplayRole )
{
Meta::TrackPtr track = m_items.at( row )->track();
Meta::AlbumPtr album = track->album();
Meta::StatisticsPtr statistics = track->statistics();
switch ( index.column() )
{
case PlaceHolder:
break;
case Album:
{
if( album )
return album->name();
break;
}
case AlbumArtist:
{
if( album )
{
Meta::ArtistPtr artist = album->albumArtist();
if( artist )
return artist->name();
}
break;
}
case Artist:
{
Meta::ArtistPtr artist = track->artist();
if( artist )
return artist->name();
break;
}
case Bitrate:
{
return Meta::prettyBitrate( track->bitrate() );
}
case Bpm:
{
if( track->bpm() > 0.0 )
return QString::number( track->bpm() );
break;
}
case Comment:
{
return track->comment();
}
case Composer:
{
Meta::ComposerPtr composer = track->composer();
if( composer )
return composer->name();
break;
}
case CoverImage:
{
if( album )
return The::svgHandler()->imageWithBorder( album, 100 ); //FIXME:size?
break;
}
case Directory:
{
if( track->playableUrl().isLocalFile() )
return track->playableUrl().adjusted(QUrl::RemoveFilename).path();
break;
}
case DiscNumber:
{
if( track->discNumber() > 0 )
return track->discNumber();
break;
}
case Filename:
{
if( track->playableUrl().isLocalFile() )
return track->playableUrl().fileName();
break;
}
case Filesize:
{
return Meta::prettyFilesize( track->filesize() );
}
case Genre:
{
Meta::GenrePtr genre = track->genre();
if( genre )
return genre->name();
break;
}
case GroupLength:
{
return Meta::secToPrettyTime( 0 );
}
case GroupTracks:
{
return QString();
}
case Labels:
{
if( track )
{
QStringList labelNames;
foreach( const Meta::LabelPtr &label, track->labels() )
{
labelNames << label->prettyName();
}
return labelNames.join( ", " );
}
break;
}
case LastPlayed:
{
if( statistics->playCount() == 0 )
return i18nc( "The amount of time since last played", "Never" );
else if( statistics->lastPlayed().isValid() )
return Amarok::verboseTimeSince( statistics->lastPlayed() );
else
return i18nc( "When this track was last played", "Unknown" );
}
case Length:
{
return Meta::msToPrettyTime( track->length() );
}
case LengthInSeconds:
{
return track->length() / 1000;
}
case Mood:
{
return QString(); //FIXME
}
case PlayCount:
{
return statistics->playCount();
}
case Rating:
{
return statistics->rating();
}
case SampleRate:
{
if( track->sampleRate() > 0 )
return track->sampleRate();
break;
}
case Score:
{
return int( statistics->score() ); // Cast to int, as we don't need to show the decimals in the view..
}
case Source:
{
QString sourceName;
Capabilities::SourceInfoCapability *sic = track->create<Capabilities::SourceInfoCapability>();
if ( sic )
{
sourceName = sic->sourceName();
delete sic;
}
else
{
sourceName = track->collection() ? track->collection()->prettyName() : QString();
}
return sourceName;
}
case SourceEmblem:
{
QPixmap emblem;
Capabilities::SourceInfoCapability *sic = track->create<Capabilities::SourceInfoCapability>();
if ( sic )
{
QString source = sic->sourceName();
if ( !source.isEmpty() )
emblem = sic->emblem();
delete sic;
}
return emblem;
}
case Title:
{
return track->prettyName();
}
case TitleWithTrackNum:
{
QString trackString;
QString trackName = track->prettyName();
if( track->trackNumber() > 0 )
{
QString trackNumber = QString::number( track->trackNumber() );
trackString = QString( trackNumber + " - " + trackName );
} else
trackString = trackName;
return trackString;
}
case TrackNumber:
{
if( track->trackNumber() > 0 )
return track->trackNumber();
break;
}
case Type:
{
return track->type();
}
case Year:
{
Meta::YearPtr year = track->year();
if( year && year->year() > 0 )
return year->year();
break;
}
default:
return QVariant(); // returning a variant instead of a string inside a variant is cheaper
}
}
// else
return QVariant();
}
Qt::DropActions
Playlist::Model::supportedDropActions() const
{
return Qt::MoveAction | QAbstractListModel::supportedDropActions();
}
Qt::ItemFlags
Playlist::Model::flags( const QModelIndex &index ) const
{
if ( index.isValid() )
return ( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsEditable );
return Qt::ItemIsDropEnabled;
}
QStringList
Playlist::Model::mimeTypes() const
{
QStringList ret = QAbstractListModel::mimeTypes();
ret << AmarokMimeData::TRACK_MIME;
ret << "text/uri-list"; //we do accept urls
return ret;
}
QMimeData*
Playlist::Model::mimeData( const QModelIndexList &indexes ) const
{
AmarokMimeData* mime = new AmarokMimeData();
Meta::TrackList selectedTracks;
foreach( const QModelIndex &it, indexes )
selectedTracks << m_items.at( it.row() )->track();
mime->setTracks( selectedTracks );
return mime;
}
bool
Playlist::Model::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int, const QModelIndex &parent )
{
if ( action == Qt::IgnoreAction )
return true;
int beginRow;
if ( row != -1 )
beginRow = row;
else if ( parent.isValid() )
beginRow = parent.row();
else
beginRow = m_items.size();
if( data->hasFormat( AmarokMimeData::TRACK_MIME ) )
{
debug() << "this is a track";
const AmarokMimeData* trackListDrag = qobject_cast<const AmarokMimeData*>( data );
if( trackListDrag )
{
Meta::TrackList tracks = trackListDrag->tracks();
qStableSort( tracks.begin(), tracks.end(), Meta::Track::lessThan );
The::playlistController()->insertTracks( beginRow, tracks );
}
return true;
}
else if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) )
{
debug() << "this is a playlist";
const AmarokMimeData* dragList = qobject_cast<const AmarokMimeData*>( data );
if( dragList )
The::playlistController()->insertPlaylists( beginRow, dragList->playlists() );
return true;
}
else if( data->hasFormat( AmarokMimeData::PODCASTEPISODE_MIME ) )
{
debug() << "this is a podcast episode";
const AmarokMimeData* dragList = qobject_cast<const AmarokMimeData*>( data );
if( dragList )
{
Meta::TrackList tracks;
foreach( Podcasts::PodcastEpisodePtr episode, dragList->podcastEpisodes() )
tracks << Meta::TrackPtr::staticCast( episode );
The::playlistController()->insertTracks( beginRow, tracks );
}
return true;
}
else if( data->hasFormat( AmarokMimeData::PODCASTCHANNEL_MIME ) )
{
debug() << "this is a podcast channel";
const AmarokMimeData* dragList = qobject_cast<const AmarokMimeData*>( data );
if( dragList )
{
Meta::TrackList tracks;
foreach( Podcasts::PodcastChannelPtr channel, dragList->podcastChannels() )
foreach( Podcasts::PodcastEpisodePtr episode, channel->episodes() )
tracks << Meta::TrackPtr::staticCast( episode );
The::playlistController()->insertTracks( beginRow, tracks );
}
return true;
}
else if( data->hasUrls() )
{
debug() << "this is _something_ with a url....";
TrackLoader *dl = new TrackLoader(); // auto-deletes itself
dl->setProperty( "beginRow", beginRow );
- connect( dl, SIGNAL(finished(Meta::TrackList)), SLOT(insertTracksFromTrackLoader(Meta::TrackList)) );
+ connect( dl, &TrackLoader::finished, this, &Model::insertTracksFromTrackLoader );
dl->init( data->urls() );
return true;
}
debug() << "I have no idea what the hell this is...";
return false;
}
void
Playlist::Model::setActiveRow( int row )
{
if ( rowExists( row ) )
{
setStateOfRow( row, Item::Played );
m_activeRow = row;
emit activeTrackChanged( m_items.at( row )->id() );
}
else
{
m_activeRow = -1;
emit activeTrackChanged( 0 );
}
}
void
Playlist::Model::emitQueueChanged()
{
emit queueChanged();
}
int
Playlist::Model::queuePositionOfRow( int row )
{
return Actions::instance()->queuePosition( idAt( row ) ) + 1;
}
Playlist::Item::State
Playlist::Model::stateOfRow( int row ) const
{
if ( rowExists( row ) )
return m_items.at( row )->state();
else
return Item::Invalid;
}
bool
Playlist::Model::containsTrack( const Meta::TrackPtr& track ) const
{
foreach( Item* i, m_items )
{
if ( i->track() == track )
return true;
}
return false;
}
int
Playlist::Model::firstRowForTrack( const Meta::TrackPtr& track ) const
{
int row = 0;
foreach( Item* i, m_items )
{
if ( i->track() == track )
return row;
row++;
}
return -1;
}
QSet<int>
Playlist::Model::allRowsForTrack( const Meta::TrackPtr& track ) const
{
QSet<int> trackRows;
int row = 0;
foreach( Item* i, m_items )
{
if ( i->track() == track )
trackRows.insert( row );
row++;
}
return trackRows;
}
Meta::TrackPtr
Playlist::Model::trackAt( int row ) const
{
if ( rowExists( row ) )
return m_items.at( row )->track();
else
return Meta::TrackPtr();
}
Meta::TrackPtr
Playlist::Model::activeTrack() const
{
if ( rowExists( m_activeRow ) )
return m_items.at( m_activeRow )->track();
else
return Meta::TrackPtr();
}
int
Playlist::Model::rowForId( const quint64 id ) const
{
return m_items.indexOf( m_itemIds.value( id ) ); // Returns -1 on miss, same as our API.
}
Meta::TrackPtr
Playlist::Model::trackForId( const quint64 id ) const
{
Item* item = m_itemIds.value( id, 0 );
if ( item )
return item->track();
else
return Meta::TrackPtr();
}
quint64
Playlist::Model::idAt( const int row ) const
{
if ( rowExists( row ) )
return m_items.at( row )->id();
else
return 0;
}
quint64
Playlist::Model::activeId() const
{
if ( rowExists( m_activeRow ) )
return m_items.at( m_activeRow )->id();
else
return 0;
}
Playlist::Item::State
Playlist::Model::stateOfId( quint64 id ) const
{
Item* item = m_itemIds.value( id, 0 );
if ( item )
return item->state();
else
return Item::Invalid;
}
void
Playlist::Model::metadataChanged( Meta::TrackPtr track )
{
int row = 0;
foreach( Item* i, m_items )
{
if ( i->track() == track )
{
// ensure that we really have the correct album subscribed (in case it changed)
Meta::AlbumPtr album = track->album();
if( album )
subscribeTo( album );
emit dataChanged( index( row, 0 ), index( row, columnCount() - 1 ) );
}
row++;
}
}
void
Playlist::Model::metadataChanged( Meta::AlbumPtr album )
{
// Mainly to get update about changed covers
// -- search for all the tracks having this album
bool found = false;
const int size = m_items.size();
for ( int i = 0; i < size; i++ )
{
if ( m_items.at( i )->track()->album() == album )
{
emit dataChanged( index( i, 0 ), index( i, columnCount() - 1 ) );
found = true;
debug()<<"Metadata updated for album"<<album->prettyName();
}
}
// -- unsubscribe if we don't have a track from that album left.
// this can happen if the album of a track changed
if( !found )
unsubscribeFrom( album );
}
bool
Playlist::Model::exportPlaylist( const QString &path, bool relative ) const
{
// check queue state
QQueue<quint64> queueIds = The::playlistActions()->queue();
QList<int> queued;
foreach( quint64 id, queueIds ) {
queued << rowForId( id );
}
return Playlists::exportPlaylistFile( tracks(), QUrl::fromLocalFile(path), relative, queued);
}
Meta::TrackList
Playlist::Model::tracks() const
{
Meta::TrackList tl;
foreach( Item* item, m_items )
tl << item->track();
return tl;
}
QString
Playlist::Model::prettyColumnName( Column index ) //static
{
switch ( index )
{
case Filename: return i18nc( "The name of the file this track is stored in", "Filename" );
case Title: return i18n( "Title" );
case Artist: return i18n( "Artist" );
case AlbumArtist: return i18n( "Album Artist" );
case Composer: return i18n( "Composer" );
case Year: return i18n( "Year" );
case Album: return i18n( "Album" );
case DiscNumber: return i18n( "Disc Number" );
case TrackNumber: return i18nc( "The Track number for this item", "Track" );
case Bpm: return i18n( "BPM" );
case Genre: return i18n( "Genre" );
case Comment: return i18n( "Comment" );
case Directory: return i18nc( "The location on disc of this track", "Directory" );
case Type: return i18n( "Type" );
case Length: return i18n( "Length" );
case Bitrate: return i18n( "Bitrate" );
case SampleRate: return i18n( "Sample Rate" );
case Score: return i18n( "Score" );
case Rating: return i18n( "Rating" );
case PlayCount: return i18n( "Play Count" );
case LastPlayed: return i18nc( "Column name", "Last Played" );
case Mood: return i18n( "Mood" );
case Filesize: return i18n( "File Size" );
default: return "This is a bug.";
}
}
////////////
//Private Methods
////////////
void
Playlist::Model::insertTracksCommand( const InsertCmdList& cmds )
{
if ( cmds.size() < 1 )
return;
setAllNewlyAddedToUnplayed();
int activeShift = 0;
int min = m_items.size() + cmds.size();
int max = 0;
int begin = cmds.at( 0 ).second;
foreach( const InsertCmd &ic, cmds )
{
min = qMin( min, ic.second );
max = qMax( max, ic.second );
activeShift += ( begin <= m_activeRow ) ? 1 : 0;
}
// actually do the insertion
beginInsertRows( QModelIndex(), min, max );
foreach( const InsertCmd &ic, cmds )
{
Meta::TrackPtr track = ic.first;
m_totalLength += track->length();
m_totalSize += track->filesize();
subscribeTo( track );
Meta::AlbumPtr album = track->album();
if( album )
subscribeTo( album );
Item* newitem = new Item( track );
m_items.insert( ic.second, newitem );
m_itemIds.insert( newitem->id(), newitem );
}
endInsertRows();
if( m_activeRow >= 0 )
m_activeRow += activeShift;
else
{
EngineController *engine = The::engineController();
if( engine ) // test cases might create a playlist without having an EngineController
{
const Meta::TrackPtr engineTrack = engine->currentTrack();
if( engineTrack )
{
int engineRow = firstRowForTrack( engineTrack );
if( engineRow > -1 )
setActiveRow( engineRow );
}
}
}
}
static bool
removeCmdLessThanByRow( const Playlist::RemoveCmd &left, const Playlist::RemoveCmd &right )
{
return left.second < right.second;
}
void
Playlist::Model::removeTracksCommand( const RemoveCmdList &passedCmds )
{
DEBUG_BLOCK
if ( passedCmds.size() < 1 )
return;
if ( passedCmds.size() == m_items.size() )
{
clearCommand();
return;
}
// sort tracks to remove by their row
RemoveCmdList cmds( passedCmds );
qSort( cmds.begin(), cmds.end(), removeCmdLessThanByRow );
// update the active row
if( m_activeRow >= 0 )
{
int activeShift = 0;
foreach( const RemoveCmd &rc, cmds )
{
if( rc.second < m_activeRow )
activeShift++;
else if( rc.second == m_activeRow )
m_activeRow = -1; // disappeared
else
break; // we got over it, nothing left to do
}
if( m_activeRow >= 0 ) // not deleted
m_activeRow -= activeShift;
}
QSet<Meta::TrackPtr> trackUnsubscribeCandidates;
QSet<Meta::AlbumPtr> albumUnsubscribeCandidates;
QListIterator<RemoveCmd> it( cmds );
int removedRows = 0;
while( it.hasNext() )
{
int startRow = it.next().second;
int endRow = startRow;
// find consecutive runs of rows, this is important to group begin/endRemoveRows(),
// which are very costly when there are many proxymodels and a view above.
while( it.hasNext() && it.peekNext().second == endRow + 1 )
{
it.next();
endRow++;
}
beginRemoveRows( QModelIndex(), startRow - removedRows, endRow - removedRows );
for( int row = startRow; row <= endRow; row++ )
{
Item *removedItem = m_items.at( row - removedRows );
m_items.removeAt( row - removedRows );
m_itemIds.remove( removedItem->id() );
const Meta::TrackPtr &track = removedItem->track();
// update totals here so they're right when endRemoveRows() called
m_totalLength -= track->length();
m_totalSize -= track->filesize();
trackUnsubscribeCandidates.insert( track );
Meta::AlbumPtr album = track->album();
if( album )
albumUnsubscribeCandidates.insert( album );
delete removedItem; // note track is by reference, needs removedItem alive
removedRows++;
}
endRemoveRows();
}
// unsubscribe from tracks no longer present in playlist
foreach( Meta::TrackPtr track, trackUnsubscribeCandidates )
{
if( !containsTrack( track ) )
unsubscribeFrom( track );
}
// unsubscribe from albums no longer present im playlist
QSet<Meta::AlbumPtr> remainingAlbums;
foreach( const Item *item, m_items )
{
Meta::AlbumPtr album = item->track()->album();
if( album )
remainingAlbums.insert( album );
}
foreach( Meta::AlbumPtr album, albumUnsubscribeCandidates )
{
if( !remainingAlbums.contains( album ) )
unsubscribeFrom( album );
}
// make sure that there are enough tracks if we just removed from a dynamic playlist.
// This call needs to be delayed or else we would mess up the undo queue
// BUG: 259675
// FIXME: removing the track and normalizing the playlist should be grouped together
// so that an undo operation undos both.
- QTimer::singleShot(0, Playlist::Actions::instance(), SLOT(normalizeDynamicPlaylist()));
+ QTimer::singleShot(0, Playlist::Actions::instance(), &Playlist::Actions::normalizeDynamicPlaylist);
}
void Playlist::Model::clearCommand()
{
setActiveRow( -1 );
beginRemoveRows( QModelIndex(), 0, rowCount() - 1 );
m_totalLength = 0;
m_totalSize = 0;
qDeleteAll( m_items );
m_items.clear();
m_itemIds.clear();
endRemoveRows();
}
// Note: this function depends on 'MoveCmdList' to be a complete "cycle", in the sense
// that if row A is moved to row B, another row MUST be moved to row A.
// Very strange API design IMHO, because it forces our caller to e.g. move ALL ROWS in
// the playlist to move row 0 to the last row. This function should just have been
// equivalent to a 'removeTracks()' followed by an 'insertTracks()' IMHO. --Nanno
void
Playlist::Model::moveTracksCommand( const MoveCmdList& cmds, bool reverse )
{
DEBUG_BLOCK
debug()<<"moveTracksCommand:"<<cmds.size()<<reverse;
if ( cmds.size() < 1 )
return;
int min = INT_MAX;
int max = INT_MIN;
foreach( const MoveCmd &rc, cmds )
{
min = qMin( min, rc.first );
max = qMax( max, rc.first );
}
if( min < 0 || max >= m_items.size() )
{
error() << "Wrong row numbers given";
return;
}
int newActiveRow = m_activeRow;
QList<Item*> oldItems( m_items );
if ( reverse )
{
foreach( const MoveCmd &mc, cmds )
{
m_items[mc.first] = oldItems.at( mc.second );
if ( m_activeRow == mc.second )
newActiveRow = mc.first;
}
}
else
{
foreach( const MoveCmd &mc, cmds )
{
m_items[mc.second] = oldItems.at( mc.first );
if ( m_activeRow == mc.first )
newActiveRow = mc.second;
}
}
// We have 3 choices:
// - Call 'beginMoveRows()' / 'endMoveRows()'. Drawback: we'd need to do N of them, all causing resorts etc.
// - Emit 'layoutAboutToChange' / 'layoutChanged'. Drawback: unspecific, 'changePersistentIndex()' complications.
// - Emit 'dataChanged'. Drawback: a bit inappropriate. But not wrong.
emit dataChanged( index( min, 0 ), index( max, columnCount() - 1 ) );
//update the active row
m_activeRow = newActiveRow;
}
// When doing a 'setStateOfItem_batch', we emit 1 crude 'dataChanged' signal. If we're
// unlucky, that signal may span many innocent rows that haven't changed at all.
// Although that "worst case" will cause unnecessary work in our listeners "upstream", it
// still has much better performance than the worst case of emitting very many tiny
// 'dataChanged' signals.
//
// Being more clever (coalesce multiple contiguous ranges, etc.) is not worth the effort.
void
Playlist::Model::setStateOfItem_batchStart()
{
m_setStateOfItem_batchMinRow = rowCount() + 1;
m_setStateOfItem_batchMaxRow = 0;
}
void
Playlist::Model::setStateOfItem_batchEnd()
{
if ( ( m_setStateOfItem_batchMaxRow - m_setStateOfItem_batchMinRow ) >= 0 )
emit dataChanged( index( m_setStateOfItem_batchMinRow, 0 ), index( m_setStateOfItem_batchMaxRow, columnCount() - 1 ) );
m_setStateOfItem_batchMinRow = -1;
}
void
Playlist::Model::setStateOfItem( Item *item, int row, Item::State state )
{
item->setState( state );
if ( m_setStateOfItem_batchMinRow == -1 ) // If not in batch mode
emit dataChanged( index( row, 0 ), index( row, columnCount() - 1 ) );
else
{
m_setStateOfItem_batchMinRow = qMin( m_setStateOfItem_batchMinRow, row );
m_setStateOfItem_batchMaxRow = qMax( m_setStateOfItem_batchMaxRow, row );
}
}
// Unimportant TODO: the performance of this function is O(n) in playlist size.
// Not a big problem, because it's called infrequently.
// Can be fixed by maintaining a new member variable 'QMultiHash<Item::State, Item*>'.
void
Playlist::Model::setAllNewlyAddedToUnplayed()
{
DEBUG_BLOCK
setStateOfItem_batchStart();
for ( int row = 0; row < rowCount(); row++ )
{
Item* item = m_items.at( row );
if ( item->state() == Item::NewlyAdded )
setStateOfItem( item, row, Item::Unplayed );
}
setStateOfItem_batchEnd();
}
void Playlist::Model::setAllUnplayed()
{
DEBUG_BLOCK
setStateOfItem_batchStart();
for ( int row = 0; row < rowCount(); row++ )
{
Item* item = m_items.at( row );
setStateOfItem( item, row, Item::Unplayed );
}
setStateOfItem_batchEnd();
}
diff --git a/src/playlist/PlaylistQueueEditor.cpp b/src/playlist/PlaylistQueueEditor.cpp
index 923604426a..483c3508c5 100644
--- a/src/playlist/PlaylistQueueEditor.cpp
+++ b/src/playlist/PlaylistQueueEditor.cpp
@@ -1,145 +1,145 @@
/****************************************************************************************
* Copyright (c) 2010 Andreas Hartmetz <ahartmetz@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistQueueEditor.h"
#include "core/meta/Meta.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
static const int s_idRole = Qt::UserRole;
static const int s_myType = QListWidgetItem::UserType;
//### due to playlists typically having no more than 10k items and no more than
// 100 queued items we can get away with using simple and slow algorithms.
PlaylistQueueEditor::PlaylistQueueEditor()
: QDialog(),
m_blockViewUpdates( false )
{
m_ui.setupUi( this );
updateView();
- connect( The::playlist()->qaim(), SIGNAL(queueChanged()), SLOT(queueChanged()) );
+ connect( qobject_cast<Playlist::Model*>(The::playlist()->qaim()), &Playlist::Model::queueChanged, this, &PlaylistQueueEditor::queueChanged );
m_ui.upButton->setIcon( QIcon::fromTheme( "go-up" ) );
m_ui.downButton->setIcon( QIcon::fromTheme( "go-down" ) );
m_ui.dequeueTrackButton->setIcon( QIcon::fromTheme( "list-remove" ) );
m_ui.clearButton->setIcon( QIcon::fromTheme( "edit-clear-list" ) );
- connect( m_ui.upButton, SIGNAL(clicked()), SLOT(moveUp()) );
- connect( m_ui.downButton, SIGNAL(clicked()), SLOT(moveDown()) );
- connect( m_ui.clearButton, SIGNAL(clicked()), SLOT(clear()) );
- connect( m_ui.dequeueTrackButton, SIGNAL(clicked()), SLOT(dequeueTrack()) );
- connect( m_ui.buttonBox->buttons().first(), SIGNAL(clicked()), SLOT(accept()) );
+ connect( m_ui.upButton, &QAbstractButton::clicked, this, &PlaylistQueueEditor::moveUp );
+ connect( m_ui.downButton, &QAbstractButton::clicked, this, &PlaylistQueueEditor::moveDown );
+ connect( m_ui.clearButton, &QAbstractButton::clicked, this, &PlaylistQueueEditor::clear );
+ connect( m_ui.dequeueTrackButton, &QAbstractButton::clicked, this, &PlaylistQueueEditor::dequeueTrack );
+ connect( m_ui.buttonBox->buttons().first(), &QAbstractButton::clicked, this, &PlaylistQueueEditor::accept );
}
void
PlaylistQueueEditor::updateView()
{
if ( m_blockViewUpdates )
return;
m_ui.listWidget->clear();
int i = 1;
foreach( quint64 id, The::playlistActions()->queue() )
{
QListWidgetItem *item = new QListWidgetItem( m_ui.listWidget, s_myType );
item->setData( s_idRole, id );
Meta::TrackPtr track = The::playlist()->trackForId( id );
Meta::ArtistPtr artist = track->artist();
QString itemText = i18nc( "Iten in queue, %1 is position, %2 artist, %3 track",
"%1: %2 - %3", i++, artist ? artist->prettyName() : i18n( "Unknown Artist" ),
track->prettyName() );
item->setText( itemText );
}
}
void
PlaylistQueueEditor::queueChanged()
{
const quint64 id = currentId();
updateView();
if ( id )
setCurrentId( id );
}
quint64
PlaylistQueueEditor::currentId()
{
if ( QListWidgetItem *item = m_ui.listWidget->currentItem() ) {
bool ok;
quint64 id = item->data( s_idRole ).toULongLong( &ok );
if ( ok )
return id;
}
return 0;
}
void
PlaylistQueueEditor::setCurrentId( quint64 newCurrentId )
{
for ( int i = 0; i < m_ui.listWidget->count(); i++ ) {
QListWidgetItem *item = m_ui.listWidget->item( i );
bool ok;
quint64 id = item->data( s_idRole ).toULongLong( &ok );
if ( ok && id == newCurrentId ) {
m_ui.listWidget->setCurrentItem( item );
break;
}
}
}
void
PlaylistQueueEditor::moveUp()
{
const quint64 id = currentId();
if ( !id )
return;
The::playlistActions()->queueMoveUp( id );
}
void
PlaylistQueueEditor::moveDown()
{
const quint64 id = currentId();
if ( !id )
return;
The::playlistActions()->queueMoveDown( id );
}
void
PlaylistQueueEditor::dequeueTrack()
{
const quint64 id = currentId();
if ( !id )
return;
The::playlistActions()->dequeue( id );
}
void
PlaylistQueueEditor::clear()
{
m_blockViewUpdates = true;
QList<int> rowsToDequeue;
foreach ( quint64 id, The::playlistActions()->queue() ) {
Meta::TrackPtr track = The::playlist()->trackForId( id );
foreach ( int row, The::playlist()->allRowsForTrack( track ) )
rowsToDequeue += row;
}
The::playlistActions()->dequeue( rowsToDequeue );
m_blockViewUpdates = false;
updateView();
}
diff --git a/src/playlist/PlaylistSortWidget.cpp b/src/playlist/PlaylistSortWidget.cpp
index 0842e9fea6..26cbb67bfd 100644
--- a/src/playlist/PlaylistSortWidget.cpp
+++ b/src/playlist/PlaylistSortWidget.cpp
@@ -1,208 +1,216 @@
/****************************************************************************************
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistSortWidget.h"
#include "core/support/Debug.h"
#include "PlaylistActions.h"
#include "PlaylistModelStack.h"
#include "proxymodels/SortScheme.h"
#include <KConfigGroup>
#include <KStandardDirs>
namespace Playlist
{
SortWidget::SortWidget( QWidget *parent )
: QWidget( parent )
{
setFixedHeight( 28 );
setContentsMargins( 3, 0, 3, 0 );
m_layout = new QHBoxLayout( this );
setLayout( m_layout );
m_layout->setSpacing( 0 );
m_layout->setContentsMargins( 0, 0, 0, 0 );
BreadcrumbItemButton *rootItem = new BreadcrumbItemButton(
QIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/playlist-sorting-16.png" ) ) ),
QString(), this );
rootItem->setToolTip( i18n( "Clear the playlist sorting configuration." ) );
m_layout->addWidget( rootItem );
- connect( rootItem, SIGNAL(clicked()), this, SLOT(trimToLevel()) );
+ connect( rootItem, &BreadcrumbItemButton::clicked, this, &SortWidget::trimToLevel );
m_ribbon = new QHBoxLayout();
m_layout->addLayout( m_ribbon );
m_ribbon->setContentsMargins( 0, 0, 0, 0 );
m_ribbon->setSpacing( 0 );
m_addButton = new BreadcrumbAddMenuButton( this );
m_addButton->setToolTip( i18n( "Add a playlist sorting level." ) );
m_layout->addWidget( m_addButton );
m_layout->addStretch( 10 );
m_urlButton = new BreadcrumbUrlMenuButton( "playlist", this );
m_layout->addWidget( m_urlButton );
- connect( m_addButton->menu(), SIGNAL(actionClicked(QString)), this, SLOT(addLevel(QString)) );
- connect( m_addButton->menu(), SIGNAL(shuffleActionClicked()), The::playlistActions(), SLOT(shuffle()) );
+ connect( m_addButton->menu(), &BreadcrumbItemMenu::actionClicked,
+ this, &SortWidget::addLevelAscending );
+ connect( m_addButton->menu(), &BreadcrumbItemMenu::shuffleActionClicked,
+ The::playlistActions(), &Actions::shuffle );
QString sortPath = Amarok::config( "Playlist Sorting" ).readEntry( "SortPath", QString() );
readSortPath( sortPath );
}
SortWidget::~SortWidget()
{}
void
SortWidget::addLevel( QString internalColumnName, Qt::SortOrder sortOrder ) //private slot
{
BreadcrumbLevel *bLevel = new BreadcrumbLevel( internalColumnName );
BreadcrumbItem *item = new BreadcrumbItem( bLevel, this );
m_ribbon->addWidget( item );
- connect( item, SIGNAL(clicked()), this, SLOT(onItemClicked()) );
- connect( item->menu(), SIGNAL(actionClicked(QString)), this, SLOT(onItemSiblingClicked(QString)) );
- connect( item->menu(), SIGNAL(shuffleActionClicked()), this, SLOT(onShuffleSiblingClicked()) );
- connect( item, SIGNAL(orderInverted()), this, SLOT(updateSortScheme()) );
+ connect( item, &BreadcrumbItem::clicked, this, &SortWidget::onItemClicked );
+ connect( item->menu(), &BreadcrumbItemMenu::actionClicked, this, &SortWidget::onItemSiblingClicked );
+ connect( item->menu(), &BreadcrumbItemMenu::shuffleActionClicked, this, &SortWidget::onShuffleSiblingClicked );
+ connect( item, &BreadcrumbItem::orderInverted, this, &SortWidget::updateSortScheme );
if( sortOrder != item->sortOrder() )
item->invertOrder();
m_addButton->updateMenu( levels() );
updateSortScheme();
}
+void
+SortWidget::addLevelAscending ( QString internalColumnName )
+{
+ addLevel(internalColumnName, Qt::AscendingOrder);
+}
+
void
SortWidget::trimToLevel( const int level )
{
for( int i = m_ribbon->count() - 1 ; i > level; i-- )
{
BreadcrumbItem *item = qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() );
m_ribbon->removeWidget( item );
item->deleteLater();
}
updateSortScheme();
m_addButton->updateMenu( levels() );
}
QStringList
SortWidget::levels() const
{
QStringList levels = QStringList();
for( int i = 0; i < m_ribbon->count(); ++i )
levels << qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->name();
return levels;
}
void
SortWidget::onItemClicked()
{
const int level = m_ribbon->indexOf( qobject_cast< QWidget * >( sender()->parent() ) );
trimToLevel( level );
}
void
SortWidget::onItemSiblingClicked( QString internalColumnName )
{
const int level = m_ribbon->indexOf( qobject_cast< QWidget * >( sender()->parent() ) );
trimToLevel( level - 1 );
addLevel( internalColumnName );
}
void
SortWidget::onShuffleSiblingClicked()
{
const int level = m_ribbon->indexOf( qobject_cast< QWidget * >( sender()->parent() ) );
trimToLevel( level - 1 );
The::playlistActions()->shuffle();
}
void
SortWidget::updateSortScheme()
{
SortScheme scheme = SortScheme();
for( int i = 0; i < m_ribbon->count(); ++i ) //could be faster if done with iterator
{
QString name( qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->name() );
Column category = columnForName( name );
Qt::SortOrder sortOrder = qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->sortOrder();
scheme.addLevel( SortLevel( category, sortOrder ) );
}
ModelStack::instance()->sortProxy()->updateSortMap( scheme );
KConfigGroup config = Amarok::config( "Playlist Sorting" );
config.writeEntry( "SortPath", sortPath() );
}
QString
SortWidget::sortPath() const
{
QString path;
for( int i = 0; i < m_ribbon->count(); ++i ) //could be faster if done with iterator
{
QString name( qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->name() );
Qt::SortOrder sortOrder = qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->sortOrder();
QString level = name + '_' + ( sortOrder ? "des" : "asc" );
path.append( ( i == m_ribbon->count() - 1 ) ? level : ( level + '-' ) );
}
return path;
}
void
SortWidget::readSortPath( const QString &sortPath )
{
trimToLevel();
QStringList levels = sortPath.split( '-' );
foreach( const QString &level, levels )
{
QStringList levelParts = level.split( '_' );
/*
* Check whether the configuration is valid. If indexOf
* returns -1, the entry is corrupted. We can't use columnForName
* here, as it will do a static_cast, which is UB when indexOf is -1
* as there's no corresponding enum value
* (C++ standard 5.2.9 Static cast [expr.static.cast] paragraph 7)
*/
if( levelParts.count() > 2
|| ( Playlist::PlaylistColumnInfos::internalNames().
indexOf( levelParts.value(0) ) == -1) )
warning() << "Playlist sorting load error: Invalid sort level " << level;
else if( levelParts.value( 1 ) == QString( "asc" ) )
addLevel( levelParts.value( 0 ), Qt::AscendingOrder );
else if( levelParts.value( 1 ) == QString( "des" ) )
addLevel( levelParts.value( 0 ), Qt::DescendingOrder );
else
warning() << "Playlist sorting load error: Invalid sort order for level " << level;
}
}
QString
SortWidget::prettySortPath() const
{
QString prettyPath;
for( int i = 0; i < m_ribbon->count(); ++i ) //could be faster if done with iterator
{
QString name( qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->name() );
QString prettyName( qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->prettyName() );
Qt::SortOrder sortOrder = qobject_cast< BreadcrumbItem * >( m_ribbon->itemAt( i )->widget() )->sortOrder();
QString prettyLevel = prettyName + ( sortOrder ? "↓" : "↑" );
prettyPath.append( ( i == m_ribbon->count() - 1 ) ? prettyLevel : ( prettyLevel + " > " ) );
//TODO: see how this behaves on RTL systems
}
return prettyPath;
}
}
diff --git a/src/playlist/PlaylistSortWidget.h b/src/playlist/PlaylistSortWidget.h
index 7a88f0c7aa..1c2ae32c31 100644
--- a/src/playlist/PlaylistSortWidget.h
+++ b/src/playlist/PlaylistSortWidget.h
@@ -1,115 +1,122 @@
/****************************************************************************************
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef PLAYLISTSORTWIDGET_H
#define PLAYLISTSORTWIDGET_H
#include "PlaylistBreadcrumbItem.h"
#include <QAction>
#include <QHBoxLayout>
namespace Playlist
{
/**
* A breadcrumb-based widget that allows the user to build a multilevel sorting scheme for
* the playlist.
* @author Téo Mrnjavac
*/
class SortWidget : public QWidget
{
Q_OBJECT
public:
/**
* Constructor.
*/
SortWidget( QWidget *parent );
/**
* Destructor.
*/
~SortWidget();
/**
* Returns the list of levels that are currently defined in the breadcrumb path.
* @return the list of names of levels.
*/
QStringList levels() const;
/**
* Generates a QString usable by a URL runner that represents the current sort scheme.
*/
QString sortPath() const;
/**
* Generate current sort scheme from a sorth path stored in a QString.
*/
void readSortPath( const QString &sortPath );
/**
* Generates a user-visible QString usable by a URL runner for the title of a bookmark.
*/
QString prettySortPath() const;
public Q_SLOTS:
/**
* Generates a new sort scheme and forwards it to Playlist::SortProxy to apply it to
* the playlist.
*/
void updateSortScheme();
/**
* Removes items from the breadcrumb path up to a certain level.
* @param level the cutoff level of the breadcrumb path.
*/
void trimToLevel( const int level = -1 );
private:
QHBoxLayout * m_ribbon;
QList< BreadcrumbItem * > m_items;
BreadcrumbAddMenuButton * m_addButton;
QHBoxLayout * m_layout;
BreadcrumbUrlMenuButton *m_urlButton;
private Q_SLOTS:
/**
* Adds a level to the breadcrumb path.
* @param internalColumnName the name of the level.
* @param sortOrder the Qt::SortOrder of the level.
*/
void addLevel( QString internalColumnName, Qt::SortOrder sortOrder = Qt::AscendingOrder );
+ /**
+ * Adds a level to the breadcrumb path.
+ * Orders the level in ascending order.
+ * @param internalColumnName the name of the level.
+ */
+ void addLevelAscending( QString internalColumnName );
+
/**
* Handles the (possible) deletion of further levels when an item is clicked.
*/
void onItemClicked();
/**
* Handles the rearrangement of the breadcrumb path when a sibling of an item is clicked.
* @param action the action in the menu.
*/
void onItemSiblingClicked( QString internalColumnName );
/**
* Handles the rearrangement of the breadcrumb path when a Shuffle action is clicked.
*/
void onShuffleSiblingClicked();
};
} //namespace Playlist
#endif //PLAYLISTSORTWIDGET_H
diff --git a/src/playlist/ProgressiveSearchWidget.cpp b/src/playlist/ProgressiveSearchWidget.cpp
index fdb3e75f0a..04dd5f2dda 100644
--- a/src/playlist/ProgressiveSearchWidget.cpp
+++ b/src/playlist/ProgressiveSearchWidget.cpp
@@ -1,388 +1,388 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ProgressiveSearchWidget.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistModel.h"
#include <QAction>
#include <KColorScheme>
#include <KConfigGroup>
#include <KHBox>
#include <KLocale>
#include <QKeyEvent>
#include <QLabel>
#include <QMenu>
#include <QToolBar>
#include <QToolButton>
namespace Playlist
{
ProgressiveSearchWidget::ProgressiveSearchWidget( QWidget * parent )
: KVBox( parent )
{
DEBUG_BLOCK
readConfig();
KHBox *searchBox = new KHBox( this );
m_searchEdit = new Amarok::LineEdit( searchBox );
m_searchEdit->setClickMessage( i18n( "Search playlist" ) );
m_searchEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
m_searchEdit->setClearButtonShown( true );
m_searchEdit->setFrame( true );
m_searchEdit->setToolTip( i18n( "Start typing to progressively search through the playlist" ) );
m_searchEdit->setFocusPolicy( Qt::ClickFocus ); // Without this, the widget goes into text input mode directly on startup
- connect( m_searchEdit, SIGNAL(textChanged(QString)), this, SLOT(slotFilterChanged(QString)) );
- connect( m_searchEdit, SIGNAL(returnPressed(QString)), this, SIGNAL(activateFilterResult()) );
- connect( m_searchEdit, SIGNAL(returnPressed(QString)), this, SLOT(slotFilterClear()) );
- connect( m_searchEdit, SIGNAL(returnPressed()), this, SLOT(defocus()) );
- connect( m_searchEdit, SIGNAL(downPressed()), this, SIGNAL(downPressed()) );
- connect( m_searchEdit, SIGNAL(upPressed()), this, SIGNAL(upPressed()) );
+ connect( m_searchEdit, &Amarok::LineEdit::textChanged, this, &ProgressiveSearchWidget::slotFilterChanged );
+ connect( m_searchEdit, &Amarok::LineEdit::returnPressed, this, &ProgressiveSearchWidget::activateFilterResult );
+ connect( m_searchEdit, &Amarok::LineEdit::returnPressed, this, &ProgressiveSearchWidget::slotFilterClear );
+ connect( m_searchEdit, &Amarok::LineEdit::returnPressed, this, &ProgressiveSearchWidget::defocus );
+ connect( m_searchEdit, &Amarok::LineEdit::downPressed, this, &ProgressiveSearchWidget::downPressed );
+ connect( m_searchEdit, &Amarok::LineEdit::upPressed, this, &ProgressiveSearchWidget::upPressed );
m_nextAction = new QAction( QIcon::fromTheme( "go-down" ), i18n( "&Next" ), this );
- connect( m_nextAction, SIGNAL(triggered()), this, SLOT(slotNext()) );
+ connect( m_nextAction, &QAction::triggered, this, &ProgressiveSearchWidget::slotNext );
m_previousAction = new QAction( QIcon::fromTheme( "go-up" ), i18n( "&Previous" ), this );
- connect( m_previousAction, SIGNAL(triggered()), this, SLOT(slotPrevious()) );
+ connect( m_previousAction, &QAction::triggered, this, &ProgressiveSearchWidget::slotPrevious );
m_nextAction->setEnabled( false );
m_previousAction->setEnabled( false );
m_menu = new QMenu( this );
QAction * searchTracksAction = new QAction( i18n( "Tracks" ), this );
searchTracksAction->setCheckable( true );
- connect( searchTracksAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchTracks(bool)) );
+ connect( searchTracksAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchTracks );
if( m_searchFieldsMask & Playlist::MatchTrack )
searchTracksAction->setChecked( true );
m_menu->addAction( searchTracksAction );
QAction * searchAlbumsAction = new QAction( i18n( "Albums" ), this );
searchAlbumsAction->setCheckable( true );
- connect( searchAlbumsAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchAlbums(bool)) );
+ connect( searchAlbumsAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchAlbums );
if( m_searchFieldsMask & Playlist::MatchAlbum )
searchAlbumsAction->setChecked( true );
m_menu->addAction( searchAlbumsAction );
QAction * searchArtistsAction = new QAction( i18n( "Artists" ), this );
searchArtistsAction->setCheckable( true );
- connect( searchArtistsAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchArtists(bool)) );
+ connect( searchArtistsAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchArtists );
if( m_searchFieldsMask & Playlist::MatchArtist )
searchArtistsAction->setChecked( true );
m_menu->addAction( searchArtistsAction );
QAction * searchGenreAction = new QAction( i18n( "Genre" ), this );
searchGenreAction->setCheckable( true );
- connect( searchGenreAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchGenre(bool)) );
+ connect( searchGenreAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchGenre );
if( m_searchFieldsMask & Playlist::MatchGenre )
searchGenreAction->setChecked( true );
m_menu->addAction( searchGenreAction );
QAction * searchComposersAction = new QAction( i18n( "Composers" ), this );
searchComposersAction->setCheckable( true );
- connect( searchComposersAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchComposers(bool)) );
+ connect( searchComposersAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchComposers );
if( m_searchFieldsMask & Playlist::MatchComposer )
searchComposersAction->setChecked( true );
m_menu->addAction( searchComposersAction );
QAction * searchRatingAction = new QAction( i18n( "Rating" ), this );
searchRatingAction->setCheckable( true );
- connect( searchRatingAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchRating(bool)) );
+ connect( searchRatingAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchRating );
if( m_searchFieldsMask & Playlist::MatchRating )
searchRatingAction->setChecked( true );
m_menu->addAction( searchRatingAction );
QAction * searchYearsAction = new QAction( i18n( "Years" ), this );
searchYearsAction->setCheckable( true );
- connect( searchYearsAction, SIGNAL(toggled(bool)), this, SLOT(slotSearchYears(bool)) );
+ connect( searchYearsAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotSearchYears );
if( m_searchFieldsMask & Playlist::MatchYear)
searchYearsAction->setChecked( true );
m_menu->addAction( searchYearsAction );
m_menu->addSeparator();
QAction * showOnlyMatchesAction = new QAction( i18n( "Show only matches" ), this );
showOnlyMatchesAction->setCheckable( true );
- connect( showOnlyMatchesAction, SIGNAL(toggled(bool)), this, SLOT(slotShowOnlyMatches(bool)) );
+ connect( showOnlyMatchesAction, &QAction::toggled, this, &ProgressiveSearchWidget::slotShowOnlyMatches );
m_toolBar = new QToolBar( searchBox );
showOnlyMatchesAction->setChecked( m_showOnlyMatches );
m_menu->addAction( showOnlyMatchesAction );
slotShowOnlyMatches( m_showOnlyMatches );
m_nextAction->setVisible( !m_showOnlyMatches );
m_previousAction->setVisible( !m_showOnlyMatches );
QAction *searchMenuAction = new QAction( QIcon::fromTheme( "preferences-other" ), i18n( "Search Preferences" ), this );
searchMenuAction->setMenu( m_menu );
m_toolBar->addAction( searchMenuAction );
QToolButton *tbutton = qobject_cast<QToolButton*>( m_toolBar->widgetForAction( searchMenuAction ) );
if( tbutton )
tbutton->setPopupMode( QToolButton::InstantPopup );
m_toolBar->setFixedHeight( m_searchEdit->sizeHint().height() );
//make sure that this edit is cleared when the playlist is cleared:
- connect( Amarok::actionCollection()->action( "playlist_clear" ), SIGNAL(triggered()), this, SLOT(slotFilterClear()) );
+ connect( Amarok::actionCollection()->action( "playlist_clear" ), &QAction::triggered, this, &ProgressiveSearchWidget::slotFilterClear );
}
void ProgressiveSearchWidget::slotFilterChanged( const QString & filter )
{
DEBUG_BLOCK
//when the clear button is pressed, we get 2 calls to this slot... filter this out as it messes with
//resetting the view:
if ( filter == m_lastFilter )
return;
debug() << "New filter: " << filter;
m_lastFilter = filter;
if( filter.isEmpty() )
{
m_nextAction->setEnabled( false );
m_previousAction->setEnabled( false );
QPalette p = m_searchEdit->palette();
p.setColor( QPalette::Base, palette().color( QPalette::Base ) );
m_searchEdit->setPalette( p );
emit( filterCleared() );
return;
}
emit( filterChanged( filter, m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotNext()
{
DEBUG_BLOCK
emit( next( m_searchEdit->text(), m_searchFieldsMask ) );
}
void ProgressiveSearchWidget::slotPrevious()
{
DEBUG_BLOCK
emit( previous( m_searchEdit->text(), m_searchFieldsMask ) );
}
void ProgressiveSearchWidget::match()
{
m_nextAction->setEnabled( true );
m_previousAction->setEnabled( true );
QPalette p = m_searchEdit->palette();
p.setColor( QPalette::Base, palette().color( QPalette::Base ) );
m_searchEdit->setPalette( p );
}
void ProgressiveSearchWidget::noMatch()
{
m_nextAction->setEnabled( false );
m_previousAction->setEnabled( false );
const KStatefulBrush backgroundBrush( KColorScheme::View, KColorScheme::NegativeBackground );
QPalette p = m_searchEdit->palette();
p.setColor( QPalette::Base, backgroundBrush.brush( m_searchEdit ).color() );
m_searchEdit->setPalette( p );
}
void ProgressiveSearchWidget::slotSearchTracks( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchTrack;
else
m_searchFieldsMask ^= Playlist::MatchTrack;
Amarok::config( "Playlist Search" ).writeEntry( "MatchTrack", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotSearchArtists( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchArtist;
else
m_searchFieldsMask ^= Playlist::MatchArtist;
Amarok::config( "Playlist Search" ).writeEntry( "MatchArtist", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotSearchAlbums( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchAlbum;
else
m_searchFieldsMask ^= Playlist::MatchAlbum;
Amarok::config( "Playlist Search" ).writeEntry( "MatchAlbum", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotSearchGenre( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchGenre;
else
m_searchFieldsMask ^= Playlist::MatchGenre;
Amarok::config( "Playlist Search" ).writeEntry( "MatchGenre", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotSearchComposers( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchComposer;
else
m_searchFieldsMask ^= Playlist::MatchComposer;
Amarok::config( "Playlist Search" ).writeEntry( "MatchComposer", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotSearchRating( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchRating;
else
m_searchFieldsMask ^= Playlist::MatchRating;
Amarok::config( "Playlist Search" ).writeEntry( "MatchRating", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::slotSearchYears( bool search )
{
if( search )
m_searchFieldsMask |= Playlist::MatchYear;
else
m_searchFieldsMask ^= Playlist::MatchYear;
Amarok::config( "Playlist Search" ).writeEntry( "MatchYear", search );
if( !m_searchEdit->text().isEmpty() )
emit( filterChanged( m_searchEdit->text(), m_searchFieldsMask, m_showOnlyMatches ) );
}
void ProgressiveSearchWidget::readConfig()
{
m_searchFieldsMask = 0;
KConfigGroup config = Amarok::config("Playlist Search");
if( config.readEntry( "MatchTrack", true ) )
m_searchFieldsMask |= Playlist::MatchTrack;
if( config.readEntry( "MatchArtist", true ) )
m_searchFieldsMask |= Playlist::MatchArtist;
if( config.readEntry( "MatchAlbum", true ) )
m_searchFieldsMask |= Playlist::MatchAlbum;
if( config.readEntry( "MatchGenre", false ) )
m_searchFieldsMask |= Playlist::MatchGenre;
if( config.readEntry( "MatchComposer", false ) )
m_searchFieldsMask |= Playlist::MatchComposer;
if( config.readEntry( "MatchRating", false ) )
m_searchFieldsMask |= Playlist::MatchRating;
if( config.readEntry( "MatchYear", false ) )
m_searchFieldsMask |= Playlist::MatchYear;
m_showOnlyMatches = config.readEntry( "ShowOnlyMatches", false );
}
void ProgressiveSearchWidget::slotShowOnlyMatches( bool onlyMatches )
{
DEBUG_BLOCK
if( onlyMatches )
{
m_toolBar->removeAction( m_previousAction );
m_toolBar->removeAction( m_nextAction );
}
else
{
m_toolBar->insertAction( m_menu->menuAction(), m_nextAction );
m_toolBar->insertAction( m_nextAction, m_previousAction );
}
m_showOnlyMatches = onlyMatches;
m_nextAction->setVisible( !onlyMatches );
m_previousAction->setVisible( !onlyMatches );
KConfigGroup cg = Amarok::config( "Playlist Search" );
cg.writeEntry( "ShowOnlyMatches", m_showOnlyMatches );
cg.sync();
emit( showOnlyMatches( onlyMatches ) );
}
void
ProgressiveSearchWidget::keyPressEvent( QKeyEvent *event )
{
if( event->matches( QKeySequence::FindNext ) )
{
event->accept();
slotNext();
}
else if( event->matches( QKeySequence::FindPrevious ) )
{
event->accept();
slotPrevious();
}
else
{
event->ignore();
KHBox::keyPressEvent( event );
}
}
void
ProgressiveSearchWidget::focusInputLine()
{
m_searchEdit->setFocus();
}
void ProgressiveSearchWidget::slotFilterClear()
{
DEBUG_BLOCK
m_searchEdit->setText( QString() );
}
} //namespace Playlist
diff --git a/src/playlist/layouts/LayoutConfigAction.cpp b/src/playlist/layouts/LayoutConfigAction.cpp
index cef4a63fa1..24a35f3fd8 100644
--- a/src/playlist/layouts/LayoutConfigAction.cpp
+++ b/src/playlist/layouts/LayoutConfigAction.cpp
@@ -1,132 +1,132 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "LayoutConfigAction.h"
#include "core/support/Debug.h"
#include "LayoutManager.h"
#include "PlaylistLayoutEditDialog.h"
#include "widgets/EditDeleteDelegate.h"
#include "widgets/EditDeleteComboBoxView.h"
#include "MainWindow.h"
#include <KStandardDirs>
#include <QLabel>
#include <QComboBox>
#include <QPixmap>
namespace Playlist
{
LayoutConfigAction::LayoutConfigAction( QWidget * parent )
: QAction( parent )
, m_layoutDialog( 0 )
{
QIcon actionIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/playlist-layouts-22.png") ) ); //TEMPORARY ICON
setIcon( actionIcon );
m_layoutMenu = new QMenu( parent );
setMenu( m_layoutMenu );
setText( i18n( "Playlist Layouts" ) );
m_configAction = new QAction( m_layoutMenu );
m_layoutMenu->addAction( m_configAction );
m_layoutMenu->addSeparator();
m_layoutActions = new QActionGroup( m_layoutMenu );
m_layoutActions->setExclusive( true );
QStringList layoutsList( LayoutManager::instance()->layouts() );
foreach( const QString &iterator, layoutsList )
{
m_layoutActions->addAction( iterator )->setCheckable( true );
}
m_layoutMenu->addActions( m_layoutActions->actions() );
int index = LayoutManager::instance()->layouts().indexOf( LayoutManager::instance()->activeLayoutName() );
if( index > -1 ) //needed to avoid crash when created a layout which is moved by the LayoutManager when sorting alphabetically.
//this should be fixed by itself when layouts ordering will be supported in the LayoutManager
m_layoutActions->actions()[ index ]->setChecked( true );
- connect( m_layoutActions, SIGNAL(triggered(QAction*)), this, SLOT(setActiveLayout(QAction*)) );
+ connect( m_layoutActions,&QActionGroup::triggered, this, &LayoutConfigAction::setActiveLayout );
- connect( LayoutManager::instance(), SIGNAL(layoutListChanged()), this, SLOT(layoutListChanged()) );
- connect( LayoutManager::instance(), SIGNAL(activeLayoutChanged()), this, SLOT(onActiveLayoutChanged()) );
+ connect( LayoutManager::instance(), &LayoutManager::layoutListChanged, this, &LayoutConfigAction::layoutListChanged );
+ connect( LayoutManager::instance(), &LayoutManager::activeLayoutChanged, this, &LayoutConfigAction::onActiveLayoutChanged );
const QIcon configIcon( "configure" );
m_configAction->setIcon( configIcon );
m_configAction->setText( i18n( "Configure Playlist Layouts..." ) );
- connect( m_configAction, SIGNAL(triggered()), this, SLOT(configureLayouts()) );
+ connect( m_configAction, &QAction::triggered, this, &LayoutConfigAction::configureLayouts );
}
LayoutConfigAction::~LayoutConfigAction()
{}
void LayoutConfigAction::setActiveLayout( QAction *layoutAction )
{
QString layoutName( layoutAction->text() );
layoutName = layoutName.remove( QChar( '&' ) ); //need to remove the & from the string, used for the shortcut key underscore
LayoutManager::instance()->setActiveLayout( layoutName );
}
void LayoutConfigAction::configureLayouts()
{
if( m_layoutDialog == 0 )
m_layoutDialog = new PlaylistLayoutEditDialog( The::mainWindow() );
m_layoutDialog->setModal( false );
- connect( m_layoutDialog, SIGNAL(accepted()), this, SLOT(layoutListChanged()) );
+ connect( m_layoutDialog, &Playlist::PlaylistLayoutEditDialog::accepted, this, &LayoutConfigAction::layoutListChanged );
m_layoutDialog->show();
}
void Playlist::LayoutConfigAction::layoutListChanged()
{
m_layoutMenu->removeAction( m_configAction );
m_layoutMenu->clear();
m_layoutMenu->addAction( m_configAction );
m_layoutMenu->addSeparator();
foreach( QAction * action, m_layoutActions->actions() )
delete action;
QStringList layoutsList( LayoutManager::instance()->layouts() );
foreach( const QString &iterator, layoutsList )
m_layoutActions->addAction( iterator )->setCheckable( true );
m_layoutMenu->addActions( m_layoutActions->actions() );
int index = LayoutManager::instance()->layouts().indexOf( LayoutManager::instance()->activeLayoutName() );
if( index > -1 ) //needed to avoid crash when created a layout which is moved by the LayoutManager when sorting alphabetically.
//this should be fixed by itself when layouts ordering will be supported in the LayoutManager
m_layoutActions->actions()[ index ]->setChecked( true );
}
void LayoutConfigAction::onActiveLayoutChanged()
{
QString layoutName( LayoutManager::instance()->activeLayoutName() );
layoutName = layoutName.remove( QChar( '&' ) ); //need to remove the & from the string, used for the shortcut key underscore
if( layoutName != QString( "%%PREVIEW%%" ) ) //if it's not just a preview
{
int index = LayoutManager::instance()->layouts().indexOf( layoutName );
if( index != -1 && m_layoutActions->actions()[ index ] != m_layoutActions->checkedAction() )
m_layoutActions->actions()[ index ]->setChecked( true );
}
}
}
diff --git a/src/playlist/layouts/LayoutEditDialog.cpp b/src/playlist/layouts/LayoutEditDialog.cpp
index 6c06b4d3b9..fbe2c6f311 100644
--- a/src/playlist/layouts/LayoutEditDialog.cpp
+++ b/src/playlist/layouts/LayoutEditDialog.cpp
@@ -1,298 +1,298 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Lübking <thomas.luebking@web.de *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "LayoutEditDialog.h"
#include "widgets/TokenWithLayout.h"
#include "widgets/TokenDropTarget.h"
#include <QIcon>
#include <KLocale>
#include <QButtonGroup>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPainter>
#include <QRadioButton>
#include <QSlider>
#include <QStyle>
#include <QStyleOptionFrame>
#include <QToolButton>
class HintingLineEdit : public QLineEdit
{
public:
HintingLineEdit( const QString &hint = QString(), QWidget *parent = 0 ) : QLineEdit( parent ), m_hint( hint )
{ }
void setHint( const QString &hint )
{
m_hint = hint;
}
protected:
void paintEvent ( QPaintEvent *pe )
{
QLineEdit::paintEvent( pe );
if ( !hasFocus() && text().isEmpty() )
{
QStyleOptionFrame opt;
initStyleOption( &opt );
QPainter p(this);
QColor fg = palette().color( foregroundRole() );
fg.setAlpha( fg.alpha() / 2 );
p.setPen( fg );
p.drawText( style()->subElementRect( QStyle::SE_LineEditContents, &opt, this ),
alignment() | Qt::TextSingleLine | Qt::TextIncludeTrailingSpaces,
m_hint );
p.end();
}
}
private:
QString m_hint;
};
LayoutEditDialog::LayoutEditDialog( QWidget *parent ) : QDialog( parent )
{
setWindowTitle( i18n( "Configuration for" ) );
QFont boldFont = font();
boldFont.setBold( true );
QVBoxLayout *l1 = new QVBoxLayout( this );
QHBoxLayout *l2 = new QHBoxLayout;
l2->addWidget( m_prefix = new HintingLineEdit( i18nc( "placeholder for a prefix", "[prefix]" ), this ) );
m_prefix->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
l2->addWidget( m_element = new QLabel( this ) );
m_element->setFont( boldFont );
l2->addWidget( m_suffix = new HintingLineEdit( i18nc( "placeholder for a suffix", "[suffix]" ), this ) );
l1->addLayout( l2 );
QFrame *line = new QFrame( this );
line->setFrameStyle( QFrame::Sunken | QFrame::HLine );
l1->addWidget( line );
QWidget *boxWidget = new QWidget( this );
QLabel *l;
#define HAVE_WIDTH_MODES 0
QHBoxLayout *l4 = new QHBoxLayout;
l = new QLabel( i18n( "Width: " ), this );
l->setFont( boldFont );
l4->addWidget( l );
l4->addWidget( m_fixedWidth = new QRadioButton( i18n( "Custom" ), this ) );
m_fixedWidth->setToolTip( i18n( "Either a fixed (absolute) value, or a relative value (e.g. 128px or 12%)." ) );
m_fixedWidth->setChecked( true );
#if HAVE_WIDTH_MODES
l4->addWidget( m_fitContent = new QRadioButton( i18n( "Fit content" ), this ) );
m_fitContent->setToolTip( i18n( "Fit the element text" ) );
#endif
l4->addWidget( m_automaticWidth = new QRadioButton( i18nc( "automatic width", "Automatic" ), this ) );
m_automaticWidth->setToolTip( i18n( "Take homogeneous part of the space available to all elements with automatic width" ) );
l4->addStretch();
- boxWidget->connect( m_fixedWidth, SIGNAL(toggled(bool)), SLOT(setEnabled(bool)) );
- connect( m_automaticWidth, SIGNAL(toggled(bool)), SLOT(setAutomaticWidth(bool)) );
+ boxWidget->connect( m_fixedWidth, &QRadioButton::toggled, this, &LayoutEditDialog::setEnabled );
+ connect( m_automaticWidth, &QRadioButton::toggled, this, &LayoutEditDialog::setAutomaticWidth );
l1->addLayout( l4 );
QHBoxLayout *l5 = new QHBoxLayout( boxWidget );
l5->addWidget( m_width = new QSlider( Qt::Horizontal, boxWidget ) );
m_width->setRange( 0, 100 );
l = new QLabel( boxWidget );
l5->addWidget( l );
// width->connect( sizeMode, SIGNAL(currentIndexChanged(int)), SLOT(setDisabled()) )
l->setNum( 0 );
- l->connect( m_width, SIGNAL(valueChanged(int)), SLOT(setNum(int)) );
+ connect( m_width, &QSlider::valueChanged, l, QOverload<int>::of(&QLabel::setNum) );
#define HAVE_METRICS 0
#if HAVE_METRICS
QComboBox *metrics = new QComboBox( this );
metrics->setFrame( false );
metrics->addItem( "%" );
metrics->addItem( "px" );
metrics->addItem( "chars" );
l5->addWidget( metrics );
#else
QLabel *metrics = new QLabel( "%", this );
l5->addWidget( metrics );
#endif
l1->addWidget( boxWidget );
line = new QFrame( this );
line->setFrameStyle( QFrame::Sunken | QFrame::HLine );
l1->addWidget( line );
QHBoxLayout *l3 = new QHBoxLayout;
l = new QLabel( i18n( "Alignment: " ), this );
l->setFont( boldFont );
l3->addWidget( l );
l3->addWidget( m_alignLeft = new QToolButton( this ) );
l3->addWidget( m_alignCenter = new QToolButton( this ) );
l3->addWidget( m_alignRight = new QToolButton( this ) );
l3->addSpacing( 12 );
l = new QLabel( i18n( "Font: " ), this );
l->setFont( boldFont );
l3->addWidget( l );
l3->addWidget( m_bold = new QToolButton( this ) );
l3->addWidget( m_italic = new QToolButton( this ) );
l3->addWidget( m_underline = new QToolButton( this ) );
l3->addStretch();
l1->addLayout( l3 );
QDialogButtonBox *box = new QDialogButtonBox(this);
box->addButton( QDialogButtonBox::Cancel );
box->addButton( QDialogButtonBox::Ok );
- connect( box, SIGNAL(rejected()), SLOT(close()) );
- connect( box, SIGNAL(accepted()), SLOT(apply()) );
+ connect( box, &QDialogButtonBox::rejected, this, &LayoutEditDialog::close );
+ connect( box, &QDialogButtonBox::accepted, this, &LayoutEditDialog::apply );
l1->addWidget( box );
l1->addStretch();
m_alignLeft->setIcon( QIcon::fromTheme( "format-justify-left" ) );
m_alignLeft->setCheckable( true );
m_alignCenter->setIcon( QIcon::fromTheme( "format-justify-center" ) );
m_alignCenter->setCheckable( true );
m_alignRight->setIcon( QIcon::fromTheme( "format-justify-right" ) );
m_alignRight->setCheckable( true );
QButtonGroup *align = new QButtonGroup( this );
align->setExclusive( true );
align->addButton( m_alignLeft );
align->addButton( m_alignCenter );
align->addButton( m_alignRight );
m_bold->setIcon( QIcon::fromTheme( "format-text-bold" ) );
m_bold->setCheckable( true );
m_italic->setIcon( QIcon::fromTheme( "format-text-italic" ) );
m_italic->setCheckable( true );
m_underline->setIcon( QIcon::fromTheme( "format-text-underline" ) );
m_underline->setCheckable( true );
}
void LayoutEditDialog::apply()
{
if( !m_token )
return;
m_token.data()->setPrefix( m_prefix->text() );
m_token.data()->setSuffix( m_suffix->text() );
m_token.data()->setWidth( m_width->value() );
if ( m_alignLeft->isChecked() )
m_token.data()->setAlignment( Qt::AlignLeft );
else if ( m_alignCenter->isChecked() )
m_token.data()->setAlignment( Qt::AlignHCenter );
else if ( m_alignRight->isChecked() )
m_token.data()->setAlignment( Qt::AlignRight );
m_token.data()->setBold( m_bold->isChecked() );
m_token.data()->setItalic( m_italic->isChecked() );
m_token.data()->setUnderline( m_underline->isChecked() );
// we do this here to avoid reliance on the connection order (i.e. prevent close before apply)
if( sender() )
close();
}
void LayoutEditDialog::close()
{
m_token.clear();
QDialog::close();
}
void LayoutEditDialog::setAutomaticWidth( bool automatic )
{
if( automatic )
{
m_previousWidth = m_width->value();
m_width->setMinimum( 0 ); // without setting the minumum we can't set the value..
m_width->setValue( 0 ); // automatic width is represented by width == 0
}
else
{
m_width->setValue( m_previousWidth );
m_width->setMinimum( 1 ); // set minimum back to 1 since "0" means automatic
}
}
void LayoutEditDialog::setToken( TokenWithLayout *t )
{
setWindowTitle( i18n( "Configuration for '%1'", t->name() ) );
apply();
m_token = t;
if ( m_token )
{
m_element->setText( m_token.data()->name() );
m_prefix->setText( m_token.data()->prefix() );
m_suffix->setText( m_token.data()->suffix() );
// Compute the remaining space from the tokens on the same line.
// this should still not be done here as it makes upward assumptions
// solution(?) token->element->row->elements
TokenDropTarget *editWidget = qobject_cast<TokenDropTarget*>( m_token.data()->parentWidget() );
if( editWidget )
{
qreal spareWidth = 100.0;
int row = editWidget->row( m_token.data() );
if( row > -1 )
{
QList<Token*> tokens = editWidget->tokensAtRow( row );
foreach ( Token *token, tokens )
{
if ( token == m_token.data() )
continue;
if ( TokenWithLayout *twl = qobject_cast<TokenWithLayout*>( token ) )
spareWidth -= twl->width() * 100.0;
}
}
int max = qMax( spareWidth, qreal( 0.0 ) );
if( max >= m_token.data()->width() * 100.0 )
m_width->setMaximum( qMax( spareWidth, qreal( 0.0 ) ) );
else
m_width->setMaximum( m_token.data()->width() * 100.0 );
}
m_width->setValue( m_token.data()->width() * 100.0 );
m_previousWidth = m_width->value();
if ( m_token.data()->width() > 0.0 )
m_fixedWidth->setChecked( true );
else
m_automaticWidth->setChecked( true );
if ( m_token.data()->alignment() & Qt::AlignLeft )
m_alignLeft->setChecked(true);
else if ( m_token.data()->alignment() & Qt::AlignHCenter )
m_alignCenter->setChecked(true);
else if ( m_token.data()->alignment() & Qt::AlignRight )
m_alignRight->setChecked(true);
m_bold->setChecked( m_token.data()->bold() );
m_italic->setChecked( m_token.data()->italic() );
m_underline->setChecked( m_token.data()->underline() );
}
}
diff --git a/src/playlist/layouts/PlaylistLayoutEditDialog.cpp b/src/playlist/layouts/PlaylistLayoutEditDialog.cpp
index 84dd7a1be5..907958774e 100644
--- a/src/playlist/layouts/PlaylistLayoutEditDialog.cpp
+++ b/src/playlist/layouts/PlaylistLayoutEditDialog.cpp
@@ -1,526 +1,528 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2010 Oleksandr Khayrullin <saniokh@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistLayoutEditDialog.h"
#include "core/support/Debug.h"
#include "playlist/layouts/LayoutManager.h"
#include "playlist/PlaylistDefines.h"
#include <KMessageBox>
#include <KInputDialog>
#include <QLineEdit>
Playlist::PlaylistLayoutEditDialog::PlaylistLayoutEditDialog( QWidget *parent )
: QDialog( parent )
{
setupUi( this );
// -- add tokens to the token pool
Column tokenValues[] = {
Album,
AlbumArtist,
Artist,
Bitrate,
Bpm,
Comment,
Composer,
Directory,
DiscNumber,
Divider,
Filename,
Filesize,
Genre,
GroupLength,
GroupTracks,
LastPlayed,
Labels,
Length,
Moodbar,
PlaceHolder,
PlayCount,
Rating,
SampleRate,
Score,
Source,
Title,
TitleWithTrackNum,
TrackNumber,
Type,
Year };
for( uint i = 0; i < sizeof( tokenValues ) / sizeof( tokenValues[0] ); i++ )
tokenPool->addToken( new Token( columnName( tokenValues[i] ),
iconName( tokenValues[i] ),
static_cast<qint64>(tokenValues[i]) ) );
m_firstActiveLayout = LayoutManager::instance()->activeLayoutName();
//add an editor to each tab
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
m_partsEdit[part] = new Playlist::LayoutEditWidget( this );
m_layoutsMap = new QMap<QString, PlaylistLayout>();
elementTabs->addTab( m_partsEdit[PlaylistLayout::Head], i18n( "Head" ) );
elementTabs->addTab( m_partsEdit[PlaylistLayout::StandardBody], i18n( "Body" ) );
elementTabs->addTab( m_partsEdit[PlaylistLayout::VariousArtistsBody], i18n( "Body (Various artists)" ) );
elementTabs->addTab( m_partsEdit[PlaylistLayout::Single], i18n( "Single" ) );
QStringList layoutNames = LayoutManager::instance()->layouts();
foreach( const QString &layoutName, layoutNames )
{
PlaylistLayout layout = LayoutManager::instance()->layout( layoutName );
layout.setDirty( false );
m_layoutsMap->insert( layoutName, layout );
}
layoutListWidget->addItems( layoutNames );
layoutListWidget->setCurrentRow( LayoutManager::instance()->layouts().indexOf( LayoutManager::instance()->activeLayoutName() ) );
setupGroupByCombo();
if ( layoutListWidget->currentItem() )
setLayout( layoutListWidget->currentItem()->text() );
- connect( previewButton, SIGNAL(clicked()), this, SLOT(preview()) );
- connect( layoutListWidget, SIGNAL(currentTextChanged(QString)), this, SLOT(setLayout(QString)) );
- connect( layoutListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(toggleEditButtons()) );
- connect( layoutListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(toggleUpDownButtons()) );
+ connect( previewButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::preview );
+ connect( layoutListWidget, &QListWidget::currentTextChanged, this, &PlaylistLayoutEditDialog::setLayout );
+ connect( layoutListWidget, &QListWidget::currentRowChanged, this, &PlaylistLayoutEditDialog::toggleEditButtons );
+ connect( layoutListWidget, &QListWidget::currentRowChanged, this, &PlaylistLayoutEditDialog::toggleUpDownButtons );
- connect( moveUpButton, SIGNAL(clicked()), this, SLOT(moveUp()) );
- connect( moveDownButton, SIGNAL(clicked()), this, SLOT(moveDown()) );
+ connect( moveUpButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::moveUp );
+ connect( moveDownButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::moveDown );
buttonBox->button(QDialogButtonBox::Apply)->setIcon( QIcon::fromTheme( "dialog-ok-apply" ) );
buttonBox->button(QDialogButtonBox::Ok)->setIcon( QIcon::fromTheme( "dialog-ok" ) );
buttonBox->button(QDialogButtonBox::Cancel)->setIcon( QIcon::fromTheme( "dialog-cancel" ) );
- connect( buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply()) );
+ connect( buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::apply );
const QIcon newIcon( "document-new" );
newLayoutButton->setIcon( newIcon );
newLayoutButton->setToolTip( i18n( "New playlist layout" ) );
- connect( newLayoutButton, SIGNAL(clicked()), this, SLOT(newLayout()) );
+ connect( newLayoutButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::newLayout );
const QIcon copyIcon( "edit-copy" );
copyLayoutButton->setIcon( copyIcon );
copyLayoutButton->setToolTip( i18n( "Copy playlist layout" ) );
- connect( copyLayoutButton, SIGNAL(clicked()), this, SLOT(copyLayout()) );
+ connect( copyLayoutButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::copyLayout );
const QIcon deleteIcon( "edit-delete" );
deleteLayoutButton->setIcon( deleteIcon );
deleteLayoutButton->setToolTip( i18n( "Delete playlist layout" ) );
- connect( deleteLayoutButton, SIGNAL(clicked()), this, SLOT(deleteLayout()) );
+ connect( deleteLayoutButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::deleteLayout );
const QIcon renameIcon( "edit-rename" );
renameLayoutButton->setIcon( renameIcon );
renameLayoutButton->setToolTip( i18n( "Rename playlist layout" ) );
- connect( renameLayoutButton, SIGNAL(clicked()), this, SLOT(renameLayout()) );
+ connect( renameLayoutButton, &QAbstractButton::clicked, this, &PlaylistLayoutEditDialog::renameLayout );
toggleEditButtons();
toggleUpDownButtons();
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
- connect( m_partsEdit[part], SIGNAL(changed()), this, SLOT(setLayoutChanged()) );
- connect( inlineControlsChekbox, SIGNAL(stateChanged(int)), this, SLOT(setLayoutChanged()) );
- connect( tooltipsCheckbox, SIGNAL(stateChanged(int)), this, SLOT(setLayoutChanged()) );
- connect( groupByComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setLayoutChanged()) );
+ connect( m_partsEdit[part], &Playlist::LayoutEditWidget::changed, this, &PlaylistLayoutEditDialog::setLayoutChanged );
+
+ connect( inlineControlsChekbox, &QCheckBox::stateChanged, this, &PlaylistLayoutEditDialog::setLayoutChanged );
+ connect( tooltipsCheckbox, &QCheckBox::stateChanged, this, &PlaylistLayoutEditDialog::setLayoutChanged );
+ connect( groupByComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &PlaylistLayoutEditDialog::setLayoutChanged );
}
Playlist::PlaylistLayoutEditDialog::~PlaylistLayoutEditDialog()
{
}
void
Playlist::PlaylistLayoutEditDialog::newLayout() //SLOT
{
bool ok;
QString layoutName = KInputDialog::getText( i18n( "Choose a name for the new playlist layout" ),
i18n( "Please enter a name for the playlist layout you are about to define:" ),QString(), &ok, this );
if( !ok )
return;
if( layoutName.isEmpty() )
{
KMessageBox::sorry( this, i18n( "Cannot create a layout with no name." ), i18n( "Layout name error" ) );
return;
}
if( m_layoutsMap->keys().contains( layoutName ) )
{
KMessageBox::sorry( this, i18n( "Cannot create a layout with the same name as an existing layout." ), i18n( "Layout name error" ) );
return;
}
if( layoutName.contains( '/' ) )
{
KMessageBox::sorry( this, i18n( "Cannot create a layout containing '/'." ), i18n( "Layout name error" ) );
return;
}
PlaylistLayout layout;
layout.setEditable( true ); //Should I use true, TRUE or 1?
layout.setDirty( true );
layoutListWidget->addItem( layoutName );
layoutListWidget->setCurrentItem( (layoutListWidget->findItems( layoutName, Qt::MatchExactly ) ).first() );
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
{
m_partsEdit[part]->clear();
layout.setLayoutForPart( (PlaylistLayout::Part)part, m_partsEdit[part]->config() );
}
m_layoutsMap->insert( layoutName, layout );
LayoutManager::instance()->addUserLayout( layoutName, layout );
setLayout( layoutName );
}
void
Playlist::PlaylistLayoutEditDialog::copyLayout()
{
LayoutItemConfig configs[PlaylistLayout::NumParts];
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
configs[part] = m_partsEdit[part]->config();
QString layoutName = layoutListWidget->currentItem()->text();
bool ok;
layoutName = KInputDialog::getText( i18n( "Choose a name for the new playlist layout" ),
i18n( "Please enter a name for the playlist layout you are about to define as copy of the layout '%1':", layoutName ),
layoutName, &ok, this );
if( !ok)
return;
if( layoutName.isEmpty() )
{
KMessageBox::sorry( this, i18n( "Cannot create a layout with no name." ), i18n( "Layout name error" ) );
return;
}
if( m_layoutsMap->keys().contains( layoutName ) )
{
KMessageBox::sorry( this, i18n( "Cannot create a layout with the same name as an existing layout." ), i18n( "Layout name error" ) );
return;
}
//layoutListWidget->addItem( layoutName );
PlaylistLayout layout;
layout.setEditable( true ); //Should I use true, TRUE or 1?
layout.setDirty( true );
configs[PlaylistLayout::Head].setActiveIndicatorRow( -1 );
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
layout.setLayoutForPart( (PlaylistLayout::Part)part, configs[part] );
layout.setInlineControls( inlineControlsChekbox->isChecked() );
layout.setTooltips( tooltipsCheckbox->isChecked() );
layout.setGroupBy( groupByComboBox->itemData( groupByComboBox->currentIndex() ).toString() );
LayoutManager::instance()->addUserLayout( layoutName, layout );
//reload from manager:
layoutListWidget->clear();
layoutListWidget->addItems( LayoutManager::instance()->layouts() );
m_layoutsMap->insert( layoutName, layout );
layoutListWidget->setCurrentItem( ( layoutListWidget->findItems( layoutName, Qt::MatchExactly ) ).first() );
setLayout( layoutName );
}
void
Playlist::PlaylistLayoutEditDialog::deleteLayout() //SLOT
{
m_layoutsMap->remove( layoutListWidget->currentItem()->text() );
if( LayoutManager::instance()->layouts().contains( layoutListWidget->currentItem()->text() ) ) //if the layout is already saved in the LayoutManager
LayoutManager::instance()->deleteLayout( layoutListWidget->currentItem()->text() ); //delete it
delete layoutListWidget->currentItem();
}
void
Playlist::PlaylistLayoutEditDialog::renameLayout()
{
PlaylistLayout layout = m_layoutsMap->value( layoutListWidget->currentItem()->text() );
QString layoutName;
while( layoutName.isEmpty() || m_layoutsMap->keys().contains( layoutName ) )
{
bool ok;
layoutName = KInputDialog::getText( i18n( "Choose a new name for the playlist layout" ),
i18n( "Please enter a new name for the playlist layout you are about to rename:" ),
layoutListWidget->currentItem()->text(), &ok, this);
if ( !ok )
{
//Cancelled so just return
return;
}
if( layoutName.isEmpty() )
KMessageBox::sorry( this, i18n( "Cannot rename a layout to have no name." ), i18n( "Layout name error" ) );
if( m_layoutsMap->keys().contains( layoutName ) )
KMessageBox::sorry( this, i18n( "Cannot rename a layout to have the same name as an existing layout." ), i18n( "Layout name error" ) );
}
m_layoutsMap->remove( layoutListWidget->currentItem()->text() );
if( LayoutManager::instance()->layouts().contains( layoutListWidget->currentItem()->text() ) ) //if the layout is already saved in the LayoutManager
LayoutManager::instance()->deleteLayout( layoutListWidget->currentItem()->text() ); //delete it
LayoutManager::instance()->addUserLayout( layoutName, layout );
m_layoutsMap->insert( layoutName, layout );
layoutListWidget->currentItem()->setText( layoutName );
setLayout( layoutName );
}
void
Playlist::PlaylistLayoutEditDialog::setLayout( const QString &layoutName ) //SLOT
{
DEBUG_BLOCK
m_layoutName = layoutName;
if( m_layoutsMap->keys().contains( layoutName ) ) //is the layout exists in the list of loaded layouts
{
debug() << "loaded layout";
PlaylistLayout layout = m_layoutsMap->value( layoutName );
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
m_partsEdit[part]->readLayout( layout.layoutForPart( (PlaylistLayout::Part)part ) );
inlineControlsChekbox->setChecked( layout.inlineControls() );
tooltipsCheckbox->setChecked( layout.tooltips() );
groupByComboBox->setCurrentIndex( groupByComboBox->findData( layout.groupBy() ) );
setEnabledTabs();
//make sure that it is not marked dirty (it will be because of the changed signal triggereing when loagin it)
//unless it is actually changed
debug() << "not dirty anyway!!";
(*m_layoutsMap)[m_layoutName].setDirty( false );
}
else
{
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
m_partsEdit[part]->clear();
}
}
void
Playlist::PlaylistLayoutEditDialog::preview()
{
PlaylistLayout layout;
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
{
LayoutItemConfig config = m_partsEdit[part]->config();
if( part == PlaylistLayout::Head )
config.setActiveIndicatorRow( -1 );
layout.setLayoutForPart( (PlaylistLayout::Part)part, config );
}
layout.setInlineControls( inlineControlsChekbox->isChecked() );
layout.setTooltips( tooltipsCheckbox->isChecked() );
layout.setGroupBy( groupByComboBox->itemData( groupByComboBox->currentIndex() ).toString() );
LayoutManager::instance()->setPreviewLayout( layout );
}
void
Playlist::PlaylistLayoutEditDialog::toggleEditButtons() //SLOT
{
if ( !layoutListWidget->currentItem() ) {
deleteLayoutButton->setEnabled( 0 );
renameLayoutButton->setEnabled( 0 );
} else if( LayoutManager::instance()->isDefaultLayout( layoutListWidget->currentItem()->text() ) ) {
deleteLayoutButton->setEnabled( 0 );
renameLayoutButton->setEnabled( 0 );
} else {
deleteLayoutButton->setEnabled( 1 );
renameLayoutButton->setEnabled( 1 );
}
}
void
Playlist::PlaylistLayoutEditDialog::toggleUpDownButtons()
{
if ( !layoutListWidget->currentItem() )
{
moveUpButton->setEnabled( 0 );
moveDownButton->setEnabled( 0 );
}
else if ( layoutListWidget->currentRow() == 0 )
{
moveUpButton->setEnabled( 0 );
if ( layoutListWidget->currentRow() >= m_layoutsMap->size() -1 )
moveDownButton->setEnabled( 0 );
else
moveDownButton->setEnabled( 1 );
}
else if ( layoutListWidget->currentRow() >= m_layoutsMap->size() -1 )
{
moveDownButton->setEnabled( 0 );
moveUpButton->setEnabled( 1 ); //we already cheked that this is not row 0
}
else
{
moveDownButton->setEnabled( 1 );
moveUpButton->setEnabled( 1 );
}
}
void
Playlist::PlaylistLayoutEditDialog::apply() //SLOT
{
foreach( const QString &layoutName, m_layoutsMap->keys() )
{
PlaylistLayout layout = m_layoutsMap->value( layoutName );
if( layout.isDirty() )
{
// search a new name for changed default layouts
if( LayoutManager::instance()->isDefaultLayout( layoutName ) )
{
QString newLayoutName = i18n( "copy of %1", layoutName );
QString orgCopyName = newLayoutName;
int copyNumber = 1;
QStringList existingLayouts = LayoutManager::instance()->layouts();
while( existingLayouts.contains( newLayoutName ) )
{
copyNumber++;
newLayoutName = i18nc( "adds a copy number to a generated name if the name already exists, for instance 'copy of Foo 2' if 'copy of Foo' is taken", "%1 %2", orgCopyName, copyNumber );
}
const QString msg = i18n( "The layout '%1' you modified is one of the default layouts and cannot be overwritten. "
"Saved as new layout '%2'", layoutName, newLayoutName );
KMessageBox::sorry( this, msg, i18n( "Default Layout" ) );
layout.setDirty( false );
m_layoutsMap->insert( newLayoutName, layout );
LayoutManager::instance()->addUserLayout( newLayoutName, layout );
layoutListWidget->addItem( newLayoutName );
if( layoutName == m_layoutName )
layoutListWidget->setCurrentItem( ( layoutListWidget->findItems( newLayoutName, Qt::MatchExactly ) ).first() );
// restore the default layout
m_layoutsMap->insert( layoutName, LayoutManager::instance()->layout( layoutName ) );
}
else
{
layout.setDirty( false );
m_layoutsMap->insert( layoutName, layout );
LayoutManager::instance()->addUserLayout( layoutName, layout );
}
}
}
LayoutManager::instance()->setActiveLayout( layoutListWidget->currentItem()->text() ); //important to override the previewed layout if preview is used
}
void
Playlist::PlaylistLayoutEditDialog::accept() //SLOT
{
apply();
QDialog::accept();
}
void
Playlist::PlaylistLayoutEditDialog::reject() //SLOT
{
DEBUG_BLOCK
debug() << "Applying initial layout: " << m_firstActiveLayout;
if( layoutListWidget->findItems( m_firstActiveLayout, Qt::MatchExactly ).isEmpty() )
LayoutManager::instance()->setActiveLayout( "Default" );
else
LayoutManager::instance()->setActiveLayout( m_firstActiveLayout );
QDialog::reject();
}
void
Playlist::PlaylistLayoutEditDialog::moveUp()
{
int newRow = LayoutManager::instance()->moveUp( m_layoutName );
layoutListWidget->clear();
layoutListWidget->addItems( LayoutManager::instance()->layouts() );
layoutListWidget->setCurrentRow( newRow );
}
void
Playlist::PlaylistLayoutEditDialog::moveDown()
{
int newRow = LayoutManager::instance()->moveDown( m_layoutName );
layoutListWidget->clear();
layoutListWidget->addItems( LayoutManager::instance()->layouts() );
layoutListWidget->setCurrentRow( newRow );
}
void
Playlist::PlaylistLayoutEditDialog::setEnabledTabs()
{
DEBUG_BLOCK
//Enable or disable tabs depending on whether grouping is allowed.
QString grouping = groupByComboBox->itemData( groupByComboBox->currentIndex() ).toString();
bool groupingEnabled = ( !grouping.isEmpty() && grouping != "None" );
if ( !groupingEnabled )
elementTabs->setCurrentWidget( m_partsEdit[PlaylistLayout::Single] );
debug() << groupByComboBox->itemData( groupByComboBox->currentIndex() ).toString();
debug() << groupingEnabled;
elementTabs->setTabEnabled( elementTabs->indexOf( m_partsEdit[PlaylistLayout::Head] ), groupingEnabled );
elementTabs->setTabEnabled( elementTabs->indexOf( m_partsEdit[PlaylistLayout::StandardBody] ), groupingEnabled );
elementTabs->setTabEnabled( elementTabs->indexOf( m_partsEdit[PlaylistLayout::VariousArtistsBody] ), groupingEnabled );
}
//Sets up a combo box that presents the possible grouping categories, as well as the option
//to perform no grouping.
//We'll use the "user data" to store the un-i18n-ized category name for internal use.
void
Playlist::PlaylistLayoutEditDialog::setupGroupByCombo()
{
foreach( const Playlist::Column &col, Playlist::groupableCategories() )
{
groupByComboBox->addItem( QIcon::fromTheme( iconName( col ) ),
columnName( col ),
QVariant( internalColumnName( col ) ) );
}
//Add the option to not perform grouping
//Use a null string to specify "no grouping"
groupByComboBox->addItem( i18n( "No Grouping" ), QVariant( "None" ) );
}
void
Playlist::PlaylistLayoutEditDialog::setLayoutChanged()
{
DEBUG_BLOCK
setEnabledTabs();
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
(*m_layoutsMap)[m_layoutName].setLayoutForPart( (PlaylistLayout::Part)part, m_partsEdit[part]->config() );
(*m_layoutsMap)[m_layoutName].setInlineControls( inlineControlsChekbox->isChecked() );
(*m_layoutsMap)[m_layoutName].setTooltips( tooltipsCheckbox->isChecked() );
(*m_layoutsMap)[m_layoutName].setGroupBy( groupByComboBox->itemData( groupByComboBox->currentIndex() ).toString() );
(*m_layoutsMap)[m_layoutName].setDirty( true );
}
diff --git a/src/playlist/navigators/DynamicTrackNavigator.cpp b/src/playlist/navigators/DynamicTrackNavigator.cpp
index c1e12371c8..e7ceca6176 100644
--- a/src/playlist/navigators/DynamicTrackNavigator.cpp
+++ b/src/playlist/navigators/DynamicTrackNavigator.cpp
@@ -1,121 +1,124 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "DynamicTrackNavigator.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "dynamic/DynamicPlaylist.h"
#include "dynamic/DynamicModel.h"
+#include "playlist/PlaylistModel.h"
#include "playlist/PlaylistController.h"
Playlist::DynamicTrackNavigator::DynamicTrackNavigator()
: m_playlist( 0 )
{
- connect( m_model->qaim(), SIGNAL(activeTrackChanged(quint64)), SLOT(trackChanged()) );
- connect( m_model->qaim(), SIGNAL(modelReset()), SLOT(repopulate()) );
+ connect( qobject_cast<Playlist::Model*>(m_model->qaim()), &Playlist::Model::activeTrackChanged,
+ this, &DynamicTrackNavigator::trackChanged );
+ connect( m_model->qaim(), &QAbstractItemModel::modelReset,
+ this, &DynamicTrackNavigator::repopulate );
- connect( Dynamic::DynamicModel::instance(), SIGNAL(activeChanged(int)),
- SLOT(activePlaylistChanged()) );
+ connect( Dynamic::DynamicModel::instance(), &Dynamic::DynamicModel::activeChanged,
+ this, &DynamicTrackNavigator::activePlaylistChanged );
activePlaylistChanged();
}
Playlist::DynamicTrackNavigator::~DynamicTrackNavigator()
{
if( !m_playlist )
m_playlist->requestAbort();
}
void
Playlist::DynamicTrackNavigator::receiveTracks( Meta::TrackList tracks )
{
The::playlistController()->insertOptioned( tracks );
}
void
Playlist::DynamicTrackNavigator::appendUpcoming()
{
// a little bit stupid. the playlist jumps to the newly inserted tracks
int updateRow = m_model->activeRow() + 1;
int rowCount = m_model->qaim()->rowCount();
int upcomingCountLag = AmarokConfig::upcomingTracks() - ( rowCount - updateRow );
if( upcomingCountLag > 0 && m_playlist )
m_playlist->requestTracks( upcomingCountLag );
}
void
Playlist::DynamicTrackNavigator::removePlayed()
{
int activeRow = m_model->activeRow();
if( activeRow > AmarokConfig::previousTracks() )
The::playlistController()->removeRows( 0, activeRow - AmarokConfig::previousTracks() );
}
void
Playlist::DynamicTrackNavigator::activePlaylistChanged()
{
DEBUG_BLOCK
Dynamic::DynamicPlaylist *newPlaylist =
Dynamic::DynamicModel::instance()->activePlaylist();
if( newPlaylist == m_playlist )
return;
if( m_playlist )
{
- disconnect( m_playlist, SIGNAL(tracksReady(Meta::TrackList)),
- this, SLOT(receiveTracks(Meta::TrackList)) );
+ disconnect( m_playlist, &Dynamic::DynamicPlaylist::tracksReady,
+ this, &DynamicTrackNavigator::receiveTracks );
m_playlist->requestAbort();
}
m_playlist = newPlaylist;
if( !m_playlist )
{
warning() << "No dynamic playlist current loaded! Creating dynamic track navigator with null playlist!";
}
else
{
- connect( m_playlist, SIGNAL(tracksReady(Meta::TrackList)),
- this, SLOT(receiveTracks(Meta::TrackList)) );
+ connect( m_playlist, &Dynamic::DynamicPlaylist::tracksReady,
+ this, &DynamicTrackNavigator::receiveTracks );
}
}
void
Playlist::DynamicTrackNavigator::trackChanged()
{
appendUpcoming();
removePlayed();
}
void
Playlist::DynamicTrackNavigator::repopulate()
{
// remove all future tracks
int activeRow = m_model->activeRow();
int rowCount = m_model->qaim()->rowCount();
if( activeRow < rowCount )
The::playlistController()->removeRows( activeRow + 1, rowCount - activeRow - 1);
appendUpcoming();
}
diff --git a/src/playlist/navigators/NavigatorConfigAction.cpp b/src/playlist/navigators/NavigatorConfigAction.cpp
index c508fafa06..689d7e9cd0 100644
--- a/src/playlist/navigators/NavigatorConfigAction.cpp
+++ b/src/playlist/navigators/NavigatorConfigAction.cpp
@@ -1,268 +1,268 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "NavigatorConfigAction.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistActions.h"
#include <QMenu>
#include <KLocale>
#include <KStandardDirs>
NavigatorConfigAction::NavigatorConfigAction( QWidget * parent )
: QAction( parent )
{
QMenu * navigatorMenu = new QMenu( 0 );
setMenu( navigatorMenu );
setText( i18n( "Track Progression" ) );
QActionGroup * navigatorActions = new QActionGroup( navigatorMenu );
navigatorActions->setExclusive( true );
m_standardNavigatorAction = navigatorActions->addAction( i18n( "Standard" ) );
m_standardNavigatorAction->setIcon( QIcon::fromTheme( "media-standard-track-progression-amarok" ) );
m_standardNavigatorAction->setCheckable( true );
//action->setIcon( true );
m_onlyQueueNavigatorAction = navigatorActions->addAction( i18n( "Only Queue" ) );
m_onlyQueueNavigatorAction->setIcon( QIcon::fromTheme( "media-standard-track-progression-amarok" ) );
m_onlyQueueNavigatorAction->setCheckable( true );
QAction * action = new QAction( parent );
action->setSeparator( true );
navigatorActions->addAction( action );
m_repeatTrackNavigatorAction = navigatorActions->addAction( i18n( "Repeat Track" ) );
m_repeatTrackNavigatorAction->setIcon( QIcon::fromTheme( "media-repeat-track-amarok" ) );
m_repeatTrackNavigatorAction->setCheckable( true );
m_repeatAlbumNavigatorAction = navigatorActions->addAction( i18n( "Repeat Album" ) );
m_repeatAlbumNavigatorAction->setIcon( QIcon::fromTheme( "media-repeat-album-amarok" ) );
m_repeatAlbumNavigatorAction->setCheckable( true );
m_repeatPlaylistNavigatorAction = navigatorActions->addAction( i18n( "Repeat Playlist" ) );
m_repeatPlaylistNavigatorAction->setIcon( QIcon::fromTheme( "media-repeat-playlist-amarok" ) );
m_repeatPlaylistNavigatorAction->setCheckable( true );
action = new QAction( parent );
action->setSeparator( true );
navigatorActions->addAction( action );
m_randomTrackNavigatorAction = navigatorActions->addAction( i18n( "Random Tracks" ) );
m_randomTrackNavigatorAction->setIcon( QIcon::fromTheme( "media-random-tracks-amarok" ) );
m_randomTrackNavigatorAction->setCheckable( true );
m_randomAlbumNavigatorAction = navigatorActions->addAction( i18n( "Random Albums" ) );
m_randomAlbumNavigatorAction->setIcon( QIcon::fromTheme( "media-random-albums-amarok" ) );
m_randomAlbumNavigatorAction->setCheckable( true );
navigatorMenu->addActions( navigatorActions->actions() );
QMenu * favorMenu = navigatorMenu->addMenu( i18n( "Favor" ) );
QActionGroup * favorActions = new QActionGroup( favorMenu );
m_favorNoneAction = favorActions->addAction( i18n( "None" ) );
m_favorNoneAction->setCheckable( true );
m_favorScoresAction = favorActions->addAction( i18n( "Higher Scores" ) );
m_favorScoresAction->setCheckable( true );
m_favorRatingsAction = favorActions->addAction( i18n( "Higher Ratings" ) );
m_favorRatingsAction->setCheckable( true );
m_favorLastPlayedAction = favorActions->addAction( i18n( "Not Recently Played" ) );
m_favorLastPlayedAction->setCheckable( true );
favorMenu->addActions( favorActions->actions() );
//make sure the correct entry is selected from start:
switch( AmarokConfig::trackProgression() )
{
case AmarokConfig::EnumTrackProgression::OnlyQueue:
m_onlyQueueNavigatorAction->setChecked( true );
setIcon( m_onlyQueueNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RepeatTrack:
m_repeatTrackNavigatorAction->setChecked( true );
setIcon( m_repeatTrackNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RepeatAlbum:
m_repeatAlbumNavigatorAction->setChecked( true );
setIcon( m_repeatAlbumNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RepeatPlaylist:
m_repeatPlaylistNavigatorAction->setChecked( true );
setIcon( m_repeatPlaylistNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RandomTrack:
m_randomTrackNavigatorAction->setChecked( true );
setIcon( m_randomTrackNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RandomAlbum:
m_randomAlbumNavigatorAction->setChecked( true );
setIcon( m_randomAlbumNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::Normal:
default:
m_standardNavigatorAction->setChecked( true );
setIcon( m_standardNavigatorAction->icon() );
break;
}
switch( AmarokConfig::favorTracks() )
{
case AmarokConfig::EnumFavorTracks::HigherScores:
m_favorScoresAction->setChecked( true );
break;
case AmarokConfig::EnumFavorTracks::HigherRatings:
m_favorRatingsAction->setChecked( true );
break;
case AmarokConfig::EnumFavorTracks::LessRecentlyPlayed:
m_favorLastPlayedAction->setChecked( true );
break;
case AmarokConfig::EnumFavorTracks::Off:
default:
m_favorNoneAction->setChecked( true );
break;
}
- connect( navigatorMenu, SIGNAL(triggered(QAction*)), this, SLOT(setActiveNavigator(QAction*)) );
- connect( favorMenu, SIGNAL(triggered(QAction*)), this, SLOT(setFavored(QAction*)) );
- connect( The::playlistActions(), SIGNAL(navigatorChanged()), this, SLOT(navigatorChanged()) );
+ connect( navigatorMenu, &QMenu::triggered, this, &NavigatorConfigAction::setActiveNavigator );
+ connect( favorMenu, &QMenu::triggered, this, &NavigatorConfigAction::setFavored );
+ connect( The::playlistActions(), &Playlist::Actions::navigatorChanged, this, &NavigatorConfigAction::navigatorChanged );
}
NavigatorConfigAction::~NavigatorConfigAction()
{
delete menu();
}
void NavigatorConfigAction::setActiveNavigator( QAction *navigatorAction )
{
DEBUG_BLOCK
if( navigatorAction == m_standardNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
setIcon( m_standardNavigatorAction->icon() );
}
else if ( navigatorAction == m_onlyQueueNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::OnlyQueue );
setIcon( m_onlyQueueNavigatorAction->icon() );
}
else if ( navigatorAction == m_repeatTrackNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatTrack );
setIcon( m_repeatTrackNavigatorAction->icon() );
}
else if ( navigatorAction == m_repeatAlbumNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatAlbum );
setIcon( m_repeatAlbumNavigatorAction->icon() );
}
else if ( navigatorAction == m_repeatPlaylistNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatPlaylist );
setIcon( m_repeatPlaylistNavigatorAction->icon() );
}
else if ( navigatorAction == m_randomTrackNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RandomTrack );
setIcon( m_randomTrackNavigatorAction->icon() );
}
else if ( navigatorAction == m_randomAlbumNavigatorAction )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RandomAlbum );
setIcon( m_randomAlbumNavigatorAction->icon() );
}
The::playlistActions()->playlistModeChanged();
}
void NavigatorConfigAction::setFavored( QAction *favorAction )
{
DEBUG_BLOCK
if( favorAction == m_favorNoneAction )
{
AmarokConfig::setFavorTracks( AmarokConfig::EnumFavorTracks::Off );
}
else if( favorAction == m_favorScoresAction )
{
AmarokConfig::setFavorTracks( AmarokConfig::EnumFavorTracks::HigherScores );
}
else if( favorAction == m_favorRatingsAction )
{
AmarokConfig::setFavorTracks( AmarokConfig::EnumFavorTracks::HigherRatings );
}
else if( favorAction == m_favorLastPlayedAction )
{
AmarokConfig::setFavorTracks( AmarokConfig::EnumFavorTracks::LessRecentlyPlayed );
}
}
void NavigatorConfigAction::navigatorChanged()
{
switch( AmarokConfig::trackProgression() )
{
case AmarokConfig::EnumTrackProgression::OnlyQueue:
m_onlyQueueNavigatorAction->setChecked( true );
setIcon( m_onlyQueueNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RepeatTrack:
m_repeatTrackNavigatorAction->setChecked( true );
setIcon( m_repeatTrackNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RepeatAlbum:
m_repeatAlbumNavigatorAction->setChecked( true );
setIcon( m_repeatAlbumNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RepeatPlaylist:
m_repeatPlaylistNavigatorAction->setChecked( true );
setIcon( m_repeatPlaylistNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RandomTrack:
m_randomTrackNavigatorAction->setChecked( true );
setIcon( m_randomTrackNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::RandomAlbum:
m_randomAlbumNavigatorAction->setChecked( true );
setIcon( m_randomAlbumNavigatorAction->icon() );
break;
case AmarokConfig::EnumTrackProgression::Normal:
default:
m_standardNavigatorAction->setChecked( true );
setIcon( m_standardNavigatorAction->icon() );
break;
}
}
diff --git a/src/playlist/navigators/NonlinearTrackNavigator.cpp b/src/playlist/navigators/NonlinearTrackNavigator.cpp
index a4855ee375..02762a984c 100644
--- a/src/playlist/navigators/NonlinearTrackNavigator.cpp
+++ b/src/playlist/navigators/NonlinearTrackNavigator.cpp
@@ -1,288 +1,290 @@
/****************************************************************************************
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::NonlinearTrackNavigator"
#include "NonlinearTrackNavigator.h"
#include "core/support/Debug.h"
+#include "playlist/PlaylistModel.h"
Playlist::NonlinearTrackNavigator::NonlinearTrackNavigator()
: m_currentItem( 0 )
{
// Connect to the QAbstractItemModel signals of the source model.
// Ignore SIGNAL dataChanged: changes in metadata etc. don't affect the random play order.
// Ignore SIGNAL layoutChanged: rows moving around doesn't affect the random play order.
- connect( m_model->qaim(), SIGNAL(modelReset()), this, SLOT(slotModelReset()) );
- connect( m_model->qaim(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotRowsInserted(QModelIndex,int,int)) );
- connect( m_model->qaim(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) );
+ connect( m_model->qaim(), &QAbstractItemModel::modelReset, this, &NonlinearTrackNavigator::slotModelReset );
+ connect( m_model->qaim(), &QAbstractItemModel::rowsInserted, this, &NonlinearTrackNavigator::slotRowsInserted );
+ connect( m_model->qaim(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &NonlinearTrackNavigator::slotRowsAboutToBeRemoved );
// Connect to the Playlist::AbstractModel signals of the source model.
- connect( m_model->qaim(), SIGNAL(activeTrackChanged(quint64)), this, SLOT(slotActiveTrackChanged(quint64)) );
+ connect( qobject_cast<Playlist::Model*>(m_model->qaim()), &Playlist::Model::activeTrackChanged,
+ this, &NonlinearTrackNavigator::slotActiveTrackChanged );
}
//!***** Keeping in-sync with the source model
void
Playlist::NonlinearTrackNavigator::loadFromSourceModel()
{
DEBUG_BLOCK
slotModelReset();
}
void
Playlist::NonlinearTrackNavigator::slotModelReset()
{
DEBUG_BLOCK
m_insertedItems.clear();
m_removedItems += allItemsSet();
int lastRowInModel = m_model->qaim()->rowCount() - 1;
if ( lastRowInModel >= 0 )
slotRowsInserted( QModelIndex(), 0, lastRowInModel );
doItemListsMaintenance();
if ( !currentItem() )
setCurrentItem( m_model->activeId() );
}
// This function can get called thousands of times during a single FilterProxy change.
// Be very efficient here! (e.g. no DEBUG_BLOCK)
void
Playlist::NonlinearTrackNavigator::slotRowsInserted( const QModelIndex& parent, int startRow, int endRow )
{
Q_UNUSED( parent );
for ( int row = startRow; row <= endRow; row++ )
{
quint64 itemId = m_model->idAt( row );
m_insertedItems.insert( itemId );
m_removedItems.remove( itemId );
}
}
// This function can get called thousands of times during a single FilterProxy change.
// Be very efficient here! (e.g. no DEBUG_BLOCK)
void
Playlist::NonlinearTrackNavigator::slotRowsAboutToBeRemoved( const QModelIndex& parent, int startRow, int endRow )
{
Q_UNUSED( parent );
for ( int row = startRow; row <= endRow; row++ )
{
quint64 itemId = m_model->idAt( row );
m_insertedItems.remove( itemId );
m_removedItems.insert( itemId );
}
}
// A general note on this function: thousands of rows can be inserted/removed by a single
// FilterProxy change. However, this function gets to process them in a big batch.
//
// So: O(n * log n) performance is good enough, but O(n^2) is not.
// (that's also why we need the 'listRemove()' helper function)
void
Playlist::NonlinearTrackNavigator::doItemListsMaintenance()
{
DEBUG_BLOCK
// Move batch instructions to local storage immediately, because we may get called recursively.
QSet<quint64> tmpInsertedItems = m_insertedItems;
m_insertedItems.clear();
QSet<quint64> tmpRemovedItems = m_removedItems;
m_removedItems.clear();
// Handle the removed items
if ( !tmpRemovedItems.isEmpty() )
{
QSet<quint64> knownRemovedItems = tmpRemovedItems & allItemsSet(); // Filter out items inserted+removed between calls to us.
Item::listRemove( m_allItemsList, tmpRemovedItems );
Item::listRemove( m_historyItems, tmpRemovedItems );
Item::listRemove( m_replayedItems, tmpRemovedItems );
Item::listRemove( m_plannedItems, tmpRemovedItems );
notifyItemsRemoved( knownRemovedItems );
if ( tmpRemovedItems.contains( currentItem() ) ) // After 'notifyItemsRemoved()', so that they get a chance to choose a new one.
setCurrentItem( 0 );
}
// Handle the newly inserted items
if ( !tmpInsertedItems.isEmpty() )
{
QSet<quint64> unknownInsertedItems = tmpInsertedItems - allItemsSet(); // Filter out items removed+reinserted between calls to us.
m_allItemsList.append( unknownInsertedItems.toList() );
m_plannedItems.clear(); // Could do this more subtly in each child class, but this is good enough.
notifyItemsInserted( unknownInsertedItems );
}
// Prune history size
while ( m_historyItems.size() > MAX_HISTORY_SIZE )
m_historyItems.removeFirst();
}
//!***** Current playlist item
quint64
Playlist::NonlinearTrackNavigator::currentItem()
{
doItemListsMaintenance();
return m_currentItem;
}
void
Playlist::NonlinearTrackNavigator::setCurrentItem( const quint64 newItem, bool goingBackward )
{
DEBUG_BLOCK
doItemListsMaintenance();
// Remember that we've played the old item.
if ( m_currentItem )
{
if ( goingBackward )
m_replayedItems.prepend( m_currentItem );
else
m_historyItems.append( m_currentItem );
}
m_currentItem = newItem;
// If the new current item happens to also be the next planned item, consider that
// plan "done". Can happen e.g. when the user manually plays our next planned item.
if ( m_currentItem )
if ( !m_plannedItems.isEmpty() && m_plannedItems.first() == m_currentItem )
m_plannedItems.removeFirst();
}
// In the normal case this signal slot is redundant, because 'requestNext|LastTrack()'
// already called 'setCurrentItem()' long before this function gets called.
//
// This signal slot takes care of some special cases, like the user clicking on
// an arbitrary item in the playlist.
void
Playlist::NonlinearTrackNavigator::slotActiveTrackChanged( const quint64 id )
{
DEBUG_BLOCK
doItemListsMaintenance();
if ( currentItem() != id ) // If the new item is not what we expected:
{
// Heuristic: if this new "current item" does not look like we're going back/fwd in
// history, then cancel "visit history" mode.
// Not important, just a small nicety. It's what the user probably wants.
if ( ( m_historyItems.isEmpty() || m_historyItems.last() != id ) &&
( !m_replayedItems.contains( id ) ) )
{
m_historyItems.append( m_replayedItems );
m_replayedItems.clear();
}
// Ditch the plan. The unexpected "current item" might change what we want to do next.
m_plannedItems.clear();
// The main thing we need to do.
setCurrentItem( id );
}
}
//!***** Choosing next playlist item
Playlist::ItemList*
Playlist::NonlinearTrackNavigator::nextItemChooseDonorList()
{
DEBUG_BLOCK
if ( !m_queue.isEmpty() ) // User-specified queue has highest priority.
return &m_queue;
else if ( !m_replayedItems.isEmpty() ) // If the user pressed "previous" once or more, first re-play those items again when we go forward again.
return &m_replayedItems;
else
{
if ( m_plannedItems.isEmpty() )
planOne();
if ( !m_plannedItems.isEmpty() ) // The normal case.
return &m_plannedItems;
else
debug() << "planOne() didn't plan a next item.";
}
return 0;
}
quint64
Playlist::NonlinearTrackNavigator::likelyNextTrack()
{
doItemListsMaintenance();
ItemList *donor = nextItemChooseDonorList();
return donor ? donor->first() : 0;
}
// We could just call 'likelyNextTrack()' and assume that we'll get a 'slotActiveTrackChanged'
// callback later. But let's follow our API strictly: update the donor list immediately.
quint64
Playlist::NonlinearTrackNavigator::requestNextTrack()
{
doItemListsMaintenance();
ItemList *donor = nextItemChooseDonorList();
quint64 nextItem = donor ? donor->takeFirst() : 0;
setCurrentItem( nextItem );
return m_currentItem;
}
//!***** Choosing previous playlist item
quint64
Playlist::NonlinearTrackNavigator::likelyLastTrack()
{
doItemListsMaintenance();
return m_historyItems.isEmpty() ? 0 : m_historyItems.last();
}
quint64
Playlist::NonlinearTrackNavigator::requestLastTrack()
{
doItemListsMaintenance();
quint64 lastItem = m_historyItems.isEmpty() ? 0 : m_historyItems.takeLast();
setCurrentItem( lastItem, true );
return m_currentItem;
}
diff --git a/src/playlist/navigators/RepeatTrackNavigator.cpp b/src/playlist/navigators/RepeatTrackNavigator.cpp
index 7745632664..951a6f1ada 100644
--- a/src/playlist/navigators/RepeatTrackNavigator.cpp
+++ b/src/playlist/navigators/RepeatTrackNavigator.cpp
@@ -1,29 +1,29 @@
/****************************************************************************************
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "RepeatTrackNavigator.h"
#include "playlist/PlaylistModelStack.h"
Playlist::RepeatTrackNavigator::RepeatTrackNavigator()
{
m_trackid = m_model->activeId();
- connect( m_model->qaim(), SIGNAL(activeTrackChanged(quint64)),
- this, SLOT(recvActiveTrackChanged(quint64)) );
+ connect( qobject_cast<Playlist::Model*>(m_model->qaim()), &Playlist::Model::activeTrackChanged,
+ this, &RepeatTrackNavigator::recvActiveTrackChanged );
}
diff --git a/src/playlist/navigators/TrackNavigator.cpp b/src/playlist/navigators/TrackNavigator.cpp
index 7de60fd0e4..a99ec9e05c 100644
--- a/src/playlist/navigators/TrackNavigator.cpp
+++ b/src/playlist/navigators/TrackNavigator.cpp
@@ -1,125 +1,124 @@
/****************************************************************************************
* Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TrackNavigator.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistModelStack.h"
#include <QQueue>
Playlist::TrackNavigator::TrackNavigator()
{
m_model = The::playlist();
// Connect to the QAbstractItemModel signals of the source model.
// Ignore SIGNAL dataChanged: we don't need to know when a playlist item changes.
// Ignore SIGNAL layoutChanged: we don't need to know when rows are moved around.
- connect( m_model->qaim(), SIGNAL(modelReset()), this, SLOT(slotModelReset()) );
- connect( Playlist::ModelStack::instance()->bottom(),
- SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
- SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) );
+ connect( m_model->qaim(), &QAbstractItemModel::modelReset, this, &TrackNavigator::slotModelReset );
+ connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::rowsAboutToBeRemoved,
+ this, &TrackNavigator::slotRowsAboutToBeRemoved );
// Ignore SIGNAL rowsInserted.
}
void
Playlist::TrackNavigator::queueIds( const QList<quint64> &ids )
{
foreach( quint64 id, ids )
{
if( !m_queue.contains( id ) )
m_queue.enqueue( id );
}
}
void
Playlist::TrackNavigator::dequeueId( const quint64 id )
{
m_queue.removeAll( id );
}
bool
Playlist::TrackNavigator::queueMoveUp( const quint64 id )
{
const int idx = m_queue.indexOf( id );
if ( idx < 1 )
return false;
quint64 temp = m_queue[ idx - 1 ];
m_queue[ idx - 1 ] = m_queue[ idx ];
m_queue[ idx ] = temp;
return true;
}
bool
Playlist::TrackNavigator::queueMoveDown( const quint64 id )
{
const int idx = m_queue.indexOf( id );
if ( idx == -1 || idx == m_queue.count() - 1 )
return false;
quint64 temp = m_queue[ idx + 1 ];
m_queue[ idx + 1 ] = m_queue[ idx ];
m_queue[ idx ] = temp;
return true;
}
int
Playlist::TrackNavigator::queuePosition( const quint64 id ) const
{
return m_queue.indexOf( id );
}
QQueue<quint64> Playlist::TrackNavigator::queue()
{
return m_queue;
}
void
Playlist::TrackNavigator::slotModelReset()
{
DEBUG_BLOCK
m_queue.clear(); // We should check 'm_model's new contents, but this is unlikely to bother anyone.
}
void
Playlist::TrackNavigator::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
Q_UNUSED( parent );
for ( int row = start; row <= end; ++row )
{
const quint64 itemId = Playlist::ModelStack::instance()->bottom()->idAt( row );
m_queue.removeAll( itemId );
}
}
quint64
Playlist::TrackNavigator::bestFallbackItem()
{
quint64 item = m_model->activeId();
if ( !item )
if ( m_model->qaim()->rowCount() > 0 )
item = m_model->idAt( 0 );
return item;
}
diff --git a/src/playlist/proxymodels/GroupingProxy.cpp b/src/playlist/proxymodels/GroupingProxy.cpp
index 13c85a7541..8b7401060d 100644
--- a/src/playlist/proxymodels/GroupingProxy.cpp
+++ b/src/playlist/proxymodels/GroupingProxy.cpp
@@ -1,368 +1,368 @@
/****************************************************************************************
* Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::GroupingProxy"
#include "GroupingProxy.h"
#include "core/collections/Collection.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "core/meta/support/MetaUtility.h"
#include "core/capabilities/SourceInfoCapability.h"
#include "core/support/Debug.h"
#include "playlist/PlaylistDefines.h"
#include <QVariant>
#include <QFileInfo>
Playlist::GroupingProxy::GroupingProxy( Playlist::AbstractModel *belowModel, QObject *parent )
: ProxyBase( belowModel, parent )
{
setGroupingCategory( QString( "Album" ) );
// Adjust our internal state based on changes in the source model.
// We connect to our own QAbstractItemModel signals, which are emitted by our
// 'QSortFilterProxyModel' parent class.
//
// Connect to 'this' instead of 'sourceModel()' for 2 reasons:
// - We happen to be a 1:1 passthrough proxy, but if we filtered/sorted rows,
// we'd want to maintain state for the rows exported by the proxy. The rows
// exported by the source model are of no direct interest to us.
//
// - Qt guarantees that our signal handlers on 'this' will be called earlier than
// any other, because we're the first to call 'connect( this )' (hey, we're the
// constructor!). So, we're guaranteed to be able to update our internal state
// before we get any 'data()' calls from "upstream" signal handlers.
//
// If we connected to 'sourceModel()', there would be no such guarantee: it
// would be highly likely that an "upstream" signal handler (connected to the
// 'this' QSFPM signal) would get called earlier, would call our 'data()'
// function, and we would return wrong answers from our stale internal state.
//
- connect( this, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(proxyDataChanged(QModelIndex,QModelIndex)) );
- connect( this, SIGNAL(layoutChanged()), this, SLOT(proxyLayoutChanged()) );
- connect( this, SIGNAL(modelReset()), this, SLOT(proxyModelReset()) );
- connect( this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(proxyRowsInserted(QModelIndex,int,int)) );
- connect( this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(proxyRowsRemoved(QModelIndex,int,int)) );
+ connect( this, &GroupingProxy::dataChanged, this, &GroupingProxy::proxyDataChanged );
+ connect( this, &GroupingProxy::layoutChanged, this, &GroupingProxy::proxyLayoutChanged );
+ connect( this, &GroupingProxy::modelReset, this, &GroupingProxy::proxyModelReset );
+ connect( this, &GroupingProxy::rowsInserted, this, &GroupingProxy::proxyRowsInserted );
+ connect( this, &GroupingProxy::rowsRemoved, this, &GroupingProxy::proxyRowsRemoved );
// No need to scan the pre-existing entries in sourceModel(), because we build our
// internal state on-the-fly.
setObjectName( "GroupingProxy" );
}
Playlist::GroupingProxy::~GroupingProxy()
{
}
QString
Playlist::GroupingProxy::groupingCategory() const
{
return m_groupingCategory;
}
void
Playlist::GroupingProxy::setGroupingCategory( const QString &groupingCategory )
{
m_groupingCategory = groupingCategory;
m_groupingCategoryIndex = groupableCategories().indexOf( columnForName( m_groupingCategory ) ); // May be -1
invalidateGrouping();
// Notify our client(s) that we may now give different answers to 'data()' calls.
// - Not 'layoutChanged': that is for when rows have been moved around, which they haven't.
// - Not 'modelReset': that is too heavy. E.g. it also invalidates QListView item selections, etc.
if ( rowCount() > 0 )
emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
}
bool
Playlist::GroupingProxy::isFirstInGroup( const QModelIndex & index )
{
Grouping::GroupMode mode = groupModeForIndex( index );
return ( (mode == Grouping::Head) || (mode == Grouping::None) );
}
bool
Playlist::GroupingProxy::isLastInGroup( const QModelIndex & index )
{
Grouping::GroupMode mode = groupModeForIndex( index );
return ( (mode == Grouping::Tail) || (mode == Grouping::None) );
}
QModelIndex
Playlist::GroupingProxy::firstIndexInSameGroup( const QModelIndex & index )
{
QModelIndex currIndex = index;
while ( ! isFirstInGroup( currIndex ) )
currIndex = currIndex.sibling( currIndex.row() - 1, currIndex.column() );
return currIndex;
}
QModelIndex
Playlist::GroupingProxy::lastIndexInSameGroup( const QModelIndex & index )
{
QModelIndex currIndex = index;
while ( ! isLastInGroup( currIndex ) )
currIndex = currIndex.sibling( currIndex.row() + 1, currIndex.column() );
return currIndex;
}
int
Playlist::GroupingProxy::groupRowCount( const QModelIndex & index )
{
return ( lastIndexInSameGroup( index ).row() - firstIndexInSameGroup( index ).row() ) + 1;
}
int
Playlist::GroupingProxy::groupPlayLength( const QModelIndex & index )
{
int totalLength = 0;
QModelIndex currIndex = firstIndexInSameGroup( index );
forever {
Meta::TrackPtr track = currIndex.data( TrackRole ).value<Meta::TrackPtr>();
if ( track )
totalLength += track->length();
else
warning() << "Playlist::GroupingProxy::groupPlayLength(): TrackPtr is 0! row =" << currIndex.row() << ", rowCount =" << rowCount();
if ( isLastInGroup( currIndex ) )
break;
currIndex = currIndex.sibling( currIndex.row() + 1, currIndex.column() );
}
return totalLength;
}
QVariant
Playlist::GroupingProxy::data( const QModelIndex& index, int role ) const
{
if( !index.isValid() )
return QVariant();
// Qt forces 'const' in our signature, but'groupModeForRow()' wants to do caching.
GroupingProxy* nonconst_this = const_cast<GroupingProxy*>( this );
switch ( role )
{
case Playlist::GroupRole:
return nonconst_this->groupModeForIndex( index );
case Playlist::GroupedTracksRole:
return nonconst_this->groupRowCount( index );
case Qt::DisplayRole:
case Qt::ToolTipRole:
switch( index.column() )
{
case GroupLength:
return Meta::msToPrettyTime( nonconst_this->groupPlayLength( index ) );
case GroupTracks:
return i18np ( "1 track", "%1 tracks", nonconst_this->groupRowCount( index ) );
}
// Fall-through!!
default:
// Nothing to do with us: let our QSortFilterProxyModel parent class handle it.
// (which will proxy the data() from the underlying model)
return QSortFilterProxyModel::data( index, role );
}
}
// Note: being clever in this function is sometimes wasted effort, because 'dataChanged'
// can cause SortProxy to nuke us with a 'layoutChanged' signal very soon anyway.
void
Playlist::GroupingProxy::proxyDataChanged( const QModelIndex& proxyTopLeft, const QModelIndex& proxyBottomRight )
{
// The preceding and succeeding rows may get a different GroupMode too, when our
// GroupMode changes.
int invalidateFirstRow = proxyTopLeft.row() - 1; // May be an invalid row number
int invalidateLastRow = proxyBottomRight.row() + 1; // May be an invalid row number
for (int row = invalidateFirstRow; row <= invalidateLastRow; row++)
m_cachedGroupModeForRow.remove( row ); // Won't choke on non-existent rows.
}
void
Playlist::GroupingProxy::proxyLayoutChanged()
{
invalidateGrouping(); // Crude but sufficient.
}
void
Playlist::GroupingProxy::proxyModelReset()
{
invalidateGrouping(); // Crude but sufficient.
}
void
Playlist::GroupingProxy::proxyRowsInserted( const QModelIndex& parent, int proxyStart, int proxyEnd )
{
Q_UNUSED( parent );
Q_UNUSED( proxyStart );
Q_UNUSED( proxyEnd );
invalidateGrouping(); // Crude but sufficient.
}
void
Playlist::GroupingProxy::proxyRowsRemoved( const QModelIndex& parent, int proxyStart, int proxyEnd )
{
Q_UNUSED( parent );
Q_UNUSED( proxyStart );
Q_UNUSED( proxyEnd );
invalidateGrouping(); // Crude but sufficient.
}
Playlist::Grouping::GroupMode
Playlist::GroupingProxy::groupModeForIndex( const QModelIndex & thisIndex )
{
Grouping::GroupMode groupMode;
groupMode = m_cachedGroupModeForRow.value( thisIndex.row(), Grouping::Invalid ); // Try to get from cache
if ( groupMode == Grouping::Invalid )
{ // Not in our cache
QModelIndex prevIndex = thisIndex.sibling( thisIndex.row() - 1, thisIndex.column() ); // May be invalid, if 'thisIndex' is the first playlist item.
QModelIndex nextIndex = thisIndex.sibling( thisIndex.row() + 1, thisIndex.column() ); // May be invalid, if 'thisIndex' is the last playlist item.
Meta::TrackPtr prevTrack = prevIndex.data( TrackRole ).value<Meta::TrackPtr>(); // Invalid index is OK:
Meta::TrackPtr thisTrack = thisIndex.data( TrackRole ).value<Meta::TrackPtr>(); // will just give an
Meta::TrackPtr nextTrack = nextIndex.data( TrackRole ).value<Meta::TrackPtr>(); // invalid TrackPtr.
bool matchBefore = shouldBeGrouped( prevTrack, thisTrack ); // Accepts invalid TrackPtrs.
bool matchAfter = shouldBeGrouped( thisTrack, nextTrack ); //
if ( !matchBefore && matchAfter )
groupMode = Grouping::Head;
else if ( matchBefore && matchAfter )
groupMode = Grouping::Body;
else if ( matchBefore && !matchAfter )
groupMode = Grouping::Tail;
else
groupMode = Grouping::None;
m_cachedGroupModeForRow.insert( thisIndex.row(), groupMode ); // Cache our decision
}
return groupMode;
}
/**
* The current implementation is a bit of a hack, but is what gives the best
* user experience.
* If a track has no data in the grouping category, it generally causes a non-match.
*/
bool
Playlist::GroupingProxy::shouldBeGrouped( Meta::TrackPtr track1, Meta::TrackPtr track2 )
{
// If the grouping category is empty or invalid, 'm_groupingCategoryIndex' will be -1.
// That will cause us to choose "no grouping".
if( !track1 || !track2 )
return false;
// DEBUG_BLOCK
// debug() << m_groupingCategoryIndex;
switch( m_groupingCategoryIndex )
{
case 0: //Album
if( track1->album() && track2->album() )
{
// don't group albums without name
if( track1->album()->prettyName().isEmpty() || track2->album()->prettyName().isEmpty() )
return false;
else
return ( *track1->album().data() ) == ( *track2->album().data() ) && ( track1->discNumber() == track2->discNumber() );
}
return false;
case 1: //Artist
if( track1->artist() && track2->artist() )
return ( *track1->artist().data() ) == ( *track2->artist().data() );
return false;
case 2: //Composer
if( track1->composer() && track2->composer() )
return ( *track1->composer().data() ) == ( *track2->composer().data() );
return false;
case 3: //Directory
return ( QFileInfo( track1->playableUrl().path() ).path() ) ==
( QFileInfo( track2->playableUrl().path() ).path() );
case 4: //Genre
if( track1->genre() && track2->genre() )
{
debug() << "gruping by genre. Comparing " << track1->genre()->prettyName() << " with " << track2->genre()->prettyName();
debug() << track1->genre().data() << " == " << track2->genre().data() << " : " << ( *track1->genre().data() == *track2->genre().data());
return ( *track1->genre().data() ) == ( *track2->genre().data() );
}
return false;
case 5: //Rating
if( track1->statistics()->rating() && track2->statistics()->rating() )
return ( track1->statistics()->rating() ) == ( track2->statistics()->rating() );
return false;
case 6: //Source
{
QString source1, source2;
Capabilities::SourceInfoCapability *sic1 = track1->create< Capabilities::SourceInfoCapability >();
Capabilities::SourceInfoCapability *sic2 = track2->create< Capabilities::SourceInfoCapability >();
if( sic1 && sic2)
{
source1 = sic1->sourceName();
source2 = sic2->sourceName();
}
delete sic1;
delete sic2;
if( sic1 && sic2 )
return source1 == source2;
// fall back to collection
return track1->collection() == track2->collection();
}
case 7: //Year
if( track1->year() && track2->year() )
return ( *track1->year().data() ) == ( *track2->year().data() );
return false;
default:
return false;
}
}
void
Playlist::GroupingProxy::invalidateGrouping()
{
m_cachedGroupModeForRow.clear();
}
diff --git a/src/playlist/view/PlaylistViewCommon.cpp b/src/playlist/view/PlaylistViewCommon.cpp
index 0a84240d1d..47709f5ee0 100644
--- a/src/playlist/view/PlaylistViewCommon.cpp
+++ b/src/playlist/view/PlaylistViewCommon.cpp
@@ -1,270 +1,285 @@
/****************************************************************************************
* Copyright (c) 2008 Bonne Eggleston <b.eggleston@gmail.com> *
* Copyright (c) 2009 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2009 Louis Bayle <louis.bayle@gmail.com> *
* Copyright (c) 2010 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistViewCommon.h"
#include "EngineController.h"
#include "GlobalCurrentTrackActions.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/capabilities/FindInSourceCapability.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "covermanager/CoverFetchingActions.h"
#include "dialogs/TagDialog.h"
#include "playlist/proxymodels/GroupingProxy.h"
+#include "playlist/view/listview/PrettyListView.h"
#include <QMenu>
#include <QObject>
#include <QModelIndex>
Playlist::ViewCommon::ViewCommon()
: m_stopAfterTrackAction( 0 )
, m_cueTrackAction( 0 )
, m_removeTracTrackAction( 0 )
, m_findInSourceAction( 0 )
{}
Playlist::ViewCommon::~ViewCommon()
{}
void
Playlist::ViewCommon::trackMenu( QWidget *parent, const QModelIndex *index, const QPoint &pos )
{
DEBUG_BLOCK
QMenu *menu = new QMenu( parent );
menu->addActions( parentCheckActions( parent, trackActionsFor( parent, index ) ) );
menu->addSeparator();
QList<QAction *> albumActionsList = parentCheckActions( parent, albumActionsFor( index ) );
if( !albumActionsList.isEmpty() )
{
// there are no cover actions if the song/album is not in the collection
QMenu *menuCover = new QMenu( i18n( "Album" ), menu );
menuCover->addActions( albumActionsList );
menuCover->setIcon( QIcon::fromTheme( "filename-album-amarok" ) );
menu->addMenu( menuCover );
menu->addSeparator();
}
menu->addActions( parentCheckActions( parent, multiSourceActionsFor( parent, index ) ) );
menu->addSeparator();
menu->addActions( parentCheckActions( parent, editActionsFor( parent, index ) ) );
menu->exec( pos );
}
QList<QAction *>
Playlist::ViewCommon::actionsFor( QWidget *parent, const QModelIndex *index )
{
QList<QAction *> actions;
QAction *separator = new QAction( parent );
separator->setSeparator( true );
actions << parentCheckActions( parent, trackActionsFor( parent, index ) );
actions << separator;
QList<QAction *> albumActionsList = parentCheckActions( parent, albumActionsFor( index ) );
if( !albumActionsList.isEmpty() )
{
actions << albumActionsList;
actions << separator;
}
actions << parentCheckActions( parent, multiSourceActionsFor( parent, index ) );
actions << separator;
actions << parentCheckActions( parent, editActionsFor( parent, index ) );
return actions;
}
QList<QAction *>
Playlist::ViewCommon::trackActionsFor( QWidget *parent, const QModelIndex *index )
{
QList<QAction *> actions;
Meta::TrackPtr track = index->data( Playlist::TrackRole ).value< Meta::TrackPtr >();
QAction *separator = new QAction( parent );
separator->setSeparator( true );
const bool isQueued = index->data( Playlist::QueuePositionRole ).toInt() != 0;
const QString queueText = !isQueued ? i18n( "Queue Track" ) : i18n( "Dequeue Track" );
//display "Queue track" option only if the track is playable
if( track->isPlayable() )
{
if( m_cueTrackAction == 0 )
{
m_cueTrackAction = new QAction( QIcon::fromTheme( "media-track-queue-amarok" ), queueText, parent );
}
else
{
m_cueTrackAction->disconnect();
m_cueTrackAction->setText( queueText );
}
- if( isQueued )
- QObject::connect( m_cueTrackAction, SIGNAL(triggered()),
- parent, SLOT(dequeueSelection()) );
- else
- QObject::connect( m_cueTrackAction, SIGNAL(triggered()),
- parent, SLOT(queueSelection()) );
+ if( auto p = static_cast<Playlist::PrettyListView*>(parent))
+ {
+ if( isQueued )
+ QObject::connect( m_cueTrackAction, &QAction::triggered,
+ p, &Playlist::PrettyListView::dequeueSelection );
+ else
+ QObject::connect( m_cueTrackAction, &QAction::triggered,
+ p, &Playlist::PrettyListView::queueSelection );
+ }
actions << m_cueTrackAction;
}
//actions << separator;
const bool isCurrentTrack = index->data( Playlist::ActiveTrackRole ).toBool();
//display "Stop after this track" option only if track is playable. not sure if this check is really needed
if( track->isPlayable() )
{
if( m_stopAfterTrackAction == 0 )
{
m_stopAfterTrackAction = new QAction( QIcon::fromTheme( "media-playback-stop-amarok" ),
i18n( "Stop Playing After This Track" ), parent );
- QObject::connect( m_stopAfterTrackAction, SIGNAL(triggered()),
- parent, SLOT(stopAfterTrack()) );
+
+ if ( auto p = static_cast<Playlist::PrettyListView*>(parent) )
+ QObject::connect( m_stopAfterTrackAction, &QAction::triggered,
+ p, &Playlist::PrettyListView::stopAfterTrack );
}
actions << m_stopAfterTrackAction;
}
//actions << separator;
if( m_removeTracTrackAction == 0 )
{
m_removeTracTrackAction = new QAction( QIcon::fromTheme( "media-track-remove-amarok" ),
i18n( "Remove From Playlist" ), parent );
- QObject::connect( m_removeTracTrackAction, SIGNAL(triggered()),
- parent, SLOT(removeSelection()) );
+
+ if ( auto p = static_cast<Playlist::PrettyListView*>(parent) )
+ QObject::connect( m_removeTracTrackAction, &QAction::triggered,
+ p, &Playlist::PrettyListView::removeSelection );
}
actions << m_removeTracTrackAction;
//lets see if parent is the currently playing tracks, and if it has CurrentTrackActionsCapability
if( isCurrentTrack )
{
//actions << separator;
QList<QAction *> globalCurrentTrackActions = The::globalCurrentTrackActions()->actions();
foreach( QAction *action, globalCurrentTrackActions )
actions << action;
if( track->has<Capabilities::ActionsCapability>() )
{
QScopedPointer<Capabilities::ActionsCapability>
ac( track->create<Capabilities::ActionsCapability>() );
if ( ac )
actions.append( ac->actions() );
}
}
if( track->has<Capabilities::FindInSourceCapability>() )
{
if( m_findInSourceAction == 0 )
{
m_findInSourceAction = new QAction( QIcon::fromTheme( "edit-find" ),
i18n( "Show in Media Sources" ), parent );
- QObject::connect( m_findInSourceAction, SIGNAL(triggered()),
- parent, SLOT(findInSource()) );
+
+ if( auto p = static_cast<Playlist::PrettyListView*>(parent) )
+ QObject::connect( m_findInSourceAction, &QAction::triggered,
+ p, &Playlist::PrettyListView::findInSource );
}
actions << m_findInSourceAction;
}
return actions;
}
QList<QAction *>
Playlist::ViewCommon::albumActionsFor( const QModelIndex *index )
{
QList<QAction *> actions;
Meta::TrackPtr track = index->data( Playlist::TrackRole ).value< Meta::TrackPtr >();
Meta::AlbumPtr album = track->album();
if( album )
{
QScopedPointer<Capabilities::ActionsCapability>
ac( album->create<Capabilities::ActionsCapability>() );
if( ac )
actions.append( ac->actions() );
}
return actions;
}
QList<QAction *>
Playlist::ViewCommon::multiSourceActionsFor( QWidget *parent, const QModelIndex *index )
{
QList<QAction *> actions;
Meta::TrackPtr track = index->data( Playlist::TrackRole ).value< Meta::TrackPtr >();
const bool isMultiSource = index->data( Playlist::MultiSourceRole ).toBool();
if( isMultiSource )
{
QAction *selectSourceAction = new QAction( QIcon::fromTheme( "media-playlist-repeat" ),
i18n( "Select Source" ), parent );
- QObject::connect( selectSourceAction, SIGNAL(triggered()), parent, SLOT(selectSource()) );
+
+ if( auto p = static_cast<Playlist::PrettyListView*>(parent) )
+ QObject::connect( selectSourceAction, &QAction::triggered, p, &Playlist::PrettyListView::selectSource );
actions << selectSourceAction;
}
return actions;
}
QList<QAction *>
Playlist::ViewCommon::editActionsFor( QWidget *parent, const QModelIndex *index )
{
QList<QAction *> actions;
Meta::TrackPtr track = index->data( Playlist::TrackRole ).value< Meta::TrackPtr >();
QAction *editAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ),
i18n( "Edit Track Details" ), parent );
editAction->setProperty( "popupdropper_svg_id", "edit" );
- QObject::connect( editAction, SIGNAL(triggered()), parent, SLOT(editTrackInformation()) );
+
+ if( auto p = static_cast<Playlist::PrettyListView*>(parent) )
+ QObject::connect( editAction, &QAction::triggered, p, &Playlist::PrettyListView::editTrackInformation );
+
actions << editAction;
return actions;
}
QList<QAction *>
Playlist::ViewCommon::parentCheckActions( QObject *parent, QList<QAction *> actions )
{
foreach( QAction *action, actions )
{
if( !action->parent() )
action->setParent( parent );
}
return actions;
}
diff --git a/src/playlist/view/listview/InlineEditorWidget.cpp b/src/playlist/view/listview/InlineEditorWidget.cpp
index 7cd5074305..7daf91067c 100644
--- a/src/playlist/view/listview/InlineEditorWidget.cpp
+++ b/src/playlist/view/listview/InlineEditorWidget.cpp
@@ -1,422 +1,423 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "InlineEditorWidget.h"
#include "SvgHandler.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "moodbar/MoodbarManager.h"
#include "playlist/PlaylistDefines.h"
#include "playlist/layouts/LayoutManager.h"
#include "playlist/proxymodels/GroupingProxy.h"
#include "playlist/view/listview/PrettyItemDelegate.h"
#include <KHBox>
#include <KRatingWidget>
#include <KVBox>
#include <QEvent>
#include <QBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QPainter>
#include <QPaintEvent>
using namespace Playlist;
InlineEditorWidget::InlineEditorWidget( QWidget * parent, const QModelIndex &index,
PlaylistLayout layout, int height, int width )
: KHBox( parent )
, m_index( index )
, m_layout( layout )
, m_widgetHeight( height )
, m_widgetWidth( width )
, m_layoutChanged( false )
{
// The line below is nice but sometimes (despite best effort) we are missing
// a pixel or two (e.g. the width of the splitter widget handle is not present
// in the delegate).
// So, to fix BR: 300118 set it to "true" to debug or have the own playlist background set it to "false"
setAutoFillBackground( true );
const int frameHMargin = style()->pixelMetric( QStyle::PM_FocusFrameHMargin );
const int frameVMargin = style()->pixelMetric( QStyle::PM_FocusFrameVMargin );
setContentsMargins( frameHMargin, frameVMargin, frameHMargin, frameVMargin );
//prevent editor closing when clicking a rating widget or pressing return in a line edit.
setFocusPolicy( Qt::StrongFocus );
createChildWidgets();
}
InlineEditorWidget::~InlineEditorWidget()
{
}
void InlineEditorWidget::createChildWidgets()
{
QBoxLayout* boxLayout = qobject_cast<QBoxLayout*>( layout() );
Q_ASSERT( boxLayout );
boxLayout->setSpacing( 0 );
//For now, we don't allow editing of the "head" data, just the body
LayoutItemConfig config = m_layout.layoutForItem( m_index );
const int rowCount = config.rows();
if( rowCount == 0 )
return;
// we have to use the same metrics as the PrettyItemDelegate or else
// the widgets will change places when editing
const int horizontalSpace = style()->pixelMetric( QStyle::PM_LayoutHorizontalSpacing );
const int frameHMargin = style()->pixelMetric( QStyle::PM_FocusFrameHMargin );
const int frameVMargin = style()->pixelMetric( QStyle::PM_FocusFrameVMargin );
int rowOffsetX = frameHMargin; // keep the text a little bit away from the border
const int coverHeight = m_widgetHeight - frameVMargin * 2;
const bool showCover = config.showCover();
if( showCover )
rowOffsetX += coverHeight + horizontalSpace/* + frameHMargin * 2*/;
const int contentHeight = m_widgetHeight - frameVMargin * 2;
int rowHeight = contentHeight / rowCount;
const int rowWidth = m_widgetWidth - rowOffsetX - frameHMargin * 2;
if( showCover )
{
QModelIndex coverIndex = m_index.model()->index( m_index.row(), CoverImage );
QPixmap albumPixmap = coverIndex.data( Qt::DisplayRole ).value<QPixmap>();
if( !albumPixmap.isNull() )
{
if( albumPixmap.width() > albumPixmap.height() )
albumPixmap = albumPixmap.scaledToWidth( coverHeight );
else
albumPixmap = albumPixmap.scaledToHeight( coverHeight );
QLabel *coverLabel = new QLabel( this );
coverLabel->setPixmap( albumPixmap );
if( albumPixmap.width() < coverHeight )
coverLabel->setContentsMargins( ( coverHeight - albumPixmap.width() ) / 2, 0,
( coverHeight - albumPixmap.width() + 1 ) / 2, 0 );
boxLayout->setStretchFactor( coverLabel, 0 );
boxLayout->addSpacing( horizontalSpace );
}
}
KVBox *rowsWidget = new KVBox( this );
// --- paint all the rows
for( int i = 0; i < rowCount; i++ )
{
LayoutItemConfigRow row = config.row( i );
const int elementCount = row.count();
QSplitter *rowWidget = new QSplitter( rowsWidget );
- connect( rowWidget, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved(int,int)) );
+ connect( rowWidget, &QSplitter::splitterMoved, this, &InlineEditorWidget::splitterMoved );
m_splitterRowMap.insert( rowWidget, i );
//we need to do a quick pass to figure out how much space is left for auto sizing elements
qreal spareSpace = 1.0;
int autoSizeElemCount = 0;
for( int k = 0; k < elementCount; ++k )
{
spareSpace -= row.element( k ).size();
if( row.element( k ).size() < 0.001 )
autoSizeElemCount++;
}
const qreal spacePerAutoSizeElem = spareSpace / (qreal)autoSizeElemCount;
//give left over pixels to the first rows. Widgets are doing it the same.
if( i == 0 )
rowHeight++;
if( i == ( contentHeight % rowCount ) )
rowHeight--;
QList<int> itemWidths;
int currentItemX = 0;
for( int j = 0; j < elementCount; ++j )
{
LayoutItemConfigRowElement element = row.element( j );
// -- calculate the size
qreal size;
if( element.size() < 0.001 )
size = spacePerAutoSizeElem;
else
size = element.size();
int itemWidth;
if( j == elementCount - 1 )
// use the full with for the last item
itemWidth = rowWidth - currentItemX;
else
itemWidth = rowWidth * size;
itemWidths.append( itemWidth );
int value = element.value();
QModelIndex textIndex = m_index.model()->index( m_index.row(), value );
QString text = textIndex.data( Qt::DisplayRole ).toString();
m_orgValues.insert( value, text );
QWidget *widget = 0;
//special case for painting the rating...
if( value == Rating )
{
int rating = textIndex.data( Qt::DisplayRole ).toInt();
KRatingWidget* ratingWidget = new KRatingWidget( 0 );
ratingWidget->setAlignment( element.alignment() );
ratingWidget->setRating( rating );
ratingWidget->setAttribute( Qt::WA_NoMousePropagation, true );
- connect( ratingWidget, SIGNAL(ratingChanged(uint)), this, SLOT(ratingValueChanged()) );
+ connect( ratingWidget, QOverload<uint>::of(&KRatingWidget::ratingChanged),
+ this, &InlineEditorWidget::ratingValueChanged );
m_editorRoleMap.insert( ratingWidget, value );
widget = ratingWidget;
}
else if( value == Divider )
{
QPixmap left = The::svgHandler()->renderSvg( "divider_left",
1, rowHeight,
"divider_left" );
QPixmap right = The::svgHandler()->renderSvg( "divider_right",
1, rowHeight,
"divider_right" );
QPixmap dividerPixmap( 2, rowHeight );
dividerPixmap.fill( Qt::transparent );
QPainter painter( &dividerPixmap );
painter.drawPixmap( 0, 0, left );
painter.drawPixmap( 1, 0, right );
QLabel* dividerLabel = new QLabel( 0 );
dividerLabel->setPixmap( dividerPixmap );
dividerLabel->setAlignment( element.alignment() );
widget = dividerLabel;
}
else if( value == Moodbar )
{
//we cannot ask the model for the moodbar directly as we have no
//way of asking for a specific size. Instead just get the track from
//the model and ask the moodbar manager ourselves.
Meta::TrackPtr track = m_index.data( TrackRole ).value<Meta::TrackPtr>();
QLabel* moodbarLabel = new QLabel( 0 );
moodbarLabel->setScaledContents( true );
if( The::moodbarManager()->hasMoodbar( track ) )
{
QPixmap moodbar = The::moodbarManager()->getMoodbar( track, itemWidth, rowHeight - 8 );
moodbarLabel->setPixmap( moodbar );
}
widget = moodbarLabel;
}
//actual playlist item text is drawn here
else
{
QLineEdit * edit = new QLineEdit( text, 0 );
edit->setFrame( false );
edit->setAlignment( element.alignment() );
edit->installEventFilter(this);
// -- set font
bool bold = element.bold();
bool italic = element.italic();
bool underline = element.underline();
QFont font = edit->font();
font.setBold( bold );
font.setItalic( italic );
font.setUnderline( underline );
edit->setFont( font );
- connect( edit, SIGNAL(editingFinished()), this, SLOT(editValueChanged()) );
+ connect( edit, &QLineEdit::editingFinished, this, &InlineEditorWidget::editValueChanged );
//check if this is a column that is editable. If not, make the
//line edit read only.
if( !isEditableColumn( static_cast<Playlist::Column>(value) ) )
{
edit->setReadOnly( true );
edit->setDisabled( true );
}
m_editorRoleMap.insert( edit, value );
widget = edit;
}
widget->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ); // or else the widget size hint influences the space it get's which messes up the layout
rowWidget->addWidget( widget );
// handles are nice, but if we don't compensate for their sizes the layout
// would be different from the item delegate
if( j > 0 )
widget->setContentsMargins( -( ( rowWidget->handleWidth() + 1 ) / 2 ), 0, 0, 0 );
if( j < elementCount - 1 )
widget->setContentsMargins( 0, 0, -( rowWidget->handleWidth() / 2 ), 0 );
currentItemX += itemWidth;
}
rowWidget->setSizes( itemWidths );
}
}
void InlineEditorWidget::editValueChanged()
{
DEBUG_BLOCK
QObject * senderObject = sender();
QLineEdit * edit = dynamic_cast<QLineEdit *>( senderObject );
if( !edit )
return;
int role = m_editorRoleMap.value( edit );
//only save values if something has actually changed.
if( m_orgValues.value( role ) != edit->text() )
{
debug() << "Storing changed value: " << edit->text();
m_changedValues.insert( role, edit->text() );
}
}
void InlineEditorWidget::ratingValueChanged()
{
DEBUG_BLOCK
KRatingWidget * edit = qobject_cast<KRatingWidget *>( sender() );
if( !edit )
return;
int role = m_editorRoleMap.value( edit );
m_changedValues.insert( role, QString::number( edit->rating() ) );
}
QMap<int, QString> InlineEditorWidget::changedValues()
{
DEBUG_BLOCK
if( m_layoutChanged )
LayoutManager::instance()->updateCurrentLayout( m_layout );
return m_changedValues;
}
void InlineEditorWidget::splitterMoved( int pos, int index )
{
DEBUG_BLOCK
Q_UNUSED( pos )
Q_UNUSED( index )
QSplitter * splitter = dynamic_cast<QSplitter *>( sender() );
if ( !splitter )
return;
int row = m_splitterRowMap.value( splitter );
debug() << "on row: " << row;
//first, get total size of all items;
QList<int> sizes = splitter->sizes();
int total = 0;
foreach( int size, sizes )
total += size;
//resize all items as the splitters take up some space, so we need to normalize the combined size to 1.
QList<qreal> newSizes;
foreach( int size, sizes )
{
qreal newSize = (qreal) size / (qreal) total;
newSizes << newSize;
}
LayoutItemConfig itemConfig = m_layout.layoutForItem( m_index );
LayoutItemConfigRow rowConfig = itemConfig.row( row );
//and now we rebuild a new layout...
LayoutItemConfigRow newRowConfig;
for( int i = 0; i<rowConfig.count(); i++ )
{
LayoutItemConfigRowElement element = rowConfig.element( i );
debug() << "item " << i << " old/new: " << element.size() << "/" << newSizes.at( i );
element.setSize( newSizes.at( i ) );
newRowConfig.addElement( element );
}
LayoutItemConfig newItemConfig;
newItemConfig.setActiveIndicatorRow( itemConfig.activeIndicatorRow() );
newItemConfig.setShowCover( itemConfig.showCover() );
for( int i = 0; i<itemConfig.rows(); i++ )
{
if( i == row )
newItemConfig.addRow( newRowConfig );
else
newItemConfig.addRow( itemConfig.row( i ) );
}
m_layout.setLayoutForPart( m_layout.partForItem( m_index ), newItemConfig );
m_layoutChanged = true;
}
bool
InlineEditorWidget::eventFilter( QObject *obj, QEvent *event )
{
QList<QWidget *> editWidgets = m_editorRoleMap.keys();
QWidget *widget = qobject_cast<QWidget *>( obj );
if( editWidgets.contains( widget ) )
{
if( event->type() == QEvent::KeyPress )
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
switch( keyEvent->key() )
{
case Qt::Key_Enter:
case Qt::Key_Return:
if( widget )
{
widget->clearFocus();
emit editingDone( this );
}
return true;
}
return false;
}
else
return false;
}
else
return KHBox::eventFilter( obj, event );
}
diff --git a/src/playlist/view/listview/PrettyItemDelegate.cpp b/src/playlist/view/listview/PrettyItemDelegate.cpp
index 3ba901dcca..97901280a8 100644
--- a/src/playlist/view/listview/PrettyItemDelegate.cpp
+++ b/src/playlist/view/listview/PrettyItemDelegate.cpp
@@ -1,940 +1,940 @@
/****************************************************************************************
* Copyright (c) 2007 Ian Monroe <ian@monroe.nu> *
* Copyright (c) 2008-2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> *
* Copyright (c) 2011 Sandeep Raghuraman <sandy.8925@gmail.com> *
* Copyright (c) 2013 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::PrettyItemDelegate"
#include "PrettyItemDelegate.h"
#include "App.h"
#include "EngineController.h"
#include "PaletteHandler.h"
#include "SvgHandler.h"
#include "QStringx.h"
#include "core/support/Debug.h"
#include "core/meta/TrackEditor.h"
#include "core/capabilities/SourceInfoCapability.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "moodbar/MoodbarManager.h"
#include "playlist/PlaylistModel.h"
#include "playlist/layouts/LayoutManager.h"
#include "playlist/proxymodels/GroupingProxy.h"
#include "playlist/view/listview/InlineEditorWidget.h"
#include <KColorScheme>
#include <kratingpainter.h> // #include <KratingPainter> does not work on some distros
#include <KWindowSystem>
#include <QAction>
#include <QFontMetricsF>
#include <QPainter>
#include <QStyleOptionSlider>
#include <QTimeLine>
#include <QTimer>
using namespace Playlist;
int Playlist::PrettyItemDelegate::s_fontHeight = 0;
Playlist::PrettyItemDelegate::PrettyItemDelegate( QObject* parent )
: QStyledItemDelegate( parent )
{
LayoutManager::instance();
m_animationTimeLine = new QTimeLine( 900, this );
m_animationTimeLine->setFrameRange( 1000, 600 );
connect( m_animationTimeLine, SIGNAL( frameChanged( int ) ), this, SIGNAL( redrawRequested() ) );
#ifdef Q_WS_X11
- connect( KWindowSystem::self(), SIGNAL( currentDesktopChanged( int ) ), this, SLOT( currentDesktopChanged() ) );
+ connect( KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &PrettyItemDelegate::currentDesktopChanged );
#endif
- connect( EngineController::instance(), SIGNAL( playbackStateChanged() ), this, SIGNAL( redrawRequested() ) );
+ connect( EngineController::instance(), &EngineController::playbackStateChanged, this, &PrettyItemDelegate::redrawRequested );
}
PrettyItemDelegate::~PrettyItemDelegate() { }
int PrettyItemDelegate::getGroupMode( const QModelIndex &index)
{
return index.data( GroupRole ).toInt();
}
int
PrettyItemDelegate::rowsForItem( const QModelIndex &index )
{
PlaylistLayout layout = LayoutManager::instance()->activeLayout();
int rowCount = 0;
if( getGroupMode( index ) == Grouping::Head )
rowCount += layout.layoutForPart( PlaylistLayout::Head ).rows();
rowCount += layout.layoutForItem( index ).rows();
return rowCount;
}
QSize
PrettyItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
s_fontHeight = option.fontMetrics.height();
// -- calculate the item height
int rowCount = rowsForItem( index );
if( LayoutManager::instance()->activeLayout().inlineControls() && index.data( ActiveTrackRole ).toBool() )
rowCount++; //add room for extras
QStyle *style;
if( QWidget *w = qobject_cast<QWidget*>(parent()) )
style = w->style();
else
style = QApplication::style();
// note: we have to be as high as the InlineEditorWidget or that would
// force re-layouts or overlap
// on the other hand we squeeze the line edits quite a lot.
int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin );
int height = rowCount * s_fontHeight + ( rowCount + 1 ) * frameVMargin;
return QSize( s_fontHeight * 20, height );
}
void
PrettyItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
PlaylistLayout layout = LayoutManager::instance()->activeLayout();
painter->save();
QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter );
painter->translate( option.rect.topLeft() );
painter->drawPixmap( 0, 0, The::svgHandler()->renderSvgWithDividers( "track", ( int )option.rect.width(), ( int )option.rect.height(), "track" ) );
painter->setPen( The::paletteHandler()->foregroundColor( painter, option.state & QStyle::State_Selected ) );
// call paint method based on type
const int groupMode = getGroupMode(index);
int rowCount = rowsForItem( index );
bool paintInlineControls = LayoutManager::instance()->activeLayout().inlineControls() && index.data( ActiveTrackRole ).toBool();
if ( groupMode == Grouping::None || groupMode == Grouping::Body || groupMode == Grouping::Tail )
{
int trackHeight = 0;
int extraHeight = 0;
QStyleOptionViewItem trackOption( option );
if ( paintInlineControls )
{
int adjustedRowCount = rowCount + 1;
trackHeight = ( option.rect.height() * rowCount ) / adjustedRowCount + 3;
extraHeight = option.rect.height() - trackHeight;
trackOption.rect = QRect( 0, 0, option.rect.width(), trackHeight );
}
paintItem( layout.layoutForItem( index ), painter, trackOption, index );
if (paintInlineControls )
{
QRect extrasRect( 0, trackHeight, option.rect.width(), extraHeight );
paintActiveTrackExtras( extrasRect, painter, index );
}
}
else if ( groupMode == Grouping::Head )
{
//we need to split up the options for the actual header and the included first track
QFont boldfont( option.font );
boldfont.setBold( true );
QFontMetricsF bfm( boldfont );
QStyleOptionViewItem headOption( option );
QStyleOptionViewItem trackOption( option );
int headRows = layout.layoutForPart( PlaylistLayout::Head ).rows();
int trackRows = layout.layoutForItem( index ).rows();
int totalRows = headRows + trackRows;
//if this layout is completely empty, bail out or we will get in divide-by-zero trouble
if ( totalRows == 0 )
{
painter->restore();
return;
}
if ( paintInlineControls )
{
totalRows = totalRows + 1;
}
int headHeight = ( headRows * option.rect.height() ) / totalRows - 2;
int trackHeight = ( trackRows * option.rect.height() ) / totalRows + 2;
if ( headRows > 0 )
{
headOption.rect = QRect( 0, 0, option.rect.width(), headHeight );
paintItem( layout.layoutForPart( PlaylistLayout::Head ), painter, headOption, index, true );
painter->translate( 0, headHeight );
}
trackOption.rect = QRect( 0, 0, option.rect.width(), trackHeight );
paintItem( layout.layoutForItem( index ), painter, trackOption, index );
if ( paintInlineControls )
{
int extraHeight = option.rect.height() - ( headHeight + trackHeight );
QRect extrasRect( 0, trackHeight, option.rect.width(), extraHeight );
paintActiveTrackExtras( extrasRect, painter, index );
}
}
else
QStyledItemDelegate::paint( painter, option, index );
painter->restore();
}
bool
PrettyItemDelegate::insideItemHeader( const QPoint& pt, const QRect& rect )
{
QRect headerBounds = rect;
headerBounds.setHeight( headerHeight() );
return headerBounds.contains( pt );
}
int
PrettyItemDelegate::headerHeight() const
{
int headRows = LayoutManager::instance()->activeLayout().layoutForPart( PlaylistLayout::Head ).rows();
if( headRows < 1 )
return 0;
QStyle *style;
if( QWidget *w = qobject_cast<QWidget*>(parent()) )
style = w->style();
else
style = QApplication::style();
int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin );
return headRows * ( s_fontHeight + frameVMargin );
}
QPointF
PrettyItemDelegate::centerImage( const QPixmap& pixmap, const QRectF& rect ) const
{
qreal pixmapRatio = ( qreal )pixmap.width() / ( qreal )pixmap.height();
qreal moveByX = 0.0;
qreal moveByY = 0.0;
if ( pixmapRatio >= 1 )
moveByY = ( rect.height() - ( rect.width() / pixmapRatio ) ) / 2.0;
else
moveByX = ( rect.width() - ( rect.height() * pixmapRatio ) ) / 2.0;
return QPointF( moveByX, moveByY );
}
void Playlist::PrettyItemDelegate::paintItem( const LayoutItemConfig &config,
QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index,
bool headerRow ) const
{
const int rowCount = config.rows();
if( rowCount == 0 )
return;
QStyle *style;
if( QWidget *w = qobject_cast<QWidget*>(parent()) )
style = w->style();
else
style = QApplication::style();
// we have to use the same metrics as the InlineEditorWidget or else
// the widgets will change places when editing
const int horizontalSpace = style->pixelMetric( QStyle::PM_LayoutHorizontalSpacing );
const int smallIconSize = style->pixelMetric( QStyle::PM_SmallIconSize );
const int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin );
const int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin );
const int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing );
int rowOffsetX = frameHMargin; // keep the text a little bit away from the border
int rowOffsetY = frameVMargin;
const int coverHeight = option.rect.height() - frameVMargin * 2;
QRectF nominalImageRect( frameHMargin, frameVMargin, coverHeight, coverHeight );
const bool showCover = config.showCover();
if( showCover )
rowOffsetX += coverHeight + horizontalSpace/* + frameHMargin * 2*/;
const int contentHeight = option.rect.height() - frameVMargin * 2;
int rowHeight = contentHeight / rowCount;
const int rowWidth = option.rect.width() - rowOffsetX - frameHMargin * 2;
// --- paint the active track background
// We do not want to paint this for head items.
if( !headerRow && index.data( ActiveTrackRole ).toBool() )
{
//paint this in 3 parts to solve stretching issues with wide playlists
//TODO: propper 9 part painting, but I don't want to bother with this until we
//get some new graphics anyway...
// -- try not to highlight the indicator row
int overlayXOffset = 0;
int overlayYOffset = config.activeIndicatorRow() * rowHeight;
int overlayHeight = option.rect.height() - overlayYOffset;
int overlayLength = option.rect.width();
int endWidth = overlayHeight / 4;
if( m_animationTimeLine->currentFrame() == m_animationTimeLine->startFrame() && EngineController::instance()->isPlaying() ) {
m_animationTimeLine->setDirection( QTimeLine::Forward );
if( m_animationTimeLine->state() == QTimeLine::NotRunning )
m_animationTimeLine->start();
}
else if( m_animationTimeLine->currentFrame() == m_animationTimeLine->endFrame() ) {
m_animationTimeLine->setDirection( QTimeLine::Backward );
m_animationTimeLine->start();
}
// Opacity is used for animating the active track item
const qreal opacity = qreal( m_animationTimeLine->currentFrame() ) / 1000;
// If opacity is not the default value we cannot render from cache
const bool skipCache = opacity == 1.0 ? false : true;
painter->drawPixmap( overlayXOffset, overlayYOffset,
The::svgHandler()->renderSvg( "active_overlay_left",
endWidth,
overlayHeight,
"active_overlay_left",
skipCache,
opacity ) );
painter->drawPixmap( overlayXOffset + endWidth, overlayYOffset,
The::svgHandler()->renderSvg( "active_overlay_mid",
overlayLength - endWidth * 2,
overlayHeight,
"active_overlay_mid",
skipCache,
opacity ) );
painter->drawPixmap( overlayXOffset + ( overlayLength - endWidth ), overlayYOffset,
The::svgHandler()->renderSvg( "active_overlay_right",
endWidth,
overlayHeight,
"active_overlay_right",
skipCache,
opacity ) );
}
// --- paint the cover
if( showCover )
{
QModelIndex coverIndex = index.model()->index( index.row(), CoverImage );
QPixmap albumPixmap = coverIndex.data( Qt::DisplayRole ).value<QPixmap>();
if( !albumPixmap.isNull() )
{
//offset cover if non square
QPointF offset = centerImage( albumPixmap, nominalImageRect );
QRectF imageRect( nominalImageRect.x() + offset.x(),
nominalImageRect.y() + offset.y(),
nominalImageRect.width() - offset.x() * 2,
nominalImageRect.height() - offset.y() * 2 );
painter->drawPixmap( imageRect, albumPixmap, QRectF( albumPixmap.rect() ) );
}
QModelIndex emblemIndex = index.model()->index( index.row(), SourceEmblem );
QPixmap emblemPixmap = emblemIndex.data( Qt::DisplayRole ).value<QPixmap>();
if( !emblemPixmap.isNull() )
painter->drawPixmap( QRectF( nominalImageRect.x(), nominalImageRect.y() , 16, 16 ), emblemPixmap, QRectF( 0, 0 , 16, 16 ) );
}
// --- paint the markers
int markerOffsetX = frameHMargin;
const int rowOffsetXBeforeMarkers = rowOffsetX;
if( !headerRow )
{
const int queuePosition = index.data( QueuePositionRole ).toInt();
if( queuePosition > 0 )
{
const int x = markerOffsetX;
const int y = nominalImageRect.y() + ( coverHeight - smallIconSize );
const QString number = QString::number( queuePosition );
const int iconWidth = option.fontMetrics.boundingRect( number ).width() + smallIconSize / 2;
const QRect rect( x, y, iconWidth, smallIconSize ); // shift text by 1 pixel left
const KColorScheme colorScheme( option.palette.currentColorGroup() );
QPen rectanglePen = painter->pen();
rectanglePen.setColor( colorScheme.foreground( KColorScheme::PositiveText ).color() );
QBrush rectangleBrush = colorScheme.background( KColorScheme::PositiveBackground );
painter->save();
painter->setPen( rectanglePen );
painter->setBrush( rectangleBrush );
painter->setRenderHint( QPainter::Antialiasing, true );
painter->drawRoundedRect( QRect( x, y - 1, iconWidth, smallIconSize ), smallIconSize / 3, smallIconSize / 3 );
painter->drawText( rect, Qt::AlignCenter, number );
painter->restore();
markerOffsetX += ( iconWidth + iconSpacing );
if ( !showCover )
rowOffsetX += ( iconWidth + iconSpacing );
else
rowOffsetX += qMax( 0, iconWidth - smallIconSize - iconSpacing );
}
if( index.data( MultiSourceRole ).toBool() )
{
const int x = markerOffsetX;
const int y = nominalImageRect.y() + ( coverHeight - smallIconSize );
painter->drawPixmap( x, y, The::svgHandler()->renderSvg( "multi_marker", smallIconSize, smallIconSize, "multi_marker" ) );
markerOffsetX += ( smallIconSize + iconSpacing );
if ( !showCover )
rowOffsetX += ( smallIconSize + iconSpacing );
}
if( index.data( StopAfterTrackRole ).toBool() )
{
const int x = markerOffsetX;
const int y = nominalImageRect.y() + ( coverHeight - smallIconSize );
painter->drawPixmap( x, y, The::svgHandler()->renderSvg( "stop_button", smallIconSize, smallIconSize, "stop_button" ) );
markerOffsetX += ( smallIconSize + iconSpacing );
if ( !showCover )
rowOffsetX += ( smallIconSize + iconSpacing );
}
}
int markersWidth = rowOffsetX - rowOffsetXBeforeMarkers;
Meta::TrackPtr trackPtr = index.data( TrackRole ).value<Meta::TrackPtr>();
QMap<QString, QString> trackArgs;
if( trackPtr )
trackArgs = buildTrackArgsMap( trackPtr );
// --- paint all the rows
for( int i = 0; i < rowCount; i++ )
{
LayoutItemConfigRow row = config.row( i );
const int elementCount = row.count();
//we need to do a quick pass to figure out how much space is left for auto sizing elements
qreal spareSpace = 1.0;
int autoSizeElemCount = 0;
for( int k = 0; k < elementCount; ++k )
{
spareSpace -= row.element( k ).size();
if( row.element( k ).size() < 0.001 )
autoSizeElemCount++;
}
const qreal spacePerAutoSizeElem = spareSpace / (qreal)autoSizeElemCount;
//give left over pixels to the first rows. Widgets are doing it the same.
if( i == 0 )
rowHeight++;
if( i == ( contentHeight % rowCount ) )
rowHeight--;
int currentItemX = rowOffsetX;
for( int j = 0; j < elementCount; ++j )
{
LayoutItemConfigRowElement element = row.element( j );
// -- calculate the size
qreal size;
if( element.size() < 0.001 )
size = spacePerAutoSizeElem;
else
size = element.size();
qreal itemWidth;
if( j == elementCount - 1 )
// use the full with for the last item
itemWidth = rowWidth - (currentItemX - rowOffsetXBeforeMarkers);
else
itemWidth = rowWidth * size - markersWidth;
markersWidth = 0; // leave columns > 0 alone, they are unaffected by markers
if( itemWidth <= 1 )
continue; // no sense to paint such small items
// -- set font
bool bold = element.bold();
bool italic = element.italic();
bool underline = element.underline();
int alignment = element.alignment();
QFont font = option.font;
font.setBold( bold );
font.setItalic( italic );
font.setUnderline( underline );
painter->setFont( font );
int value = element.value();
QModelIndex textIndex = index.model()->index( index.row(), value );
// -- paint the element
if( value == Rating )
{
int rating = textIndex.data( Qt::DisplayRole ).toInt();
KRatingPainter::paintRating( painter,
QRect( currentItemX, rowOffsetY,
itemWidth, rowHeight ),
element.alignment(), rating );
}
else if( value == Divider )
{
QPixmap left = The::svgHandler()->renderSvg( "divider_left",
1, rowHeight ,
"divider_left" );
QPixmap right = The::svgHandler()->renderSvg( "divider_right",
1, rowHeight,
"divider_right" );
if( alignment & Qt::AlignLeft )
{
painter->drawPixmap( currentItemX, rowOffsetY, left );
painter->drawPixmap( currentItemX + 1, rowOffsetY, right );
}
else if( alignment & Qt::AlignRight )
{
painter->drawPixmap( currentItemX + itemWidth - 1, rowOffsetY, left );
painter->drawPixmap( currentItemX + itemWidth, rowOffsetY, right );
}
else
{
int center = currentItemX + ( itemWidth / 2 );
painter->drawPixmap( center, rowOffsetY, left );
painter->drawPixmap( center + 1, rowOffsetY, right );
}
}
else if( value == Moodbar )
{
//we cannot ask the model for the moodbar directly as we have no
//way of asking for a specific size. Instead just get the track from
//the model and ask the moodbar manager ourselves.
Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>();
if( The::moodbarManager()->hasMoodbar( track ) )
{
QPixmap moodbar = The::moodbarManager()->getMoodbar( track, itemWidth, rowHeight - 8 );
painter->drawPixmap( currentItemX, rowOffsetY + 4, moodbar );
}
}
//actual playlist item text is drawn here
else
{
//TODO: get rid of passing TrackPtr as data, use custom role instead
Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>();
QString text = textIndex.data( Qt::DisplayRole ).toString();
Amarok::QStringx prefix( element.prefix() );
Amarok::QStringx suffix( element.suffix() );
text = prefix.namedOptArgs( trackArgs ) + text + suffix.namedOptArgs( trackArgs );
text = QFontMetricsF( font ).elidedText( text, Qt::ElideRight, itemWidth );
//if the track can't be played, it should be grayed out to show that it is unavailable
if( !track->isPlayable() )
{
painter->save();
QPen grayPen = painter->pen();
grayPen.setColor( QColor( 127, 127, 127 ) ); // TODO: use disabled role
painter->setPen( grayPen );
painter->drawText( QRect( currentItemX + frameHMargin, rowOffsetY,
itemWidth - frameHMargin * 2, rowHeight ),
alignment, text );
painter->restore();
}
else
{
painter->drawText( QRect( currentItemX + frameHMargin, rowOffsetY,
itemWidth - frameHMargin * 2, rowHeight ),
alignment, text );
}
}
currentItemX += itemWidth;
}
rowOffsetY += rowHeight;
}
}
void Playlist::PrettyItemDelegate::paintActiveTrackExtras( const QRect &rect, QPainter* painter, const QModelIndex& index ) const
{
Q_UNUSED( index );
int x = rect.x();
int y = rect.y();
int width = rect.width();
int height = rect.height();
int buttonSize = height - 4;
QStyle *style;
if( QWidget *w = qobject_cast<QWidget*>(parent()) )
style = w->style();
else
style = QApplication::style();
// some style margins:
int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin );
int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing );
//just paint some "buttons for now
int offset = x + frameHMargin;
painter->drawPixmap( offset, y + 2,
The::svgHandler()->renderSvg( "back_button",
buttonSize, buttonSize,
"back_button" ) );
if( The::engineController()->isPlaying() )
{
offset += ( buttonSize + iconSpacing );
painter->drawPixmap( offset, y + 2,
The::svgHandler()->renderSvg( "pause_button",
buttonSize, buttonSize,
"pause_button" ) );
}
else
{
offset += ( buttonSize + iconSpacing );
painter->drawPixmap( offset, y + 2,
The::svgHandler()->renderSvg( "play_button",
buttonSize, buttonSize,
"play_button" ) );
}
offset += ( buttonSize + iconSpacing );
painter->drawPixmap( offset, y + 2,
The::svgHandler()->renderSvg( "stop_button",
buttonSize, buttonSize,
"stop_button" ) );
offset += ( buttonSize + iconSpacing );
painter->drawPixmap( offset, y + 2,
The::svgHandler()->renderSvg( "next_button",
buttonSize, buttonSize,
"next_button" ) );
offset += ( buttonSize + iconSpacing );
long trackLength = The::engineController()->trackLength();
long trackPos = The::engineController()->trackPositionMs();
qreal trackPercentage = 0.0;
if ( trackLength > 0 )
trackPercentage = ( (qreal) trackPos / (qreal) trackLength );
int sliderWidth = width - ( offset + frameHMargin );
QStyleOptionSlider opt;
opt.rect.setRect( offset, y, sliderWidth, height );
The::svgHandler()->paintCustomSlider( painter, &opt, trackPercentage, false );
}
bool Playlist::PrettyItemDelegate::clicked( const QPoint &pos, const QRect &itemRect, const QModelIndex& index )
{
//for now, only handle clicks in the currently playing item.
if ( !index.data( ActiveTrackRole ).toBool() )
return false;
//also, if we are not using the inline controls, we should not react to these clicks at all
if( !LayoutManager::instance()->activeLayout().inlineControls() )
return false;
int rowCount = rowsForItem( index );
int modifiedRowCount = rowCount + 1;
int height = itemRect.height();
int baseHeight = ( height * rowCount ) / modifiedRowCount + 3;
int extrasHeight = height - baseHeight;
int extrasOffsetY = height - extrasHeight;
int buttonSize = extrasHeight - 4;
QStyle *style;
if( QWidget *w = qobject_cast<QWidget*>(parent()) )
style = w->style();
else
style = QApplication::style();
// some style margins:
int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin );
int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing );
int offset = frameHMargin;
QRect backRect( offset, extrasOffsetY + 2, buttonSize, buttonSize );
if( backRect.contains( pos ) )
{
Amarok::actionCollection()->action( "prev" )->trigger();
return true;
}
offset += ( buttonSize + iconSpacing );
QRect playRect( offset, extrasOffsetY + 2, buttonSize, buttonSize );
if( playRect.contains( pos ) )
{
Amarok::actionCollection()->action( "play_pause" )->trigger();
return true;
}
offset += ( buttonSize + iconSpacing );
QRect stopRect( offset, extrasOffsetY + 2, buttonSize, buttonSize );
if( stopRect.contains( pos ) )
{
Amarok::actionCollection()->action( "stop" )->trigger();
return true;
}
offset += ( buttonSize + iconSpacing );
QRect nextRect( offset, extrasOffsetY + 2, buttonSize, buttonSize );
if( nextRect.contains( pos ) )
{
Amarok::actionCollection()->action( "next" )->trigger();
return true;
}
offset += ( buttonSize + iconSpacing );
//handle clicks on the slider
int sliderWidth = itemRect.width() - ( offset + iconSpacing );
int knobSize = buttonSize - 2;
QRect sliderActiveRect( offset, extrasOffsetY + 3, sliderWidth, knobSize );
if( sliderActiveRect.contains( pos ) )
{
int xSliderPos = pos.x() - offset;
long trackLength = EngineController::instance()->trackLength();
qreal percentage = (qreal) xSliderPos / (qreal) sliderWidth;
EngineController::instance()->seekTo( trackLength * percentage );
return true;
}
return false;
}
QWidget* Playlist::PrettyItemDelegate::createEditor( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
Q_UNUSED( option );
int editorHeight = sizeHint(option, index).height();
int editorWidth = sizeHint(option, index).width();
if( getGroupMode( index ) == Grouping::Head )
editorHeight -= headerHeight();
InlineEditorWidget *editor = new InlineEditorWidget( parent, index,
LayoutManager::instance()->activeLayout(), editorHeight, editorWidth );
- connect( editor, SIGNAL(editingDone(InlineEditorWidget*)),
- this, SLOT(editorDone(InlineEditorWidget*)) );
+ connect( editor, &InlineEditorWidget::editingDone,
+ this, &PrettyItemDelegate::editorDone );
return editor;
}
void Playlist::PrettyItemDelegate::setModelData( QWidget * editor, QAbstractItemModel * model, const QModelIndex &index ) const
{
Q_UNUSED( model )
InlineEditorWidget * inlineEditor = qobject_cast<InlineEditorWidget *>( editor );
if( !inlineEditor )
return;
QMap<int, QString> changeMap = inlineEditor->changedValues();
debug() << "got inline editor!!";
debug() << "changed values map: " << changeMap;
//ok, now get the track, figure out if it is editable and if so, apply new values.
//It's as simple as that! :-)
Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>();
if( !track )
return;
// this does not require TrackEditor
if( changeMap.contains( Rating ) )
{
int rating = changeMap.value( Rating ).toInt();
track->statistics()->setRating( rating );
changeMap.remove( Rating );
}
Meta::TrackEditorPtr ec = track->editor();
if( !ec )
return;
QList<int> columns = changeMap.keys();
foreach( int column, columns )
{
QString value = changeMap.value( column );
switch( column )
{
case Album:
ec->setAlbum( value );
break;
case Artist:
ec->setArtist( value );
break;
case Comment:
ec->setComment( value );
break;
case Composer:
ec->setComposer( value );
break;
case DiscNumber:
{
int discNumber = value.toInt();
ec->setDiscNumber( discNumber );
break;
}
case Genre:
ec->setGenre( value );
break;
case Rating:
break; // we've already set the rating, this even shouldn't be here
case Title:
ec->setTitle( value );
break;
case TitleWithTrackNum:
{
debug() << "parse TitleWithTrackNum";
//we need to parse out the track number and the track name (and check
//if the string is even valid...)
//QRegExp rx("(\\d+)\\s-\\s(.*))");
QRegExp rx("(\\d+)(\\s-\\s)(.*)");
if ( rx.indexIn( value ) != -1) {
int trackNumber = rx.cap( 1 ).toInt();
QString trackName = rx.cap( 3 );
debug() << "split TitleWithTrackNum into " << trackNumber << " and " << trackName;
ec->setTrackNumber( trackNumber );
ec->setTitle( trackName );
}
break;
}
case TrackNumber:
{
int TrackNumber = value.toInt();
ec->setTrackNumber( TrackNumber );
break;
}
case Year:
ec->setYear( value.toInt() );
break;
case Bpm:
ec->setBpm( value.toFloat() );
break;
}
}
}
void
Playlist::PrettyItemDelegate::updateEditorGeometry( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
Q_UNUSED( index )
QRect editorRect( option.rect );
if( getGroupMode( index ) == Grouping::Head )
editorRect.setTop( editorRect.top() + headerHeight() );
editor->setFixedSize( editorRect.size() );
editor->setGeometry( editorRect );
}
void
Playlist::PrettyItemDelegate::editorDone( InlineEditorWidget * editor )
{
emit commitData( editor );
}
void
Playlist::PrettyItemDelegate::currentDesktopChanged()
{
// Optimization for X11/Linux desktops:
// Don't update the animation if Amarok is not on the active virtual desktop.
m_animationTimeLine->setPaused( !The::mainWindow()->isOnCurrentDesktop() );
}
QMap<QString, QString>
Playlist::PrettyItemDelegate::buildTrackArgsMap( const Meta::TrackPtr track ) const
{
QMap<QString, QString> args;
QString artist = track->artist() ? track->artist()->name() : QString();
QString albumartist;
if( track->album() && track->album()->hasAlbumArtist() )
albumartist = track->album()->albumArtist()->name();
else
albumartist = artist;
args["title"] = track->name();
args["composer"] = track->composer() ? track->composer()->name() : QString();
// if year == 0 then we don't want include it
QString year = track->year() ? track->year()->name() : QString();
args["year"] = year.localeAwareCompare( "0" ) == 0 ? QString() : year;
args["album"] = track->album() ? track->album()->name() : QString();
if( track->discNumber() )
args["discnumber"] = QString::number( track->discNumber() );
args["genre"] = track->genre() ? track->genre()->name() : QString();
args["comment"] = track->comment();
args["artist"] = artist;
args["albumartist"] = albumartist;
args["initial"] = albumartist.mid( 0, 1 ).toUpper(); //artists starting with The are already handled above
args["filetype"] = track->type();
args["rating"] = track->statistics()->rating();
args["filesize"] = track->filesize();
args["length"] = track->length() / 1000;
if ( track->trackNumber() )
{
QString trackNum = QString( "%1" ).arg( track->trackNumber(), 2, 10, QChar('0') );
args["track"] = trackNum;
}
return args;
}
diff --git a/src/playlist/view/listview/PrettyListView.cpp b/src/playlist/view/listview/PrettyListView.cpp
index 2d649f0613..fbf0cf4a30 100644
--- a/src/playlist/view/listview/PrettyListView.cpp
+++ b/src/playlist/view/listview/PrettyListView.cpp
@@ -1,1049 +1,1051 @@
/****************************************************************************************
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 John Atkinson <john@fauxnetic.co.uk> *
* Copyright (c) 2009-2010 Oleksandr Khayrullin <saniokh@gmail.com> *
* Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Playlist::PrettyListView"
#include "PrettyListView.h"
#include "amarokconfig.h"
#include "AmarokMimeData.h"
#include "context/ContextView.h"
#include "context/popupdropper/libpud/PopupDropperItem.h"
#include "context/popupdropper/libpud/PopupDropper.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "dialogs/TagDialog.h"
#include "GlobalCurrentTrackActions.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/capabilities/FindInSourceCapability.h"
#include "core/capabilities/MultiSourceCapability.h"
#include "core/meta/Meta.h"
#include "PaletteHandler.h"
#include "playlist/layouts/LayoutManager.h"
#include "playlist/proxymodels/GroupingProxy.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "playlist/PlaylistController.h"
#include "playlist/view/PlaylistViewCommon.h"
#include "playlist/PlaylistDefines.h"
#include "PopupDropperFactory.h"
#include "SvgHandler.h"
#include "SourceSelectionPopup.h"
#include <QApplication>
#include <QMenu>
#include <QUrl>
#include <KLocale>
#include <QClipboard>
#include <QContextMenuEvent>
#include <QDropEvent>
#include <QItemSelection>
#include <QKeyEvent>
#include <QListView>
#include <QModelIndex>
#include <QMouseEvent>
#include <QPainter>
#include <QPalette>
#include <QPersistentModelIndex>
#include <QScrollBar>
#include <QTimer>
Playlist::PrettyListView::PrettyListView( QWidget* parent )
: QListView( parent )
, ViewCommon()
, m_headerPressIndex( QModelIndex() )
, m_mousePressInHeader( false )
, m_skipAutoScroll( false )
, m_firstScrollToActiveTrack( true )
, m_rowsInsertedScrollItem( 0 )
, m_showOnlyMatches( false )
, m_pd( 0 )
{
// QAbstractItemView basics
setModel( The::playlist()->qaim() );
m_prettyDelegate = new PrettyItemDelegate( this );
- connect( m_prettyDelegate, SIGNAL( redrawRequested() ), this, SLOT( redrawActive() ) );
+ connect( m_prettyDelegate, &PrettyItemDelegate::redrawRequested, this, &Playlist::PrettyListView::redrawActive );
setItemDelegate( m_prettyDelegate );
setSelectionMode( ExtendedSelection );
setDragDropMode( DragDrop );
setDropIndicatorShown( false ); // we draw our own drop indicator
setEditTriggers ( SelectedClicked | EditKeyPressed );
setAutoScroll( true );
setVerticalScrollMode( ScrollPerPixel );
setMouseTracking( true );
// Rendering adjustments
setFrameShape( QFrame::NoFrame );
setAlternatingRowColors( true) ;
The::paletteHandler()->updateItemView( this );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(newPalette(QPalette)) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &PrettyListView::newPalette );
setAutoFillBackground( false );
// Signal connections
- connect( this, SIGNAL(doubleClicked(QModelIndex)),
- this, SLOT(trackActivated(QModelIndex)) );
- connect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
- this, SLOT(slotSelectionChanged()) );
+ connect( this, &Playlist::PrettyListView::doubleClicked,
+ this, &Playlist::PrettyListView::trackActivated );
+ connect( selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &Playlist::PrettyListView::slotSelectionChanged );
- connect( LayoutManager::instance(), SIGNAL(activeLayoutChanged()), this, SLOT(playlistLayoutChanged()) );
+ connect( LayoutManager::instance(), &LayoutManager::activeLayoutChanged, this, &Playlist::PrettyListView::playlistLayoutChanged );
- connect( model(), SIGNAL(activeTrackChanged(quint64)), this, SLOT(slotPlaylistActiveTrackChanged()) );
-
- connect( model(), SIGNAL(queueChanged()), viewport(), SLOT(update()) );
+ if (auto m = static_cast<Playlist::Model*>(model()))
+ {
+ connect( m, &Playlist::Model::activeTrackChanged, this, &Playlist::PrettyListView::slotPlaylistActiveTrackChanged );
+ connect( m, &Playlist::Model::queueChanged, viewport(), QOverload<>::of(&QWidget::update) );
+ }
// Warning, this one doesn't connect to the normal 'model()' (i.e. '->top()'), but to '->bottom()'.
- connect( Playlist::ModelStack::instance()->bottom(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(bottomModelRowsInserted(QModelIndex,int,int)) );
+ connect( Playlist::ModelStack::instance()->bottom(), &Playlist::Model::rowsInserted, this, &Playlist::PrettyListView::bottomModelRowsInserted );
// Timers
m_proxyUpdateTimer = new QTimer( this );
m_proxyUpdateTimer->setSingleShot( true );
- connect( m_proxyUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProxyTimeout()) );
+ connect( m_proxyUpdateTimer, &QTimer::timeout, this, &Playlist::PrettyListView::updateProxyTimeout );
m_animationTimer = new QTimer(this);
- connect( m_animationTimer, SIGNAL(timeout()), this, SLOT(redrawActive()) );
+ connect( m_animationTimer, &QTimer::timeout, this, &Playlist::PrettyListView::redrawActive );
m_animationTimer->setInterval( 250 );
playlistLayoutChanged();
// We do the following call here to be formally correct, but note:
// - It happens to be redundant, because 'playlistLayoutChanged()' already schedules
// another one, via a QTimer( 0 ).
// - Both that one and this one don't work right (they scroll like 'PositionAtTop',
// not 'PositionAtCenter'). This is probably because MainWindow changes its
// geometry in a QTimer( 0 )? As a fix, MainWindow does a 'slotShowActiveTrack()'
// at the end of its QTimer slot, which will finally scroll to the right spot.
slotPlaylistActiveTrackChanged();
}
Playlist::PrettyListView::~PrettyListView()
{}
int
Playlist::PrettyListView::verticalOffset() const
{
int ret = QListView::verticalOffset();
if ( verticalScrollBar() && verticalScrollBar()->maximum() )
ret += verticalScrollBar()->value() * 10 / verticalScrollBar()->maximum();
return ret;
}
void
Playlist::PrettyListView::editTrackInformation()
{
Meta::TrackList tl;
foreach( const QModelIndex &index, selectedIndexes() )
{
tl.append( index.data( TrackRole ).value<Meta::TrackPtr>() );
}
if( !tl.isEmpty() )
{
TagDialog *dialog = new TagDialog( tl, this );
dialog->show();
}
}
void
Playlist::PrettyListView::playFirstSelected()
{
QModelIndexList selected = selectedIndexes();
if( !selected.isEmpty() )
trackActivated( selected.first() );
}
void
Playlist::PrettyListView::removeSelection()
{
QList<int> sr = selectedRows();
if( !sr.isEmpty() )
{
// Now that we have the list of selected rows in the topmost proxy, we can perform the
// removal.
The::playlistController()->removeRows( sr );
// Next, we look for the first row.
int firstRow = sr.first();
foreach( int i, sr )
{
if( i < firstRow )
firstRow = i;
}
//Select the track occupied by the first deleted track. Also move the current item to here as
//button presses up or down wil otherwise not behave as expected.
firstRow = qBound( 0, firstRow, model()->rowCount() - 1 );
QModelIndex newSelectionIndex = model()->index( firstRow, 0 );
setCurrentIndex( newSelectionIndex );
selectionModel()->select( newSelectionIndex, QItemSelectionModel::Select );
}
}
void
Playlist::PrettyListView::queueSelection()
{
Actions::instance()->queue( selectedRows() );
}
void
Playlist::PrettyListView::dequeueSelection()
{
Actions::instance()->dequeue( selectedRows() );
}
void
Playlist::PrettyListView::switchQueueState() // slot
{
DEBUG_BLOCK
const bool isQueued = currentIndex().data( Playlist::QueuePositionRole ).toInt() != 0;
isQueued ? dequeueSelection() : queueSelection();
}
void Playlist::PrettyListView::selectSource()
{
DEBUG_BLOCK
QList<int> rows = selectedRows();
//for now, bail out of more than 1 row...
if ( rows.count() != 1 )
return;
//get the track...
QModelIndex index = model()->index( rows.at( 0 ), 0 );
Meta::TrackPtr track = index.data( Playlist::TrackRole ).value< Meta::TrackPtr >();
//get multiSource capability:
Capabilities::MultiSourceCapability *msc = track->create<Capabilities::MultiSourceCapability>();
if ( msc )
{
debug() << "sources: " << msc->sources();
SourceSelectionPopup * sourceSelector = new SourceSelectionPopup( this, msc );
sourceSelector->show();
//dialog deletes msc when done with it.
}
}
void
Playlist::PrettyListView::scrollToActiveTrack()
{
DEBUG_BLOCK
if( m_skipAutoScroll )
{
m_skipAutoScroll = false;
return;
}
QModelIndex activeIndex = model()->index( The::playlist()->activeRow(), 0, QModelIndex() );
if ( activeIndex.isValid() )
{
scrollTo( activeIndex, QAbstractItemView::PositionAtCenter );
m_firstScrollToActiveTrack = false;
m_rowsInsertedScrollItem = 0; // This "new active track" scroll supersedes a pending "rows inserted" scroll.
}
}
void
Playlist::PrettyListView::downOneTrack()
{
DEBUG_BLOCK
moveTrackSelection( 1 );
}
void
Playlist::PrettyListView::upOneTrack()
{
DEBUG_BLOCK
moveTrackSelection( -1 );
}
void
Playlist::PrettyListView::moveTrackSelection( int offset )
{
if ( offset == 0 )
return;
int finalRow = model()->rowCount() - 1;
int target = 0;
if ( offset < 0 )
target = finalRow;
QList<int> rows = selectedRows();
if ( rows.count() > 0 )
target = rows.at( 0 ) + offset;
target = qBound(0, target, finalRow);
QModelIndex index = model()->index( target, 0 );
setCurrentIndex( index );
}
void
Playlist::PrettyListView::slotPlaylistActiveTrackChanged()
{
DEBUG_BLOCK
// A playlist 'activeTrackChanged' signal happens:
// - During startup, on "saved playlist" load. (Might happen before this view exists)
// - When Amarok starts playing a new item in the playlist.
// In that case, don't auto-scroll if the user doesn't like us to.
if( AmarokConfig::autoScrollPlaylist() || m_firstScrollToActiveTrack )
scrollToActiveTrack();
}
void
Playlist::PrettyListView::slotSelectionChanged()
{
m_lastTimeSelectionChanged = QDateTime::currentDateTime();
}
void
Playlist::PrettyListView::trackActivated( const QModelIndex& idx )
{
DEBUG_BLOCK
m_skipAutoScroll = true; // we don't want to do crazy view changes when selecting an item in the view
Actions::instance()->play( idx );
//make sure that the track we just activated is also set as the current index or
//the selected index will get moved to the first row, making keyboard navigation difficult (BUG 225791)
selectionModel_setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect );
setFocus();
}
// The following 2 functions are a workaround for crash BUG 222961 and BUG 229240:
// There appears to be a bad interaction between Qt 'setCurrentIndex()' and
// Qt 'selectedIndexes()' / 'selectionModel()->select()' / 'scrollTo()'.
//
// 'setCurrentIndex()' appears to do something bad with its QModelIndex parameter,
// leading to a crash deep within Qt.
//
// It might be our fault, but we suspect a bug in Qt. (Qt 4.6 at least)
//
// The problem goes away if we use a fresh QModelIndex, which we also don't re-use
// afterwards.
void
Playlist::PrettyListView::setCurrentIndex( const QModelIndex &index )
{
QModelIndex indexCopy = model()->index( index.row(), index.column() );
QListView::setCurrentIndex( indexCopy );
}
void
Playlist::PrettyListView::selectionModel_setCurrentIndex( const QModelIndex &index, QItemSelectionModel::SelectionFlags command )
{
QModelIndex indexCopy = model()->index( index.row(), index.column() );
selectionModel()->setCurrentIndex( indexCopy, command );
}
void
Playlist::PrettyListView::showEvent( QShowEvent* event )
{
- QTimer::singleShot( 0, this, SLOT(fixInvisible()) );
+ QTimer::singleShot( 0, this, &Playlist::PrettyListView::fixInvisible );
QListView::showEvent( event );
}
// This method is a workaround for BUG 184714.
//
// It prevents the playlist from becoming invisible (clear) after changing the model, while Amarok is hidden in the tray.
// Without this workaround the playlist stays invisible when the application is restored from the tray.
// This is especially a problem with the Dynamic Playlist mode, which modifies the model without user interaction.
//
// The bug only seems to happen with Qt 4.5.x, so it might actually be a bug in Qt.
void
Playlist::PrettyListView::fixInvisible() //SLOT
{
// DEBUG_BLOCK
// Part 1: Palette change
newPalette( palette() );
// Part 2: Change item selection
const QItemSelection oldSelection( selectionModel()->selection() );
selectionModel()->clear();
selectionModel()->select( oldSelection, QItemSelectionModel::SelectCurrent );
// NOTE: A simple update() call is not sufficient, but in fact the above two steps are required.
}
void
Playlist::PrettyListView::contextMenuEvent( QContextMenuEvent* event )
{
DEBUG_BLOCK
QModelIndex index = indexAt( event->pos() );
if ( !index.isValid() )
return;
//Ctrl + Right Click is used for queuing
if( event->modifiers() & Qt::ControlModifier )
return;
trackMenu( this, &index, event->globalPos() );
event->accept();
}
void
Playlist::PrettyListView::dragLeaveEvent( QDragLeaveEvent* event )
{
m_mousePressInHeader = false;
m_dropIndicator = QRect( 0, 0, 0, 0 );
QListView::dragLeaveEvent( event );
}
void
Playlist::PrettyListView::stopAfterTrack()
{
const quint64 id = currentIndex().data( UniqueIdRole ).value<quint64>();
if( Actions::instance()->willStopAfterTrack( id ) )
Actions::instance()->stopAfterPlayingTrack( 0 ); // disable stopping
else
Actions::instance()->stopAfterPlayingTrack( id );
}
void
Playlist::PrettyListView::findInSource()
{
DEBUG_BLOCK
Meta::TrackPtr track = currentIndex().data( TrackRole ).value<Meta::TrackPtr>();
if ( track )
{
if( track->has<Capabilities::FindInSourceCapability>() )
{
Capabilities::FindInSourceCapability *fis = track->create<Capabilities::FindInSourceCapability>();
if ( fis )
{
fis->findInSource();
}
delete fis;
}
}
}
void
Playlist::PrettyListView::dragEnterEvent( QDragEnterEvent *event )
{
const QMimeData *mime = event->mimeData();
if( mime->hasUrls() ||
mime->hasFormat( AmarokMimeData::TRACK_MIME ) ||
mime->hasFormat( AmarokMimeData::PLAYLIST_MIME ) ||
mime->hasFormat( AmarokMimeData::PODCASTEPISODE_MIME ) ||
mime->hasFormat( AmarokMimeData::PODCASTCHANNEL_MIME ) )
{
event->acceptProposedAction();
}
}
void
Playlist::PrettyListView::dragMoveEvent( QDragMoveEvent* event )
{
QModelIndex index = indexAt( event->pos() );
if ( index.isValid() )
{
m_dropIndicator = visualRect( index );
}
else
{
// draw it on the bottom of the last item
index = model()->index( model()->rowCount() - 1, 0, QModelIndex() );
m_dropIndicator = visualRect( index );
m_dropIndicator = m_dropIndicator.translated( 0, m_dropIndicator.height() );
}
QListView::dragMoveEvent( event );
}
void
Playlist::PrettyListView::dropEvent( QDropEvent* event )
{
DEBUG_BLOCK
QRect oldDrop = m_dropIndicator;
m_dropIndicator = QRect( 0, 0, 0, 0 );
if ( qobject_cast<PrettyListView*>( event->source() ) == this )
{
QAbstractItemModel* plModel = model();
int targetRow = indexAt( event->pos() ).row();
targetRow = ( targetRow < 0 ) ? plModel->rowCount() : targetRow; // target of < 0 means we dropped on the end of the playlist
QList<int> sr = selectedRows();
int realtarget = The::playlistController()->moveRows( sr, targetRow );
QItemSelection selItems;
foreach( int row, sr )
{
Q_UNUSED( row )
selItems.select( plModel->index( realtarget, 0 ), plModel->index( realtarget, 0 ) );
realtarget++;
}
selectionModel()->select( selItems, QItemSelectionModel::ClearAndSelect );
event->accept();
}
else
{
QListView::dropEvent( event );
}
// add some padding around the old drop area which to repaint, as we add offsets when painting. See paintEvent().
oldDrop.adjust( -6, -6, 6, 6 );
repaint( oldDrop );
}
void
Playlist::PrettyListView::keyPressEvent( QKeyEvent *event )
{
if( event->matches( QKeySequence::Delete ) )
{
removeSelection();
event->accept();
}
else if( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return )
{
trackActivated( currentIndex() );
event->accept();
}
else if( event->matches( QKeySequence::SelectAll ) )
{
QModelIndex topIndex = model()->index( 0, 0 );
QModelIndex bottomIndex = model()->index( model()->rowCount() - 1, 0 );
QItemSelection selItems( topIndex, bottomIndex );
selectionModel()->select( selItems, QItemSelectionModel::ClearAndSelect );
event->accept();
}
else
QListView::keyPressEvent( event );
}
void
Playlist::PrettyListView::mousePressEvent( QMouseEvent* event )
{
//get the item that was clicked
QModelIndex index = indexAt( event->pos() );
//first of all, if a left click, check if the delegate wants to do something about this click
if( event->button() == Qt::LeftButton )
{
//we need to translate the position of the click into something relative to the item that was clicked.
QRect itemRect = visualRect( index );
QPoint relPos = event->pos() - itemRect.topLeft();
if( m_prettyDelegate->clicked( relPos, itemRect, index ) )
{
event->accept();
return; //click already handled...
}
}
if ( mouseEventInHeader( event ) && ( event->button() == Qt::LeftButton ) )
{
m_mousePressInHeader = true;
m_headerPressIndex = QPersistentModelIndex( index );
int rows = index.data( GroupedTracksRole ).toInt();
QModelIndex bottomIndex = model()->index( index.row() + rows - 1, 0 );
//offset by 1 as the actual header item is selected in QListView::mousePressEvent( event ); and is otherwise deselected again
QItemSelection selItems( model()->index( index.row() + 1, 0 ), bottomIndex );
QItemSelectionModel::SelectionFlags command = headerPressSelectionCommand( index, event );
selectionModel()->select( selItems, command );
// TODO: if you're doing shift-select on rows above the header, then the rows following the header will be lost from the selection
selectionModel_setCurrentIndex( index, QItemSelectionModel::NoUpdate );
}
else
{
m_mousePressInHeader = false;
}
if ( event->button() == Qt::MidButton )
{
QUrl url( QApplication::clipboard()->text() );
if ( url.isValid() )
{
QList<QUrl> urls = QList<QUrl>() << url;
if( index.isValid() )
The::playlistController()->insertUrls( index.row() + 1, urls );
else
The::playlistController()->insertOptioned( urls, Playlist::OnAppendToPlaylistAction );
}
}
// This should always be forwarded, as it is used to determine the offset
// relative to the mouse of the selection we are dragging!
QListView::mousePressEvent( event );
// This must go after the call to the super class as the current index is not yet selected otherwise
// Queueing support for Ctrl Right click
if( event->button() == Qt::RightButton && event->modifiers() & Qt::ControlModifier )
{
QList<int> list;
if (selectedRows().contains( index.row()) )
{
// select all selected rows if mouse is over selection area
list = selectedRows();
}
else
{
// select only current mouse-over-index if mouse is out of selection area
list.append( index.row() );
}
if( index.data( Playlist::QueuePositionRole ).toInt() != 0 )
Actions::instance()->dequeue( list );
else
Actions::instance()->queue( list );
}
}
void
Playlist::PrettyListView::mouseReleaseEvent( QMouseEvent* event )
{
if ( mouseEventInHeader( event ) && ( event->button() == Qt::LeftButton ) && m_mousePressInHeader && m_headerPressIndex.isValid() )
{
QModelIndex index = indexAt( event->pos() );
if ( index == m_headerPressIndex )
{
int rows = index.data( GroupedTracksRole ).toInt();
QModelIndex bottomIndex = model()->index( index.row() + rows - 1, 0 );
QItemSelection selItems( index, bottomIndex );
QItemSelectionModel::SelectionFlags command = headerReleaseSelectionCommand( index, event );
selectionModel()->select( selItems, command );
}
event->accept();
}
else
{
QListView::mouseReleaseEvent( event );
}
m_mousePressInHeader = false;
}
bool
Playlist::PrettyListView::mouseEventInHeader( const QMouseEvent* event ) const
{
QModelIndex index = indexAt( event->pos() );
if ( index.data( GroupRole ).toInt() == Grouping::Head )
{
QPoint mousePressPos = event->pos();
mousePressPos.rx() += horizontalOffset();
mousePressPos.ry() += verticalOffset();
return m_prettyDelegate->insideItemHeader( mousePressPos, rectForIndex( index ) );
}
return false;
}
void
Playlist::PrettyListView::paintEvent( QPaintEvent *event )
{
if( m_dropIndicator.isValid() ||
model()->rowCount( rootIndex() ) == 0 )
{
QPainter painter( viewport() );
if( m_dropIndicator.isValid() )
{
const QPoint offset( 6, 0 );
QColor c = QApplication::palette().color( QPalette::Highlight );
painter.setPen( QPen( c, 6, Qt::SolidLine, Qt::RoundCap ) );
painter.drawLine( m_dropIndicator.topLeft() + offset,
m_dropIndicator.topRight() - offset );
}
if( model()->rowCount( rootIndex() ) == 0 )
{
// here we assume that an empty list is caused by the filter if it's active
QString emptyText;
if( m_showOnlyMatches && Playlist::ModelStack::instance()->bottom()->rowCount() > 0 )
emptyText = i18n( "Tracks have been hidden due to the active search." );
else
emptyText = i18n( "Add some songs here by dragging them from all around." );
QColor c = QApplication::palette().color( foregroundRole() );
c.setAlpha( c.alpha() / 2 );
painter.setPen( c );
painter.drawText( rect(),
Qt::AlignCenter | Qt::TextWordWrap,
emptyText );
}
}
QListView::paintEvent( event );
}
void
Playlist::PrettyListView::startDrag( Qt::DropActions supportedActions )
{
DEBUG_BLOCK
QModelIndexList indices = selectedIndexes();
if( indices.isEmpty() )
return; // no items selected in the view, abort. See bug 226167
//Waah? when a parent item is dragged, startDrag is called a bunch of times
static bool ongoingDrags = false;
if( ongoingDrags )
return;
ongoingDrags = true;
/* FIXME: disabled temporarily for KF5 porting
if( !m_pd )
m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
*/
if( m_pd && m_pd->isHidden() )
{
m_pd->setSvgRenderer( The::svgHandler()->getRenderer( "amarok/images/pud_items.svg" ) );
qDebug() << "svgHandler SVG renderer is " << (QObject*)(The::svgHandler()->getRenderer( "amarok/images/pud_items.svg" ));
qDebug() << "m_pd SVG renderer is " << (QObject*)(m_pd->svgRenderer());
qDebug() << "does play exist in renderer? " << ( The::svgHandler()->getRenderer( "amarok/images/pud_items.svg" )->elementExists( "load" ) );
QList<QAction*> actions = actionsFor( this, &indices.first() );
foreach( QAction * action, actions )
m_pd->addItem( The::popupDropperFactory()->createItem( action ), true );
m_pd->show();
}
QListView::startDrag( supportedActions );
debug() << "After the drag!";
if( m_pd )
{
debug() << "clearing PUD";
- connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(clear()) );
+ connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear );
m_pd->hide();
}
ongoingDrags = false;
}
bool
Playlist::PrettyListView::edit( const QModelIndex &index, EditTrigger trigger, QEvent *event )
{
// we want to prevent a click to change the selection and open the editor (BR 220818)
if( m_lastTimeSelectionChanged.msecsTo( QDateTime::currentDateTime() ) < qApp->doubleClickInterval() + 50 )
return false;
return QListView::edit( index, trigger, event );
}
QItemSelectionModel::SelectionFlags
Playlist::PrettyListView::headerPressSelectionCommand( const QModelIndex& index, const QMouseEvent* event ) const
{
if ( !index.isValid() )
return QItemSelectionModel::NoUpdate;
const bool shiftKeyPressed = event->modifiers() & Qt::ShiftModifier;
//const bool controlKeyPressed = event->modifiers() & Qt::ControlModifier;
const bool indexIsSelected = selectionModel()->isSelected( index );
const bool controlKeyPressed = event->modifiers() & Qt::ControlModifier;
if ( shiftKeyPressed )
return QItemSelectionModel::SelectCurrent;
if ( indexIsSelected && controlKeyPressed ) //make this consistent with how single items work. This also makes it possible to drag the header
return QItemSelectionModel::Deselect;
return QItemSelectionModel::Select;
}
QItemSelectionModel::SelectionFlags
Playlist::PrettyListView::headerReleaseSelectionCommand( const QModelIndex& index, const QMouseEvent* event ) const
{
if ( !index.isValid() )
return QItemSelectionModel::NoUpdate;
const bool shiftKeyPressed = event->modifiers() & Qt::ShiftModifier;
const bool controlKeyPressed = event->modifiers() & Qt::ControlModifier;
if ( !controlKeyPressed && !shiftKeyPressed )
return QItemSelectionModel::ClearAndSelect;
return QItemSelectionModel::NoUpdate;
}
QList<int>
Playlist::PrettyListView::selectedRows() const
{
QList<int> rows;
foreach( const QModelIndex &idx, selectedIndexes() )
rows.append( idx.row() );
return rows;
}
void Playlist::PrettyListView::newPalette( const QPalette & palette )
{
Q_UNUSED( palette )
The::paletteHandler()->updateItemView( this );
reset();
}
void Playlist::PrettyListView::find( const QString &searchTerm, int fields, bool filter )
{
bool updateProxy = false;
if ( ( The::playlist()->currentSearchFields() != fields ) || ( The::playlist()->currentSearchTerm() != searchTerm ) )
updateProxy = true;
m_searchTerm = searchTerm;
m_fields = fields;
m_filter = filter;
int row = The::playlist()->find( m_searchTerm, m_fields );
if( row != -1 )
{
//select this track
QModelIndex index = model()->index( row, 0 );
QItemSelection selItems( index, index );
selectionModel()->select( selItems, QItemSelectionModel::SelectCurrent );
}
//instead of kicking the proxy right away, start a small timeout.
//this stops us from updating it for each letter of a long search term,
//and since it does not affect any views, this is fine. Worst case is that
//a navigator skips to a track form the old search if the track change happens
//before this timeout. Only start count if values have actually changed!
if ( updateProxy )
startProxyUpdateTimeout();
}
void Playlist::PrettyListView::findNext( const QString & searchTerm, int fields )
{
DEBUG_BLOCK
QList<int> selected = selectedRows();
bool updateProxy = false;
if ( ( The::playlist()->currentSearchFields() != fields ) || ( The::playlist()->currentSearchTerm() != searchTerm ) )
updateProxy = true;
int currentRow = -1;
if( selected.size() > 0 )
currentRow = selected.last();
int row = The::playlist()->findNext( searchTerm, currentRow, fields );
if( row != -1 )
{
//select this track
QModelIndex index = model()->index( row, 0 );
QItemSelection selItems( index, index );
selectionModel()->select( selItems, QItemSelectionModel::SelectCurrent );
QModelIndex foundIndex = model()->index( row, 0, QModelIndex() );
setCurrentIndex( foundIndex );
if ( foundIndex.isValid() )
scrollTo( foundIndex, QAbstractItemView::PositionAtCenter );
emit( found() );
}
else
emit( notFound() );
if ( updateProxy )
The::playlist()->filterUpdated();
}
void Playlist::PrettyListView::findPrevious( const QString & searchTerm, int fields )
{
DEBUG_BLOCK
QList<int> selected = selectedRows();
bool updateProxy = false;
if ( ( The::playlist()->currentSearchFields() != fields ) || ( The::playlist()->currentSearchTerm() != searchTerm ) )
updateProxy = true;
int currentRow = model()->rowCount();
if( selected.size() > 0 )
currentRow = selected.first();
int row = The::playlist()->findPrevious( searchTerm, currentRow, fields );
if( row != -1 )
{
//select this track
QModelIndex index = model()->index( row, 0 );
QItemSelection selItems( index, index );
selectionModel()->select( selItems, QItemSelectionModel::SelectCurrent );
QModelIndex foundIndex = model()->index( row, 0, QModelIndex() );
setCurrentIndex( foundIndex );
if ( foundIndex.isValid() )
scrollTo( foundIndex, QAbstractItemView::PositionAtCenter );
emit( found() );
}
else
emit( notFound() );
if ( updateProxy )
The::playlist()->filterUpdated();
}
void Playlist::PrettyListView::clearSearchTerm()
{
DEBUG_BLOCK
// Choose a focus item, to scroll to later.
QModelIndex focusIndex;
QModelIndexList selected = selectedIndexes();
if( !selected.isEmpty() )
focusIndex = selected.first();
else
focusIndex = indexAt( QPoint( 0, 0 ) );
// Remember the focus item id, because the row numbers change when we reset the filter.
quint64 focusItemId = The::playlist()->idAt( focusIndex.row() );
The::playlist()->clearSearchTerm();
The::playlist()->filterUpdated();
// Now scroll to the focus item.
QModelIndex newIndex = model()->index( The::playlist()->rowForId( focusItemId ), 0, QModelIndex() );
if ( newIndex.isValid() )
scrollTo( newIndex, QAbstractItemView::PositionAtCenter );
}
void Playlist::PrettyListView::startProxyUpdateTimeout()
{
DEBUG_BLOCK
if ( m_proxyUpdateTimer->isActive() )
m_proxyUpdateTimer->stop();
m_proxyUpdateTimer->setInterval( 200 );
m_proxyUpdateTimer->start();
}
void Playlist::PrettyListView::updateProxyTimeout()
{
DEBUG_BLOCK
The::playlist()->filterUpdated();
int row = The::playlist()->find( m_searchTerm, m_fields );
if( row != -1 )
{
QModelIndex foundIndex = model()->index( row, 0, QModelIndex() );
setCurrentIndex( foundIndex );
if ( !m_filter )
{
if ( foundIndex.isValid() )
scrollTo( foundIndex, QAbstractItemView::PositionAtCenter );
}
emit( found() );
}
else
emit( notFound() );
}
void Playlist::PrettyListView::showOnlyMatches( bool onlyMatches )
{
m_showOnlyMatches = onlyMatches;
The::playlist()->showOnlyMatches( onlyMatches );
}
// Handle scrolling to newly inserted playlist items.
// Warning, this slot is connected to the 'rowsInserted' signal of the *bottom* model,
// not the normal top model.
// The reason: FilterProxy can emit *A LOT* (thousands) of 'rowsInserted' signals when its
// search string changes. For that case we don't want to do any scrollTo() at all.
void
Playlist::PrettyListView::bottomModelRowsInserted( const QModelIndex& parent, int start, int end )
{
Q_UNUSED( parent )
Q_UNUSED( end )
// skip scrolling if tracks were added while playlist is in dynamicMode
if( m_rowsInsertedScrollItem == 0 && !AmarokConfig::dynamicMode() )
{
m_rowsInsertedScrollItem = Playlist::ModelStack::instance()->bottom()->idAt( start );
- QTimer::singleShot( 0, this, SLOT(bottomModelRowsInsertedScroll()) );
+ QTimer::singleShot( 0, this, &Playlist::PrettyListView::bottomModelRowsInsertedScroll );
}
}
void Playlist::PrettyListView::bottomModelRowsInsertedScroll()
{
DEBUG_BLOCK
if( m_rowsInsertedScrollItem )
{ // Note: we don't bother handling the case "first inserted item in bottom model
// does not have a row in the top 'model()' due to FilterProxy" nicely.
int firstRowInserted = The::playlist()->rowForId( m_rowsInsertedScrollItem ); // In the *top* model.
QModelIndex index = model()->index( firstRowInserted, 0 );
if( index.isValid() )
scrollTo( index, QAbstractItemView::PositionAtCenter );
m_rowsInsertedScrollItem = 0;
}
}
void Playlist::PrettyListView::redrawActive()
{
int activeRow = The::playlist()->activeRow();
QModelIndex index = model()->index( activeRow, 0, QModelIndex() );
update( index );
}
void Playlist::PrettyListView::playlistLayoutChanged()
{
if ( LayoutManager::instance()->activeLayout().inlineControls() )
m_animationTimer->start();
else
m_animationTimer->stop();
// -- update the tooltip columns in the playlist model
bool tooltipColumns[Playlist::NUM_COLUMNS];
for( int i=0; i<Playlist::NUM_COLUMNS; ++i )
tooltipColumns[i] = true;
// bool excludeCover = false;
for( int part = 0; part < PlaylistLayout::NumParts; part++ )
{
// bool single = ( part == PlaylistLayout::Single );
Playlist::PlaylistLayout layout = Playlist::LayoutManager::instance()->activeLayout();
Playlist::LayoutItemConfig item = layout.layoutForPart( (PlaylistLayout::Part)part );
for (int activeRow = 0; activeRow < item.rows(); activeRow++)
{
for (int activeElement = 0; activeElement < item.row(activeRow).count();activeElement++)
{
Playlist::Column column = (Playlist::Column)item.row(activeRow).element(activeElement).value();
tooltipColumns[column] = false;
}
}
// excludeCover |= item.showCover();
}
Playlist::Model::setTooltipColumns( tooltipColumns );
Playlist::Model::enableToolTip( Playlist::LayoutManager::instance()->activeLayout().tooltips() );
update();
// Schedule a re-scroll to the active playlist row. Assumption: Qt will run this *after* the repaint.
- QTimer::singleShot( 0, this, SLOT(slotPlaylistActiveTrackChanged()) );
+ QTimer::singleShot( 0, this, &Playlist::PrettyListView::slotPlaylistActiveTrackChanged );
}
diff --git a/src/playlist/view/listview/PrettyListView.h b/src/playlist/view/listview/PrettyListView.h
index ce63746d02..18903c4f2e 100644
--- a/src/playlist/view/listview/PrettyListView.h
+++ b/src/playlist/view/listview/PrettyListView.h
@@ -1,159 +1,159 @@
/****************************************************************************************
* Copyright (c) 2008 Soren Harward <stharward@gmail.com> *
* Copyright (c) 2009 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef PRETTYLISTVIEW_H
#define PRETTYLISTVIEW_H
#include "PrettyItemDelegate.h"
#include "playlist/proxymodels/GroupingProxy.h"
#include "playlist/view/PlaylistViewCommon.h"
#include <QListView>
#include <QModelIndex>
#include <QPersistentModelIndex>
#include <QRect>
#include <QAction>
#include <QDateTime>
#include <QTimer>
class PopupDropper;
class QContextMenuEvent;
class QDragLeaveEvent;
class QDragMoveEvent;
class QDropEvent;
class QKeyEvent;
class QMouseEvent;
class QPaintEvent;
class QTimer;
namespace Playlist
{
class PrettyListView : public QListView, public ViewCommon
{
Q_OBJECT
public:
PrettyListView( QWidget* parent = 0 );
~PrettyListView();
protected:
int verticalOffset() const;
Q_SIGNALS:
void found();
void notFound();
// these slots are used by the ContextMenu
public Q_SLOTS:
void editTrackInformation();
void playFirstSelected();
void dequeueSelection();
void queueSelection();
/* Switch queue state for selected rows in playlist */
void switchQueueState();
void removeSelection();
void stopAfterTrack();
void scrollToActiveTrack();
void selectSource();
void downOneTrack();
void upOneTrack();
// Workaround for BUG 222961 and BUG 229240: see implementation for more comments.
void setCurrentIndex( const QModelIndex &index );
void selectionModel_setCurrentIndex( const QModelIndex &index, QItemSelectionModel::SelectionFlags command ); // Never call selectionModel()->setCurrentIndex() directly!
void find( const QString & searchTerm, int fields, bool filter );
void findNext( const QString & searchTerm, int fields );
void findPrevious( const QString & searchTerm, int fields );
void clearSearchTerm();
void showOnlyMatches( bool onlyMatches );
+ void findInSource();
protected:
void showEvent( QShowEvent* );
void contextMenuEvent( QContextMenuEvent* );
void dragEnterEvent( QDragEnterEvent *event );
void dragLeaveEvent( QDragLeaveEvent* );
void dragMoveEvent( QDragMoveEvent* );
void dropEvent( QDropEvent* );
void keyPressEvent( QKeyEvent* );
void mousePressEvent( QMouseEvent* );
void mouseReleaseEvent( QMouseEvent* );
/** Draws a "drop here" text if empty */
void paintEvent( QPaintEvent* );
void startDrag( Qt::DropActions supportedActions );
bool edit( const QModelIndex &index, EditTrigger trigger, QEvent *event );
protected Q_SLOTS:
void newPalette( const QPalette & palette );
private Q_SLOTS:
void slotPlaylistActiveTrackChanged();
void bottomModelRowsInserted( const QModelIndex& parent, int start, int end );
void bottomModelRowsInsertedScroll();
void moveTrackSelection( int offset );
void slotSelectionChanged();
void trackActivated( const QModelIndex& );
void updateProxyTimeout();
void fixInvisible(); // Workaround for BUG 184714; see implementation for more comments.
void redrawActive();
void playlistLayoutChanged();
- void findInSource();
private:
bool mouseEventInHeader( const QMouseEvent* ) const;
QItemSelectionModel::SelectionFlags headerPressSelectionCommand( const QModelIndex&, const QMouseEvent* ) const;
QItemSelectionModel::SelectionFlags headerReleaseSelectionCommand( const QModelIndex&, const QMouseEvent* ) const;
void startProxyUpdateTimeout();
QRect m_dropIndicator;
QPersistentModelIndex m_headerPressIndex;
bool m_mousePressInHeader;
bool m_skipAutoScroll;
bool m_firstScrollToActiveTrack;
quint64 m_rowsInsertedScrollItem;
QString m_searchTerm;
int m_fields;
bool m_filter;
bool m_showOnlyMatches;
QTimer *m_proxyUpdateTimer;
PopupDropper *m_pd;
PrettyItemDelegate * m_prettyDelegate;
QTimer *m_animationTimer;
QDateTime m_lastTimeSelectionChanged; // we want to prevent a click to change the selection and open the editor (BR 220818)
public:
QList<int> selectedRows() const;
};
}
#endif
diff --git a/src/playlist/view/listview/SourceSelectionPopup.cpp b/src/playlist/view/listview/SourceSelectionPopup.cpp
index 803fa805c7..57ecd9d7e3 100644
--- a/src/playlist/view/listview/SourceSelectionPopup.cpp
+++ b/src/playlist/view/listview/SourceSelectionPopup.cpp
@@ -1,96 +1,96 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SourceSelectionPopup.h"
#include <QIcon>
#include <KLocale>
#include <QPushButton>
#include <QLabel>
#include <QListWidget>
#include <QListWidgetItem>
#include <QVBoxLayout>
namespace Playlist {
SourceSelectionPopup::SourceSelectionPopup( QWidget * parent, Capabilities::MultiSourceCapability * msc )
: QDialog( parent )
, m_msc( msc )
{
QLabel * label = new QLabel( i18n( "The following sources are available for this track:" ) );
label->setWordWrap( true );
m_listWidget = new QListWidget();
QPushButton * okButton = new QPushButton( i18n( "OK" ) );
- connect( okButton, SIGNAL(clicked()), SLOT(accept()) );
+ connect( okButton, &QPushButton::clicked, this, &SourceSelectionPopup::accept );
- connect( m_listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(sourceSelected(QListWidgetItem*)) );
+ connect( m_listWidget, &QListWidget::itemDoubleClicked, this, &SourceSelectionPopup::sourceSelected );
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget( label );
layout->addWidget( m_listWidget );
layout->addWidget( okButton );
setLayout( layout );
int i = 0;
foreach( const QString &source, m_msc->sources() )
{
if ( i == m_msc->current() )
new QListWidgetItem( QIcon::fromTheme( "arrow-right" ), source, m_listWidget ) ;
else
new QListWidgetItem( source, m_listWidget );
i++;
}
}
SourceSelectionPopup::~SourceSelectionPopup()
{
delete m_msc;
}
void SourceSelectionPopup::sourceSelected( QListWidgetItem * item )
{
//get row of item:
int currentSource = m_listWidget->row( item );
m_msc->setSource( currentSource );
m_listWidget->clear();
int i = 0;
foreach( const QString &source, m_msc->sources() )
{
if ( i == m_msc->current() )
new QListWidgetItem( QIcon::fromTheme( "arrow-right" ), source, m_listWidget ) ;
else
new QListWidgetItem( source, m_listWidget );
i++;
}
}
}
diff --git a/src/playlistgenerator/ConstraintGroup.cpp b/src/playlistgenerator/ConstraintGroup.cpp
index c98ef4c5c4..4080fd1125 100644
--- a/src/playlistgenerator/ConstraintGroup.cpp
+++ b/src/playlistgenerator/ConstraintGroup.cpp
@@ -1,315 +1,315 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "APG::ConstraintGroup"
#include "ConstraintGroup.h"
#include "ConstraintFactory.h"
#include "constraints/Matching.h"
#include "core/meta/Meta.h"
#include "core/collections/QueryMaker.h"
#include "core/support/Debug.h"
#include <QList>
#include <QString>
#include <QtGlobal>
ConstraintGroup::ConstraintGroup( QDomElement& xmlelem, ConstraintNode* p ) : ConstraintNode( p )
{
DEBUG_BLOCK
if ( xmlelem.tagName() == "group" ) {
if ( xmlelem.attribute( "matchtype" ) == "any" ) {
m_matchtype = MatchAny;
} else {
m_matchtype = MatchAll;
}
} else if ( xmlelem.tagName() == "constrainttree" ) {
// root node of a constraint tree
m_matchtype = MatchAll;
} else {
m_matchtype = MatchAll;
}
debug() << getName();
}
ConstraintGroup::ConstraintGroup( ConstraintNode* p ) : ConstraintNode( p ), m_matchtype( MatchAll )
{
DEBUG_BLOCK
debug() << "new default ConstraintGroup";
}
ConstraintGroup*
ConstraintGroup::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
ConstraintGroup* cg = new ConstraintGroup( xmlelem, p );
ConstraintFactory* cfact = ConstraintFactory::instance();
// Load the children, which can be either groups or constraints
for ( int i = 0; i < xmlelem.childNodes().count(); i++ ) {
QDomElement childXmlElem = xmlelem.childNodes().item( i ).toElement();
if ( !childXmlElem.isNull() ) {
if ( childXmlElem.tagName() == "group" ) {
cfact->createGroup( childXmlElem, cg );
} else if ( childXmlElem.tagName() == "constraint" ) {
cfact->createConstraint( childXmlElem, cg );
} else {
debug() << "unknown child: " << childXmlElem.nodeName();
}
}
}
return cg;
}
ConstraintGroup*
ConstraintGroup::createNew( ConstraintNode* p )
{
return new ConstraintGroup( p );
}
QString
ConstraintGroup::getName() const
{
if ( m_matchtype == MatchAny ) {
return QString( i18nc("name of a type of constraint group", "\"Match Any\" group") );
} else if ( m_matchtype == MatchAll ) {
return QString( i18nc("name of a type of constraint group", "\"Match All\" group") );
} else {
return QString( i18nc("name of a type of constraint group", "Unknown match group") );
}
}
QWidget*
ConstraintGroup::editWidget() const
{
ConstraintGroupEditWidget* e = new ConstraintGroupEditWidget( m_matchtype );
- connect( e, SIGNAL(clickedMatchAny()), this, SLOT(setMatchAny()) );
- connect( e, SIGNAL(clickedMatchAll()), this, SLOT(setMatchAll()) );
+ connect( e, &ConstraintGroupEditWidget::clickedMatchAny, this, &ConstraintGroup::setMatchAny );
+ connect( e, &ConstraintGroupEditWidget::clickedMatchAll, this, &ConstraintGroup::setMatchAll );
return e;
}
void
ConstraintGroup::toXml( QDomDocument& doc, QDomElement& elem ) const
{
QDomElement group;
if ( elem.tagName() == "generatorpreset" ) {
group = doc.createElement( "constrainttree" ); // unmodifiable root element of the constraint tree
} else {
group = doc.createElement( "group" );
if ( m_matchtype == MatchAny ) {
group.setAttribute( "matchtype", "any" );
} else {
group.setAttribute( "matchtype", "all" );
}
}
foreach( ConstraintNode* child, m_children ) {
child->toXml( doc, group );
}
elem.appendChild( group );
}
Collections::QueryMaker*
ConstraintGroup::initQueryMaker( Collections::QueryMaker* qm ) const
{
if ( m_children.size() > 0 ) {
if ( m_matchtype == MatchAny ) {
qm->beginOr();
} else if ( m_matchtype == MatchAll ) {
qm->beginAnd();
} else {
return qm;
}
foreach( ConstraintNode* child, m_children ) {
child->initQueryMaker( qm );
}
return qm->endAndOr();
} else {
return qm;
}
}
double
ConstraintGroup::satisfaction( const Meta::TrackList& l ) const
{
// shortcut if the playlist is empty
if ( l.size() <= 0 ) {
return 1.0;
}
if ( m_children.isEmpty() ) {
return 1.0;
}
double s;
if ( m_matchtype == MatchAny ) {
s = 0.0;
} else if ( m_matchtype == MatchAll ) {
s = 1.0;
} else {
return 1.0;
}
QHash<int,int> constraintMatchTypes;
// TODO: there's got to be a more efficient way of handling interdependent constraints
for ( int i = 0; i < m_children.size(); i++ ) {
ConstraintNode* child = m_children[i];
double chS = child->satisfaction( l );
if ( m_matchtype == MatchAny ) {
s = qMax( s, chS );
} else if ( m_matchtype == MatchAll ) {
s = qMin( s, chS );
}
// prepare for proper handling of non-independent constraints
ConstraintTypes::MatchingConstraint* cge = dynamic_cast<ConstraintTypes::MatchingConstraint*>( child );
if ( cge ) {
constraintMatchTypes.insertMulti( cge->constraintMatchType(), i );
}
}
// remove the independent constraints from the hash
foreach( int key, constraintMatchTypes.uniqueKeys() ) {
QList<int> vals = constraintMatchTypes.values( key );
if ( vals.size() <= 1 ) {
constraintMatchTypes.remove( key );
}
}
return combineInterdependentConstraints( l, s, constraintMatchTypes );
}
quint32
ConstraintGroup::suggestPlaylistSize() const
{
quint32 s = 0;
quint32 c = 0;
foreach( ConstraintNode* child, m_children ) {
quint32 x = child->suggestPlaylistSize();
if ( x > 0 ) {
s += x;
c++;
}
}
if ( c > 0 ) {
return s / c;
} else {
return 0;
}
}
double
ConstraintGroup::combineInterdependentConstraints( const Meta::TrackList& l, const double s, const QHash<int,int>& cmt ) const
{
/* Handle interdependent constraints properly.
* See constraints/Matching.h for a description of why this is necessary. */
foreach( int key, cmt.uniqueKeys() ) {
QList<int> vals = cmt.values( key );
// set up the blank matching array
QBitArray m;
if ( m_matchtype == MatchAny ) {
m = QBitArray( l.size(), false );
} else {
m = QBitArray( l.size(), true );
}
// combine the arrays from the appropriate constraints
foreach( int v, vals ) {
ConstraintTypes::MatchingConstraint* cge = dynamic_cast<ConstraintTypes::MatchingConstraint*>( m_children[v] );
if ( m_matchtype == MatchAny ) {
m |= cge->whatTracksMatch( l );
} else if ( m_matchtype == MatchAll ) {
m &= cge->whatTracksMatch( l );
}
}
// turn the array into a satisfaction value
double chS = 0.0;
for ( int j = 0; j < l.size(); j++ ) {
if ( m.testBit( j ) ) {
chS += 1.0;
}
}
chS /= ( double )l.size();
// choose the appropriate satisfaction value
if ( m_matchtype == MatchAny ) {
return qMax( s, chS );
} else if ( m_matchtype == MatchAll ) {
return qMin( s, chS );
} else {
return s;
}
}
return s;
}
void
ConstraintGroup::setMatchAny()
{
m_matchtype = MatchAny;
emit dataChanged();
}
void
ConstraintGroup::setMatchAll()
{
m_matchtype = MatchAll;
emit dataChanged();
}
/******************************
* Edit Widget *
******************************/
ConstraintGroupEditWidget::ConstraintGroupEditWidget( const ConstraintGroup::MatchType t )
{
ui.setupUi( this );
QMetaObject::connectSlotsByName( this );
switch( t ) {
case ConstraintGroup::MatchAny:
ui.radioButton_MatchAny->setChecked( true );
break;
case ConstraintGroup::MatchAll:
ui.radioButton_MatchAll->setChecked( true );
break;
}
}
void
ConstraintGroupEditWidget::on_radioButton_MatchAll_clicked( bool c )
{
if ( c ) {
emit clickedMatchAll();
emit updated();
}
}
void
ConstraintGroupEditWidget::on_radioButton_MatchAny_clicked( bool c )
{
if ( c ) {
emit clickedMatchAny();
emit updated();
}
}
diff --git a/src/playlistgenerator/ConstraintSolver.cpp b/src/playlistgenerator/ConstraintSolver.cpp
index f8ec2e623a..4efbf8ed29 100644
--- a/src/playlistgenerator/ConstraintSolver.cpp
+++ b/src/playlistgenerator/ConstraintSolver.cpp
@@ -1,428 +1,432 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "APG::ConstraintSolver"
// WORKAROUND for QTBUG-25960. Required for Qt versions < 4.8.5 in combination with libc++.
#define QT_NO_STL 1
#include <qiterator.h>
#undef QT_NO_STL
#include "ConstraintSolver.h"
#include "Constraint.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "core/interfaces/Logger.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "playlist/PlaylistModel.h"
#include <KRandom>
#include <QHash>
#include <QMutexLocker>
#include <QStringList>
#include <QTimer>
#include <ThreadWeaver/ThreadWeaver>
#include <algorithm> // STL algorithms
#include <cmath>
#include <typeinfo>
const int APG::ConstraintSolver::QUALITY_RANGE = 10;
APG::ConstraintSolver::ConstraintSolver( ConstraintNode* r, int qualityFactor )
: QObject()
, ThreadWeaver::Job()
, m_satisfactionThreshold( 0.95 )
, m_finalSatisfaction( 0.0 )
, m_constraintTreeRoot( r )
, m_domainReductionFailed( false )
, m_readyToRun( false )
, m_abortRequested( false )
, m_maxGenerations( 100 )
, m_populationSize( 40 )
, m_suggestedPlaylistSize( 15 )
{
Q_UNUSED( qualityFactor); // FIXME
m_serialNumber = KRandom::random();
if ( !m_constraintTreeRoot ) {
error() << "No constraint tree was passed to the solver. Aborting.";
m_readyToRun = true;
m_abortRequested = true;
return;
}
m_qm = CollectionManager::instance()->queryMaker();
if ( m_qm ) {
debug() << "New ConstraintSolver with serial number" << m_serialNumber;
m_qm->setQueryType( Collections::QueryMaker::Track );
- connect( m_qm, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(receiveQueryMakerData(Meta::TrackList)), Qt::QueuedConnection );
- connect( m_qm, SIGNAL(queryDone()), this, SLOT(receiveQueryMakerDone()), Qt::QueuedConnection );
+ connect( m_qm, &Collections::QueryMaker::newTracksReady,
+ this, &APG::ConstraintSolver::receiveQueryMakerData, Qt::QueuedConnection );
+ connect( m_qm, &Collections::QueryMaker::queryDone,
+ this, &APG::ConstraintSolver::receiveQueryMakerDone, Qt::QueuedConnection );
m_constraintTreeRoot->initQueryMaker( m_qm );
m_qm->run();
} else {
debug() << "The ConstraintSolver could not find any queryable collections. No results will be returned.";
m_readyToRun = true;
m_abortRequested = true;
}
}
APG::ConstraintSolver::~ConstraintSolver()
{
if ( m_qm ) {
m_qm->abortQuery();
m_qm->deleteLater();
m_qm = 0;
}
}
Meta::TrackList
APG::ConstraintSolver::getSolution() const
{
return m_solvedPlaylist;
}
bool
APG::ConstraintSolver::satisfied() const
{
return m_finalSatisfaction > m_satisfactionThreshold;
}
bool
APG::ConstraintSolver::canBeExecuted()
{
/* This is a hopefully superfluous check to ensure that the Solver job
* doesn't get run until it's ready (ie, when QueryMaker has finished).
* This shouldn't ever return false, because hopefully the job won't even
* get queued until it's ready to run. See the comments in
* Preset::queueSolver() for more information. -- sth */
return m_readyToRun;
}
void
APG::ConstraintSolver::requestAbort()
{
if ( m_qm ) {
m_qm->abortQuery();
m_qm->deleteLater();
m_qm = 0;
}
m_abortRequested = true;
}
bool
APG::ConstraintSolver::success() const
{
return !m_abortRequested;
}
void
APG::ConstraintSolver::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
if ( !m_readyToRun ) {
error() << "DANGER WILL ROBINSON! A ConstraintSolver (serial no:" << m_serialNumber << ") tried to run before its QueryMaker finished!";
m_abortRequested = true;
return;
}
if ( m_domain.empty() ) {
debug() << "The QueryMaker returned no tracks";
return;
} else {
debug() << "Domain has" << m_domain.size() << "tracks";
}
debug() << "Running ConstraintSolver" << m_serialNumber;
emit totalSteps( m_maxGenerations );
// GENETIC ALGORITHM LOOP
Population population;
quint32 generation = 0;
Meta::TrackList* best = NULL;
while ( !m_abortRequested && ( generation < m_maxGenerations ) ) {
quint32 s = m_constraintTreeRoot->suggestPlaylistSize();
m_suggestedPlaylistSize = (s > 0) ? s : m_suggestedPlaylistSize;
fill_population( population );
best = find_best( population );
if ( population.value( best ) < m_satisfactionThreshold ) {
select_population( population, best );
mutate_population( population );
generation++;
emit incrementProgress();
} else {
break;
}
}
debug() << "solution at" << (void*)(best);
m_solvedPlaylist = best->mid( 0 );
m_finalSatisfaction = m_constraintTreeRoot->satisfaction( m_solvedPlaylist );
/* clean up */
Population::iterator it = population.begin();
while ( it != population.end() ) {
delete it.key();
it = population.erase( it );
}
emit endProgressOperation( this );
}
void APG::ConstraintSolver::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void APG::ConstraintSolver::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
APG::ConstraintSolver::receiveQueryMakerData( Meta::TrackList results )
{
m_domainMutex.lock();
m_domain += results;
m_domainMutex.unlock();
}
void
APG::ConstraintSolver::receiveQueryMakerDone()
{
m_qm->deleteLater();
m_qm = 0;
if (( m_domain.size() > 0 ) || m_domainReductionFailed ) {
if ( m_domain.size() <= 0 ) {
Amarok::Components::logger()->shortMessage( i18n("The playlist generator failed to load any tracks from the collection.") );
}
m_readyToRun = true;
emit readyToRun();
} else {
Amarok::Components::logger()->longMessage(
i18n("There are no tracks that match all constraints. " \
"The playlist generator will find the tracks that match best, " \
"but you may want to consider loosening the constraints to find more tracks.") );
m_domainReductionFailed = true;
// need a new query maker without constraints
m_qm = CollectionManager::instance()->queryMaker();
if ( m_qm ) {
- connect( m_qm, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(receiveQueryMakerData(Meta::TrackList)), Qt::QueuedConnection );
- connect( m_qm, SIGNAL(queryDone()), this, SLOT(receiveQueryMakerDone()), Qt::QueuedConnection );
+ connect( m_qm, &Collections::QueryMaker::newTracksReady,
+ this, &APG::ConstraintSolver::receiveQueryMakerData, Qt::QueuedConnection );
+ connect( m_qm, &Collections::QueryMaker::queryDone,
+ this, &APG::ConstraintSolver::receiveQueryMakerDone, Qt::QueuedConnection );
m_qm->setQueryType( Collections::QueryMaker::Track );
m_qm->run();
}
}
}
void
APG::ConstraintSolver::fill_population( Population& population )
{
for ( int i = population.size(); quint32(i) < m_populationSize; i++ ) {
Meta::TrackList* tl = new Meta::TrackList( sample( m_domain, playlist_size() ) );
double s = m_constraintTreeRoot->satisfaction( (*tl) );
population.insert( tl, s );
}
}
Meta::TrackList* APG::ConstraintSolver::find_best(const APG::ConstraintSolver::Population& population ) const
{
Population::const_iterator it = std::max_element( population.constBegin(), population.constEnd(), &pop_comp );
return it.key();
}
void
APG::ConstraintSolver::select_population( APG::ConstraintSolver::Population& population, Meta::TrackList* best )
{
Population::Iterator it = population.begin();
while ( it != population.end() ) {
if ( it.key() == best ) {
++it;// Always keep the best solution, no matter how bad it is
if ( it == population.end() )
break;
}
if ( select( it.value() ) ) {
++it;
} else {
delete it.key();
it = population.erase( it );
}
}
}
void
APG::ConstraintSolver::mutate_population( APG::ConstraintSolver::Population& population )
{
if ( population.size() < 1 )
return;
const double mutantPercentage = 0.35; // TODO: tune this parameter
QList<Meta::TrackList*> parents( population.keys() );
int maxMutants = (int)( mutantPercentage * (double)(m_populationSize) );
for ( int i = parents.size(); i < maxMutants; i++ ) {
int idx = KRandom::random() % parents.size();
Meta::TrackList* child = new Meta::TrackList( *(parents.at( idx )) );
int op = KRandom::random() % 5;
int s = child->size();
switch (op) {
case 0:
child->removeAt( KRandom::random() % s );
case 1:
child->insert( KRandom::random() % ( s + 1 ), random_track_from_domain() );
case 2:
child->replace( KRandom::random() % s, random_track_from_domain() );
case 3:
child->swap( KRandom::random() % s, KRandom::random() % s );
case 4:
child = crossover( child, parents.at( KRandom::random() % parents.size() ) );
default:
(void)0; // effectively a no-op. the default is here so that the compiler doesn't complain about missing default in switch
}
population.insert( child, m_constraintTreeRoot->satisfaction( *child ) );
}
return;
}
Meta::TrackList*
APG::ConstraintSolver::crossover( Meta::TrackList* top, Meta::TrackList* bot ) const
{
const double crossoverPt = 0.5; // TODO: choose different values
int topV = (int)( crossoverPt * (double)top->size() );
int botV = (int)( crossoverPt * (double)bot->size() );
Meta::TrackList* newlist = new Meta::TrackList( top->mid( 0, topV ) );
newlist->append( bot->mid( botV ) );
delete top;
return newlist;
}
bool
APG::ConstraintSolver::pop_comp( double a, double b )
{
return ( a < b );
}
Meta::TrackPtr
APG::ConstraintSolver::random_track_from_domain() const
{
return m_domain.at( KRandom::random() % m_domain.size() );
}
Meta::TrackList
APG::ConstraintSolver::sample( Meta::TrackList domain, const int sampleSize ) const
{
std::random_shuffle( domain.begin(), domain.end() );
return domain.mid( 0, sampleSize );
}
quint32
APG::ConstraintSolver::playlist_size() const
{
return rng_poisson( (double)m_suggestedPlaylistSize );
}
bool
APG::ConstraintSolver::select( const double satisfaction ) const
{
double x = (double)KRandom::random()/(double)RAND_MAX;
const double scale = -30.0; // TODO: make adjustable
return ( x < 1.0 / ( 1.0 + exp( scale * (satisfaction-0.8) ) ) );
}
void
APG::ConstraintSolver::dump_population( const Population& population ) const
{
DEBUG_BLOCK
for ( Population::ConstIterator it = population.constBegin(); it != population.constEnd(); ++it ) {
Meta::TrackList* tl = it.key();
debug() << "at" << (void*)(tl) << "satisfaction:" << it.value();
foreach ( Meta::TrackPtr t, (*tl) ) {
debug() << "\ttrack:" << t->prettyName();
}
}
}
double
APG::ConstraintSolver::rng_gaussian( const double mu, const double sigma ) const
{
/* adapted from randist/gauss.c in GNU Scientific Library 1.14 */
double u, v, x, y, Q;
const double s = 0.449871;
const double t = -0.386595;
const double a = 0.19600;
const double b = 0.25472;
const double r1 = 0.27597;
const double r2 = 0.27846;
do {
u = 1 - rng_uniform();
v = ( rng_uniform() - 0.5 ) * 1.7156;
x = u - s;
y = fabs (v) - t;
Q = x * x + y * (a * y - b * x);
} while (Q >= r1 && (Q > r2 || v * v > -4 * u * u * log (u)));
return mu + ( sigma * (v / u) );
}
quint32
APG::ConstraintSolver::rng_poisson( const double mu ) const
{
if ( mu >= 25.0 ) {
double v = rng_gaussian( mu, sqrt( mu ) );
return ( v < 0.0 ) ? 0 : (quint32)v;
}
const double emu = exp( -mu );
double prod = 1.0;
quint32 k = 0;
do {
prod *= rng_uniform();
k++;
}
while ( prod > emu );
return k - 1;
}
double
APG::ConstraintSolver::rng_uniform() const
{
return ( (double)KRandom::random() / (double)(RAND_MAX) );
}
diff --git a/src/playlistgenerator/Preset.cpp b/src/playlistgenerator/Preset.cpp
index eea0c278ea..c28bede480 100644
--- a/src/playlistgenerator/Preset.cpp
+++ b/src/playlistgenerator/Preset.cpp
@@ -1,164 +1,164 @@
/****************************************************************************************
* Copyright (c) 2008-2010 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "APG::Preset"
#include "Preset.h"
#include "ConstraintNode.h"
#include "ConstraintFactory.h"
#include "ConstraintSolver.h"
#include "constraints/TrackSpreader.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "playlist/PlaylistController.h"
#include <KLocale>
#include <QDomElement>
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/QObjectDecorator>
APG::PresetPtr
APG::Preset::createFromXml( QDomElement& xmlelem )
{
DEBUG_BLOCK
if ( xmlelem.isNull() ) {
PresetPtr t( new Preset( i18n("New playlist preset") ) );
return t;
} else {
PresetPtr t( new Preset( i18n("Unnamed playlist preset"), xmlelem ) );
return t;
}
}
APG::PresetPtr
APG::Preset::createNew()
{
DEBUG_BLOCK
PresetPtr t( new Preset( i18n("New playlist preset") ) );
return t;
}
APG::Preset::Preset( const QString& title, QDomElement& xmlelem )
: m_title( title )
, m_constraintTreeRoot( 0 )
{
if ( xmlelem.hasAttribute( "title" ) ) {
m_title = xmlelem.attribute( "title" );
} else {
m_title = i18n("Unnamed playlist preset");
}
for ( int i = 0; i < xmlelem.childNodes().count(); i++ ) {
QDomElement childXmlElem = xmlelem.childNodes().item( i ).toElement();
if ( !childXmlElem.isNull() ) {
if ( childXmlElem.tagName() == "constrainttree" ) {
m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( childXmlElem, 0 );
} else {
error() << "unknown child: " << childXmlElem.nodeName();
}
}
}
if ( !m_constraintTreeRoot ) {
m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( 0 );
}
}
APG::Preset::Preset( const QString& title )
: m_title( title )
{
m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( 0 );
}
APG::Preset::~Preset()
{
delete m_constraintTreeRoot;
}
QDomElement*
APG::Preset::toXml( QDomDocument& xmldoc ) const
{
QDomElement e = xmldoc.createElement( "generatorpreset" );
e.setAttribute( "title", m_title );
m_constraintTreeRoot->toXml( xmldoc, e );
return new QDomElement( e );
}
void
APG::Preset::generate( int q )
{
ConstraintSolver* solver = new ConstraintSolver( m_constraintTreeRoot, q );
- connect( solver, SIGNAL(readyToRun()), this, SLOT(queueSolver()) );
+ connect( solver, &ConstraintSolver::readyToRun, this, &Preset::queueSolver );
}
void APG::Preset::queueSolver() {
/* Workaround for a design quirk of Weavers. A Weaver will not
* continuously poll queued but previously unrunnable jobs to see if they
* are are runnable (and won't start them if they are), so what tends to
* happen is that if a job is queued before it's ready to run, it fails the
* canBeExecuted() check, and sits in the queue until something wakes up
* the Weaver (eg, queueing another job). Eventually, you get a pileup of
* obsolete jobs in the queue, and those that do run properly return
* obsolete results. So to avoid that problem, we avoid queueing the job
* until it's ready to run, and then the Weaver will start running it
* pretty much immediately. -- sth */
emit lock( true );
ConstraintSolver* s = static_cast<ConstraintSolver*>( sender() );
Amarok::Components::logger()->newProgressOperation( s, i18n("Generating a new playlist"), s->iterationCount(), s, SLOT(requestAbort()), Qt::QueuedConnection );
- connect( s, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(solverFinished(ThreadWeaver::JobPointer)), Qt::QueuedConnection );
+ connect( s, &APG::ConstraintSolver::done, this, &Preset::solverFinished, Qt::QueuedConnection );
m_constraintTreeRoot->addChild( ConstraintTypes::TrackSpreader::createNew( m_constraintTreeRoot ), 0 ); // private mandatory constraint
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(s) );
}
void
APG::Preset::solverFinished( ThreadWeaver::JobPointer job )
{
m_constraintTreeRoot->removeChild( 0 ); // remove the TrackSpreader
ConstraintSolver* solver = static_cast<ConstraintSolver*>( job.data() );
if ( job->success() ) {
debug() << "Solver" << solver->serial() << "finished successfully";
if ( !solver->satisfied() ) {
Amarok::Components::logger()->longMessage(
i18n("The playlist generator created a playlist which does not meet all "
"of your constraints. If you are not satisfied with the results, "
"try loosening or removing some constraints and then generating a "
"new playlist.") );
}
The::playlistController()->insertOptioned( solver->getSolution(), Playlist::OnReplacePlaylistAction );
} else {
debug() << "Ignoring results from aborted Solver" << solver->serial();
}
ThreadWeaver::QObjectDecorator *qs = new ThreadWeaver::QObjectDecorator(job.data());
qs->deleteLater();
emit lock( false );
}
diff --git a/src/playlistgenerator/PresetEditDialog.cpp b/src/playlistgenerator/PresetEditDialog.cpp
index ff2d9c7b2e..99b676d7a8 100644
--- a/src/playlistgenerator/PresetEditDialog.cpp
+++ b/src/playlistgenerator/PresetEditDialog.cpp
@@ -1,137 +1,138 @@
/****************************************************************************************
* Copyright (c) 2008-2010 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "APG::PresetEditDialog"
#include "PresetEditDialog.h"
#include "ConstraintNode.h"
#include "ConstraintFactory.h"
#include "TreeController.h"
#include "TreeModel.h"
#include "core/support/Debug.h"
#include <QMenu>
#include <QMenuBar>
#include <QSignalMapper>
#include <QWhatsThis>
APG::PresetEditDialog::PresetEditDialog( PresetPtr p )
: QDialog( 0 )
, m_preset( p )
{
DEBUG_BLOCK
ui.setupUi( this );
TreeModel* model = new TreeModel( m_preset->constraintTreeRoot(), this );
m_controller = new TreeController( model, ui.constraintTreeView, this );
ui.lineEdit_Title->setText( m_preset->title() );
ui.constraintTreeView->setModel( model );
ui.constraintTreeView->setSelectionMode( QAbstractItemView::SingleSelection );
ui.constraintTreeView->setHeaderHidden( true );
- connect( ui.constraintTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
- this, SLOT(currentNodeChanged(QModelIndex)) );
+ connect( ui.constraintTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
+ this, &PresetEditDialog::currentNodeChanged );
ui.constraintTreeView->setCurrentIndex( model->index( 0, 0 ) ); // select the visible root constraint
ui.constraintTreeView->expandAll();
QSignalMapper* adderMapper = new QSignalMapper( this );
- connect( adderMapper, SIGNAL(mapped(QString)), this, SLOT(addNode(QString)) );
+ connect( adderMapper, QOverload<const QString&>::of(&QSignalMapper::mapped),
+ this, &PresetEditDialog::addNode );
QMenuBar* menuBar_Actions = new QMenuBar( this );
menuBar_Actions->setNativeMenuBar( false ); //required to make the menu work on OS X
QAction* a;
QMenu* m = new QMenu( i18n("Add new"), this );
a = m->addAction( QString( i18n("Constraint Group") ) );
- connect( a, SIGNAL(triggered(bool)), adderMapper, SLOT(map()) );
+ connect( a, &QAction::triggered, adderMapper, QOverload<>::of(&QSignalMapper::map) );
adderMapper->setMapping( a, i18n("Constraint Group") );
foreach( const QString& name, ConstraintFactory::instance()->i18nNames() ) {
a = m->addAction( name );
- connect( a, SIGNAL(triggered(bool)), adderMapper, SLOT(map()) );
+ connect( a, &QAction::triggered, adderMapper, QOverload<>::of(&QSignalMapper::map) );
adderMapper->setMapping( a, name );
}
menuBar_Actions->addMenu( m );
a = menuBar_Actions->addAction( i18n("Remove selected") );
- connect( a, SIGNAL(triggered(bool)), m_controller, SLOT(removeNode()) );
+ connect( a, &QAction::triggered, m_controller, &APG::TreeController::removeNode );
menuBar_Actions->addSeparator();
a = QWhatsThis::createAction( this );
a->setIcon( QIcon() );
menuBar_Actions->addAction( a );
ui.treeLayout->insertWidget( 0, menuBar_Actions );
- connect( ui.buttonBox, SIGNAL(accepted()), this, SLOT(accept()) );
- connect( ui.buttonBox, SIGNAL(rejected()), this, SLOT(reject()) );
+ connect( ui.buttonBox, &QDialogButtonBox::accepted, this, &APG::PresetEditDialog::accept );
+ connect( ui.buttonBox, &QDialogButtonBox::rejected, this, &APG::PresetEditDialog::reject );
QMetaObject::connectSlotsByName( this );
}
void
APG::PresetEditDialog::addNode( const QString& name )
{
debug() << "Adding new" << name;
if ( name == i18n("Constraint Group") ) {
m_controller->addGroup();
} else {
m_controller->addConstraint( name );
}
}
void
APG::PresetEditDialog::removeNode()
{
debug() << "Removing selected node";
m_controller->removeNode();
}
void
APG::PresetEditDialog::currentNodeChanged( const QModelIndex& index )
{
if( index.isValid() )
{
ConstraintNode* n = static_cast<ConstraintNode*>( index.internalPointer() );
if ( !m_widgetStackPages.contains( n ) ) {
debug() << "Inserting new constraint edit widget into the stack";
QWidget* w = n->editWidget();
m_widgetStackPages.insert( n, ui.stackedWidget_Editors->addWidget( w ) );
}
ui.stackedWidget_Editors->setCurrentIndex( m_widgetStackPages.value( n ) );
}
}
void
APG::PresetEditDialog::accept()
{
QDialog::accept();
}
void
APG::PresetEditDialog::reject()
{
QDialog::reject();
}
void
APG::PresetEditDialog::on_lineEdit_Title_textChanged( const QString& t )
{
m_preset->setTitle( t );
}
diff --git a/src/playlistgenerator/PresetModel.cpp b/src/playlistgenerator/PresetModel.cpp
index e9c7a6f971..7f31da84c7 100644
--- a/src/playlistgenerator/PresetModel.cpp
+++ b/src/playlistgenerator/PresetModel.cpp
@@ -1,375 +1,374 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "APG::PresetModel"
#include "PresetModel.h"
#include "amarokconfig.h"
#include "core/interfaces/Logger.h"
#include "core/collections/Collection.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "playlistgenerator/Preset.h"
#include "playlistgenerator/PresetEditDialog.h"
#include <KFileDialog>
#include <QUrl>
#include <QAbstractItemModel>
#include <QDesktopServices>
#include <QDialog>
#include <QDomDocument>
#include <QDomElement>
#include <QFile>
#include <QList>
#include <QVariant>
APG::PresetModel* APG::PresetModel::s_instance = 0;
APG::PresetModel* APG::PresetModel::instance()
{
if ( s_instance == 0 ) {
s_instance = new PresetModel();
}
return s_instance;
}
void
APG::PresetModel::destroy()
{
s_instance->savePresetsToXml( Amarok::saveLocation() + "playlistgenerator.xml", s_instance->m_presetList );
delete s_instance;
s_instance = 0;
}
APG::PresetModel::PresetModel()
: QAbstractListModel()
, m_activePresetIndex( 0 )
{
loadPresetsFromXml( Amarok::saveLocation() + "playlistgenerator.xml", true );
}
APG::PresetModel::~PresetModel()
{
while ( m_presetList.size() > 0 ) {
m_presetList.takeFirst()->deleteLater();
}
}
QVariant
APG::PresetModel::data( const QModelIndex& idx, int role ) const
{
if ( !idx.isValid() )
return QVariant();
if ( idx.row() >= m_presetList.size() )
return QVariant();
APG::PresetPtr item = m_presetList.at( idx.row() );
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return item->title();
break;
default:
return QVariant();
}
return QVariant();
}
QModelIndex
APG::PresetModel::index( int row, int column, const QModelIndex& ) const
{
if ( rowCount() <= row )
return QModelIndex();
return createIndex( row, column);
}
int
APG::PresetModel::rowCount( const QModelIndex& ) const
{
return m_presetList.size();
}
APG::PresetPtr
APG::PresetModel::activePreset() const
{
if ( m_activePresetIndex && m_activePresetIndex->isValid() )
return m_presetList.at( m_activePresetIndex->row() );
else
return APG::PresetPtr();
}
void
APG::PresetModel::addNew()
{
insertPreset( APG::Preset::createNew() );
}
void
APG::PresetModel::edit()
{
editPreset( createIndex( m_activePresetIndex->row(), 0 ) );
}
void
APG::PresetModel::editPreset( const QModelIndex& index )
{
// TODO: possible enhancement: instead of using a modal dialog, use a QMap that allows
// only one dialog per preset to be open at once
PresetPtr ps = m_presetList.at( index.row() );
QDialog* d = new PresetEditDialog( ps );
d->exec();
}
void
APG::PresetModel::exportActive()
{
- KFileDialog* d = new ExportDialog( activePreset() );
- connect( d, SIGNAL(pleaseExport(QString,QList<APG::PresetPtr>)),
- this, SLOT(savePresetsToXml(QString,QList<APG::PresetPtr>)) );
+ auto d = new ExportDialog( activePreset() );
+ connect( d, &ExportDialog::pleaseExport, this, &PresetModel::savePresetsToXml );
d->exec();
}
void
APG::PresetModel::import()
{
QString filename = KFileDialog::getOpenFileName( QUrl( QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ),
QString("*.xml|" + i18n("Preset files (*.xml)") ),
0,
i18n("Import preset") );
if( !filename.isEmpty() )
loadPresetsFromXml( filename );
}
void
APG::PresetModel::removeActive()
{
if ( m_presetList.size() < 1 )
return;
if ( m_activePresetIndex && m_activePresetIndex->isValid() ) {
int row = m_activePresetIndex->row();
beginRemoveRows( QModelIndex(), row, row );
APG::PresetPtr p = m_presetList.takeAt( row );
p->deleteLater();
endRemoveRows();
}
}
void
APG::PresetModel::runGenerator( int q )
{
activePreset()->generate( q );
}
void
APG::PresetModel::setActivePreset( const QModelIndex& index )
{
if ( m_activePresetIndex )
delete m_activePresetIndex;
m_activePresetIndex = new QPersistentModelIndex( index );
}
void
-APG::PresetModel::savePresetsToXml() const
+APG::PresetModel::savePresetsToXmlDefault() const
{
savePresetsToXml( Amarok::saveLocation() + "playlistgenerator.xml", m_presetList );
}
void
APG::PresetModel::savePresetsToXml( const QString& filename, const QList<APG::PresetPtr> &pl ) const
{
QDomDocument xmldoc;
QDomElement base = xmldoc.createElement( "playlistgenerator" );
QList<QDomNode*> nodes;
foreach ( APG::PresetPtr ps, pl ) {
QDomElement* elemPtr = ps->toXml( xmldoc );
base.appendChild( (*elemPtr) );
nodes << elemPtr;
}
xmldoc.appendChild( base );
QFile file( filename );
if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) ) {
QTextStream out( &file );
out.setCodec( "UTF-8" );
xmldoc.save( out, 2, QDomNode::EncodingFromTextStream );
if( !filename.contains( "playlistgenerator.xml" ) )
{
Amarok::Components::logger()->longMessage( i18n("Preset exported to %1", filename),
Amarok::Logger::Information );
}
}
else
{
Amarok::Components::logger()->longMessage(
i18n("Preset could not be exported to %1", filename), Amarok::Logger::Error );
error() << "Can not write presets to " << filename;
}
qDeleteAll( nodes );
}
void
APG::PresetModel::loadPresetsFromXml( const QString& filename, bool createDefaults )
{
QFile file( filename );
if ( file.open( QIODevice::ReadOnly ) ) {
QDomDocument document;
if ( document.setContent( &file ) ) {
debug() << "Reading presets from" << filename;
parseXmlToPresets( document );
} else {
error() << "Failed to read" << filename;
Amarok::Components::logger()->longMessage(
i18n("Presets could not be imported from %1", filename),
Amarok::Logger::Error );
}
file.close();
} else {
if ( !createDefaults ) {
Amarok::Components::logger()->longMessage(
i18n("%1 could not be opened for preset import", filename),
Amarok::Logger::Error );
} else {
QDomDocument document;
QString translatedPresetExamples( presetExamples.arg(
i18n("Example 1: new tracks added this week") ).arg(
i18n("Example 2: rock or pop music") ).arg(
i18n("Example 3: about one hour of tracks from different artists") ).arg(
i18n("Example 4: like my favorite radio station") ).arg(
i18n("Example 5: an 80-minute CD of rock, metal, and industrial") ) );
document.setContent( translatedPresetExamples );
debug() << "Reading built-in example presets";
parseXmlToPresets( document );
}
error() << "Can not open" << filename;
}
}
void
APG::PresetModel::insertPreset( APG::PresetPtr ps )
{
if ( ps ) {
int row = m_presetList.size();
beginInsertRows( QModelIndex(), row, row );
m_presetList.append( ps );
endInsertRows();
connect( ps.data(), SIGNAL(lock(bool)), this, SIGNAL(lock(bool)) );
}
}
void
APG::PresetModel::parseXmlToPresets( QDomDocument& document )
{
QDomElement rootelem = document.documentElement();
for ( int i = 0; i < rootelem.childNodes().count(); i++ ) {
QDomElement e = rootelem.childNodes().at( i ).toElement();
if ( e.tagName() == "generatorpreset" ) {
debug() << "creating a new generator preset";
insertPreset( APG::Preset::createFromXml( e ) );
} else {
debug() << "Don't know what to do with tag: " << e.tagName();
}
}
}
/*
* ExportDialog nested class
*/
APG::PresetModel::ExportDialog::ExportDialog( APG::PresetPtr ps )
: KFileDialog( QUrl( QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ),
QString("*.xml|" + i18n("Preset files (*.xml)") ),
0 )
{
m_presetsToExportList.append( ps );
setMode( KFile::File );
setSelection( ps->title() + ".xml" );
setOperationMode( KFileDialog::Saving );
setKeepLocation( true );
QWidget::setWindowTitle( i18n( "Export \"%1\" preset", ps->title() ) );
- connect( this, SIGNAL(okClicked()), this, SLOT(recvAccept()) );
+ connect( this, &ExportDialog::accept, this, &ExportDialog::recvAccept );
}
APG::PresetModel::ExportDialog::~ExportDialog() {}
void
APG::PresetModel::ExportDialog::recvAccept() const
{
emit pleaseExport( selectedFile(), m_presetsToExportList );
}
const QString APG::PresetModel::presetExamples =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<playlistgenerator>"
" <generatorpreset title=\"%1\">"
" <constrainttree>"
" <group matchtype=\"all\">"
" <constraint field=\"create date\" comparison=\"3\" invert=\"false\" type=\"TagMatch\" value=\"7 days\" strictness=\"0.8\"/>"
" <constraint field=\"play count\" comparison=\"1\" invert=\"false\" type=\"TagMatch\" value=\"\" strictness=\"1\"/>"
" </group>"
" </constrainttree>"
" </generatorpreset>"
" <generatorpreset title=\"%2\">"
" <constrainttree>"
" <group matchtype=\"any\">"
" <constraint field=\"genre\" comparison=\"3\" invert=\"false\" type=\"TagMatch\" value=\"Rock\" strictness=\"1\"/>"
" <constraint field=\"genre\" comparison=\"3\" invert=\"false\" type=\"TagMatch\" value=\"Pop\" strictness=\"1\"/>"
" </group>"
" </constrainttree>"
" </generatorpreset>"
" <generatorpreset title=\"%3\">"
" <constrainttree>"
" <group matchtype=\"all\">"
" <constraint comparison=\"1\" duration=\"3600000\" type=\"PlaylistDuration\" strictness=\"0.3\"/>"
" <constraint field=\"2\" type=\"PreventDuplicates\"/>"
" </group>"
" </constrainttree>"
" </generatorpreset>"
" <generatorpreset title=\"%4\">"
" <constrainttree>"
" <group matchtype=\"all\">"
" <constraint field=\"0\" type=\"PreventDuplicates\"/>"
" <constraint field=\"last played\" comparison=\"3\" invert=\"true\" type=\"TagMatch\" value=\"7 days\" strictness=\"0.4\"/>"
" <constraint field=\"rating\" comparison=\"2\" invert=\"false\" type=\"TagMatch\" value=\"6\" strictness=\"1\"/>"
" <constraint comparison=\"1\" duration=\"10800000\" type=\"PlaylistDuration\" strictness=\"0.3\"/>"
" </group>"
" </constrainttree>"
" </generatorpreset>"
" <generatorpreset title=\"%5\">"
" <constrainttree>"
" <group matchtype=\"all\">"
" <group matchtype=\"any\">"
" <constraint field=\"genre\" comparison=\"3\" invert=\"false\" type=\"TagMatch\" value=\"Rock\" strictness=\"1\"/>"
" <constraint field=\"genre\" comparison=\"3\" invert=\"false\" type=\"TagMatch\" value=\"Metal\" strictness=\"1\"/>"
" <constraint field=\"genre\" comparison=\"3\" invert=\"false\" type=\"TagMatch\" value=\"Industrial\" strictness=\"1\"/>"
" </group>"
" <group matchtype=\"all\">"
" <constraint comparison=\"2\" duration=\"4500000\" type=\"PlaylistDuration\" strictness=\"0.4\"/>"
" <constraint comparison=\"0\" duration=\"4800000\" type=\"PlaylistDuration\" strictness=\"1\"/>"
" </group>"
" </group>"
" </constrainttree>"
" </generatorpreset>"
"</playlistgenerator>";
diff --git a/src/playlistgenerator/PresetModel.h b/src/playlistgenerator/PresetModel.h
index 3fa78c3534..bcd49d7c73 100644
--- a/src/playlistgenerator/PresetModel.h
+++ b/src/playlistgenerator/PresetModel.h
@@ -1,101 +1,101 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef APG_PRESET_MODEL
#define APG_PRESET_MODEL
#include "Preset.h"
#include <KFileDialog>
#include <QAbstractItemModel>
#include <QList>
#include <QString>
class ConstraintModel;
class QPersistentModelIndex;
namespace APG {
class PresetModel : public QAbstractListModel {
Q_OBJECT
public:
static PresetModel* instance();
static void destroy();
static const QString presetExamples; // holds a hard-coded set of example presets
// that are loaded the first time the
// user opens the APG
// Overloaded QAbstractListModel methods
QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const;
QModelIndex index( int, int, const QModelIndex& parent = QModelIndex() ) const;
QModelIndex parent( const QModelIndex& ) const { return QModelIndex(); }
int rowCount( const QModelIndex& parent = QModelIndex() ) const;
int columnCount( const QModelIndex& ) const { return 1; }
APG::PresetPtr activePreset() const;
Q_SIGNALS:
void lock( bool ); // disable the edit widgets if the solver is running
public Q_SLOTS:
void addNew();
void edit();
void editPreset( const QModelIndex& );
void exportActive();
void import();
void removeActive();
void runGenerator( int );
void setActivePreset( const QModelIndex& );
- void savePresetsToXml() const; // force saving to default location
+ void savePresetsToXmlDefault() const; // force saving to default location
private Q_SLOTS:
void savePresetsToXml( const QString&, const QList<APG::PresetPtr> & ) const;
void loadPresetsFromXml( const QString&, bool createDefaults = false );
private:
PresetModel();
~PresetModel();
static PresetModel* s_instance;
class ExportDialog;
void insertPreset(APG::PresetPtr);
void parseXmlToPresets( QDomDocument& );
QPersistentModelIndex* m_activePresetIndex;
QList<APG::PresetPtr> m_presetList;
}; // class PresetModel
class PresetModel::ExportDialog : public KFileDialog {
Q_OBJECT
public:
ExportDialog( APG::PresetPtr );
~ExportDialog();
Q_SIGNALS:
void pleaseExport( const QString&, const QList<APG::PresetPtr> ) const;
private Q_SLOTS:
void recvAccept() const;
private:
QList<APG::PresetPtr> m_presetsToExportList;
};
} //namespace APG
#endif // APG_PRESET_MODEL
diff --git a/src/playlistgenerator/constraints/Checkpoint.cpp b/src/playlistgenerator/constraints/Checkpoint.cpp
index dc49d61e8f..7e16073853 100644
--- a/src/playlistgenerator/constraints/Checkpoint.cpp
+++ b/src/playlistgenerator/constraints/Checkpoint.cpp
@@ -1,428 +1,428 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Constraint::Checkpoint"
#include "Checkpoint.h"
#include "playlistgenerator/Constraint.h"
#include "playlistgenerator/ConstraintFactory.h"
#include "core/meta/Meta.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <QUrl>
#include <algorithm>
#include <climits>
#include <math.h>
Constraint*
ConstraintTypes::Checkpoint::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
if ( p ) {
return new Checkpoint( xmlelem, p );
} else {
return 0;
}
}
Constraint*
ConstraintTypes::Checkpoint::createNew( ConstraintNode* p )
{
if ( p ) {
return new Checkpoint( p );
} else {
return 0;
}
}
ConstraintFactoryEntry*
ConstraintTypes::Checkpoint::registerMe()
{
return new ConstraintFactoryEntry( "Checkpoint",
i18n("Checkpoint"),
i18n("Fixes a track, album, or artist to a certain position in the playlist"),
&Checkpoint::createFromXml, &Checkpoint::createNew );
}
ConstraintTypes::Checkpoint::Checkpoint( QDomElement& xmlelem, ConstraintNode* p )
: Constraint( p )
, m_position( 0 )
, m_strictness( 1.0 )
, m_checkpointType( CheckpointTrack )
, m_matcher( 0 )
{
QDomAttr a;
a = xmlelem.attributeNode( "position" );
if ( !a.isNull() )
m_position = a.value().toInt();
a = xmlelem.attributeNode( "checkpointtype" );
if ( !a.isNull() )
m_checkpointType = static_cast<CheckpointType>( a.value().toInt() );
a = xmlelem.attributeNode( "trackurl" );
if ( !a.isNull() ) {
Meta::TrackPtr trk = CollectionManager::instance()->trackForUrl( QUrl( a.value() ) );
if ( trk ) {
if ( m_checkpointType == CheckpointAlbum ) {
m_checkpointObject = Meta::DataPtr::dynamicCast( trk->album() );
} else if ( m_checkpointType == CheckpointArtist ) {
m_checkpointObject = Meta::DataPtr::dynamicCast( trk->artist() );
} else {
m_checkpointObject = Meta::DataPtr::dynamicCast( trk );
}
}
}
setCheckpoint( m_checkpointObject );
a = xmlelem.attributeNode( "strictness" );
if ( !a.isNull() )
m_strictness = a.value().toDouble();
debug() << getName();
}
ConstraintTypes::Checkpoint::Checkpoint( ConstraintNode* p )
: Constraint( p )
, m_position( 0 )
, m_strictness( 1.0 )
, m_checkpointType( CheckpointTrack )
, m_matcher( 0 )
{
}
ConstraintTypes::Checkpoint::~Checkpoint()
{
delete m_matcher;
}
QWidget*
ConstraintTypes::Checkpoint::editWidget() const
{
CheckpointEditWidget* e = new CheckpointEditWidget( m_position, static_cast<int>( 10*m_strictness ), m_checkpointObject );
- connect( e, SIGNAL(positionChanged(int)), this, SLOT(setPosition(int)) );
- connect( e, SIGNAL(strictnessChanged(int)), this, SLOT(setStrictness(int)) );
- connect( e, SIGNAL(checkpointChanged(Meta::DataPtr)), this, SLOT(setCheckpoint(Meta::DataPtr)) );
+ connect( e, &CheckpointEditWidget::positionChanged, this, &Checkpoint::setPosition );
+ connect( e, &CheckpointEditWidget::strictnessChanged, this, &Checkpoint::setStrictness );
+ connect( e, &CheckpointEditWidget::checkpointChanged, this, &Checkpoint::setCheckpoint );
return e;
}
void
ConstraintTypes::Checkpoint::toXml( QDomDocument& doc, QDomElement& elem ) const
{
if( !m_checkpointObject )
return;
QDomElement c = doc.createElement( "constraint" );
QDomText t = doc.createTextNode( getName() );
c.appendChild( t );
c.setAttribute( "type", "Checkpoint" );
c.setAttribute( "position", m_position );
c.setAttribute( "checkpointtype", m_checkpointType );
Meta::TrackPtr r;
Meta::ArtistPtr a;
Meta::AlbumPtr l;
switch ( m_checkpointType ) {
case CheckpointTrack:
r = Meta::TrackPtr::dynamicCast( m_checkpointObject );
c.setAttribute( "trackurl", r->uidUrl() );
break;
case CheckpointAlbum:
l = Meta::AlbumPtr::dynamicCast( m_checkpointObject );
if ( l->tracks().length() > 0 ) {
r = l->tracks().first();
c.setAttribute( "trackurl", r->uidUrl() );
}
break;
case CheckpointArtist:
a = Meta::ArtistPtr::dynamicCast( m_checkpointObject );
if ( a->tracks().length() > 0 ) {
r = a->tracks().first();
c.setAttribute( "trackurl", r->uidUrl() );
}
break;
}
c.setAttribute( "strictness", QString::number( m_strictness ) );
elem.appendChild( c );
}
QString
ConstraintTypes::Checkpoint::getName() const
{
QString name( i18n("Checkpoint: %1") );
Meta::TrackPtr t;
Meta::AlbumPtr l;
Meta::ArtistPtr r;
switch ( m_checkpointType ) {
case CheckpointTrack:
t = Meta::TrackPtr::dynamicCast( m_checkpointObject );
if ( t == Meta::TrackPtr() ) {
name = name.arg( i18n("unassigned") );
} else {
name = name.arg( i18n("\"%1\" (track) by %2", t->prettyName(), t->artist()->prettyName() ) );
}
break;
case CheckpointAlbum:
l = Meta::AlbumPtr::dynamicCast( m_checkpointObject );
if ( l == Meta::AlbumPtr() ) {
name = name.arg( i18n("unassigned") );
} else {
if ( l->hasAlbumArtist() ) {
name = name.arg( i18n("\"%1\" (album) by %2", l->prettyName(), l->albumArtist()->prettyName() ) );
} else {
name = name.arg( i18n("\"%1\" (album)", l->prettyName() ) );
}
}
break;
case CheckpointArtist:
r = Meta::ArtistPtr::dynamicCast( m_checkpointObject );
if ( r == Meta::ArtistPtr() ) {
name = name.arg( i18n("unassigned") );
} else {
name = name.arg( i18n("\"%1\" (artist)", r->prettyName() ) );
}
break;
}
return name;
}
double
ConstraintTypes::Checkpoint::satisfaction( const Meta::TrackList& tl ) const
{
// What are the ending time boundaries of each track in this playlist?
qint64 start = 0;
QList< qint64 > boundaries;
foreach ( const Meta::TrackPtr t, tl ) {
boundaries << ( start += t->length() );
}
// Is the playlist long enough to contain the checkpoint?
if ( boundaries.last() < m_position ) {
return 0.0; // no, it does not
}
// Where are the appropriate tracks in this playlist?
QList<int> locs = m_matcher->find( tl );
if ( locs.size() < 1 ) {
return 0.0; // none found
} else {
qint64 best = boundaries.last(); // the length of the playlist is the upper bound for distances
foreach ( int i, locs ) {
qint64 start = ( i>0 )?boundaries.at( i-1 ):0;
qint64 end = boundaries.at( i );
if ( (start <= m_position) && ( end >= m_position ) ) {
// checkpoint position has a match flanking it
return 1.0;
} else if ( end < m_position ) {
// appropriate track is before the checkpoint
best = (best < (m_position - end))?best:(m_position - end);
} else if ( start > m_position ) {
// appropriate track is after the checkpoint
best = (best < (start - m_position))?best:(start - m_position);
} else {
warning() << "WTF JUST HAPPENED?" << m_position << "(" << start << "," << end << ")";
}
}
return penalty( best );
}
warning() << "Improper exit condition";
return 0.0;
}
double
ConstraintTypes::Checkpoint::penalty( const qint64 d ) const
{
return exp( d / ( -( 120000.0 * ( 1.0 + ( 8.0 * m_strictness ) ) ) ) );
}
quint32
ConstraintTypes::Checkpoint::suggestPlaylistSize() const
{
return static_cast<quint32>( m_position / 300000 ) + 1;
}
void
ConstraintTypes::Checkpoint::setPosition( const int v )
{
m_position = static_cast<qint64>( v );
}
void
ConstraintTypes::Checkpoint::setStrictness( const int sv )
{
m_strictness = static_cast<double>(sv)/10.0;
}
void
ConstraintTypes::Checkpoint::setCheckpoint( const Meta::DataPtr& data )
{
if ( data == Meta::DataPtr() )
return;
delete m_matcher;
if ( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( data ) ) {
m_checkpointType = CheckpointTrack;
m_matcher = new TrackMatcher( track );
debug() << "setting checkpoint track:" << track->prettyName();
} else if ( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( data ) ) {
m_checkpointType = CheckpointAlbum;
m_matcher = new AlbumMatcher( album );
debug() << "setting checkpoint album:" << album->prettyName();
} else if ( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( data ) ) {
debug() << "setting checkpoint artist:" << artist->prettyName();
m_matcher = new ArtistMatcher( artist );
m_checkpointType = CheckpointArtist;
}
m_checkpointObject = data;
emit dataChanged();
}
/******************************
* Track Matcher *
******************************/
ConstraintTypes::Checkpoint::TrackMatcher::TrackMatcher( const Meta::TrackPtr& t )
: m_trackToMatch( t )
{
}
ConstraintTypes::Checkpoint::TrackMatcher::~TrackMatcher()
{
}
QList<int>
ConstraintTypes::Checkpoint::TrackMatcher::find( const Meta::TrackList& tl ) const
{
QList<int> positions;
for ( int i = 0; i < tl.length(); i++ ) {
if ( tl.at( i ) == m_trackToMatch ) {
positions << i;
}
}
return positions;
}
bool
ConstraintTypes::Checkpoint::TrackMatcher::match( const Meta::TrackPtr& t ) const
{
return ( t == m_trackToMatch );
}
/******************************
* Artist Matcher *
******************************/
ConstraintTypes::Checkpoint::ArtistMatcher::ArtistMatcher( const Meta::ArtistPtr& a )
: m_artistToMatch( a )
{
}
ConstraintTypes::Checkpoint::ArtistMatcher::~ArtistMatcher()
{
}
QList<int>
ConstraintTypes::Checkpoint::ArtistMatcher::find( const Meta::TrackList& tl ) const
{
QList<int> positions;
for ( int i = 0; i < tl.length(); i++ ) {
if ( tl.at( i )->artist() == m_artistToMatch ) {
positions << i;
}
}
return positions;
}
bool
ConstraintTypes::Checkpoint::ArtistMatcher::match( const Meta::TrackPtr& t ) const
{
return ( t->artist() == m_artistToMatch );
}
/******************************
* Album Matcher *
******************************/
ConstraintTypes::Checkpoint::AlbumMatcher::AlbumMatcher( const Meta::AlbumPtr& l )
: m_albumToMatch( l )
{
}
ConstraintTypes::Checkpoint::AlbumMatcher::~AlbumMatcher()
{
}
QList<int>
ConstraintTypes::Checkpoint::AlbumMatcher::find( const Meta::TrackList& tl ) const
{
QList<int> positions;
for ( int i = 0; i < tl.length(); i++ ) {
if ( tl.at( i )->album() == m_albumToMatch ) {
positions << i;
}
}
return positions;
}
bool
ConstraintTypes::Checkpoint::AlbumMatcher::match( const Meta::TrackPtr& t ) const
{
return ( t->album() == m_albumToMatch );
}
/******************************
* Edit Widget *
******************************/
ConstraintTypes::CheckpointEditWidget::CheckpointEditWidget( const qint64 length,
const int strictness,
const Meta::DataPtr& data ) : QWidget( 0 )
{
ui.setupUi( this );
ui.timeEdit_Position->setTime( QTime().addMSecs( length ) );
ui.slider_Strictness->setValue( strictness );
ui.trackSelector->setData( data );
}
void
ConstraintTypes::CheckpointEditWidget::on_timeEdit_Position_timeChanged( const QTime& t )
{
emit positionChanged( QTime().msecsTo( t ) );
emit updated();
}
void
ConstraintTypes::CheckpointEditWidget::on_slider_Strictness_valueChanged( const int v )
{
emit strictnessChanged( v );
emit updated();
}
void
ConstraintTypes::CheckpointEditWidget::on_trackSelector_selectionChanged( const Meta::DataPtr& data )
{
emit checkpointChanged( data );
emit updated();
}
diff --git a/src/playlistgenerator/constraints/PlaylistDuration.cpp b/src/playlistgenerator/constraints/PlaylistDuration.cpp
index 56a9ec86c8..5da04b65ad 100644
--- a/src/playlistgenerator/constraints/PlaylistDuration.cpp
+++ b/src/playlistgenerator/constraints/PlaylistDuration.cpp
@@ -1,216 +1,216 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Constraint::PlaylistDuration"
#include "PlaylistDuration.h"
#include "core/meta/Meta.h"
#include "playlistgenerator/Constraint.h"
#include "playlistgenerator/ConstraintFactory.h"
#include <stdlib.h>
#include <math.h>
Constraint*
ConstraintTypes::PlaylistDuration::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
if ( p ) {
return new PlaylistDuration( xmlelem, p );
} else {
return 0;
}
}
Constraint*
ConstraintTypes::PlaylistDuration::createNew( ConstraintNode* p )
{
if ( p ) {
return new PlaylistDuration( p );
} else {
return 0;
}
}
ConstraintFactoryEntry*
ConstraintTypes::PlaylistDuration::registerMe()
{
return new ConstraintFactoryEntry( "PlaylistDuration",
i18n("Playlist Duration"),
i18n("Sets the preferred duration of the playlist"),
&PlaylistDuration::createFromXml, &PlaylistDuration::createNew );
}
ConstraintTypes::PlaylistDuration::PlaylistDuration( QDomElement& xmlelem, ConstraintNode* p )
: Constraint( p )
, m_duration( 0 )
, m_comparison( CompareNumEquals )
, m_strictness( 1.0 )
{
QDomAttr a;
a = xmlelem.attributeNode( "duration" );
if ( !a.isNull() ) {
m_duration = a.value().toInt();
} else {
// Accommodate schema change when PlaylistLength became PlaylistDuration
a = xmlelem.attributeNode( "length" );
if ( !a.isNull() )
m_duration = a.value().toInt();
}
a = xmlelem.attributeNode( "comparison" );
if ( !a.isNull() )
m_comparison = a.value().toInt();
a = xmlelem.attributeNode( "strictness" );
if ( !a.isNull() )
m_strictness = a.value().toDouble();
}
ConstraintTypes::PlaylistDuration::PlaylistDuration( ConstraintNode* p )
: Constraint( p )
, m_duration( 0 )
, m_comparison( CompareNumEquals )
, m_strictness( 1.0 )
{
}
QWidget*
ConstraintTypes::PlaylistDuration::editWidget() const
{
PlaylistDurationEditWidget* e = new PlaylistDurationEditWidget( m_duration, m_comparison, static_cast<int>( 10*m_strictness ) );
- connect( e, SIGNAL(comparisonChanged(int)), this, SLOT(setComparison(int)) );
- connect( e, SIGNAL(durationChanged(int)), this, SLOT(setDuration(int)) );
- connect( e, SIGNAL(strictnessChanged(int)), this, SLOT(setStrictness(int)) );
+ connect( e, &PlaylistDurationEditWidget::comparisonChanged, this, &PlaylistDuration::setComparison );
+ connect( e, &PlaylistDurationEditWidget::durationChanged, this, &PlaylistDuration::setDuration );
+ connect( e, &PlaylistDurationEditWidget::strictnessChanged, this, &PlaylistDuration::setStrictness );
return e;
}
void
ConstraintTypes::PlaylistDuration::toXml( QDomDocument& doc, QDomElement& elem ) const
{
QDomElement c = doc.createElement( "constraint" );
c.setAttribute( "type", "PlaylistDuration" );
c.setAttribute( "duration", QString::number( m_duration ) );
c.setAttribute( "comparison", QString::number( m_comparison ) );
c.setAttribute( "strictness", QString::number( m_strictness ) );
elem.appendChild( c );
}
QString
ConstraintTypes::PlaylistDuration::getName() const
{
KLocalizedString v;
if ( m_comparison == CompareNumEquals ) {
v = ki18nc( "%1 is a length of time (e.g. 5:00 for 5 minutes)", "Playlist duration: equals %1");
} else if ( m_comparison == CompareNumGreaterThan ) {
v = ki18nc( "%1 is a length of time (e.g. 5:00 for 5 minutes)", "Playlist duration: more than %1");
} else if ( m_comparison == CompareNumLessThan ) {
v = ki18nc( "%1 is a length of time (e.g. 5:00 for 5 minutes)", "Playlist duration: less than %1");
} else {
v = ki18n( "Playlist duration: unknown");
}
v = v.subs( QTime().addMSecs( m_duration ).toString( "H:mm:ss" ) );
return v.toString();
}
double
ConstraintTypes::PlaylistDuration::satisfaction( const Meta::TrackList& tl ) const
{
qint64 l = 0;
foreach( Meta::TrackPtr t, tl ) {
l += t->length();
}
double factor = m_strictness * 0.0003;
if ( m_comparison == CompareNumEquals ) {
return 4.0 / ( ( 1.0 + exp( factor*( double )( l - m_duration ) ) )*( 1.0 + exp( factor*( double )( m_duration - l ) ) ) );
} else if ( m_comparison == CompareNumLessThan ) {
return 1.0 / ( 1.0 + exp( factor*( double )( l - m_duration ) ) );
} else if ( m_comparison == CompareNumGreaterThan ) {
return 1.0 / ( 1.0 + exp( factor*( double )( m_duration - l ) ) );
}
return 1.0;
}
quint32
ConstraintTypes::PlaylistDuration::suggestPlaylistSize() const
{
if ( m_comparison == CompareNumLessThan ) {
return static_cast<quint32>( m_duration ) / 300000 ;
} else if ( m_comparison == CompareNumGreaterThan ) {
return static_cast<quint32>( m_duration ) / 180000;
} else {
return static_cast<quint32>( m_duration ) / 240000;
}
}
void
ConstraintTypes::PlaylistDuration::setComparison( const int c )
{
m_comparison = c;
emit dataChanged();
}
void
ConstraintTypes::PlaylistDuration::setDuration( const int v )
{
m_duration = v;
emit dataChanged();
}
void
ConstraintTypes::PlaylistDuration::setStrictness( const int sv )
{
m_strictness = static_cast<double>(sv)/10.0;
}
/******************************
* Edit Widget *
******************************/
ConstraintTypes::PlaylistDurationEditWidget::PlaylistDurationEditWidget( const int duration,
const int comparison,
const int strictness ) : QWidget( 0 )
{
ui.setupUi( this );
ui.timeEdit_Duration->setTime( QTime().addMSecs( duration ) );
ui.comboBox_Comparison->setCurrentIndex( comparison );
ui.slider_Strictness->setValue( strictness );
}
void
ConstraintTypes::PlaylistDurationEditWidget::on_timeEdit_Duration_timeChanged( const QTime& t )
{
emit durationChanged( QTime().msecsTo( t ) );
emit updated();
}
void
ConstraintTypes::PlaylistDurationEditWidget::on_comboBox_Comparison_currentIndexChanged( const int v )
{
emit comparisonChanged( v );
emit updated();
}
void
ConstraintTypes::PlaylistDurationEditWidget::on_slider_Strictness_valueChanged( const int v )
{
emit strictnessChanged( v );
emit updated();
}
diff --git a/src/playlistgenerator/constraints/PlaylistFileSize.cpp b/src/playlistgenerator/constraints/PlaylistFileSize.cpp
index c485513062..206d5b3ada 100644
--- a/src/playlistgenerator/constraints/PlaylistFileSize.cpp
+++ b/src/playlistgenerator/constraints/PlaylistFileSize.cpp
@@ -1,262 +1,262 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Constraint::PlaylistFileSize"
#include "PlaylistFileSize.h"
#include "core/meta/Meta.h"
#include "playlistgenerator/Constraint.h"
#include "playlistgenerator/ConstraintFactory.h"
#include <stdlib.h>
#include <math.h>
#include <KGlobal>
Constraint*
ConstraintTypes::PlaylistFileSize::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
if ( p ) {
return new PlaylistFileSize( xmlelem, p );
} else {
return 0;
}
}
Constraint*
ConstraintTypes::PlaylistFileSize::createNew( ConstraintNode* p )
{
if ( p ) {
return new PlaylistFileSize( p );
} else {
return 0;
}
}
ConstraintFactoryEntry*
ConstraintTypes::PlaylistFileSize::registerMe()
{
return new ConstraintFactoryEntry( "PlaylistFileSize",
i18n("Total File Size of Playlist"),
i18n("Sets the preferred total file size of the playlist"),
&PlaylistFileSize::createFromXml, &PlaylistFileSize::createNew );
}
ConstraintTypes::PlaylistFileSize::PlaylistFileSize( QDomElement& xmlelem, ConstraintNode* p )
: Constraint( p )
, m_size( 700 )
, m_unit( 1 )
, m_comparison( CompareNumEquals )
, m_strictness( 1.0 )
{
QDomAttr a;
a = xmlelem.attributeNode( "size" );
if ( !a.isNull() )
m_size = a.value().toInt();
a = xmlelem.attributeNode( "unit" );
if ( !a.isNull() )
m_unit = a.value().toInt();
a = xmlelem.attributeNode( "comparison" );
if ( !a.isNull() )
m_comparison = a.value().toInt();
a = xmlelem.attributeNode( "strictness" );
if ( !a.isNull() )
m_strictness = a.value().toDouble();
}
ConstraintTypes::PlaylistFileSize::PlaylistFileSize( ConstraintNode* p )
: Constraint( p )
, m_size( 700 )
, m_unit( 1 )
, m_comparison( CompareNumEquals )
, m_strictness( 1.0 )
{
}
QWidget*
ConstraintTypes::PlaylistFileSize::editWidget() const
{
PlaylistFileSizeEditWidget* e = new PlaylistFileSizeEditWidget( m_size, m_unit, m_comparison, static_cast<int>( 10*m_strictness ) );
- connect( e, SIGNAL(comparisonChanged(int)), this, SLOT(setComparison(int)) );
- connect( e, SIGNAL(sizeChanged(int)), this, SLOT(setSize(int)) );
- connect( e, SIGNAL(unitChanged(int)), this, SLOT(setUnit(int)) );
- connect( e, SIGNAL(strictnessChanged(int)), this, SLOT(setStrictness(int)) );
+ connect( e, &PlaylistFileSizeEditWidget::comparisonChanged, this, &PlaylistFileSize::setComparison );
+ connect( e, &PlaylistFileSizeEditWidget::sizeChanged, this, &PlaylistFileSize::setSize );
+ connect( e, &PlaylistFileSizeEditWidget::unitChanged, this, &PlaylistFileSize::setUnit );
+ connect( e, &PlaylistFileSizeEditWidget::strictnessChanged, this, &PlaylistFileSize::setStrictness );
return e;
}
void
ConstraintTypes::PlaylistFileSize::toXml( QDomDocument& doc, QDomElement& elem ) const
{
QDomElement c = doc.createElement( "constraint" );
c.setAttribute( "type", "PlaylistFileSize" );
c.setAttribute( "size", QString::number( m_size ) );
c.setAttribute( "unit", QString::number( m_unit ) );
c.setAttribute( "comparison", QString::number( m_comparison ) );
c.setAttribute( "strictness", QString::number( m_strictness ) );
elem.appendChild( c );
}
QString
ConstraintTypes::PlaylistFileSize::getName() const
{
KLocalizedString v;
if ( m_comparison == CompareNumEquals ) {
v = ki18nc( "%1 is a file size (e.g. 50 MB)", "Total file size of playlist: equals %1");
} else if ( m_comparison == CompareNumGreaterThan ) {
v = ki18nc( "%1 is a file size (e.g. 50 MB)", "Total file size of playlist: more than %1");
} else if ( m_comparison == CompareNumLessThan ) {
v = ki18nc( "%1 is a file size (e.g. 50 MB)", "Total file size of playlist: less than %1");
} else {
v = ki18n( "Total file size of playlist: unknown");
}
v = v.subs( KGlobal::locale()->formatByteSize( (double)getWantedSize(), 1, KLocale::MetricBinaryDialect ) );
return v.toString();
}
double
ConstraintTypes::PlaylistFileSize::satisfaction( const Meta::TrackList& tl ) const
{
quint64 tlSize = 0;
foreach ( const Meta::TrackPtr t, tl ) {
// Boy it sure would be nice if Qt had a "reduce" function built in
tlSize += static_cast<quint64>( t->filesize() );
}
quint64 wantedSize = getWantedSize();
if ( m_comparison == CompareNumEquals ) {
if ( tlSize > wantedSize )
return ( tlSize == wantedSize ) ? 1.0 : transformFileSize( tlSize - wantedSize );
else
return ( tlSize == wantedSize ) ? 1.0 : transformFileSize( wantedSize - tlSize );
} else if ( m_comparison == CompareNumGreaterThan ) {
return ( tlSize > wantedSize ) ? 1.0 : transformFileSize( wantedSize - tlSize );
} else if ( m_comparison == CompareNumLessThan ) {
return ( tlSize < wantedSize ) ? 1.0 : transformFileSize( tlSize - wantedSize );
} else {
return 0.0;
}
}
quint32
ConstraintTypes::PlaylistFileSize::suggestPlaylistSize() const
{
// estimate that each file is about 8MB large
quint64 s = getWantedSize() / static_cast<quint64>( 8000000 );
return static_cast<quint32>( s );
}
quint64
ConstraintTypes::PlaylistFileSize::getWantedSize() const
{
switch ( m_unit ) {
case 0:
return static_cast<quint64>( m_size ) * Q_INT64_C( 1000 );
case 1:
return static_cast<quint64>( m_size ) * Q_INT64_C( 1000000 );
case 2:
return static_cast<quint64>( m_size ) * Q_INT64_C( 1000000000 );
case 3:
return static_cast<quint64>( m_size ) * Q_INT64_C( 1000000000000 );
default:
return static_cast<quint64>( m_size ) * Q_INT64_C( 1 );
}
}
double
ConstraintTypes::PlaylistFileSize::transformFileSize( const quint64 delta ) const
{
// Note: delta must be positive
const double factor = m_strictness * 3e-9;
return 1.0 / (1.0 + exp( factor*(double)(delta)));
}
void
ConstraintTypes::PlaylistFileSize::setComparison( const int c )
{
m_comparison = c;
emit dataChanged();
}
void
ConstraintTypes::PlaylistFileSize::setSize( const int l )
{
m_size = l;
emit dataChanged();
}
void
ConstraintTypes::PlaylistFileSize::setStrictness( const int sv )
{
m_strictness = static_cast<double>(sv)/10.0;
}
void
ConstraintTypes::PlaylistFileSize::setUnit( const int u )
{
m_unit = u;
emit dataChanged();
}
/******************************
* Edit Widget *
******************************/
ConstraintTypes::PlaylistFileSizeEditWidget::PlaylistFileSizeEditWidget( const int size,
const int unit,
const int comparison,
const int strictness ) : QWidget( 0 )
{
ui.setupUi( this );
ui.spinBox_Size->setValue( size );
ui.comboBox_Unit->setCurrentIndex( unit );
ui.comboBox_Comparison->setCurrentIndex( comparison );
ui.slider_Strictness->setValue( strictness );
}
void
ConstraintTypes::PlaylistFileSizeEditWidget::on_spinBox_Size_valueChanged( const int l )
{
emit sizeChanged( l );
emit updated();
}
void
ConstraintTypes::PlaylistFileSizeEditWidget::on_comboBox_Unit_currentIndexChanged( const int l )
{
emit unitChanged( l );
emit updated();
}
void
ConstraintTypes::PlaylistFileSizeEditWidget::on_comboBox_Comparison_currentIndexChanged( const int v )
{
emit comparisonChanged( v );
emit updated();
}
void
ConstraintTypes::PlaylistFileSizeEditWidget::on_slider_Strictness_valueChanged( const int v )
{
emit strictnessChanged( v );
emit updated();
}
diff --git a/src/playlistgenerator/constraints/PlaylistLength.cpp b/src/playlistgenerator/constraints/PlaylistLength.cpp
index fb0a516f07..b72003290c 100644
--- a/src/playlistgenerator/constraints/PlaylistLength.cpp
+++ b/src/playlistgenerator/constraints/PlaylistLength.cpp
@@ -1,218 +1,218 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Constraint::PlaylistLength"
#include "PlaylistLength.h"
#include "playlistgenerator/Constraint.h"
#include "playlistgenerator/ConstraintFactory.h"
#include <stdlib.h>
#include <math.h>
Constraint*
ConstraintTypes::PlaylistLength::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
if ( p ) {
return new PlaylistLength( xmlelem, p );
} else {
return 0;
}
}
Constraint*
ConstraintTypes::PlaylistLength::createNew( ConstraintNode* p )
{
if ( p ) {
return new PlaylistLength( p );
} else {
return 0;
}
}
ConstraintFactoryEntry*
ConstraintTypes::PlaylistLength::registerMe()
{
return new ConstraintFactoryEntry( "PlaylistLength",
i18n("Playlist Length"),
i18n("Sets the preferred number of tracks in the playlist"),
&PlaylistLength::createFromXml, &PlaylistLength::createNew );
}
ConstraintTypes::PlaylistLength::PlaylistLength( QDomElement& xmlelem, ConstraintNode* p )
: Constraint( p )
, m_length( 30 )
, m_comparison( CompareNumEquals )
, m_strictness( 1.0 )
{
QDomAttr a;
a = xmlelem.attributeNode( "length" );
if ( !a.isNull() ) {
m_length = a.value().toInt();
/* after 2.3.2, what was the PlaylistLength constraint became the
* PlaylistDuration constraint, so this works around the instance when
* a user loads an XML file generated with the old code -- sth*/
if ( m_length > 1000 )
m_length /= 240000;
}
a = xmlelem.attributeNode( "comparison" );
if ( !a.isNull() )
m_comparison = a.value().toInt();
a = xmlelem.attributeNode( "strictness" );
if ( !a.isNull() )
m_strictness = a.value().toDouble();
}
ConstraintTypes::PlaylistLength::PlaylistLength( ConstraintNode* p )
: Constraint( p )
, m_length( 30 )
, m_comparison( CompareNumEquals )
, m_strictness( 1.0 )
{
}
QWidget*
ConstraintTypes::PlaylistLength::editWidget() const
{
PlaylistLengthEditWidget* e = new PlaylistLengthEditWidget( m_length, m_comparison, static_cast<int>( 10*m_strictness ) );
- connect( e, SIGNAL(comparisonChanged(int)), this, SLOT(setComparison(int)) );
- connect( e, SIGNAL(lengthChanged(int)), this, SLOT(setLength(int)) );
- connect( e, SIGNAL(strictnessChanged(int)), this, SLOT(setStrictness(int)) );
+ connect( e, &PlaylistLengthEditWidget::comparisonChanged, this, &PlaylistLength::setComparison );
+ connect( e, &PlaylistLengthEditWidget::lengthChanged, this, &PlaylistLength::setLength );
+ connect( e, &PlaylistLengthEditWidget::strictnessChanged, this, &PlaylistLength::setStrictness );
return e;
}
void
ConstraintTypes::PlaylistLength::toXml( QDomDocument& doc, QDomElement& elem ) const
{
QDomElement c = doc.createElement( "constraint" );
c.setAttribute( "type", "PlaylistLength" );
c.setAttribute( "length", QString::number( m_length ) );
c.setAttribute( "comparison", QString::number( m_comparison ) );
c.setAttribute( "strictness", QString::number( m_strictness ) );
elem.appendChild( c );
}
QString
ConstraintTypes::PlaylistLength::getName() const
{
KLocalizedString v;
if ( m_comparison == CompareNumEquals ) {
v = ki18ncp( "%1 is a number", "Playlist length: 1 track", "Playlist length: %1 tracks");
} else if ( m_comparison == CompareNumGreaterThan ) {
v = ki18ncp( "%1 is a number", "Playlist length: more than 1 track",
"Playlist length: more than %1 tracks");
} else if ( m_comparison == CompareNumLessThan ) {
v = ki18ncp( "%1 is a number", "Playlist length: less than 1 track",
"Playlist length: less than %1 tracks");
} else {
v = ki18n( "Playlist length: unknown");
}
v = v.subs( m_length );
return v.toString();
}
double
ConstraintTypes::PlaylistLength::satisfaction( const Meta::TrackList& tl ) const
{
quint32 l = static_cast<quint32>( tl.size() );
if ( m_comparison == CompareNumEquals ) {
if ( l > m_length )
return ( l == m_length ) ? 1.0 : transformLength( l - m_length );
else
return ( l == m_length ) ? 1.0 : transformLength( m_length - l );
} else if ( m_comparison == CompareNumGreaterThan ) {
return ( l > m_length ) ? 1.0 : transformLength( m_length - l );
} else if ( m_comparison == CompareNumLessThan ) {
return ( l < m_length ) ? 1.0 : transformLength( l - m_length );
} else {
return 0.0;
}
}
quint32
ConstraintTypes::PlaylistLength::suggestPlaylistSize() const
{
return m_length;
}
double
ConstraintTypes::PlaylistLength::transformLength( const int delta ) const
{
// Note: delta must be positive
const double w = 5.0;
return exp( -2.0 * ( 0.01 + m_strictness ) / w * ( delta + 1 ) );
}
void
ConstraintTypes::PlaylistLength::setComparison( const int c )
{
m_comparison = c;
emit dataChanged();
}
void
ConstraintTypes::PlaylistLength::setLength( const int l )
{
m_length = static_cast<quint32>(l);
emit dataChanged();
}
void
ConstraintTypes::PlaylistLength::setStrictness( const int sv )
{
m_strictness = static_cast<double>(sv)/10.0;
}
/******************************
* Edit Widget *
******************************/
ConstraintTypes::PlaylistLengthEditWidget::PlaylistLengthEditWidget( const int length,
const int comparison,
const int strictness ) : QWidget( 0 )
{
ui.setupUi( this );
ui.spinBox_Length->setValue( length );
ui.comboBox_Comparison->setCurrentIndex( comparison );
ui.slider_Strictness->setValue( strictness );
}
void
ConstraintTypes::PlaylistLengthEditWidget::on_spinBox_Length_valueChanged( const int l )
{
emit lengthChanged( l );
emit updated();
}
void
ConstraintTypes::PlaylistLengthEditWidget::on_comboBox_Comparison_currentIndexChanged( const int v )
{
emit comparisonChanged( v );
emit updated();
}
void
ConstraintTypes::PlaylistLengthEditWidget::on_slider_Strictness_valueChanged( const int v )
{
emit strictnessChanged( v );
emit updated();
}
diff --git a/src/playlistgenerator/constraints/PreventDuplicates.cpp b/src/playlistgenerator/constraints/PreventDuplicates.cpp
index d54848c225..e6811a737b 100644
--- a/src/playlistgenerator/constraints/PreventDuplicates.cpp
+++ b/src/playlistgenerator/constraints/PreventDuplicates.cpp
@@ -1,168 +1,168 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Constraint::PreventDuplicates"
#include "PreventDuplicates.h"
#include "playlistgenerator/Constraint.h"
#include "playlistgenerator/ConstraintFactory.h"
#include "core/meta/Meta.h"
#include <QSet>
#include <math.h>
Constraint*
ConstraintTypes::PreventDuplicates::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
if ( p )
return new PreventDuplicates( xmlelem, p );
else
return 0;
}
Constraint*
ConstraintTypes::PreventDuplicates::createNew( ConstraintNode* p )
{
if ( p )
return new PreventDuplicates( p );
else
return 0;
}
ConstraintFactoryEntry*
ConstraintTypes::PreventDuplicates::registerMe()
{
return new ConstraintFactoryEntry( "PreventDuplicates",
i18n("Prevent Duplicates"),
i18n("Prevents duplicate tracks, albums, or artists from appearing in the playlist"),
&PreventDuplicates::createFromXml, &PreventDuplicates::createNew );
}
ConstraintTypes::PreventDuplicates::PreventDuplicates( QDomElement& xmlelem, ConstraintNode* p )
: Constraint( p )
{
QDomAttr a;
a = xmlelem.attributeNode( "field" );
if ( !a.isNull() ) {
m_field = static_cast<DupeField>( a.value().toInt() );
}
}
ConstraintTypes::PreventDuplicates::PreventDuplicates( ConstraintNode* p )
: Constraint( p )
, m_field( DupeTrack )
{
}
QWidget*
ConstraintTypes::PreventDuplicates::editWidget() const
{
PreventDuplicatesEditWidget* e = new PreventDuplicatesEditWidget( m_field );
- connect( e, SIGNAL(fieldChanged(int)), this, SLOT(setField(int)) );
+ connect( e, &PreventDuplicatesEditWidget::fieldChanged, this, &PreventDuplicates::setField );
return e;
}
void
ConstraintTypes::PreventDuplicates::toXml( QDomDocument& doc, QDomElement& elem ) const
{
QDomElement c = doc.createElement( "constraint" );
c.setAttribute( "type", "PreventDuplicates" );
c.setAttribute( "field", QString::number( m_field ) );
elem.appendChild( c );
}
QString
ConstraintTypes::PreventDuplicates::getName() const
{
switch ( m_field ) {
case DupeTrack:
return i18n("Prevent duplicate tracks");
case DupeArtist:
return i18n("Prevent duplicate artists");
case DupeAlbum:
return i18n("Prevent duplicate albums");
}
return QString();
}
double
ConstraintTypes::PreventDuplicates::satisfaction( const Meta::TrackList& tl ) const
{
int d = 0;
QSet<Meta::TrackPtr> tracks;
QSet<Meta::AlbumPtr> albums;
QSet<Meta::ArtistPtr> artists;
switch ( m_field ) {
case DupeTrack:
foreach( Meta::TrackPtr t, tl ) {
if ( tracks.contains(t) ) {
d++;
} else {
tracks.insert(t);
}
}
break;
case DupeAlbum:
foreach( Meta::TrackPtr t, tl ) {
if ( albums.contains(t->album()) ) {
d++;
} else {
albums.insert(t->album());
}
}
break;
case DupeArtist:
foreach( Meta::TrackPtr t, tl ) {
if ( artists.contains(t->artist()) ) {
d++;
} else {
artists.insert(t->artist());
}
}
break;
}
return exp( (double)d / -3.0 );
}
void
ConstraintTypes::PreventDuplicates::setField( const int c )
{
m_field = static_cast<DupeField>( c );
emit dataChanged();
}
/******************************
* Edit Widget *
******************************/
ConstraintTypes::PreventDuplicatesEditWidget::PreventDuplicatesEditWidget( const int field )
: QWidget( 0 )
{
ui.setupUi( this );
ui.comboBox_Field->setCurrentIndex( field );
}
void
ConstraintTypes::PreventDuplicatesEditWidget::on_comboBox_Field_currentIndexChanged( const int v )
{
emit fieldChanged( v );
emit updated();
}
diff --git a/src/playlistgenerator/constraints/TagMatch.cpp b/src/playlistgenerator/constraints/TagMatch.cpp
index 1c0715f827..c16340a953 100644
--- a/src/playlistgenerator/constraints/TagMatch.cpp
+++ b/src/playlistgenerator/constraints/TagMatch.cpp
@@ -1,754 +1,755 @@
/****************************************************************************************
* Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "Constraint::TagMatch"
#include "TagMatch.h"
#include "playlistgenerator/Constraint.h"
#include "playlistgenerator/ConstraintFactory.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "core/support/Debug.h"
#include <math.h>
#include <stdlib.h>
Constraint*
ConstraintTypes::TagMatch::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
{
if ( p )
return new TagMatch( xmlelem, p );
else
return 0;
}
Constraint*
ConstraintTypes::TagMatch::createNew( ConstraintNode* p )
{
if ( p )
return new TagMatch( p );
else
return 0;
}
ConstraintFactoryEntry*
ConstraintTypes::TagMatch::registerMe()
{
return new ConstraintFactoryEntry( "TagMatch",
i18n("Match Tags"),
i18n("Make all tracks in the playlist match the specified characteristic"),
&TagMatch::createFromXml, &TagMatch::createNew );
}
ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p )
: MatchingConstraint( p )
, m_comparer( new Comparer() )
, m_fieldsModel( new TagMatchFieldsModel() )
{
QDomAttr a;
a = xmlelem.attributeNode( "field" );
if ( !a.isNull() ) {
if ( m_fieldsModel->contains( a.value() ) )
m_field = a.value();
}
a = xmlelem.attributeNode( "comparison" );
if ( !a.isNull() ) {
m_comparison = a.value().toInt();
}
a = xmlelem.attributeNode( "value" );
if ( !a.isNull() ) {
if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
m_value = a.value().toInt();
} else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
if ( m_comparison == CompareDateWithin ) {
QStringList parts = a.value().split(' ');
if ( parts.size() == 2 ) {
int u = parts.at( 0 ).toInt();
int v = 0;
if ( parts.at( 1 ) == "months" )
v = 1;
else if ( parts.at( 1 ) == "years" )
v = 2;
m_value = QVariant::fromValue( DateRange( u, v ) );
} else
m_value = QVariant::fromValue( DateRange( 0, 0 ) );
} else
m_value = QDate::fromString( a.value(), Qt::ISODate );
} else { // String type
m_value = a.value();
}
}
a = xmlelem.attributeNode( "invert" );
if ( !a.isNull() && a.value() == "true" )
m_invert = true;
else
m_invert = false;
a = xmlelem.attributeNode( "strictness" );
if ( !a.isNull() )
m_strictness = a.value().toDouble();
}
ConstraintTypes::TagMatch::TagMatch( ConstraintNode* p )
: MatchingConstraint( p )
, m_comparison( CompareStrEquals )
, m_field( "title" )
, m_invert( false )
, m_strictness( 1.0 )
, m_value()
, m_comparer( new Comparer() )
, m_fieldsModel( new TagMatchFieldsModel() )
{
}
ConstraintTypes::TagMatch::~TagMatch()
{
delete m_comparer;
delete m_fieldsModel;
}
QWidget*
ConstraintTypes::TagMatch::editWidget() const
{
TagMatchEditWidget* e = new TagMatchEditWidget(
m_comparison,
m_field,
m_invert,
static_cast<int>( m_strictness * 10 ),
m_value );
- connect( e, SIGNAL(comparisonChanged(int)), this, SLOT(setComparison(int)) );
- connect( e, SIGNAL(fieldChanged(QString)), this, SLOT(setField(QString)) );
- connect( e, SIGNAL(invertChanged(bool)), this, SLOT(setInvert(bool)) );
- connect( e, SIGNAL(strictnessChanged(int)), this, SLOT(setStrictness(int)) );
- connect( e, SIGNAL(valueChanged(QVariant)), this, SLOT(setValue(QVariant)) );
+ connect( e, &TagMatchEditWidget::comparisonChanged, this, &TagMatch::setComparison );
+ connect( e, &TagMatchEditWidget::fieldChanged, this, &TagMatch::setField );
+ connect( e, &TagMatchEditWidget::invertChanged, this, &TagMatch::setInvert );
+ connect( e, &TagMatchEditWidget::strictnessChanged, this, &TagMatch::setStrictness );
+ connect( e, &TagMatchEditWidget::valueChanged, this, &TagMatch::setValue );
return e;
}
void
ConstraintTypes::TagMatch::toXml( QDomDocument& doc, QDomElement& elem ) const
{
QDomElement c = doc.createElement( "constraint" );
c.setAttribute( "type", "TagMatch" );
c.setAttribute( "field", m_field );
c.setAttribute( "comparison", m_comparison );
c.setAttribute( "value", valueToString() );
if ( m_invert )
c.setAttribute( "invert", "true" );
else
c.setAttribute( "invert", "false" );
c.setAttribute( "strictness", QString::number( m_strictness ) );
elem.appendChild( c );
}
QString
ConstraintTypes::TagMatch::getName() const
{
QString v( i18nc( "%1 = empty string or \"not\"; "
"%2 = a metadata field, like \"title\" or \"artist name\"; "
"%3 = a predicate, can be equals, starts with, ends with or contains; "
"%4 = a string to match; "
"Example: Match tag: not title contains \"foo\"", "Match tag:%1 %2 %3 %4") );
v = v.arg( ( m_invert ? i18n(" not") : "" ), m_fieldsModel->pretty_name_of( m_field ), comparisonToString() );
if ( m_field == "rating" ) {
double r = m_value.toDouble() / 2.0;
return v.arg( i18ncp("number of stars in the rating of a track", "%1 star", "%1 stars", r) );
} else if ( m_field == "length" ) {
return v.arg( QTime().addMSecs( m_value.toInt() ).toString( "H:mm:ss" ) );
} else {
if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
// put quotes around any strings (eg, track title or artist name) ...
QString s = QString( i18nc("an arbitrary string surrounded by quotes", "\"%1\"") ).arg( valueToString() );
return v.arg( s );
} else {
// ... but don't quote put quotes around anything else
return v.arg( valueToString() );
}
}
}
Collections::QueryMaker*
ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const
{
if ( ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) ) {
int v = m_value.toInt();
int range = static_cast<int>( m_comparer->rangeNum( m_strictness, m_fieldsModel->meta_value_of( m_field ) ) );
if ( m_comparison == CompareNumEquals ) {
if ( !m_invert ) {
if ( m_strictness < 0.99 ) { // fuzzy approximation of "1.0"
qm->beginAnd();
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
qm->endAndOr();
} else {
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
}
} else {
if ( m_strictness > 0.99 ) {
qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
}
}
} else if ( m_comparison == CompareNumGreaterThan ) {
if ( m_invert )
qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::GreaterThan );
else
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
} else if ( m_comparison == CompareNumLessThan ) {
if ( m_invert )
qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::LessThan );
else
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
}
} else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
uint referenceDate = 0;
int range = m_comparer->rangeDate( m_strictness );
if ( m_comparison == CompareDateBefore ) {
referenceDate = m_value.toDateTime().toTime_t();
if ( m_invert )
qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::LessThan );
else
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
} else if ( m_comparison == CompareDateOn ) {
referenceDate = m_value.toDateTime().toTime_t();
if ( !m_invert ) {
qm->beginAnd();
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
qm->endAndOr();
}
} else if ( m_comparison == CompareDateAfter ) {
referenceDate = m_value.toDateTime().toTime_t();
if ( m_invert )
qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
else
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
} else if ( m_comparison == CompareDateWithin ) {
QDateTime now = QDateTime::currentDateTime();
DateRange r = m_value.value<DateRange>();
switch ( r.second ) {
case 0:
referenceDate = now.addDays( -1 * r.first ).toTime_t();
break;
case 1:
referenceDate = now.addMonths( -1 * r.first ).toTime_t();
break;
case 2:
referenceDate = now.addYears( -1 * r.first ).toTime_t();
break;
default:
break;
}
if ( m_invert )
qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
else
qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
}
} else if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
if ( m_comparison == CompareStrEquals ) {
if ( m_invert )
qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
else
qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
} else if ( m_comparison == CompareStrStartsWith ) {
if ( m_invert )
qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
else
qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
} else if ( m_comparison == CompareStrEndsWith ) {
if ( m_invert )
qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
else
qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
} else if ( m_comparison == CompareStrContains ) {
if ( m_invert )
qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
else
qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
}
// TODO: regexp
} else {
error() << "TagMatch cannot initialize QM for unknown type";
}
return qm;
}
double
ConstraintTypes::TagMatch::satisfaction( const Meta::TrackList& tl ) const
{
double satisfaction = 0.0;
foreach( Meta::TrackPtr t, tl ) {
if ( matches( t ) ) {
satisfaction += 1.0;
}
}
satisfaction /= ( double )tl.size();
return satisfaction;
}
const QBitArray
ConstraintTypes::TagMatch::whatTracksMatch( const Meta::TrackList& tl )
{
QBitArray match = QBitArray( tl.size() );
for ( int i = 0; i < tl.size(); i++ ) {
if ( matches( tl.at( i ) ) )
match.setBit( i, true );
}
return match;
}
int
ConstraintTypes::TagMatch::constraintMatchType() const
{
return ( 0 << 28 ) + m_fieldsModel->index_of( m_field );
}
QString
ConstraintTypes::TagMatch::comparisonToString() const
{
if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
if ( m_comparison == CompareNumEquals ) {
return QString( i18nc("a numerical tag (like year or track number) equals a value","equals") );
} else if ( m_comparison == CompareNumGreaterThan ) {
return QString( i18n("greater than") );
} else if ( m_comparison == CompareNumLessThan ) {
return QString( i18n("less than") );
}
} else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
if ( m_comparison == CompareDateBefore ) {
return QString( i18n("before") );
} else if ( m_comparison == CompareDateOn ) {
return QString( i18n("on") );
} else if ( m_comparison == CompareDateAfter ) {
return QString( i18n("after") );
} else if ( m_comparison == CompareDateWithin ) {
return QString( i18n("within") );
}
} else {
if ( m_comparison == CompareStrEquals ) {
return QString( i18nc("an alphabetical tag (like title or artist name) equals some string","equals") );
} else if ( m_comparison == CompareStrStartsWith ) {
return QString( i18nc("an alphabetical tag (like title or artist name) starts with some string","starts with") );
} else if ( m_comparison == CompareStrEndsWith ) {
return QString( i18nc("an alphabetical tag (like title or artist name) ends with some string","ends with") );
} else if ( m_comparison == CompareStrContains ) {
return QString( i18nc("an alphabetical tag (like title or artist name) contains some string","contains") );
} else if ( m_comparison == CompareStrRegExp ) {
return QString( i18n("regexp") );
}
}
return QString( i18n("unknown comparison") );
}
QString
ConstraintTypes::TagMatch::valueToString() const
{
if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
if ( m_comparison != CompareDateWithin ) {
return m_value.toDate().toString( Qt::ISODate );
} else {
KLocalizedString unit;
switch ( m_value.value<DateRange>().second ) {
case 0:
unit = ki18np("%1 day", "%1 days");
break;
case 1:
unit = ki18np("%1 month", "%1 months");
break;
case 2:
unit = ki18np("%1 year", "%1 years");
break;
default:
break;
}
return unit.subs( m_value.value<DateRange>().first ).toString();
}
} else {
return m_value.toString();
}
}
bool
ConstraintTypes::TagMatch::matches( Meta::TrackPtr track ) const
{
if ( !m_matchCache.contains( track ) ) {
double v = 0.0;
qint64 fmv = m_fieldsModel->meta_value_of( m_field );
switch ( fmv ) {
case Meta::valUrl:
v = m_comparer->compareStr( track->prettyUrl(), m_comparison, m_value.toString() );
break;
case Meta::valTitle:
v = m_comparer->compareStr( track->prettyName(), m_comparison, m_value.toString() );
break;
case Meta::valArtist:
v = m_comparer->compareStr( track->artist()->prettyName(), m_comparison, m_value.toString() );
break;
case Meta::valAlbum:
v = m_comparer->compareStr( track->album()->prettyName(), m_comparison, m_value.toString() );
break;
case Meta::valGenre:
v = m_comparer->compareStr( track->genre()->prettyName(), m_comparison, m_value.toString() );
break;
case Meta::valComposer:
v = m_comparer->compareStr( track->composer()->prettyName(), m_comparison, m_value.toString() );
break;
case Meta::valYear:
v = m_comparer->compareNum( track->year()->prettyName().toInt(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valComment:
v = m_comparer->compareStr( track->comment(), m_comparison, m_value.toString() );
break;
case Meta::valTrackNr:
v = m_comparer->compareNum( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valDiscNr:
v = m_comparer->compareNum( track->discNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valLength:
v = m_comparer->compareNum( track->length(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valBitrate:
v = m_comparer->compareNum( track->bitrate(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valFilesize:
v = m_comparer->compareNum( track->filesize(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valCreateDate:
v = m_comparer->compareDate( track->createDate().toTime_t(), m_comparison, m_value, m_strictness );
break;
case Meta::valScore:
v = m_comparer->compareNum( track->statistics()->score(), m_comparison, m_value.toDouble(), m_strictness, fmv );
break;
case Meta::valRating:
v = m_comparer->compareNum( track->statistics()->rating(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valFirstPlayed:
v = m_comparer->compareDate( track->statistics()->firstPlayed().toTime_t(), m_comparison, m_value, m_strictness );
break;
case Meta::valLastPlayed:
v = m_comparer->compareDate( track->statistics()->lastPlayed().toTime_t(), m_comparison, m_value, m_strictness );
break;
case Meta::valPlaycount:
v = m_comparer->compareNum( track->statistics()->playCount(), m_comparison, m_value.toInt(), m_strictness, fmv );
break;
case Meta::valLabel:
v = m_comparer->compareLabels( track, m_comparison, m_value.toString() );
break;
default:
v = 0.0;
break;
}
if ( m_invert )
v = 1.0 - v;
m_matchCache.insert( track, ( v > ( (double)qrand() / (double)RAND_MAX ) ) );
}
return m_matchCache.value( track );
}
void
ConstraintTypes::TagMatch::setComparison( int c )
{
m_comparison = c;
m_matchCache.clear();
emit dataChanged();
}
void
ConstraintTypes::TagMatch::setField( const QString& s )
{
m_field = s;
m_matchCache.clear();
emit dataChanged();
}
void
ConstraintTypes::TagMatch::setInvert( bool v )
{
if ( m_invert != v ) {
foreach( const Meta::TrackPtr t, m_matchCache.keys() ) {
m_matchCache.insert( t, !m_matchCache.value( t ) );
}
}
m_invert = v;
emit dataChanged();
}
void
ConstraintTypes::TagMatch::setStrictness( int v )
{
m_strictness = static_cast<double>( v ) / 10.0;
m_matchCache.clear();
}
void
ConstraintTypes::TagMatch::setValue( const QVariant& v )
{
m_value = v;
m_matchCache.clear();
emit dataChanged();
}
/******************************
* Edit Widget *
******************************/
ConstraintTypes::TagMatchEditWidget::TagMatchEditWidget(
const int comparison,
const QString& field,
const bool invert,
const int strictness,
const QVariant& value )
: QWidget( 0 )
, m_fieldsModel( new TagMatchFieldsModel() )
{
ui.setupUi( this );
// plural support in combobox labels
- connect( ui.spinBox_ValueDateValue, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateComboBoxLabels(int)) );
+ connect( ui.spinBox_ValueDateValue, QOverload<int>::of(&KIntSpinBox::valueChanged),
+ this, &TagMatchEditWidget::slotUpdateComboBoxLabels );
ui.comboBox_ValueDateUnit->insertItem(0, i18ncp("within the last %1 days", "day", "days", 0));
ui.comboBox_ValueDateUnit->insertItem(1, i18ncp("within the last %1 months", "month", "months", 0));
ui.comboBox_ValueDateUnit->insertItem(2, i18ncp("within the last %1 years", "year", "years", 0));
// fill in appropriate defaults for some attributes
ui.kdatewidget_DateSpecific->setDate( QDate::currentDate() );
// fill in user-specified values before the slots have been connected to we don't have to call back to the constraint a dozen times
ui.comboBox_Field->setModel( m_fieldsModel );
ui.checkBox_Invert->setChecked( invert );
if ( field == "rating" ) {
ui.comboBox_ComparisonRating->setCurrentIndex( comparison );
ui.slider_StrictnessRating->setValue( strictness );
ui.rating_RatingValue->setRating( value.toInt() );
} else if ( field == "length" ) {
ui.comboBox_ComparisonTime->setCurrentIndex( comparison );
ui.slider_StrictnessTime->setValue( strictness );
ui.timeEdit_TimeValue->setTime( QTime().addMSecs( value.toInt() ) );
} else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeInt ) {
ui.comboBox_ComparisonInt->setCurrentIndex( comparison );
ui.slider_StrictnessInt->setValue( strictness );
ui.spinBox_ValueInt->setValue( value.toInt() );
} else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
ui.comboBox_ComparisonDate->setCurrentIndex( comparison );
ui.slider_StrictnessDate->setValue( strictness );
if ( comparison == TagMatch::CompareDateWithin ) {
ui.stackedWidget_Date->setCurrentIndex( 1 );
ui.spinBox_ValueDateValue->setValue( value.value<DateRange>().first );
ui.comboBox_ValueDateUnit->setCurrentIndex( value.value<DateRange>().second );
} else {
ui.stackedWidget_Date->setCurrentIndex( 0 );
ui.kdatewidget_DateSpecific->setDate( value.toDate() );
}
} else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeString ) {
ui.comboBox_ComparisonString->setCurrentIndex( comparison );
ui.lineEdit_StringValue->setText( value.toString() );
}
// set this after the slot has been connected so that it also sets the field page correctly
ui.comboBox_Field->setCurrentIndex( m_fieldsModel->index_of( field ) );
}
ConstraintTypes::TagMatchEditWidget::~TagMatchEditWidget()
{
delete m_fieldsModel;
}
// ComboBox slots for comparisons
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonDate_currentIndexChanged( int c )
{
if ( c == TagMatch::CompareDateWithin )
ui.stackedWidget_Date->setCurrentIndex( 1 );
else
ui.stackedWidget_Date->setCurrentIndex( 0 );
emit comparisonChanged( c );
}
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonInt_currentIndexChanged( int c )
{
emit comparisonChanged( c );
}
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonRating_currentIndexChanged( int c )
{
emit comparisonChanged( c );
}
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonString_currentIndexChanged( int c )
{
emit comparisonChanged( c );
}
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonTime_currentIndexChanged( int c )
{
emit comparisonChanged( c );
}
// ComboBox slots for field
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_Field_currentIndexChanged( int idx )
{
QString field = m_fieldsModel->field_at( idx );
int c = 0;
int s = 0;
QVariant v;
if ( field == "length" ) {
ui.stackedWidget_Field->setCurrentIndex( 3 );
c = ui.comboBox_ComparisonTime->currentIndex();
s = ui.slider_StrictnessTime->value();
v = QTime().msecsTo( ui.timeEdit_TimeValue->time() );
} else if ( field == "rating" ) {
ui.stackedWidget_Field->setCurrentIndex( 4 );
c = ui.comboBox_ComparisonRating->currentIndex();
s = ui.slider_StrictnessRating->value();
v = ui.rating_RatingValue->rating();
} else {
if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeInt ) {
ui.stackedWidget_Field->setCurrentIndex( 0 );
c = ui.comboBox_ComparisonInt->currentIndex();
s = ui.slider_StrictnessInt->value();
v = ui.spinBox_ValueInt->value();
} else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
ui.stackedWidget_Field->setCurrentIndex( 1 );
c = ui.comboBox_ComparisonDate->currentIndex();
s = ui.slider_StrictnessDate->value();
if ( c == TagMatch::CompareDateWithin ) {
ui.stackedWidget_Date->setCurrentIndex( 1 );
int a = ui.spinBox_ValueDateValue->value();
int b = ui.comboBox_ValueDateUnit->currentIndex();
v = QVariant::fromValue( DateRange( a, b ) );
} else {
ui.stackedWidget_Date->setCurrentIndex( 0 );
v = ui.kdatewidget_DateSpecific->date();
}
} else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeString ) {
ui.stackedWidget_Field->setCurrentIndex( 2 );
c = ui.comboBox_ComparisonString->currentIndex();
s = 1.0;
v = ui.lineEdit_StringValue->text();
}
}
// TODO: set range limitations and default values depending on field
emit fieldChanged( field );
emit valueChanged( v );
emit comparisonChanged( c );
emit strictnessChanged( s );
}
// Invert checkbox slot
void
ConstraintTypes::TagMatchEditWidget::on_checkBox_Invert_clicked( bool v )
{
emit invertChanged( v );
}
// Strictness Slider slots
void
ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessDate_valueChanged( int v )
{
emit strictnessChanged( v );
}
void
ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessInt_valueChanged( int v )
{
emit strictnessChanged( v );
}
void
ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessRating_valueChanged( int v )
{
emit strictnessChanged( v );
}
void
ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessTime_valueChanged( int v )
{
emit strictnessChanged( v );
}
// various value slots
void
ConstraintTypes::TagMatchEditWidget::on_kdatewidget_DateSpecific_changed( const QDate& v )
{
emit valueChanged( QVariant( v ) );
}
void
ConstraintTypes::TagMatchEditWidget::on_comboBox_ValueDateUnit_currentIndexChanged( int u )
{
int v = ui.spinBox_ValueDateValue->value();
emit valueChanged( QVariant::fromValue( DateRange( v, u ) ) );
}
void
ConstraintTypes::TagMatchEditWidget::on_spinBox_ValueDateValue_valueChanged( int v )
{
int u = ui.comboBox_ValueDateUnit->currentIndex();
emit valueChanged( QVariant::fromValue( DateRange( v, u ) ) );
}
void
ConstraintTypes::TagMatchEditWidget::on_spinBox_ValueInt_valueChanged( int v )
{
emit valueChanged( QVariant( v ) );
}
void
ConstraintTypes::TagMatchEditWidget::on_lineEdit_StringValue_textChanged( const QString& v )
{
emit valueChanged( QVariant( v ) );
}
void
ConstraintTypes::TagMatchEditWidget::on_rating_RatingValue_ratingChanged( int v )
{
emit valueChanged( QVariant( v ) );
}
void
ConstraintTypes::TagMatchEditWidget::on_timeEdit_TimeValue_timeChanged( const QTime& t )
{
int v = QTime().msecsTo( t );
emit valueChanged( QVariant( v ) );
}
void
ConstraintTypes::TagMatchEditWidget::slotUpdateComboBoxLabels( int value )
{
ui.comboBox_ValueDateUnit->setItemText(0, i18ncp("within the last %1 days", "day", "days", value));
ui.comboBox_ValueDateUnit->setItemText(1, i18ncp("within the last %1 months", "month", "months", value));
ui.comboBox_ValueDateUnit->setItemText(2, i18ncp("within the last %1 years", "year", "years", value));
}
diff --git a/src/playlistmanager/PlaylistManager.cpp b/src/playlistmanager/PlaylistManager.cpp
index 925045b0b3..311a519bc5 100644
--- a/src/playlistmanager/PlaylistManager.cpp
+++ b/src/playlistmanager/PlaylistManager.cpp
@@ -1,531 +1,532 @@
/****************************************************************************************
* Copyright (c) 2007 Bart Cerneels <bart.cerneels@kde.org> *
* Copyright (c) 2011 Lucas Lira Gomes <x8lucas8x@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistManager.h"
#include "amarokurls/AmarokUrl.h"
#include "amarokconfig.h"
#include "App.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/playlists/types/file/PlaylistFile.h"
#include "playlist/PlaylistModelStack.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "core/podcasts/PodcastProvider.h"
#include "file/PlaylistFileProvider.h"
#include "file/KConfigSyncRelStore.h"
#include "core-impl/podcasts/sql/SqlPodcastProvider.h"
#include "playlistmanager/sql/SqlUserPlaylistProvider.h"
#include "playlistmanager/SyncedPlaylist.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "core/interfaces/Logger.h"
#include "browsers/playlistbrowser/UserPlaylistModel.h"
#include <kdirlister.h>
#include <kio/jobclasses.h>
#include <kio/job.h>
#include <KInputDialog>
#include <KLocale>
#include <QUrl>
#include <QFileInfo>
#include <typeinfo>
using namespace Meta;
using namespace Playlists;
PlaylistManager *PlaylistManager::s_instance = 0;
namespace The
{
PlaylistManager *playlistManager() { return PlaylistManager::instance(); }
}
PlaylistManager *
PlaylistManager::instance()
{
return s_instance ? s_instance : new PlaylistManager();
}
void
PlaylistManager::destroy()
{
if (s_instance) {
delete s_instance;
s_instance = 0;
}
}
PlaylistManager::PlaylistManager()
{
s_instance = this;
m_syncRelStore = new KConfigSyncRelStore();
m_playlistFileProvider = new Playlists::PlaylistFileProvider();
addProvider( m_playlistFileProvider, UserPlaylist );
m_defaultPodcastProvider = new Podcasts::SqlPodcastProvider();
addProvider( m_defaultPodcastProvider, PlaylistManager::PodcastChannel );
CollectionManager::instance()->addTrackProvider( m_defaultPodcastProvider );
m_defaultUserPlaylistProvider = new Playlists::SqlUserPlaylistProvider();
addProvider( m_defaultUserPlaylistProvider, UserPlaylist );
}
PlaylistManager::~PlaylistManager()
{
delete m_defaultPodcastProvider;
delete m_defaultUserPlaylistProvider;
delete m_playlistFileProvider;
delete m_syncRelStore;
}
bool
PlaylistManager::hasToSync( Playlists::PlaylistPtr master, Playlists::PlaylistPtr slave )
{
DEBUG_BLOCK
debug() << "master: " << master->uidUrl();
debug() << "slave: " << slave->uidUrl();
if( !m_syncRelStore )
return false;
return m_syncRelStore->hasToSync( master, slave );
}
void
PlaylistManager::addProvider( Playlists::PlaylistProvider *provider, int category )
{
bool newCategory = false;
if( !m_providerMap.uniqueKeys().contains( category ) )
newCategory = true;
//disconnect all signals connected to this object to be sure.
provider->disconnect( this, 0 );
m_providerMap.insert( category, provider );
- connect( provider, SIGNAL(updated()), SLOT(slotUpdated()));
- connect( provider, SIGNAL(playlistAdded(Playlists::PlaylistPtr)),
- SLOT(slotPlaylistAdded(Playlists::PlaylistPtr)));
- connect( provider, SIGNAL(playlistRemoved(Playlists::PlaylistPtr)),
- SLOT(slotPlaylistRemoved(Playlists::PlaylistPtr)));
+ connect( provider, &Playlists::PlaylistProvider::updated,
+ this, &PlaylistManager::slotUpdated );
+ connect( provider, &Playlists::PlaylistProvider::playlistAdded,
+ this, &PlaylistManager::slotPlaylistAdded );
+ connect( provider, &Playlists::PlaylistProvider::playlistRemoved,
+ this, &PlaylistManager::slotPlaylistRemoved );
if( newCategory )
emit categoryAdded( category );
emit providerAdded( provider, category );
emit updated( category );
loadPlaylists( provider, category );
}
void
PlaylistManager::loadPlaylists( Playlists::PlaylistProvider *provider, int category )
{
foreach( Playlists::PlaylistPtr playlist, provider->playlists() )
addPlaylist( playlist, category );
}
void
PlaylistManager::addPlaylist( Playlists::PlaylistPtr playlist, int category )
{
SyncedPlaylistPtr syncedPlaylist = m_syncRelStore->asSyncedPlaylist( playlist );
//NULL when not synced or a slave added before it's master copy ("early slave")
if( syncedPlaylist )
{
if( !m_syncedPlaylistMap.keys().contains( syncedPlaylist ) )
{
//this can only happen when playlist == the master of the syncedPlaylist
//Search for any slaves created before their master ("early slaves")
//To set-up a sync between them
//Only search in the category of the new playlist, i.e. no cross category syncing.
foreach( Playlists::PlaylistPtr existingPlaylist, m_playlistMap.values( category ) )
{
//If this is a slave asSyncedPlaylist() will make it part of the syncedPlaylist
if( m_syncRelStore->asSyncedPlaylist( existingPlaylist ) == syncedPlaylist )
{
m_playlistMap.remove( category, existingPlaylist );
if( !m_syncedPlaylistMap.values( syncedPlaylist ).contains( existingPlaylist ) )
m_syncedPlaylistMap.insert( syncedPlaylist, existingPlaylist );
}
}
}
if( !m_syncedPlaylistMap.values( syncedPlaylist ).contains( playlist ) )
{
m_syncedPlaylistMap.insert( syncedPlaylist, playlist );
//The synchronosation will be done in the next mainloop run
m_syncNeeded.append( syncedPlaylist );
- QTimer::singleShot( 0, this, SLOT(slotSyncNeeded()) );
+ QTimer::singleShot( 0, this, &PlaylistManager::slotSyncNeeded );
}
//deliberatly reusing the passed argument
playlist = PlaylistPtr::dynamicCast( syncedPlaylist );
if( m_playlistMap.values( category ).contains( playlist ) )
{
//no need to add it again but do let the model know something changed.
emit playlistUpdated( playlist, category );
return;
}
}
m_playlistMap.insert( category, playlist );
//reemit so models know about new playlist in their category
emit playlistAdded( playlist, category );
}
void
PlaylistManager::removeProvider( Playlists::PlaylistProvider *provider )
{
DEBUG_BLOCK
if( !provider )
return;
if( !m_providerMap.values( provider->category() ).contains( provider ) )
{
return;
}
removePlaylists( provider );
m_providerMap.remove( provider->category(), provider );
emit providerRemoved( provider, provider->category() );
emit updated( provider->category() );
}
void
PlaylistManager::removePlaylists( Playlists::PlaylistProvider *provider )
{
foreach( Playlists::PlaylistPtr playlist, m_playlistMap.values( provider->category() ) )
if( playlist->provider() && playlist->provider() == provider )
{
foreach( SyncedPlaylistPtr syncedPlaylist, m_syncedPlaylistMap.keys( playlist ) )
m_syncedPlaylistMap.remove( syncedPlaylist, playlist );
removePlaylist( playlist, provider->category() );
}
}
void
PlaylistManager::removePlaylist( Playlists::PlaylistPtr playlist, int category )
{
if( typeid( *playlist.data() ) == typeid( SyncedPlaylist ) )
{
SyncedPlaylistPtr syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist );
//TODO: this might be wrong if there were multiple playlists from the same provider.
//remove the specific child playlist, not all from same provider.
syncedPlaylist->removePlaylistsFrom( playlist->provider() );
if( syncedPlaylist->isEmpty() )
m_playlistMap.remove( category, playlist );
m_syncNeeded.removeAll( syncedPlaylist );
}
else
{
m_playlistMap.remove( category, playlist );
}
emit playlistRemoved( playlist, category );
}
void
PlaylistManager::slotUpdated()
{
Playlists::PlaylistProvider *provider =
dynamic_cast<Playlists::PlaylistProvider *>( QObject::sender() );
if( !provider )
return;
//forcefull reload all the providers playlists.
//This is an expensive operation, the provider should use playlistAdded/Removed signals instead.
removePlaylists( provider );
loadPlaylists( provider, provider->category() );
emit updated( provider->category() );
}
void
PlaylistManager::slotPlaylistAdded( Playlists::PlaylistPtr playlist )
{
addPlaylist( playlist, playlist->provider()->category() );
}
void
PlaylistManager::slotPlaylistRemoved( Playlists::PlaylistPtr playlist )
{
removePlaylist( playlist, playlist->provider()->category() );
}
Playlists::PlaylistList
PlaylistManager::playlistsOfCategory( int playlistCategory )
{
return m_playlistMap.values( playlistCategory );
}
PlaylistProviderList
PlaylistManager::providersForCategory( int playlistCategory )
{
return m_providerMap.values( playlistCategory );
}
Playlists::PlaylistProvider *
PlaylistManager::playlistProvider(int category, QString name)
{
QList<Playlists::PlaylistProvider *> providers( m_providerMap.values( category ) );
QListIterator<Playlists::PlaylistProvider *> i(providers);
while( i.hasNext() )
{
Playlists::PlaylistProvider * p = static_cast<Playlists::PlaylistProvider *>( i.next() );
if( p->prettyName() == name )
return p;
}
return 0;
}
bool
PlaylistManager::save( Meta::TrackList tracks, const QString &name,
Playlists::PlaylistProvider *toProvider, bool editName )
{
//if toProvider is 0 use the default Playlists::UserPlaylistProvider (SQL)
Playlists::UserPlaylistProvider *prov = toProvider
? qobject_cast<Playlists::UserPlaylistProvider *>( toProvider )
: m_defaultUserPlaylistProvider;
if( !prov || !prov->isWritable() )
return false;
Playlists::PlaylistPtr playlist = prov->save( tracks, name );
if( playlist.isNull() )
return false;
if( editName )
rename( playlist );
return true;
}
bool
PlaylistManager::import( const QString& fromLocation )
{
// used by: PlaylistBrowserNS::UserModel::dropMimeData()
AMAROK_DEPRECATED
DEBUG_BLOCK
if( !m_playlistFileProvider )
{
debug() << "ERROR: m_playlistFileProvider was null";
return false;
}
return m_playlistFileProvider->import( QUrl(fromLocation) );
}
void
PlaylistManager::rename( Playlists::PlaylistPtr playlist )
{
if( playlist.isNull() )
return;
AmarokUrl("amarok://navigate/playlists/user playlists").run();
emit renamePlaylist( playlist ); // connected to PlaylistBrowserModel
}
bool
PlaylistManager::rename( PlaylistPtr playlist, const QString &newName )
{
Playlists::UserPlaylistProvider *provider =
qobject_cast<Playlists::UserPlaylistProvider *>( playlist->provider() );
if( !provider || !provider->isWritable() )
return false;
provider->renamePlaylist( playlist, newName );
return true;
}
bool
PlaylistManager::deletePlaylists( Playlists::PlaylistList playlistlist )
{
// Map the playlists to their respective providers
QHash<Playlists::UserPlaylistProvider*, Playlists::PlaylistList> provLists;
foreach( Playlists::PlaylistPtr playlist, playlistlist )
{
// Get the providers of the respective playlists
Playlists::UserPlaylistProvider *prov = qobject_cast<Playlists::UserPlaylistProvider *>(
getProvidersForPlaylist( playlist ).first() );
if( prov )
{
Playlists::PlaylistList pllist;
pllist << playlist;
// If the provider already has at least one playlist to delete, add another to its list
if( provLists.contains( prov ) )
{
provLists[ prov ] << pllist;
}
// If we are adding a new provider, put it in the hash, initialize its list
else
provLists.insert( prov, pllist );
}
}
// Pass each list of playlists to the respective provider for deletion
bool removedSuccess = true;
foreach( Playlists::UserPlaylistProvider* prov, provLists.keys() )
{
removedSuccess = prov->deletePlaylists( provLists.value( prov ) ) && removedSuccess;
}
return removedSuccess;
}
QList<Playlists::PlaylistProvider*>
PlaylistManager::getProvidersForPlaylist( const Playlists::PlaylistPtr playlist )
{
QList<Playlists::PlaylistProvider*> providers;
if( playlist.isNull() )
return providers;
SyncedPlaylistPtr syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist );
if( syncedPlaylist && m_syncedPlaylistMap.keys().contains( syncedPlaylist ) )
{
foreach( Playlists::PlaylistPtr playlist, m_syncedPlaylistMap.values( syncedPlaylist ) )
if( !providers.contains( playlist->provider() ) )
providers << playlist->provider();
return providers;
}
Playlists::PlaylistProvider* provider = playlist->provider();
if( provider )
return providers << provider;
//Iteratively check all providers' playlists for ownership
QList< Playlists::PlaylistProvider* > userPlaylists = m_providerMap.values( UserPlaylist );
foreach( Playlists::PlaylistProvider* provider, userPlaylists )
{
if( provider->playlists().contains( playlist ) )
return providers << provider;
}
return providers;
}
bool
PlaylistManager::isWritable( const Playlists::PlaylistPtr &playlist )
{
Playlists::UserPlaylistProvider *provider
= qobject_cast<Playlists::UserPlaylistProvider *>( getProvidersForPlaylist( playlist ).first() );
if( provider )
return provider->isWritable();
else
return false;
}
void
PlaylistManager::completePodcastDownloads()
{
foreach( Playlists::PlaylistProvider *prov, providersForCategory( PodcastChannel ) )
{
Podcasts::PodcastProvider *podcastProvider = dynamic_cast<Podcasts::PodcastProvider *>( prov );
if( !podcastProvider )
continue;
podcastProvider->completePodcastDownloads();
}
}
void
PlaylistManager::setupSync( const Playlists::PlaylistPtr master, const Playlists::PlaylistPtr slave )
{
DEBUG_BLOCK
debug() << "master: " << master->uidUrl();
debug() << "slave: " << slave->uidUrl();
//If there is no sync relation established between these two, then we must setup a sync.
if( hasToSync( master, slave ) )
return;
Playlists::PlaylistPtr tempMaster;
Playlists::PlaylistPtr tempSlave;
m_syncRelStore->addSync( master, slave );
foreach( const Playlists::PlaylistPtr tempPlaylist, m_playlistMap )
{
if( master == tempPlaylist )
{
tempMaster = tempPlaylist;
break;
}
}
foreach( const Playlists::PlaylistPtr tempPlaylist, m_playlistMap )
{
if( slave == tempPlaylist )
{
tempSlave = tempPlaylist;
break;
}
}
if( tempMaster && tempSlave )
{
SyncedPlaylistPtr syncedPlaylist = m_syncRelStore->asSyncedPlaylist( tempMaster );
m_syncRelStore->asSyncedPlaylist( tempSlave );
Playlists::PlaylistPtr syncedPlaylistPtr =
Playlists::PlaylistPtr::dynamicCast( syncedPlaylist );
int category = syncedPlaylist->master()->provider()->category();
if( !m_playlistMap.values( category ).contains( syncedPlaylistPtr ) )
{
removePlaylist( tempMaster, tempMaster->provider()->category() );
removePlaylist( tempSlave, tempSlave->provider()->category() );
m_syncedPlaylistMap.insert( syncedPlaylist, tempMaster );
m_syncedPlaylistMap.insert( syncedPlaylist, tempSlave );
m_playlistMap.insert( category, syncedPlaylistPtr );
//reemit so models know about new playlist in their category
emit playlistAdded( syncedPlaylistPtr, category );
}
}
}
void PlaylistManager::slotSyncNeeded()
{
foreach( SyncedPlaylistPtr syncedPlaylist, m_syncNeeded )
if ( syncedPlaylist->syncNeeded() )
syncedPlaylist->doSync();
m_syncNeeded.clear();
}
diff --git a/src/playlistmanager/file/PlaylistFileProvider.cpp b/src/playlistmanager/file/PlaylistFileProvider.cpp
index 1df926d413..9308c4fcbf 100644
--- a/src/playlistmanager/file/PlaylistFileProvider.cpp
+++ b/src/playlistmanager/file/PlaylistFileProvider.cpp
@@ -1,346 +1,346 @@
/****************************************************************************************
* Copyright (c) 2007 Bart Cerneels <bart.cerneels@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlaylistFileProvider.h"
#include "App.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "core/interfaces/Logger.h"
#include "core-impl/playlists/types/file/asx/ASXPlaylist.h"
#include "core-impl/playlists/types/file/m3u/M3UPlaylist.h"
#include "core-impl/playlists/types/file/pls/PLSPlaylist.h"
#include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h"
#include "playlist/PlaylistModelStack.h"
#include "playlistmanager/PlaylistManager.h"
#include <KDialog>
#include <KInputDialog>
#include <KLocale>
#include <QUrl>
#include <QAction>
#include <QDir>
#include <QLabel>
#include <QString>
#include <QTimer>
using Playlist::ModelStack;
namespace Playlists {
PlaylistFileProvider::PlaylistFileProvider()
: UserPlaylistProvider()
, m_playlistsLoaded( false )
, m_saveLaterTimer( 0 )
{
//playlists are lazy loaded but we can count how many we'll load already
QStringList keys = loadedPlaylistsConfig().keyList();
foreach( const QString &key, keys )
{
QUrl url( key );
//Don't load these from the config file, they are read from the directory anyway
if( KIO::upUrl(url).matches( QUrl::fromUserInput(Amarok::saveLocation("playlists")), QUrl::StripTrailingSlash ) )
continue;
m_urlsToLoad << url;
}
//also add all files in the $KDEHOME/share/apps/amarok/playlists
QDir playlistDir = QDir( Amarok::saveLocation( "playlists" ), "",
QDir::Name,
QDir::Files | QDir::Readable );
foreach( const QString &file, playlistDir.entryList() )
{
QUrl url( playlistDir.path() );
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + '/' + ( file ));
if( Playlists::isPlaylist( url ) )
m_urlsToLoad << url;
}
}
PlaylistFileProvider::~PlaylistFileProvider()
{
DEBUG_BLOCK
//remove all, well add them again soon
loadedPlaylistsConfig().deleteGroup();
//Write loaded playlists to config file
foreach( Playlists::PlaylistFilePtr playlistFile, m_playlists )
{
QUrl url = playlistFile->uidUrl();
//only save files NOT in "playlists", those are automatically loaded.
if( KIO::upUrl(url).matches( QUrl::fromUserInput(Amarok::saveLocation( "playlists" )), QUrl::StripTrailingSlash ) )
continue;
//debug() << "storing to rc-file: " << url.url();
loadedPlaylistsConfig().writeEntry( url.url(), playlistFile->groups() );
}
loadedPlaylistsConfig().sync();
}
QString
PlaylistFileProvider::prettyName() const
{
return i18n( "Playlist Files on Disk" );
}
QIcon PlaylistFileProvider::icon() const
{
return QIcon::fromTheme( "folder-documents" );
}
int
PlaylistFileProvider::playlistCount() const
{
return m_playlists.count() + m_urlsToLoad.count();
}
Playlists::PlaylistList
PlaylistFileProvider::playlists()
{
Playlists::PlaylistList playlists;
if( !m_playlistsLoaded )
{
//trigger a lazy load the playlists
- QTimer::singleShot(0, this, SLOT(loadPlaylists()) );
+ QTimer::singleShot(0, this, &PlaylistFileProvider::loadPlaylists );
return playlists;
}
foreach( const Playlists::PlaylistFilePtr &playlistFile, m_playlists )
{
Playlists::PlaylistPtr playlist = Playlists::PlaylistPtr::dynamicCast( playlistFile );
if( !playlist.isNull() )
playlists << playlist;
}
return playlists;
}
Playlists::PlaylistPtr
PlaylistFileProvider::save( const Meta::TrackList &tracks, const QString &name )
{
DEBUG_BLOCK
QString filename = name.isEmpty() ? QDateTime::currentDateTime().toString( "ddd MMMM d yy hh-mm") : name;
filename.replace( QLatin1Char('/'), QLatin1Char('-') );
filename.replace( QLatin1Char('\\'), QLatin1Char('-') );
Playlists::PlaylistFormat format = Playlists::getFormat( QUrl::fromUserInput(filename) );
if( format == Playlists::Unknown ) // maybe the name just had a dot in it. We just add .xspf
{
format = Playlists::XSPF;
filename.append( QLatin1String( ".xspf" ) );
}
QUrl path( Amarok::saveLocation( "playlists" ) );
path = path.adjusted(QUrl::StripTrailingSlash);
path.setPath(path.path() + '/' + ( Amarok::vfatPath( filename ) ));
if( QFileInfo( path.toLocalFile() ).exists() )
{
//TODO:request overwrite
return Playlists::PlaylistPtr();
}
Playlists::PlaylistFile *playlistFile = 0;
switch( format )
{
case Playlists::ASX:
playlistFile = new Playlists::ASXPlaylist( path, this );
break;
case Playlists::PLS:
playlistFile = new Playlists::PLSPlaylist( path, this );
break;
case Playlists::M3U:
playlistFile = new Playlists::M3UPlaylist( path, this );
break;
case Playlists::XSPF:
playlistFile = new Playlists::XSPFPlaylist( path, this );
break;
case Playlists::XML:
case Playlists::RAM:
case Playlists::SMIL:
case Playlists::Unknown:
// this should not happen since we set the format to XSPF above.
return Playlists::PlaylistPtr();
}
playlistFile->setName( filename );
playlistFile->addTracks( tracks );
playlistFile->save( true );
Playlists::PlaylistFilePtr playlistPtr( playlistFile );
m_playlists << playlistPtr;
//just in case there wasn't one loaded before.
m_playlistsLoaded = true;
Playlists::PlaylistPtr playlist( playlistFile );
emit playlistAdded( playlist );
return playlist;
}
bool
PlaylistFileProvider::import( const QUrl &path )
{
DEBUG_BLOCK
if( !path.isValid() )
{
error() << "path is not valid!";
return false;
}
foreach( Playlists::PlaylistFilePtr playlistFile, m_playlists )
{
if( !playlistFile )
{
error() << "Could not cast down.";
error() << "m_playlists got corrupted! " << __FILE__ << ":" << __LINE__;
continue;
}
if( playlistFile->uidUrl() == path )
{
debug() << "Playlist " << path.path() << " was already imported";
return false;
}
}
debug() << "Importing playlist file " << path;
if( path == QUrl::fromLocalFile(Amarok::defaultPlaylistPath()) )
{
error() << "trying to load saved session playlist at %s" << path.path();
return false;
}
Playlists::PlaylistFilePtr playlistFile = Playlists::loadPlaylistFile( path, this );
if( !playlistFile )
return false;
m_playlists << playlistFile;
//just in case there wasn't one loaded before.
m_playlistsLoaded = true;
emit playlistAdded( PlaylistPtr( playlistFile.data() ) );
return true;
}
void
PlaylistFileProvider::renamePlaylist( Playlists::PlaylistPtr playlist, const QString &newName )
{
DEBUG_BLOCK
playlist->setName( newName );
}
bool
PlaylistFileProvider::deletePlaylists( const Playlists::PlaylistList &playlists )
{
Playlists::PlaylistFileList playlistFiles;
foreach( Playlists::PlaylistPtr playlist, playlists )
{
Playlists::PlaylistFilePtr playlistFile =
Playlists::PlaylistFilePtr::dynamicCast( playlist );
if( !playlistFile.isNull() )
playlistFiles << playlistFile;
}
return deletePlaylistFiles( playlistFiles );
}
bool
PlaylistFileProvider::deletePlaylistFiles( Playlists::PlaylistFileList playlistFiles )
{
foreach( Playlists::PlaylistFilePtr playlistFile, playlistFiles )
{
m_playlists.removeAll( playlistFile );
loadedPlaylistsConfig().deleteEntry( playlistFile->uidUrl().url() );
QFile::remove( playlistFile->uidUrl().path() );
emit playlistRemoved( Playlists::PlaylistPtr::dynamicCast( playlistFile ) );
}
loadedPlaylistsConfig().sync();
return true;
}
void
PlaylistFileProvider::loadPlaylists()
{
if( m_urlsToLoad.isEmpty() )
return;
//arbitrary number of playlists to load during one mainloop run: 5
for( int i = 0; i < qMin( m_urlsToLoad.count(), 5 ); i++ )
{
QUrl url = m_urlsToLoad.takeFirst();
QString groups = loadedPlaylistsConfig().readEntry( url.url() );
Playlists::PlaylistFilePtr playlist = Playlists::loadPlaylistFile( url, this );
if( !playlist )
{
Amarok::Components::logger()->longMessage(
i18n("The playlist file \"%1\" could not be loaded.", url.fileName() ),
Amarok::Logger::Error
);
continue;
}
if( !groups.isEmpty() && playlist->isWritable() )
playlist->setGroups( groups.split( ',', QString::SkipEmptyParts ) );
m_playlists << playlist;
emit playlistAdded( PlaylistPtr( playlist.data() ) );
}
//give the mainloop time to run
if( !m_urlsToLoad.isEmpty() )
- QTimer::singleShot( 0, this, SLOT(loadPlaylists()) );
+ QTimer::singleShot( 0, this, &PlaylistFileProvider::loadPlaylists );
}
void
PlaylistFileProvider::saveLater( Playlists::PlaylistFilePtr playlist )
{
//WARNING: this assumes the playlistfile uses it's m_url for uidUrl
if( playlist->uidUrl().isEmpty() )
return;
if( !m_saveLaterPlaylists.contains( playlist ) )
m_saveLaterPlaylists << playlist;
if( !m_saveLaterTimer )
{
m_saveLaterTimer = new QTimer( this );
m_saveLaterTimer->setSingleShot( true );
m_saveLaterTimer->setInterval( 0 );
- connect( m_saveLaterTimer, SIGNAL(timeout()), SLOT(slotSaveLater()) );
+ connect( m_saveLaterTimer, &QTimer::timeout, this, &PlaylistFileProvider::slotSaveLater );
}
m_saveLaterTimer->start();
}
void
PlaylistFileProvider::slotSaveLater() //SLOT
{
foreach( Playlists::PlaylistFilePtr playlist, m_saveLaterPlaylists )
{
playlist->save( true ); //TODO: read relative type when loading
}
m_saveLaterPlaylists.clear();
}
KConfigGroup
PlaylistFileProvider::loadedPlaylistsConfig() const
{
return Amarok::config( "Loaded Playlist Files" );
}
} //namespace Playlists
diff --git a/src/scanner/AbstractDirectoryWatcher.cpp b/src/scanner/AbstractDirectoryWatcher.cpp
index e572764941..e474b38479 100644
--- a/src/scanner/AbstractDirectoryWatcher.cpp
+++ b/src/scanner/AbstractDirectoryWatcher.cpp
@@ -1,195 +1,195 @@
/****************************************************************************************
* Copyright (c) 2010-2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AbstractDirectoryWatcher"
#include "AbstractDirectoryWatcher.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include <KDirWatch>
#include <QTimer>
#include <QFileInfo>
#include <QMutexLocker>
static const int WATCH_INTERVAL = 60 * 1000; // = 60 seconds
static const int DELAYED_SCAN_INTERVAL = 2 * 1000; // = 2 seconds
AbstractDirectoryWatcher::AbstractDirectoryWatcher()
: QObject()
, ThreadWeaver::Job()
, m_delayedScanTimer( 0 )
, m_watcher( 0 )
, m_aborted( false )
, m_blocked( false )
{
m_delayedScanTimer = new QTimer( this );
m_delayedScanTimer->setSingleShot( true );
- connect( m_delayedScanTimer, SIGNAL(timeout()), this, SLOT(delayTimeout()) );
+ connect( m_delayedScanTimer, &QTimer::timeout, this, &AbstractDirectoryWatcher::delayTimeout );
// -- create a new watcher
m_watcher = new KDirWatch( this );
- connect( m_watcher, SIGNAL(dirty(QString)),
- this, SLOT(delayedScan(QString)) );
- connect( m_watcher, SIGNAL(created(QString)),
- this, SLOT(delayedScan(QString)) );
- connect( m_watcher, SIGNAL(deleted(QString)),
- this, SLOT(delayedScan(QString)) );
+ connect( m_watcher, &KDirWatch::dirty,
+ this, &AbstractDirectoryWatcher::delayedScan );
+ connect( m_watcher, &KDirWatch::created,
+ this, &AbstractDirectoryWatcher::delayedScan );
+ connect( m_watcher, &KDirWatch::deleted,
+ this, &AbstractDirectoryWatcher::delayedScan );
m_watcher->startScan( false );
}
void
AbstractDirectoryWatcher::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
// TODO: re-create the watcher if scanRecursively has changed
QSet<QString> oldWatchDirs;
forever {
m_mutex.lock();
m_waitCondition.wait( &m_mutex, WATCH_INTERVAL );
if( m_aborted )
break;
// -- start scan
if( AmarokConfig::monitorChanges() )
{
if( m_watcher->isStopped() )
{
// Check if directories changed while we didn't have a watcher
QList<QUrl> urls;
foreach( const QString &path, collectionFolders() )
{
urls.append( QUrl::fromLocalFile( path ) );
}
emit requestScan( urls, GenericScanManager::PartialUpdateScan );
m_watcher->startScan( true );
}
// -- update the KDirWatch with the current set of directories
QSet<QString> dirs = collectionFolders().toSet();
// - add new
QSet<QString> newDirs = dirs - oldWatchDirs;
foreach( const QString& dir, newDirs )
{
m_watcher->addDir( dir,
AmarokConfig::scanRecursively() ?
KDirWatch::WatchSubDirs : KDirWatch::WatchDirOnly );
}
// - remove old
QSet<QString> removeDirs = oldWatchDirs - dirs;
foreach( const QString& dir, removeDirs )
{
m_watcher->removeDir( dir );
}
oldWatchDirs = dirs;
}
else
{
if( !m_watcher->isStopped() )
m_watcher->stopScan();
}
m_mutex.unlock();
}
}
void
AbstractDirectoryWatcher::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
AbstractDirectoryWatcher::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
AbstractDirectoryWatcher::abort()
{
m_aborted = true;
m_waitCondition.wakeAll();
}
void
AbstractDirectoryWatcher::setBlockScanning( bool block )
{
m_blocked = block;
// send out the old requests
if( !m_blocked )
delayTimeout();
}
void
AbstractDirectoryWatcher::delayTimeout()
{
QMutexLocker locker( &m_dirsMutex );
if( m_blocked || m_aborted )
return;
if( m_scanDirsRequested.isEmpty() )
return;
emit requestScan( m_scanDirsRequested.toList(), GenericScanManager::PartialUpdateScan );
m_scanDirsRequested.clear();
}
void
AbstractDirectoryWatcher::delayedScan( const QString &path )
{
QFileInfo info( path );
if( info.isDir() )
addDirToList( path );
else
addDirToList( info.path() );
m_delayedScanTimer->start( DELAYED_SCAN_INTERVAL );
}
void
AbstractDirectoryWatcher::addDirToList( const QString &directory )
{
QMutexLocker locker( &m_dirsMutex );
debug() << "addDirToList for"<<directory;
m_scanDirsRequested.insert( QUrl::fromUserInput(directory) );
}
diff --git a/src/scanner/AbstractScanResultProcessor.cpp b/src/scanner/AbstractScanResultProcessor.cpp
index 55e663e2fc..4aec06db03 100644
--- a/src/scanner/AbstractScanResultProcessor.cpp
+++ b/src/scanner/AbstractScanResultProcessor.cpp
@@ -1,307 +1,310 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2009-2010 Jeff Mitchell <mitchell@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AbstractScanResultProcessor"
#include "AbstractScanResultProcessor.h"
#include "collectionscanner/Album.h"
#include "collectionscanner/Directory.h"
#include "collectionscanner/Playlist.h"
#include "collectionscanner/Track.h"
#include "core/interfaces/Logger.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "core-impl/collections/support/ArtistHelper.h"
#include "playlistmanager/PlaylistManager.h"
#include "scanner/GenericScanManager.h"
AbstractScanResultProcessor::AbstractScanResultProcessor( GenericScanManager* manager, QObject* parent )
: QObject( parent )
, m_manager( manager )
, m_type( GenericScanManager::PartialUpdateScan )
{
- connect( manager, SIGNAL(started(GenericScanManager::ScanType)),
- SLOT(scanStarted(GenericScanManager::ScanType)) );
- connect( manager, SIGNAL(directoryCount(int)), SLOT(scanDirectoryCount(int)) );
- connect( manager, SIGNAL(directoryScanned(QSharedPointer<CollectionScanner::Directory>)),
- SLOT(scanDirectoryScanned(QSharedPointer<CollectionScanner::Directory>)) );
- connect( manager, SIGNAL(succeeded()), SLOT(scanSucceeded()) );
- connect( manager, SIGNAL(failed(QString)), SLOT(scanFailed(QString)) );
+ connect( manager, &GenericScanManager::started,
+ this, &AbstractScanResultProcessor::scanStarted );
+ connect( manager, &GenericScanManager::directoryCount,
+ this, &AbstractScanResultProcessor::scanDirectoryCount );
+ connect( manager, &GenericScanManager::directoryScanned,
+ this, &AbstractScanResultProcessor::scanDirectoryScanned );
+ connect( manager, &GenericScanManager::succeeded,
+ this, &AbstractScanResultProcessor::scanSucceeded );
+ connect( manager, &GenericScanManager::failed,
+ this, &AbstractScanResultProcessor::scanFailed );
}
AbstractScanResultProcessor::~AbstractScanResultProcessor()
{
cleanupMembers();
}
void
AbstractScanResultProcessor::scanStarted( GenericScanManager::ScanType type )
{
DEBUG_BLOCK;
m_type = type;
// -- show the progress operation for the job
if( Amarok::Components::logger() )
Amarok::Components::logger()->newProgressOperation( this,
i18n( "Scanning music" ),
100,
this,
SLOT(abort()) );
}
void
AbstractScanResultProcessor::scanDirectoryCount( int count )
{
// message( i18np("Found one directory", "Found %1 directories", count ) );
debug() << "got" << count << "directories";
emit totalSteps( count * 2 );
}
void
AbstractScanResultProcessor::scanDirectoryScanned( QSharedPointer<CollectionScanner::Directory> dir )
{
m_directories.append( dir );
emit incrementProgress();
}
void
AbstractScanResultProcessor::scanSucceeded()
{
DEBUG_BLOCK;
// the default for albums with several artists is that it's a compilation
// however, some album names are unlikely to be a compilation
static QStringList nonCompilationAlbumNames;
if( nonCompilationAlbumNames.isEmpty() )
{
nonCompilationAlbumNames
<< "" // don't throw together albums without name. At least not here
<< "Best Of"
<< "Anthology"
<< "Hit collection"
<< "Greatest Hits"
<< "All Time Greatest Hits"
<< "Live";
}
// -- commit the directories
foreach( QSharedPointer<CollectionScanner::Directory> dir, m_directories )
{
commitDirectory( dir );
// -- sort the tracks into albums
QSet<CollectionScanner::Album*> dirAlbums;
QSet<QString> dirAlbumNames;
QList<CollectionScanner::Track*> tracks = dir->tracks();
// check what album names we have
foreach( CollectionScanner::Track* track, dir->tracks() )
{
if( !track->album().isEmpty() )
dirAlbumNames.insert( track->album() );
}
// use the directory name as album name
QString fallbackAlbumName = ( dirAlbumNames.isEmpty() ?
QDir( dir->path() ).dirName() :
QString() );
foreach( CollectionScanner::Track* track, dir->tracks() )
{
CollectionScanner::Album *album = sortTrack( track, fallbackAlbumName );
dirAlbums.insert( album );
dirAlbumNames.insert( track->album() );
}
// if all the tracks from this directory end up in one album
// (or they have at least the same name) then it's likely that an image
// from this directory could be a cover
if( dirAlbumNames.count() == 1 )
(*dirAlbums.begin())->setCovers( dir->covers() );
}
// --- add all albums
QList<QString> keys = m_albumNames.uniqueKeys();
emit totalSteps( m_directories.count() + keys.count() ); // update progress bar
foreach( const QString &key, keys )
{
// --- commit the albums as compilation or normal album
QList<CollectionScanner::Album*> albums = m_albumNames.values( key );
// if we have multiple albums with the same name, check if it
// might be a compilation
for( int i = albums.count() - 1; i >= 0; --i )
{
CollectionScanner::Album *album = albums.at( i );
// commit all albums with a track with the noCompilation flag
if( album->isNoCompilation() ||
nonCompilationAlbumNames.contains( album->name(), Qt::CaseInsensitive ) )
commitAlbum( albums.takeAt( i ) );
}
// only one album left. (that either means all have the same album artist tag
// or none has one. In the last case we try to guess an album artist)
if( albums.count() == 1 )
{
CollectionScanner::Album *album = albums.takeFirst();
// look if all the tracks have the same (guessed) artist.
// It's no compilation.
bool isCompilation = false;
bool firstTrack = true;
QString albumArtist;
foreach( CollectionScanner::Track *track, album->tracks() )
{
QString trackAlbumArtist =
ArtistHelper::bestGuessAlbumArtist( track->albumArtist(),
track->artist(),
track->genre(),
track->composer() );
if( firstTrack )
albumArtist = trackAlbumArtist;
firstTrack = false;
if( trackAlbumArtist.isEmpty() || track->isCompilation() ||
albumArtist != trackAlbumArtist )
isCompilation = true;
}
if( !isCompilation )
album->setArtist( albumArtist );
commitAlbum( album );
}
// several albums with different album artist.
else if( albums.count() > 1 )
{
while( !albums.isEmpty() )
commitAlbum( albums.takeFirst() );
}
emit incrementProgress();
}
// -- now check if some of the tracks are not longer used and also not moved to another directory
foreach( QSharedPointer<CollectionScanner::Directory> dir, m_directories )
if( !dir->isSkipped() )
deleteDeletedTracksAndSubdirs( dir );
// -- delete all not-found directories (special handling for PartialUpdateScan):
deleteDeletedDirectories();
cleanupMembers();
emit endProgressOperation( this );
}
void
AbstractScanResultProcessor::scanFailed( const QString& text )
{
message( text );
cleanupMembers();
emit endProgressOperation( this );
}
void
AbstractScanResultProcessor::abort()
{
m_manager->abort();
}
void
AbstractScanResultProcessor::commitDirectory( QSharedPointer<CollectionScanner::Directory> dir )
{
if( dir->path().isEmpty() )
{
warning() << "got directory with no path from the scanner, not adding";
return;
}
// --- add all playlists
foreach( const CollectionScanner::Playlist &playlist, dir->playlists() )
commitPlaylist( playlist );
}
void
AbstractScanResultProcessor::commitPlaylist( const CollectionScanner::Playlist &playlist )
{
// debug() << "commitPlaylist on " << playlist->path();
if( The::playlistManager() )
The::playlistManager()->import( "file:"+playlist.path() );
}
/** This will just put the tracks into an album.
@param album the name of the target album
@returns true if the track was put into an album
*/
CollectionScanner::Album*
AbstractScanResultProcessor::sortTrack( CollectionScanner::Track *track, const QString &dirName )
{
QString albumName = track->album();
// we allow albums with empty name and nonepty artist, see bug 272471
QString albumArtist = track->albumArtist();
if( track->isCompilation() )
albumArtist.clear(); // no album artist denotes a compilation
if( track->isNoCompilation() && albumArtist.isEmpty() )
albumArtist = ArtistHelper::bestGuessAlbumArtist( track->albumArtist(),
track->artist(),
track->genre(),
track->composer() );
if( albumName.isEmpty() && albumArtist.isEmpty() ) // try harder to set at least one
albumName = dirName;
AlbumKey key( albumName, albumArtist );
CollectionScanner::Album *album;
if( m_albums.contains( key ) )
album = m_albums.value( key );
else
{
album = new CollectionScanner::Album( albumName, albumArtist );
m_albums.insert( key, album );
m_albumNames.insert( albumName, album );
}
album->addTrack( track );
return album;
}
void
AbstractScanResultProcessor::cleanupMembers()
{
// note: qt had problems with CLEAN_ALL macro
QHash<QString, CollectionScanner::Album*>::const_iterator i = m_albumNames.constBegin();
while (i != m_albumNames.constEnd()) {
delete i.value();
++i;
}
m_albumNames.clear();
m_albums.clear();
m_directories.clear();
}
diff --git a/src/scanner/GenericScanManager.cpp b/src/scanner/GenericScanManager.cpp
index f18fee964e..66833c69ff 100644
--- a/src/scanner/GenericScanManager.cpp
+++ b/src/scanner/GenericScanManager.cpp
@@ -1,163 +1,165 @@
/****************************************************************************************
* Copyright (c) 2003-2008 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2008-2009 Jeff Mitchell <mitchell@kde.org> *
* Copyright (c) 2010-2011 Ralf Engels <ralf-engels@gmx.de> *
* Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "GenericScanManager"
#include "GenericScanManager.h"
#include "core/support/Debug.h"
#include "scanner/GenericScannerJob.h"
#include <ThreadWeaver/Queue>
#include <QFileInfo>
#include <QSharedPointer>
GenericScanManager::GenericScanManager( QObject *parent )
: QObject( parent )
, m_scannerJob( )
{
qRegisterMetaType<GenericScanManager::ScanType>( "GenericScanManager::ScanType" );
qRegisterMetaType<QSharedPointer<CollectionScanner::Directory> >( "QSharedPointer<CollectionScanner::Directory>" );
}
GenericScanManager::~GenericScanManager()
{
abort();
}
bool
GenericScanManager::isRunning()
{
QMutexLocker locker( &m_mutex );
return m_scannerJob;
}
QString
GenericScanManager::getBatchFile( const QStringList& scanDirsRequested )
{
Q_UNUSED( scanDirsRequested );
return QString();
}
void
GenericScanManager::requestScan( QList<QUrl> directories, ScanType type )
{
DEBUG_BLOCK;
QMutexLocker locker( &m_mutex );
if( m_scannerJob )
{
//TODO: add to queue requests
error() << "Scanner already running, not starting another instance.";
return;
}
QSet<QString> scanDirsSet;
foreach( const QUrl &url, directories )
{
if( !url.isLocalFile() )
{
warning() << "scan of non-local directory" << url << "requested, skipping it.";
continue;
}
QString path = url.adjusted(QUrl::StripTrailingSlash).path();
if( !QFileInfo( path ).isDir() )
{
warning() << "scan of a non-directory" << path << "requested, skipping it.";
continue;
}
//TODO: most local path
scanDirsSet << path;
}
// we cannot skip the scan even for empty scanDirsSet and non-partial scan, bug 316216
if( scanDirsSet.isEmpty() && type == PartialUpdateScan )
return; // nothing to do
m_scannerJob = new GenericScannerJob( this, scanDirsSet.toList(), type );
connectSignalsToJob();
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(m_scannerJob) );
}
void
GenericScanManager::requestImport( QIODevice *input, ScanType type )
{
QMutexLocker locker( &m_mutex );
if( m_scannerJob )
{
//TODO: add to queue requests
error() << "Scanner already running";
return;
}
m_scannerJob = new GenericScannerJob( this, input, type );
connectSignalsToJob();
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(m_scannerJob) );
}
void
GenericScanManager::abort()
{
QMutexLocker locker( &m_mutex );
if( m_scannerJob )
m_scannerJob->abort();
}
void
GenericScanManager::slotSucceeded()
{
{
QMutexLocker locker( &m_mutex );
m_scannerJob = 0;
}
emit succeeded();
}
void
GenericScanManager::slotFailed( const QString& message )
{
{
QMutexLocker locker( &m_mutex );
m_scannerJob = 0;
}
emit failed( message );
}
void
GenericScanManager::connectSignalsToJob()
{
// we used to have direct connections here, but that caused too much work being done
// int the non-main thread, even in code that wasn't thread-safe, which lead to
// crashes (bug 319835) and other potential data races
- connect( m_scannerJob, SIGNAL(started(GenericScanManager::ScanType)),
- SIGNAL(started(GenericScanManager::ScanType)) );
- connect( m_scannerJob, SIGNAL(directoryCount(int)), SIGNAL(directoryCount(int)) );
- connect( m_scannerJob, SIGNAL(directoryScanned(QSharedPointer<CollectionScanner::Directory>)),
- SIGNAL(directoryScanned(QSharedPointer<CollectionScanner::Directory>)) );
-
- connect( m_scannerJob, SIGNAL(succeeded()), SLOT(slotSucceeded()) );
- connect( m_scannerJob, SIGNAL(failed(QString)), SLOT(slotFailed(QString)) );
+ connect( m_scannerJob, QOverload<ScanType>::of(&GenericScannerJob::started),
+ this, &GenericScanManager::started );
+ connect( m_scannerJob, &GenericScannerJob::directoryCount,
+ this, &GenericScanManager::directoryCount);
+ connect( m_scannerJob, &GenericScannerJob::directoryScanned,
+ this, &GenericScanManager::directoryScanned );
+ connect( m_scannerJob, &GenericScannerJob::succeeded,
+ this, &GenericScanManager::slotSucceeded );
+ connect( m_scannerJob, QOverload<QString>::of(&GenericScannerJob::failed),
+ this, &GenericScanManager::slotFailed );
}
diff --git a/src/scanner/GenericScannerJob.cpp b/src/scanner/GenericScannerJob.cpp
index a0bc26466f..43a6d4d88a 100644
--- a/src/scanner/GenericScannerJob.cpp
+++ b/src/scanner/GenericScannerJob.cpp
@@ -1,428 +1,428 @@
/****************************************************************************************
* Copyright (c) 2003-2008 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2008-2009 Jeff Mitchell <mitchell@kde.org> *
* Copyright (c) 2010-2011 Ralf Engels <ralf-engels@gmx.de> *
* Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org> *
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "GenericScannerJob"
#include "GenericScannerJob.h"
#include "App.h"
#include "GenericScanManager.h"
#include "core/support/Debug.h"
#include "collectionscanner/ScanningState.h"
#include <KProcess>
#include <QFile>
#include <QSharedMemory>
#include <QStandardPaths>
#include <QUuid>
static const int MAX_RESTARTS = 40;
static const int SHARED_MEMORY_SIZE = 1024 * 1024; // 1 MB shared memory
GenericScannerJob::GenericScannerJob( GenericScanManager* manager,
QStringList scanDirsRequested,
GenericScanManager::ScanType type,
bool recursive, bool detectCharset )
: QObject()
, ThreadWeaver::Job( )
, m_manager( manager )
, m_type( type )
, m_scanDirsRequested( scanDirsRequested )
, m_input( 0 )
, m_restartCount( 0 )
, m_abortRequested( false )
, m_scanner( 0 )
, m_scannerStateMemory( 0 )
, m_recursive( recursive )
, m_charsetDetect( detectCharset )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)),
- this, SLOT(deleteLater()) ); // auto delete
+ connect( this, &GenericScannerJob::done,
+ this, &GenericScannerJob::deleteLater ); // auto delete
}
GenericScannerJob::GenericScannerJob( GenericScanManager* manager,
QIODevice *input,
GenericScanManager::ScanType type )
: QObject()
, ThreadWeaver::Job( )
, m_manager( manager )
, m_type( type )
, m_input( input )
, m_restartCount( 0 )
, m_abortRequested( false )
, m_scanner( 0 )
, m_scannerStateMemory( 0 )
, m_recursive( true )
, m_charsetDetect( false )
{
- connect( this, SIGNAL(done(ThreadWeaver::JobPointer)),
- this, SLOT(deleteLater()) ); // auto delete
+ connect( this, &GenericScannerJob::done,
+ this, &GenericScannerJob::deleteLater ); // auto delete
}
GenericScannerJob::~GenericScannerJob()
{
delete m_scanner;
delete m_scannerStateMemory;
if( !m_batchfilePath.isEmpty() )
QFile( m_batchfilePath ).remove();
}
void
GenericScannerJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
// -- initialize the input
// - from io device
if( m_input )
{
m_reader.setDevice( m_input );
}
// - from process
else
{
if( !createScannerProcess() )
return;
}
emit started( m_type );
// -- read the input and loop
bool finished = false;
do
{
// -- check if we were aborted, have finished or need to wait for new data
{
QMutexLocker locker( &m_mutex );
if( m_abortRequested )
{
debug() << "Aborting ScannerJob";
emit failed( i18n( "Abort for scanner requested" ) );
closeScannerProcess();
return;
}
}
if( m_scanner )
{
if( m_reader.atEnd() )
getScannerOutput();
if( m_scanner->exitStatus() != QProcess::NormalExit )
{
if( !restartScannerProcess() )
return;
}
}
// -- scan as many directory tags as we added to the data
finished = parseScannerOutput();
} while( !finished &&
(!m_reader.hasError() || m_reader.error() == QXmlStreamReader::PrematureEndOfDocumentError) );
{
QMutexLocker locker( &m_mutex );
if( !finished && m_reader.hasError() )
{
warning() << "Aborting ScannerJob with error" << m_reader.errorString();
emit failed( i18n( "Aborting scanner with error: %1", m_reader.errorString() ) );
closeScannerProcess();
return;
}
else
{
emit succeeded();
closeScannerProcess();
return;
}
}
}
void
GenericScannerJob::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void
GenericScannerJob::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
GenericScannerJob::abort()
{
QMutexLocker locker( &m_mutex );
m_abortRequested = true;
}
QString
GenericScannerJob::scannerPath()
{
// Defined in the tests so we use the recently built scanner for testing
const QString overridePath = qApp->property( "overrideUtilitiesPath" ).toString();
QString path;
if( overridePath.isEmpty() ) // Not running a test
{
path = QStandardPaths::findExecutable( "amarokcollectionscanner" );
// TODO: Not sure this is still useful...
// If the binary is not in $PATH, then search in the application folder too
if( path.isEmpty() )
path = App::applicationDirPath() + "/amarokcollectionscanner";
}
else
{
// Running a test, use the path + append collectionscanner
path = overridePath + "/collectionscanner/amarokcollectionscanner";
}
if( !QFile::exists( path ) )
{
error() << "Cannot find amarokcollectionscanner! Check your install";
emit failed( i18n( "Could not find amarokcollectionscanner!" ) );
return QString();
}
return path;
}
bool
GenericScannerJob::createScannerProcess( bool restart )
{
// -- create the shared memory
if( !m_scannerStateMemory && !restart )
{
QString sharedMemoryKey = "AmarokScannerMemory"+QUuid::createUuid().toString();
m_scannerStateMemory = new QSharedMemory( sharedMemoryKey );
if( !m_scannerStateMemory->create( SHARED_MEMORY_SIZE ) )
{
warning() << "Unable to create shared memory for collection scanner";
warning() << "Shared Memory error: " << m_scannerStateMemory->errorString();
delete m_scannerStateMemory;
m_scannerStateMemory = 0;
}
}
// -- create the scanner process
KProcess *scanner = new KProcess(); //not parented since in a different thread
scanner->setOutputChannelMode( KProcess::OnlyStdoutChannel );
// debug() << "creating options";
*scanner << scannerPath() << "--idlepriority";
if( m_type != GenericScanManager::FullScan ) // we don't need a batch file for a full scan
m_batchfilePath = m_manager->getBatchFile( m_scanDirsRequested );
if( m_type != GenericScanManager::FullScan )
*scanner << "-i";
if( !m_batchfilePath.isEmpty() )
*scanner << "--batch" << m_batchfilePath;
if( m_recursive )
*scanner << "-r";
if( m_charsetDetect )
*scanner << "-c";
if( restart )
*scanner << "-s";
// debug() << "creating shared memory";
if( m_scannerStateMemory )
*scanner << "--sharedmemory" << m_scannerStateMemory->key();
*scanner << m_scanDirsRequested;
// debug() << "starting";
scanner->start();
if( !scanner->waitForStarted( 5000 ) )
{
delete scanner;
warning() << "Unable to start Amarok collection scanner.";
emit failed( i18n("Unable to start Amarok collection scanner." ) );
return false;
}
// debug() << "finished";
m_scanner = scanner;
return true;
}
bool
GenericScannerJob::restartScannerProcess()
{
if( m_scanner->exitStatus() == QProcess::NormalExit )
return true; // all shiny. no need to restart
m_restartCount++;
warning() << __PRETTY_FUNCTION__ << scannerPath().toLocal8Bit().data()
<< "crashed, restart count is " << m_restartCount;
// -- try to determine the offending file
QStringList badFiles;
if( m_scannerStateMemory )
{
using namespace CollectionScanner;
ScanningState scanningState;
scanningState.setKey( m_scannerStateMemory->key() );
scanningState.readFull();
badFiles << scanningState.badFiles();
// yes, the last file is also bad, CollectionScanner only adds it after restart
badFiles << scanningState.lastFile();
debug() << __PRETTY_FUNCTION__ << "lastDirectory" << scanningState.lastDirectory();
debug() << __PRETTY_FUNCTION__ << "lastFile" << scanningState.lastFile();
}
else
debug() << __PRETTY_FUNCTION__ << "m_scannerStateMemory is null";
// -- delete the old scanner
delete m_scanner;
m_scanner = 0;
if( m_restartCount >= MAX_RESTARTS )
{
debug() << __PRETTY_FUNCTION__ << "Following files made amarokcollectionscanner (or TagLib) crash:";
foreach( const QString &file, badFiles )
debug() << __PRETTY_FUNCTION__ << file;
// TODO: this message doesn't seem to be propagated to the UI
QString text = i18n( "The collection scan had to be aborted. Too many crashes (%1) "
"were encountered during the scan. Following files caused the crashes:\n\n%2",
m_restartCount, badFiles.join( "\n" ) );
emit failed( text );
return false;
}
createScannerProcess( true );
return (m_scanner != 0);
}
void
GenericScannerJob::closeScannerProcess()
{
if( !m_scanner )
return;
m_scanner->close();
m_scanner->waitForFinished(); // waits at most 3 seconds
delete m_scanner;
m_scanner = 0;
}
bool
GenericScannerJob::parseScannerOutput()
{
// DEBUG_BLOCK;
while( !m_reader.atEnd() )
{
// -- check if we were aborted, have finished or need to wait for new data
{
QMutexLocker locker( &m_mutex );
if( m_abortRequested )
return false;
}
m_reader.readNext();
if( m_reader.hasError() )
{
return false;
}
else if( m_reader.isStartElement() )
{
QStringRef name = m_reader.name();
if( name == "scanner" )
{
int totalCount = m_reader.attributes().value( "count" ).toString().toInt();
emit directoryCount( totalCount );
}
else if( name == "directory" )
{
QSharedPointer<CollectionScanner::Directory> dir( new CollectionScanner::Directory( &m_reader ) );
emit directoryScanned( dir );
}
else
{
warning() << "Unexpected xml start element"<<name<<"in input";
m_reader.skipCurrentElement();
}
}
else if( m_reader.isEndElement() )
{
if( m_reader.name() == "scanner" ) // ok. finished
return true;
}
else if( m_reader.isEndDocument() )
{
return true;
}
}
return false;
}
void
GenericScannerJob::getScannerOutput()
{
if( !m_scanner->waitForReadyRead( -1 ) )
return;
QByteArray newData = m_scanner->readAll();
m_incompleteTagBuffer += newData;
int index = m_incompleteTagBuffer.lastIndexOf( "</scanner>" );
if( index >= 0 )
{
// append new data (we need to be locked. the reader is probalby not thread save)
m_reader.addData( QString( m_incompleteTagBuffer.left( index + 10 ) ) );
m_incompleteTagBuffer = m_incompleteTagBuffer.mid( index + 10 );
}
else
{
index = m_incompleteTagBuffer.lastIndexOf( "</directory>" );
if( index >= 0 )
{
// append new data (we need to be locked. the reader is probalby not thread save)
m_reader.addData( QString( m_incompleteTagBuffer.left( index + 12 ) ) );
m_incompleteTagBuffer = m_incompleteTagBuffer.mid( index + 12 );
}
}
}
diff --git a/src/scripting/scriptconsole/ScriptConsole.cpp b/src/scripting/scriptconsole/ScriptConsole.cpp
index a34a299664..52b0c2ecb5 100644
--- a/src/scripting/scriptconsole/ScriptConsole.cpp
+++ b/src/scripting/scriptconsole/ScriptConsole.cpp
@@ -1,425 +1,426 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ScriptConsole.h"
#define DEBUG_PREFIX "ScriptConsole"
#include "core/support/Debug.h"
#include "MainWindow.h"
#include "ScriptEditorDocument.h"
#include "ScriptConsoleItem.h"
#include <QApplication>
#include <QFileDialog>
#include <QListWidget>
#include <QMenuBar>
#include <QScriptEngine>
#include <QSettings>
#include <QTemporaryFile>
#include <QToolBar>
#include <QAction>
#include <KMessageBox>
#include <KStandardDirs>
#include <KTextEditor/Editor>
#include <KTextEditor/View>
#include <KLocalizedString>
#include <KGlobal>
using namespace AmarokScript;
using namespace ScriptConsoleNS;
QWeakPointer<ScriptConsole> ScriptConsole::s_instance;
ScriptConsole*
ScriptConsole::instance()
{
if( !s_instance )
s_instance = new ScriptConsole( The::mainWindow() );
return s_instance.data();
}
//private
ScriptConsole::ScriptConsole( QWidget *parent )
: QMainWindow( parent, Qt::Window )
{
m_editor = KTextEditor::Editor::instance();
if ( !m_editor )
{
KMessageBox::error( 0, i18n("A KDE text-editor component could not be found.\n"
"Please check your KDE installation. Exiting the console!") );
deleteLater();
return;
}
m_scriptListDock = new ScriptListDockWidget( this );
m_debugger = new QScriptEngineDebugger( this );
setDockNestingEnabled( true );
setWindowTitle( i18n( "Script Console" ) );
setObjectName( "scriptconsole" );
m_debugger->setAutoShowStandardWindow( false );
m_codeWidget = getWidget( "Code", QScriptEngineDebugger::CodeWidget );
addDockWidget( Qt::BottomDockWidgetArea, m_codeWidget );
QList<QDockWidget*> debugWidgets = QList<QDockWidget*>() << getWidget( i18n( "Console" ), QScriptEngineDebugger::ConsoleWidget )
<< getWidget( i18n( "Error" ), QScriptEngineDebugger::ErrorLogWidget )
<< getWidget( i18n( "Debug Output" ), QScriptEngineDebugger::DebugOutputWidget )
<< getWidget( i18n( "Loaded Scripts" ), QScriptEngineDebugger::ScriptsWidget )
<< getWidget( i18n( "Breakpoints" ), QScriptEngineDebugger::BreakpointsWidget )
<< getWidget( i18n( "Stack" ), QScriptEngineDebugger::StackWidget )
<< getWidget( i18n( "Locals" ), QScriptEngineDebugger::LocalsWidget );
foreach( QDockWidget *widget, debugWidgets )
{
addDockWidget( Qt::BottomDockWidgetArea, widget );
}
addDockWidget( Qt::BottomDockWidgetArea, m_scriptListDock );
tabifyDockWidget( debugWidgets[0], debugWidgets[1] );
tabifyDockWidget( debugWidgets[1], debugWidgets[2] );
tabifyDockWidget( debugWidgets[3], debugWidgets[4] );
tabifyDockWidget( debugWidgets[5], debugWidgets[6] );
QMenuBar *bar = new QMenuBar( this );
setMenuBar( bar );
bar->addMenu( m_debugger->createStandardMenu( this ) );
QToolBar *toolBar = m_debugger->createStandardToolBar( this );
QAction *action = new QAction( i18n( "Stop" ), this );
action->setIcon( QApplication::style()->standardIcon( QStyle::SP_MediaStop ) );
- connect( action, SIGNAL(toggled(bool)), SLOT(slotAbortEvaluation()) );
+ connect( action, &QAction::toggled, this, &ScriptConsole::slotAbortEvaluation );
toolBar->addAction( action );
action = new QAction( QIcon::fromTheme( "media-playback-start" ), i18n("Execute Script"), this );
action->setShortcut( Qt::CTRL + Qt::Key_Enter );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotExecuteNewScript()) );
+ connect( action, &QAction::triggered, this, &ScriptConsole::slotExecuteNewScript );
toolBar->addAction( action );
action = new QAction( QIcon::fromTheme( "document-new" ), i18n( "&New Script" ), this );
action->setShortcut( Qt::CTRL + Qt::Key_N );
toolBar->addAction( action );
- connect( action, SIGNAL(triggered(bool)), SLOT(slotNewScript()) );
+ connect( action, &QAction::triggered, this, &ScriptConsole::slotNewScript );
action = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete Script" ), this );
toolBar->addAction( action );
- connect( action, SIGNAL(triggered(bool)), m_scriptListDock, SLOT(removeCurrentScript()) );
+ connect( action, &QAction::triggered, m_scriptListDock, &ScriptListDockWidget::removeCurrentScript );
action = new QAction( i18n( "&Clear All Scripts" ), this );
toolBar->addAction( action );
- connect( action, SIGNAL(triggered(bool)), m_scriptListDock, SLOT(clear()) );
+ connect( action, &QAction::triggered, m_scriptListDock, &ScriptListDockWidget::clear );
action = new QAction( i18n("Previous Script"), this );
action->setShortcut( QKeySequence::MoveToPreviousPage );
- connect( action, SIGNAL(triggered(bool)), m_scriptListDock, SLOT(prev()) );
+ connect( action, &QAction::triggered, m_scriptListDock, &ScriptListDockWidget::prev );
toolBar->addAction( action );
action = new QAction( i18n("Next Script"), this );
action->setShortcut( QKeySequence::MoveToNextPage );
- connect( action, SIGNAL(triggered(bool)), m_scriptListDock, SLOT(next()) );
+ connect( action, &QAction::triggered, m_scriptListDock, &ScriptListDockWidget::next );
toolBar->addAction( action );
addToolBar( toolBar );
QMenu *viewMenu = new QMenu( this );
viewMenu->setTitle( i18n( "&View" ) );
foreach( QDockWidget *dockWidget, findChildren<QDockWidget*>() )
{
if( dockWidget->parentWidget() == this )
viewMenu->addAction( dockWidget->toggleViewAction() );
}
menuBar()->addMenu( viewMenu );
addDockWidget( Qt::BottomDockWidgetArea, m_scriptListDock );
- connect( m_scriptListDock, SIGNAL(edit(ScriptConsoleItem*)), SLOT(slotEditScript(ScriptConsoleItem*)) );
- connect( m_scriptListDock, SIGNAL(currentItemChanged(ScriptConsoleItem*)), SLOT(setCurrentScriptItem(ScriptConsoleItem*)) );
+ connect( m_scriptListDock, &ScriptListDockWidget::edit, this, &ScriptConsole::slotEditScript );
+ connect( m_scriptListDock, &ScriptListDockWidget::currentItemChanged, this, &ScriptConsole::setCurrentScriptItem );
QListWidgetItem *item = new QListWidgetItem( "The Amarok Script Console allows you to easily execute"
"JavaScript with access to all functions\nand methods you would"
"have in an Amarok script.\nInformation on scripting for Amarok is"
"available at:\nhttp://community.kde.org/Amarok/Development#Scripting"
"\nExecute code: CTRL-Enter\nBack in code history: Page Up"
"\nForward in code history: Page Down"
"See the debugger manual at: <link here>"
, 0 );
item->setFlags( Qt::NoItemFlags );
m_scriptListDock->addItem( item );
QSettings settings( "KDE", "Amarok" );
settings.beginGroup( "ScriptConsole" );
restoreGeometry( settings.value("geometry").toByteArray() );
m_savePath = settings.value("savepath").toString();
settings.endGroup();
if( m_savePath.isEmpty() )
m_savePath = QUrl( KStandardDirs::locateLocal( "data", "amarok/scriptconsole/" ) ).path();
slotNewScript();
- connect( m_debugger, SIGNAL(evaluationSuspended()), SLOT(slotEvaluationSuspended()) );
- connect( m_debugger, SIGNAL(evaluationResumed()), SLOT(slotEvaluationResumed()) );
+ connect( m_debugger, &QScriptEngineDebugger::evaluationSuspended, this, &ScriptConsole::slotEvaluationSuspended );
+ connect( m_debugger, &QScriptEngineDebugger::evaluationResumed, this, &ScriptConsole::slotEvaluationResumed );
show();
raise();
}
void
ScriptConsole::slotExecuteNewScript()
{
if( m_scriptItem.data()->document()->text().isEmpty() )
return;
QScriptSyntaxCheckResult syntaxResult = m_scriptItem.data()->engine()->checkSyntax( m_scriptItem.data()->document()->text() );
if( QScriptSyntaxCheckResult::Valid != syntaxResult.state() )
{
debug() << "Syntax error: " << syntaxResult.errorLineNumber() << syntaxResult.errorMessage();
KTextEditor::View *view = dynamic_cast<KTextEditor::View*>( m_codeWidget->widget() );
ScriptEditorDocument::highlight( view, syntaxResult.errorLineNumber(), QColor( 255, 0, 0 ) );
int response = KMessageBox::warningContinueCancel( this, i18n( "Syntax error at line %1, continue anyway?\nError: %2",
syntaxResult.errorLineNumber(), syntaxResult.errorMessage() ),
i18n( "Syntax Error" ) );
if( response == KMessageBox::Cancel )
return;
}
m_scriptItem.data()->document()->save();
m_codeWidget->setWidget( m_debugger->widget( QScriptEngineDebugger::CodeWidget ) );
m_scriptItem.data()->start( false );
}
void
ScriptConsole::closeEvent( QCloseEvent *event )
{
QSettings settings( "Amarok", "Script Console" );
settings.beginGroup( "ScriptConsole" );
settings.setValue( "geometry", saveGeometry() );
settings.setValue( "savepath", m_savePath );
settings.endGroup();
QMainWindow::closeEvent( event );
deleteLater();
}
void
ScriptConsole::slotEditScript( ScriptConsoleItem *item )
{
if( m_scriptItem.data()->running() && KMessageBox::warningContinueCancel( this, i18n( "This will stop this script! Continue?" ), QString(), KStandardGuiItem::cont()
, KStandardGuiItem::cancel(), "stopRunningScriptWarning" ) == KMessageBox::Cancel )
return;
item->pause();
setCurrentScriptItem( item );
}
ScriptConsoleItem*
ScriptConsole::createScriptItem( const QString &script )
{
if( ( m_savePath.isEmpty() || !QDir( m_savePath ).exists() )
&& ( m_savePath = QFileDialog::getExistingDirectory(this, i18n( "Choose where to save your scripts" ), "~",
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks) ).isEmpty() )
return 0;
QString scriptPath;
QString scriptName;
do
{
scriptName = QString( "Script-%1" ).arg( qrand() );
scriptPath = QString( "%1/%2" ).arg( m_savePath, scriptName );
} while ( QDir( scriptPath ).exists() );
QDir().mkdir( scriptPath );
ScriptEditorDocument *document = new ScriptEditorDocument( this, m_editor->createDocument( 0 ) );
document->setText( script );
ScriptConsoleItem *scriptItem = new ScriptConsoleItem( this, scriptName, "Generic", scriptPath, document );
return scriptItem;
}
ScriptConsole::~ScriptConsole()
{
m_debugger->detach();
}
void
ScriptConsole::slotEvaluationSuspended()
{
if( !m_scriptItem )
{
slotNewScript();
return;
}
debug() << "Is Evaluating() " << m_scriptItem.data()->engine()->isEvaluating();
debug() << "Exception isValid()" << m_scriptItem.data()->engine()->uncaughtException().isValid();
if( m_scriptItem.data()->engine() && m_scriptItem.data()->engine()->uncaughtException().isValid() )
return;
KTextEditor::View *view = m_scriptItem.data()->createEditorView( m_codeWidget );
view->installEventFilter( this );
view->document()->installEventFilter( this );
m_codeWidget->setWidget( view );
}
void
ScriptConsole::slotEvaluationResumed()
{
debug() << "Is Evaluating() " << m_scriptItem.data()->engine()->isEvaluating();
debug() << "Exception isValid()" << m_scriptItem.data()->engine()->uncaughtException().isValid();
if( !m_scriptItem.data()->engine() || !m_scriptItem.data()->engine()->isEvaluating() )
return;
KTextEditor::View *view = m_scriptItem.data()->createEditorView( m_codeWidget );
view->installEventFilter( this );
m_codeWidget->setWidget( view );
}
void
ScriptConsole::slotAbortEvaluation()
{
m_scriptItem.data()->pause();
}
QDockWidget*
ScriptConsole::getWidget( const QString &title, QScriptEngineDebugger::DebuggerWidget widget )
{
QDockWidget *debugWidget = new QDockWidget( title, this );
debugWidget->setWidget( m_debugger->widget( widget ) );
return debugWidget;
}
void
ScriptConsole::setCurrentScriptItem( ScriptConsoleItem *item )
{
if( !item || m_scriptItem.data() == item )
return;
m_debugger->detach();
m_debugger->attachTo( item->engine() );
m_scriptItem = item;
if( item->engine() && item->engine()->isEvaluating() )
{
m_codeWidget->setWidget( m_debugger->widget( QScriptEngineDebugger::CodeWidget ) );
}
else
{
KTextEditor::View *view = item->createEditorView( m_codeWidget );
view->installEventFilter( this );
m_codeWidget->setWidget( view );
}
}
void
ScriptConsole::slotNewScript()
{
ScriptConsoleItem *item = createScriptItem( "" );
m_scriptListDock->addScript( item );
setCurrentScriptItem( item );
}
bool
ScriptConsole::eventFilter( QObject *watched, QEvent *event )
{
Q_UNUSED( watched )
if( event->type() == QEvent::KeyPress )
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
if( keyEvent == QKeySequence::MoveToNextPage )
{
m_scriptListDock->next();
return true;
}
else if( keyEvent == QKeySequence::MoveToPreviousPage )
{
m_scriptListDock->prev();
return true;
}
}
return false;
}
ScriptListDockWidget::ScriptListDockWidget( QWidget *parent )
: QDockWidget( i18n( "Scripts" ), parent )
{
QWidget *widget = new KVBox( this );
setWidget( widget );
m_scriptListWidget = new QListWidget( widget );
m_scriptListWidget->setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
- connect( m_scriptListWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotDoubleClicked(QModelIndex)) );
- connect( m_scriptListWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
- SLOT(slotCurrentItemChanged(QListWidgetItem*,QListWidgetItem*)) );
+ connect( m_scriptListWidget, &QListWidget::doubleClicked,
+ this, &ScriptListDockWidget::slotDoubleClicked );
+ connect( m_scriptListWidget, &QListWidget::currentItemChanged,
+ this, &ScriptListDockWidget::slotCurrentItemChanged );
}
void
ScriptListDockWidget::addScript( ScriptConsoleItem *script )
{
QListWidgetItem *item = new QListWidgetItem( script->name(), 0 );
item->setData( ScriptRole, QVariant::fromValue<ScriptConsoleItem*>( script ) );
m_scriptListWidget->addItem( item );
m_scriptListWidget->setCurrentItem( item );
}
void
ScriptListDockWidget::removeCurrentScript()
{
QListWidgetItem *item = m_scriptListWidget->takeItem( m_scriptListWidget->currentRow() );
ScriptConsoleItem *scriptItem = qvariant_cast<ScriptConsoleItem*>( item->data( ScriptRole ) );
switch( KMessageBox::warningYesNoCancel( this, i18n( "Remove script file from disk?" ), i18n( "Remove Script" ) ) )
{
case KMessageBox::Cancel:
return;
case KMessageBox::Yes:
scriptItem->setClearOnDeletion( true );
default:
break;
}
scriptItem->stop();
scriptItem->deleteLater();
delete item;
}
void
ScriptListDockWidget::slotCurrentItemChanged( QListWidgetItem *newItem, QListWidgetItem *oldItem )
{
Q_UNUSED( oldItem )
emit currentItemChanged( newItem ? qvariant_cast<ScriptConsoleItem*>( newItem->data(ScriptRole) ) : 0 );
}
void
ScriptListDockWidget::slotDoubleClicked( const QModelIndex &index )
{
emit edit( qvariant_cast<ScriptConsoleItem*>( index.data(ScriptRole) ) );
}
void
ScriptListDockWidget::clear()
{
if( sender() && KMessageBox::warningContinueCancel( 0, i18n("Are you absolutely certain?") ) == KMessageBox::Cancel )
return;
for( int i = 0; i<m_scriptListWidget->count(); ++i )
qvariant_cast<ScriptConsoleItem*>( m_scriptListWidget->item( i )->data( ScriptRole ) )->deleteLater();
m_scriptListWidget->clear();
}
void
ScriptListDockWidget::addItem( QListWidgetItem *item )
{
m_scriptListWidget->addItem( item );
}
ScriptListDockWidget::~ScriptListDockWidget()
{
clear();
}
void
ScriptListDockWidget::next()
{
int currentRow = m_scriptListWidget->currentRow();
m_scriptListWidget->setCurrentRow( currentRow > 1 ? currentRow - 1 : currentRow );
}
void
ScriptListDockWidget::prev()
{
int currentRow = m_scriptListWidget->currentRow();
m_scriptListWidget->setCurrentRow( currentRow + 1 < m_scriptListWidget->count() ? currentRow + 1 : currentRow );
}
diff --git a/src/scripting/scriptengine/AmarokCollectionViewScript.cpp b/src/scripting/scriptengine/AmarokCollectionViewScript.cpp
index 19c133e13b..469f2009b7 100644
--- a/src/scripting/scriptengine/AmarokCollectionViewScript.cpp
+++ b/src/scripting/scriptengine/AmarokCollectionViewScript.cpp
@@ -1,420 +1,421 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "CollectionViewScript"
#include "AmarokCollectionViewScript.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include "browsers/CollectionTreeView.h"
#include "browsers/collectionbrowser/CollectionWidget.h"
#include "browsers/collectionbrowser/CollectionBrowserTreeView.h"
#include "browsers/CollectionTreeItem.h"
#include "browsers/CollectionTreeItemModelBase.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/collections/support/TextualQueryFilter.h"
#include "MainWindow.h"
#include "ScriptingDefines.h"
#include "widgets/SearchWidget.h"
#include <QMenu>
#include <QMetaEnum>
#include <QScriptEngine>
Q_DECLARE_METATYPE( QAction* )
Q_DECLARE_METATYPE( QList<QAction*> )
using namespace AmarokScript;
QMap<QString, AmarokCollectionViewScript*> AmarokCollectionViewScript::s_instances;
QWeakPointer<Selection> AmarokCollectionViewScript::s_selection;
AmarokCollectionViewScript::AmarokCollectionViewScript( AmarokScriptEngine *engine, const QString &scriptName )
: QObject( engine )
, m_collectionWidget( The::mainWindow()->collectionBrowser() )
, m_engine( engine )
, m_scriptName( scriptName )
, m_categoryEnum( metaObject()->enumerator( metaObject()->indexOfEnumerator("Category") ) )
{
QScriptValue scriptObject = engine->newQObject( this, QScriptEngine::AutoOwnership,
QScriptEngine::ExcludeSuperClassContents );
QScriptValue windowObject = engine->globalObject().property( "Amarok" ).property( "Window" );
Q_ASSERT( !windowObject.isUndefined() );
windowObject.setProperty( "CollectionView", scriptObject );
const QMetaEnum typeEnum = CollectionTreeItem::staticMetaObject.enumerator( CollectionTreeItem::staticMetaObject.indexOfEnumerator( "Type" ) );
Q_ASSERT( typeEnum.isValid() );
scriptObject.setProperty( "Type", engine->enumObject( typeEnum ) );
Q_ASSERT( m_categoryEnum.isValid() );
scriptObject.setProperty( "Category", engine->enumObject( m_categoryEnum ) );
qScriptRegisterMetaType<CollectionTreeItem*>( engine, CollectionViewItem::toScriptValue, fromScriptValue<CollectionTreeItem*, CollectionViewItem> );
engine->registerArrayType< QList<CollectionTreeItem*> >();
engine->registerArrayType<QActionList>();
s_instances[m_scriptName] = this;
connect( The::mainWindow()->collectionBrowser()->searchWidget(), SIGNAL(filterChanged(QString)), SIGNAL(filterChanged(QString)) );
}
AmarokCollectionViewScript::~AmarokCollectionViewScript()
{
s_instances.remove( m_scriptName );
if( s_instances.isEmpty() )
delete s_selection.data();
}
void
AmarokCollectionViewScript::setFilter( const QString &filter )
{
return m_collectionWidget->setFilter( filter );
}
QString
AmarokCollectionViewScript::filter() const
{
return m_collectionWidget->filter();
}
QActionList
AmarokCollectionViewScript::actions()
{
QScriptValue actions = m_actionFunction.call( QScriptValue(), QScriptValueList() << selectionScriptValue() );
QActionList actionList = m_engine->fromScriptValue<QActionList>( actions );
debug() << "Received " << actionList.size() << " actions";
return actionList;
}
void
AmarokCollectionViewScript::setAction( const QScriptValue &value )
{
m_actionFunction = value;
}
void
AmarokCollectionViewScript::createScriptedActions( QMenu &menu, const QModelIndexList &indices )
{
debug() << "Checking for scripted actions";
if( s_selection )
delete s_selection.data();
if( s_instances.isEmpty() )
return;
s_selection = new Selection( indices );
foreach( const QString &scriptName, s_instances.keys() )
{
if( s_instances[scriptName] )
{
debug() << "Adding actions for script " << scriptName;
menu.addSeparator();
foreach( QAction *action, s_instances[scriptName]->actions() )
{
if( !action )
{
debug() << "Null action received from script " << scriptName;
continue;
}
action->setParent( &menu );
menu.addAction( action );
}
}
}
}
QScriptValue
AmarokCollectionViewScript::selectionScriptValue()
{
return m_engine->newQObject( s_selection.data(), QScriptEngine::QtOwnership,
QScriptEngine::ExcludeSuperClassContents );
}
Selection*
AmarokCollectionViewScript::selection()
{
return s_selection.data();
}
void
AmarokCollectionViewScript::setShowCovers( bool shown )
{
CollectionWidget::instance()->slotShowCovers( shown );
}
void
AmarokCollectionViewScript::setShowTrackNumbers( bool shown )
{
CollectionWidget::instance()->slotShowTrackNumbers( shown );
}
void
AmarokCollectionViewScript::setShowYears( bool shown )
{
CollectionWidget::instance()->slotShowYears( shown );
}
bool
AmarokCollectionViewScript::showCovers()
{
return AmarokConfig::showAlbumArt();
}
bool
AmarokCollectionViewScript::showTrackNumbers()
{
return AmarokConfig::showTrackNumbers();
}
bool
AmarokCollectionViewScript::showYears()
{
return AmarokConfig::showYears();
}
bool
AmarokCollectionViewScript::mergedView() const
{
return m_collectionWidget->viewMode() == CollectionWidget::UnifiedCollection;
}
void
AmarokCollectionViewScript::setMergedView( bool merged )
{
CollectionWidget::instance()->toggleView( merged );
}
QList<int>
AmarokCollectionViewScript::levels() const
{
QList<int> levels;
foreach( CategoryId::CatMenuId level, m_collectionWidget->currentView()->levels() )
levels << level;
return levels;
}
void
AmarokCollectionViewScript::setLevel( int level, int type )
{
if( m_categoryEnum.valueToKey( type ) )
return m_collectionWidget->currentView()->setLevel( level, CategoryId::CatMenuId( type ) );
m_engine->currentContext()->throwError( QScriptContext::TypeError, "Invalid category!" );
}
void
AmarokCollectionViewScript::setLevels( const QList<int> &levels )
{
QList<CategoryId::CatMenuId> catLevels;
foreach( int level, levels )
{
if( !m_categoryEnum.valueToKey( level ) )
{
m_engine->currentContext()->throwError( QScriptContext::TypeError, "Invalid category!" );
return;
}
catLevels << CategoryId::CatMenuId( level );
}
m_collectionWidget->setLevels( catLevels );
}
///////////////////////////////////////////////////////////
// CollectionViewItem
///////////////////////////////////////////////////////////
CollectionTreeItem*
CollectionViewItem::child( int row )
{
return m_item->child( row );
}
int
CollectionViewItem::childCount() const
{
return m_item->childCount();
}
QList<CollectionTreeItem*>
CollectionViewItem::children() const
{
return m_item->children();
}
CollectionViewItem::CollectionViewItem( CollectionTreeItem *item, QObject *parent )
: QObject( parent )
, m_item( item )
{}
bool
CollectionViewItem::isTrackItem() const
{
return m_item->isTrackItem();
}
int
CollectionViewItem::level() const
{
return m_item->level();
}
CollectionTreeItem*
CollectionViewItem::parent() const
{
return m_item->parent();
}
Collections::Collection*
CollectionViewItem::parentCollection() const
{
return m_item->parentCollection();
}
int
CollectionViewItem::row() const
{
return m_item->row();
}
bool
CollectionViewItem::isCollection() const
{
return m_item->type() == CollectionTreeItem::Collection;
}
CollectionTreeItem*
CollectionViewItem::data() const
{
return m_item;
}
QScriptValue
CollectionViewItem::toScriptValue( QScriptEngine *engine, CollectionTreeItem* const &item )
{
CollectionViewItem *proto = new CollectionViewItem( item, AmarokCollectionViewScript::selection() );
QScriptValue val = engine->newQObject( proto, QScriptEngine::AutoOwnership,
QScriptEngine::ExcludeSuperClassContents );
return val;
}
Meta::TrackPtr
CollectionViewItem::track()
{
return Meta::TrackPtr::dynamicCast( m_item->data() );
}
bool
CollectionViewItem::isAlbumItem() const
{
return m_item->isAlbumItem();
}
bool
CollectionViewItem::isDataItem() const
{
return m_item->isDataItem();
}
bool
CollectionViewItem::isNoLabelItem() const
{
return m_item->isNoLabelItem();
}
bool
CollectionViewItem::isVariousArtistItem() const
{
return m_item->isVariousArtistItem();
}
bool
CollectionViewItem::childrenLoaded() const
{
return m_item->isTrackItem() || !m_item->requiresUpdate();
}
void
CollectionViewItem::loadChildren()
{
if( !m_item->requiresUpdate() )
return;
CollectionTreeItemModelBase *model = getModel();
- connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(slotDataChanged(QModelIndex,QModelIndex)) );
+ connect( model, &CollectionTreeItemModelBase::dataChanged,
+ this, &CollectionViewItem::slotDataChanged );
model->ensureChildrenLoaded( m_item );
}
void
CollectionViewItem::slotDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
{
Q_UNUSED( bottomRight )
if( static_cast<CollectionTreeItem*>( topLeft.internalPointer() ) != m_item )
return;
emit loaded( m_item );
- Q_ASSERT( disconnect( sender(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, 0 ) );
+ Q_ASSERT( disconnect( qobject_cast<QAbstractItemModel*>(sender()), &QAbstractItemModel::dataChanged, this, 0 ) );
}
Collections::QueryMaker*
CollectionViewItem::queryMaker()
{
Collections::QueryMaker *qm = 0;
if( The::mainWindow()->collectionBrowser()->viewMode() == CollectionWidget::NormalCollections )
qm = m_item->queryMaker();
else
qm = CollectionManager::instance()->queryMaker();
addFilter( qm );
return qm;
}
void
CollectionViewItem::addFilter( Collections::QueryMaker *queryMaker )
{
if( !queryMaker )
return;
CollectionTreeItemModelBase *model = getModel();
for( CollectionTreeItem *tmp = m_item; tmp; tmp = tmp->parent() )
tmp->addMatch( queryMaker, model->levelCategory( tmp->level() - 1 ) );
Collections::addTextualFilter( queryMaker, model->currentFilter() );
}
CollectionTreeItemModelBase*
CollectionViewItem::getModel()
{
QSortFilterProxyModel *proxyModel = dynamic_cast<QSortFilterProxyModel*>( The::mainWindow()->collectionBrowser()->currentView()->model() );
return dynamic_cast<CollectionTreeItemModelBase*>( proxyModel ? proxyModel->sourceModel() : 0 );
}
///////////////////////////////////////////////////////////
// Selection
///////////////////////////////////////////////////////////
bool
Selection::singleCollection() const
{
return CollectionTreeView::onlyOneCollection( m_indices );
}
QList<CollectionTreeItem*>
Selection::selectedItems()
{
QList<CollectionTreeItem*> collectionItems;
foreach( const QModelIndex &index, m_indices )
collectionItems << static_cast<CollectionTreeItem*>( index.internalPointer() );
return collectionItems;
}
Selection::Selection( const QModelIndexList &indices )
: QObject( 0 )
, m_indices( indices )
{}
Collections::QueryMaker*
Selection::queryMaker()
{
return The::mainWindow()->collectionBrowser()->currentView()->createMetaQueryFromItems( selectedItems().toSet(), true );
}
diff --git a/src/scripting/scriptengine/AmarokEngineScript.cpp b/src/scripting/scriptengine/AmarokEngineScript.cpp
index 17eacf73a9..dd712d5a6c 100644
--- a/src/scripting/scriptengine/AmarokEngineScript.cpp
+++ b/src/scripting/scriptengine/AmarokEngineScript.cpp
@@ -1,283 +1,283 @@
/****************************************************************************************
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokEngineScript.h"
#include "AmarokEqualizerScript.h"
#include "App.h"
#include "EngineController.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModel.h"
#include <QScriptEngine>
using namespace AmarokScript;
AmarokEngineScript::AmarokEngineScript( QScriptEngine* scriptEngine )
: QObject( scriptEngine )
{
QScriptValue scriptObject = scriptEngine->newQObject( this, QScriptEngine::AutoOwnership
, QScriptEngine::ExcludeSuperClassContents );
scriptEngine->globalObject().property( "Amarok" ).setProperty( "Engine", scriptObject );
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(trackPositionChanged(qint64,bool)),
- this, SLOT(trackPositionChanged(qint64)) );
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SIGNAL(trackChanged()) );
- connect( engine, SIGNAL(paused()), this, SLOT(slotPaused()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)), this, SLOT(slotPlaying()) );
- connect( engine, SIGNAL(stopped(qint64,qint64)), this, SIGNAL(trackFinished()) );
- connect( engine, SIGNAL(currentMetadataChanged(QVariantMap)),
- this, SLOT(slotNewMetaData()) );
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- this, SLOT(slotNewMetaData()) );
- connect( engine, SIGNAL(albumMetadataChanged(Meta::AlbumPtr)),
- this, SLOT(slotNewMetaData()) );
- connect( engine, SIGNAL(volumeChanged(int)), this, SIGNAL(volumeChanged(int)) );
+ connect( engine, &EngineController::trackPositionChanged,
+ this, &AmarokEngineScript::trackPositionChanged );
+ connect( engine, &EngineController::trackChanged, this, &AmarokEngineScript::trackChanged );
+ connect( engine, &EngineController::paused, this, &AmarokEngineScript::slotPaused );
+ connect( engine, &EngineController::trackPlaying, this, &AmarokEngineScript::slotPlaying );
+ connect( engine, &EngineController::stopped, this, &AmarokEngineScript::trackFinished );
+ connect( engine, &EngineController::currentMetadataChanged,
+ this, &AmarokEngineScript::slotNewMetaData );
+ connect( engine, &EngineController::trackMetadataChanged,
+ this, &AmarokEngineScript::slotNewMetaData );
+ connect( engine, &EngineController::albumMetadataChanged,
+ this, &AmarokEngineScript::slotNewMetaData );
+ connect( engine, &EngineController::volumeChanged, this, &AmarokEngineScript::volumeChanged );
new AmarokEqualizerScript( scriptEngine );
}
void
AmarokEngineScript::Play() const
{
The::engineController()->play();
}
void
AmarokEngineScript::Stop( bool forceInstant ) const
{
The::engineController()->stop( forceInstant );
}
void
AmarokEngineScript::Pause() const
{
The::engineController()->pause();
}
void
AmarokEngineScript::Next() const
{
The::playlistActions()->next();
}
void
AmarokEngineScript::Prev() const
{
The::playlistActions()->back();
}
void
AmarokEngineScript::PlayPause() const
{
The::engineController()->playPause();
}
void
AmarokEngineScript::Seek( int ms ) const
{
The::engineController()->seekTo( ms );
}
void
AmarokEngineScript::SeekRelative( int ms ) const
{
The::engineController()->seekBy( ms );
}
void
AmarokEngineScript::SeekForward( int ms ) const
{
The::engineController()->seekBy( ms );
}
void
AmarokEngineScript::SeekBackward( int ms ) const
{
The::engineController()->seekBy( -ms );
}
int
AmarokEngineScript::IncreaseVolume( int ticks )
{
return The::engineController()->increaseVolume( ticks );
}
int
AmarokEngineScript::DecreaseVolume( int ticks )
{
return The::engineController()->decreaseVolume( ticks );
}
void
AmarokEngineScript::Mute()
{
The::engineController()->toggleMute();
}
int
AmarokEngineScript::trackPosition() const
{
return The::engineController()->trackPosition();
}
int
AmarokEngineScript::trackPositionMs() const
{
return The::engineController()->trackPositionMs();
}
int
AmarokEngineScript::engineState() const
{
if( The::engineController()->isPlaying() )
return Playing;
else if( The::engineController()->isPaused() )
return Paused;
else
return Stopped;
}
Meta::TrackPtr
AmarokEngineScript::currentTrack() const
{
Meta::TrackPtr track = The::engineController()->currentTrack();
return track;
}
void
AmarokEngineScript::trackPositionChanged( qint64 pos )
{
emit trackSeeked( pos );
}
void
AmarokEngineScript::slotNewMetaData()
{
emit newMetaData( QHash<qint64, QString>(), false );
}
void
AmarokEngineScript::slotPaused()
{
emit trackPlayPause( Paused );
}
void
AmarokEngineScript::slotPlaying()
{
emit trackPlayPause( Playing );
}
bool
AmarokEngineScript::randomMode() const
{
return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack ||
AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum;
}
bool
AmarokEngineScript::dynamicMode() const
{
return AmarokConfig::dynamicMode();
}
bool
AmarokEngineScript::repeatPlaylist() const
{
return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist;
}
bool
AmarokEngineScript::repeatTrack() const
{
return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack;
}
void
AmarokEngineScript::setRandomMode( bool enable )
{
if( enable )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RandomTrack );
The::playlistActions()->playlistModeChanged();
}
else if( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
The::playlistActions()->playlistModeChanged();
}
}
void
AmarokEngineScript::setRepeatPlaylist( bool enable )
{
if( enable )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatPlaylist );
The::playlistActions()->playlistModeChanged();
}
else if( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
The::playlistActions()->playlistModeChanged();
}
}
void
AmarokEngineScript::setRepeatTrack( bool enable )
{
if( enable )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::RepeatTrack );
The::playlistActions()->playlistModeChanged();
}
else if( AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack )
{
AmarokConfig::setTrackProgression( AmarokConfig::EnumTrackProgression::Normal );
The::playlistActions()->playlistModeChanged();
}
}
int
AmarokEngineScript::volume() const
{
return The::engineController()->volume();
}
void
AmarokEngineScript::setVolume( int percent )
{
The::engineController()->setVolume( percent );
}
int
AmarokEngineScript::fadeoutLength() const
{
return AmarokConfig::fadeoutLength();
}
void
AmarokEngineScript::setFadeoutLength( int length )
{
if( length < 400 )
debug() << "Fadeout length must be >= 400";
else
AmarokConfig::setFadeoutLength( length );
}
diff --git a/src/scripting/scriptengine/AmarokEqualizerScript.cpp b/src/scripting/scriptengine/AmarokEqualizerScript.cpp
index 46aca830b9..5ffd502f66 100644
--- a/src/scripting/scriptengine/AmarokEqualizerScript.cpp
+++ b/src/scripting/scriptengine/AmarokEqualizerScript.cpp
@@ -1,144 +1,144 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokEqualizerScript.h"
#include "amarokconfig.h"
#include "EngineController.h"
#include "equalizer/EqualizerPresets.h"
#include <QScriptEngine>
using namespace AmarokScript;
AmarokEqualizerScript::AmarokEqualizerScript( QScriptEngine *scriptEngine )
: QObject( scriptEngine )
{
QScriptValue scriptObject = scriptEngine->newQObject( this, QScriptEngine::AutoOwnership
, QScriptEngine::ExcludeSuperClassContents );
scriptEngine->globalObject().property( "Amarok" ).property( "Engine" ).setProperty( "Equalizer", scriptObject );
EqualizerController *equalizer = The::engineController()->equalizerController();
- connect( equalizer, SIGNAL(gainsChanged(QList<int>)), SIGNAL(gainsChanged(QList<int>)) );
- connect( equalizer, SIGNAL(presetsChanged(QString)), SIGNAL(presetsChanged(QString)) );
- connect( equalizer, SIGNAL(presetApplied(int)), SLOT(equalizerPresetApplied(int)) );
+ connect( equalizer, &EqualizerController::gainsChanged, this, &AmarokEqualizerScript::gainsChanged );
+ connect( equalizer, &EqualizerController::presetsChanged, this, &AmarokEqualizerScript::presetsChanged );
+ connect( equalizer, &EqualizerController::presetApplied, this, &AmarokEqualizerScript::equalizerPresetApplied );
}
// script invokable
bool
AmarokEqualizerScript::deletePreset( const QString &presetName )
{
return The::engineController()->equalizerController()->deletePreset( presetName );
}
void
AmarokEqualizerScript::savePreset( const QString &name, const QList<int> &presetGains )
{
The::engineController()->equalizerController()->savePreset( name, presetGains );
}
//private
bool
AmarokEqualizerScript::enabled()
{
return The::engineController()->equalizerController()->enabled();
}
QStringList
AmarokEqualizerScript::bandFrequencies() const
{
return The::engineController()->equalizerController()->eqBandsFreq();
}
QStringList
AmarokEqualizerScript::defaultPresetList() const
{
return EqualizerPresets::eqDefaultPresetsList();
}
void
AmarokEqualizerScript::equalizerPresetApplied( int index )
{
emit presetApplied( EqualizerPresets::eqGlobalList().value( index ) );
}
QList<int>
AmarokEqualizerScript::gains() const
{
return The::engineController()->equalizerController()->gains();
}
QList<int>
AmarokEqualizerScript::getPresetGains( const QString &presetName )
{
return EqualizerPresets::eqCfgGetPresetVal( presetName );
}
QStringList
AmarokEqualizerScript::globalPresetList() const
{
return EqualizerPresets::eqGlobalList();
}
bool
AmarokEqualizerScript::isSupported() const
{
return The::engineController()->equalizerController()->isEqSupported();
}
int
AmarokEqualizerScript::maxGain() const
{
return The::engineController()->equalizerController()->eqMaxGain();
}
QString
AmarokEqualizerScript::selectedPreset() const
{
return The::engineController()->equalizerController()->equalizerPreset();
}
void
AmarokEqualizerScript::setEqualizerPreset( const QString &name ) const
{
- The::engineController()->equalizerController()->applyEqualizerPreset( name );
+ The::engineController()->equalizerController()->applyEqualizerPresetByName( name );
}
QStringList
AmarokEqualizerScript::translatedDefaultPresetList() const
{
return EqualizerPresets::eqDefaultTranslatedPresetsList();
}
QStringList
AmarokEqualizerScript::translatedGlobalPresetList() const
{
return EqualizerPresets::eqGlobalTranslatedList();
}
QStringList
AmarokEqualizerScript::userPresets() const
{
return EqualizerPresets::eqUserList();
}
void
AmarokEqualizerScript::setGains( QList<int> gains )
{
The::engineController()->equalizerController()->setGains( gains );
}
diff --git a/src/scripting/scriptengine/AmarokLyricsScript.cpp b/src/scripting/scriptengine/AmarokLyricsScript.cpp
index 65a17b89e8..6dc8312a79 100644
--- a/src/scripting/scriptengine/AmarokLyricsScript.cpp
+++ b/src/scripting/scriptengine/AmarokLyricsScript.cpp
@@ -1,133 +1,133 @@
/****************************************************************************************
* Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> *
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AmarokLyricsScript"
#include "AmarokLyricsScript.h"
#include "EngineController.h"
#include "scripting/scriptmanager/ScriptManager.h"
#include "context/LyricsManager.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <QApplication>
#include <QByteArray>
#include <QScriptEngine>
#include <QTextCodec>
#include <QTextDocument>
#include <QScriptEngineDebugger>
using namespace AmarokScript;
AmarokLyricsScript::AmarokLyricsScript( QScriptEngine *engine )
: QObject( engine )
{
QScriptValue scriptObject = engine->newQObject( this, QScriptEngine::AutoOwnership,
QScriptEngine::ExcludeSuperClassContents );
engine->globalObject().property( "Amarok" ).setProperty( "Lyrics", scriptObject );
- connect( ScriptManager::instance(), SIGNAL(fetchLyrics(QString,QString,QString,Meta::TrackPtr)),
- SIGNAL(fetchLyrics(QString,QString,QString,Meta::TrackPtr)) );
+ connect( ScriptManager::instance(), &ScriptManager::fetchLyrics,
+ this, &AmarokLyricsScript::fetchLyrics );
}
void
AmarokLyricsScript::showLyrics( const QString &lyrics ) const
{
DEBUG_BLOCK
Meta::TrackPtr track = The::engineController()->currentTrack();
if( !track )
return;
/* FIXME: disabled temporarily for KF5 porting.
LyricsManager::self()->lyricsResult( lyrics, false );
*/
}
void
AmarokLyricsScript::showLyricsHtml( const QString &lyrics ) const
{
DEBUG_BLOCK
Meta::TrackPtr track = The::engineController()->currentTrack();
if( !track )
return;
/* FIXME: disabled temporarily for KF5 porting.
LyricsManager::self()->lyricsResultHtml( lyrics, false );
*/
}
void
AmarokLyricsScript::showLyricsError( const QString &error ) const
{
DEBUG_BLOCK
/* FIXME: disabled temporarily for KF5 porting.
LyricsManager::self()->lyricsError( error );
*/
}
void
AmarokLyricsScript::showLyricsNotFound( const QString &msg ) const
{
DEBUG_BLOCK
/* FIXME: disabled temporarily for KF5 porting.
LyricsManager::self()->lyricsNotFound( msg );
*/
}
QString
AmarokLyricsScript::escape( const QString &str )
{
return Qt::escape( str );
}
void
AmarokLyricsScript::setLyricsForTrack( const QString &trackUrl, const QString &lyrics ) const
{
/* FIXME: disabled temporarily for KF5 porting.
LyricsManager::self()->setLyricsForTrack( trackUrl, lyrics );
*/
}
QString
AmarokLyricsScript::toUtf8( const QByteArray &lyrics, const QString &encoding )
{
QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() );
if( !codec )
return QString();
return codec->toUnicode( lyrics );
}
QString
AmarokLyricsScript::QStringtoUtf8( const QString &lyrics, const QString &encoding )
{
QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() );
if( !codec )
return QString();
return codec->toUnicode( lyrics.toLatin1() );
}
QByteArray
AmarokLyricsScript::fromUtf8( const QString &str, const QString &encoding )
{
QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() );
if( !codec )
return QByteArray();
return codec->fromUnicode( str );
}
diff --git a/src/scripting/scriptengine/AmarokNetworkScript.cpp b/src/scripting/scriptengine/AmarokNetworkScript.cpp
index 0f3cba8236..6debbaf664 100644
--- a/src/scripting/scriptengine/AmarokNetworkScript.cpp
+++ b/src/scripting/scriptengine/AmarokNetworkScript.cpp
@@ -1,241 +1,241 @@
/****************************************************************************************
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "AmarokNetworkScript"
#include "AmarokNetworkScript.h"
#include "App.h"
#include "core/support/Debug.h"
#include <QScriptEngine>
#include <QTextCodec>
#include <KLocalizedString>
using namespace AmarokScript;
AmarokDownloadHelper *AmarokDownloadHelper::s_instance = 0;
Downloader::Downloader( QScriptEngine* engine )
: QObject( engine )
, m_scriptEngine( engine )
{
engine->setDefaultPrototype( qMetaTypeId<Downloader*>(), QScriptValue() );
const QScriptValue stringCtor = engine->newFunction( stringDownloader_prototype_ctor );
engine->globalObject().setProperty( "Downloader", stringCtor ); //kept for compat
engine->globalObject().setProperty( "StringDownloader", stringCtor ); //added for clarity
const QScriptValue dataCtor = engine->newFunction( dataDownloader_prototype_ctor );
engine->globalObject().setProperty( "DataDownloader", dataCtor );
}
QScriptValue
Downloader::stringDownloader_prototype_ctor( QScriptContext* context, QScriptEngine* engine )
{
return init( context, engine, true );
}
QScriptValue
Downloader::dataDownloader_prototype_ctor( QScriptContext* context, QScriptEngine* engine )
{
if( engine->importedExtensions().contains( "qt.core" ) )
return init( context, engine, false );
else
{
context->throwError( i18nc("do not translate 'DataDownloader' or 'qt.core'", "qt.core must be loaded to use DataDownloader" ) );
return engine->toScriptValue( false );
}
}
QScriptValue
Downloader::init( QScriptContext* context, QScriptEngine *engine, bool stringResult )
{
// from QtScript API docs
DEBUG_BLOCK
QScriptValue object;
if( context->isCalledAsConstructor() )
object = context->thisObject();
else
{
object = engine->newObject();
object.setPrototype( context->callee().property("prototype") );
}
if( context->argumentCount() < 2 )
{
debug() << "ERROR! Constructor not called with enough arguments:" << context->argumentCount();
return object;
}
if( !context->argument( 1 ).isFunction() ) //TODO: check QUrl
{
debug() << "ERROR! Constructor not called with a QUrl and function!";
return object;
}
QUrl url( qscriptvalue_cast<QUrl>( context->argument( 0 ) ) );
// start download, and connect to it
//FIXME: url is not working directly.
if( stringResult )
{
QString encoding = "UTF-8";
if( context->argumentCount() == 3 ) // encoding specified
encoding = context->argument( 2 ).toString();
AmarokDownloadHelper::instance()->newStringDownload( url, engine, context->argument( 1 ), encoding );
}
else
AmarokDownloadHelper::instance()->newDataDownload( url, engine, context->argument( 1 ) );
// connect to a local slot to extract the qstring
//qScriptConnect( job, SIGNAL(result(KJob*)), object, fetchResult( job ) );
return object;
}
///////
// Class AmarokDownloadHelper
//////
AmarokDownloadHelper::AmarokDownloadHelper()
{
s_instance = this;
- connect( The::networkAccessManager(), SIGNAL(requestRedirected(QUrl,QUrl)),
- this, SLOT(requestRedirected(QUrl,QUrl)) );
+ connect( The::networkAccessManager(), &NetworkAccessManagerProxy::requestRedirectedUrl,
+ this, &AmarokDownloadHelper::requestRedirected );
}
void
AmarokDownloadHelper::newStringDownload( const QUrl &url, QScriptEngine* engine, QScriptValue obj, QString encoding )
{
m_encodings[ url ] = encoding;
newDownload( url, engine, obj, SLOT(resultString(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) );
}
void
AmarokDownloadHelper::newDataDownload( const QUrl &url, QScriptEngine* engine, QScriptValue obj )
{
newDownload( url, engine, obj, SLOT(resultData(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) );
}
void
AmarokDownloadHelper::newDownload( const QUrl &url, QScriptEngine* engine, QScriptValue obj, const char *slot )
{
m_values[ url ] = obj;
m_engines[ url ] = engine;
The::networkAccessManager()->getData( url, this, slot );
}
void
AmarokDownloadHelper::requestRedirected( const QUrl &sourceUrl, const QUrl &targetUrl )
{
DEBUG_BLOCK
// Move all entries from "url" to "targetUrl".
updateUrl< QScriptEngine* >( m_engines, sourceUrl, targetUrl );
updateUrl< QScriptValue >( m_values, sourceUrl, targetUrl );
updateUrl< QString >( m_encodings, sourceUrl, targetUrl );
}
void
AmarokDownloadHelper::resultData( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e )
{
if( !m_values.contains( url ) )
return;
if( e.code != QNetworkReply::NoError )
warning() << "Error fetching data:" << e.description;
QScriptValue obj = m_values.value( url );
QScriptEngine* engine = m_engines.value( url );
cleanUp( url );
// now send the data to the associated script object
if( !obj.isFunction() )
{
debug() << "script object is valid but not a function!!";
return;
}
if( !engine )
{
debug() << "stored script engine is not valid!";
return;
}
QScriptValueList args;
args << engine->toScriptValue( data );
obj.call( obj, args );
}
void
AmarokDownloadHelper::resultString( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e )
{
if( !m_values.contains( url ) )
return;
if( e.code != QNetworkReply::NoError )
warning() << "Error fetching string:" << e.description;
QScriptValue obj = m_values.value( url );
QScriptEngine* engine = m_engines.value( url );
QString encoding = m_encodings.value( url );
cleanUp( url );
QString str;
if( encoding.isEmpty() )
{
str = QString( data );
}
else
{
QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() );
str = codec->toUnicode( data );
}
// now send the data to the associated script object
if( !obj.isFunction() )
{
debug() << "script object is valid but not a function!!";
return;
}
if( !engine )
{
debug() << "stored script engine is not valid!";
return;
}
QScriptValueList args;
args << engine->toScriptValue( str );
obj.call( obj, args );
}
void
AmarokDownloadHelper::cleanUp( const QUrl &url )
{
m_values.remove( url );
m_engines.remove( url );
m_encodings.remove( url );
}
AmarokDownloadHelper*
AmarokDownloadHelper::instance()
{
if( !s_instance )
s_instance = new AmarokDownloadHelper();
return s_instance;
}
diff --git a/src/scripting/scriptengine/ScriptingDefines.cpp b/src/scripting/scriptengine/ScriptingDefines.cpp
index e9d9819f66..ebacf954a8 100644
--- a/src/scripting/scriptengine/ScriptingDefines.cpp
+++ b/src/scripting/scriptengine/ScriptingDefines.cpp
@@ -1,103 +1,103 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ScriptingDefines.h"
#include "core/support/Debug.h"
#include <QMetaEnum>
#include <QTimer>
using namespace AmarokScript;
AmarokScriptEngine::AmarokScriptEngine( QObject *parent )
: QScriptEngine(parent)
, internalObject( "UndocumentedAmarokScriptingInternals" )
{
QScriptValue scriptObject = newQObject( this, QtOwnership,
ExcludeChildObjects | ExcludeSuperClassContents );
globalObject().setProperty( internalObject, scriptObject, QScriptValue::ReadOnly );
QScriptValue setTimeoutObject = scriptObject.property( "setTimeout" );
Q_ASSERT( !setTimeoutObject.isUndefined() );
Q_ASSERT( !globalObject().property( internalObject ).property( "invokableDeprecatedCall" ).isUndefined() );
globalObject().setProperty( "setTimeout", setTimeoutObject );
}
void
AmarokScriptEngine::setDeprecatedProperty( const QString &parent, const QString &name, const QScriptValue &property )
{
const QString objName = QString( "%1%2" ).arg( name, QString::number( qrand() ) );
globalObject().property( internalObject ).setProperty( objName, property, QScriptValue::ReadOnly | QScriptValue::SkipInEnumeration );
const QString command = QString( "Object.defineProperty( %1, \"%2\", {get : function(){ var iobj= %3; iobj.invokableDeprecatedCall(\""
" %1.%2 \"); return iobj.%4; },\
enumerable : true,\
configurable : false} );" )
.arg( parent, name, internalObject, objName );
evaluate( command );
}
void
AmarokScriptEngine::invokableDeprecatedCall( const QString &call )
{
warning() << "Deprecated function " + call;
emit deprecatedCall( call );
}
void
AmarokScriptEngine::setTimeout( const QScriptValue &function, int time, const QScriptValue &thisObject, const QScriptValue &args )
{
QTimer *timer = new QTimer( this );
timer->setSingleShot( true );
timer->setInterval( time );
m_callbacks[timer] = QScriptValueList() << function << thisObject << args;
- connect( timer, SIGNAL(timeout()), this, SLOT(slotTimeout()) );
+ connect( timer, &QTimer::timeout, this, &AmarokScriptEngine::slotTimeout );
timer->start();
}
void
AmarokScriptEngine::slotTimeout()
{
QObject *timer = sender();
if( !timer )
return;
QScriptValueList args;
QScriptValue thisObject;
if( m_callbacks[timer].size() > 1 )
{
thisObject = m_callbacks[timer][1];
if( m_callbacks[timer].size() == 3 )
for ( quint32 i = 0; i < m_callbacks[timer][2].property("length").toUInt32(); ++i )
args << m_callbacks[timer][2].property( i );
}
m_callbacks[timer][0].call( thisObject, args );
m_callbacks.remove( timer );
timer->deleteLater();
}
QScriptValue
AmarokScriptEngine::enumObject( const QMetaEnum &metaEnum )
{
QScriptValue enumObj = newObject();
for( int i = 0; i< metaEnum.keyCount(); ++i )
enumObj.setProperty( metaEnum.key(i), QScriptEngine::toScriptValue( metaEnum.value(i) ) );
return enumObj;
}
AmarokScriptEngine::~AmarokScriptEngine()
{}
diff --git a/src/scripting/scriptengine/exporters/MetaTypeExporter.cpp b/src/scripting/scriptengine/exporters/MetaTypeExporter.cpp
index d4f31c227f..6eb8f24b8e 100644
--- a/src/scripting/scriptengine/exporters/MetaTypeExporter.cpp
+++ b/src/scripting/scriptengine/exporters/MetaTypeExporter.cpp
@@ -1,452 +1,452 @@
/****************************************************************************************
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* Copyright (c) 2008 Ian Monroe <ian@monroe.nu> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MetaTypeExporter.h"
#include "core/support/Debug.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/meta/Statistics.h"
#include "core/meta/TrackEditor.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/collections/support/jobs/WriteTagsJob.h"
#include "core-impl/meta/proxy/MetaProxy.h"
#include "MetaTagLib.h"
#include "scripting/scriptengine/ScriptingDefines.h"
#include <ThreadWeaver/Queue>
#include <ThreadWeaver/Job>
#include <QScriptContext>
#include <QScriptEngine>
Q_DECLARE_METATYPE( StringMap )
using namespace AmarokScript;
#define CHECK_TRACK( X ) if( !m_track ){ warning() << "Invalid track!"; return X; };
#define GET_TRACK_EC( X ) CHECK_TRACK() \
Meta::TrackEditorPtr ec = m_track->editor(); \
if( ec ) \
{ \
X; \
}
void
MetaTrackPrototype::init( QScriptEngine *engine )
{
qScriptRegisterMetaType<Meta::TrackPtr>( engine, toScriptValue<Meta::TrackPtr, MetaTrackPrototype>, fromScriptValue<Meta::TrackPtr, MetaTrackPrototype> );
qScriptRegisterMetaType<Meta::TrackList>( engine, toScriptArray, fromScriptArray );
qScriptRegisterMetaType<StringMap>( engine, toScriptMap, fromScriptMap );
qScriptRegisterMetaType<Meta::FieldHash>( engine, toScriptTagMap, fromScriptTagMap );
engine->globalObject().setProperty( "Track", engine->newFunction( trackCtor ) );
}
QScriptValue
MetaTrackPrototype::trackCtor( QScriptContext *context, QScriptEngine *engine )
{
if( context->argumentCount() < 1 )
return context->throwError( QScriptContext::SyntaxError, "Not enough arguments! Pass the track url." );
QUrl url( qscriptvalue_cast<QUrl>( context->argument( 0 ) ) );
if( !url.isValid() )
return context->throwError( QScriptContext::TypeError, "Invalid QUrl" );
MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( url ) );
proxyTrack->setTitle( url.fileName() ); // set temporary name
return engine->newQObject( new MetaTrackPrototype( Meta::TrackPtr( proxyTrack.data() ) )
, QScriptEngine::ScriptOwnership
, QScriptEngine::ExcludeSuperClassContents );
}
MetaTrackPrototype::MetaTrackPrototype( const Meta::TrackPtr &track )
: QObject( 0 )
, m_track( track )
{
}
Meta::FieldHash
MetaTrackPrototype::tags() const
{
if( !isLoadedAndLocal() )
return Meta::FieldHash();
return Meta::Tag::readTags( m_track->playableUrl().path() );
}
int
MetaTrackPrototype::sampleRate() const
{
CHECK_TRACK( 0 )
return m_track->sampleRate();
}
int
MetaTrackPrototype::bitrate() const
{
CHECK_TRACK( 0 )
return m_track->bitrate();
}
double
MetaTrackPrototype::score() const
{
CHECK_TRACK( 0.0 )
return m_track->statistics()->score();
}
int
MetaTrackPrototype::rating() const
{
CHECK_TRACK( 0 )
return m_track->statistics()->rating();
}
bool
MetaTrackPrototype::inCollection() const
{
CHECK_TRACK( false )
return m_track->inCollection();
}
QString
MetaTrackPrototype::type() const
{
CHECK_TRACK( QString() )
return m_track->type();
}
qint64
MetaTrackPrototype::length() const
{
CHECK_TRACK( 0 )
return m_track->length();
}
int
MetaTrackPrototype::fileSize() const
{
CHECK_TRACK( 0 )
return m_track->filesize();
}
int
MetaTrackPrototype::trackNumber() const
{
CHECK_TRACK( 0 )
return m_track->trackNumber();
}
int
MetaTrackPrototype::discNumber() const
{
CHECK_TRACK( 0 )
return m_track->discNumber();
}
int
MetaTrackPrototype::playCount() const
{
CHECK_TRACK( 0 )
return m_track->statistics()->playCount();
}
bool
MetaTrackPrototype::playable() const
{
CHECK_TRACK( false )
return m_track->isPlayable();
}
QString
MetaTrackPrototype::album() const
{
CHECK_TRACK( QString() )
return m_track->album() ? m_track->album()->prettyName() : QString();
}
QString
MetaTrackPrototype::artist() const
{
CHECK_TRACK( QString() )
return m_track->artist() ? m_track->artist()->prettyName() : QString();
}
QString
MetaTrackPrototype::composer() const
{
CHECK_TRACK( QString() )
return m_track->composer() ? m_track->composer()->prettyName() : QString();
}
QString
MetaTrackPrototype::genre() const
{
CHECK_TRACK( QString() )
return m_track->genre() ? m_track->genre()->prettyName() : QString();
}
int
MetaTrackPrototype::year() const
{
CHECK_TRACK( 0 )
return m_track->year() ? m_track->year()->year() : 0;
}
QString
MetaTrackPrototype::comment() const
{
CHECK_TRACK( QString() )
return m_track->comment();
}
QString
MetaTrackPrototype::path() const
{
CHECK_TRACK( QString() )
return m_track->playableUrl().path();
}
QString
MetaTrackPrototype::title() const
{
CHECK_TRACK ( QString() )
return m_track->prettyName();
}
QString
MetaTrackPrototype::imageUrl() const
{
CHECK_TRACK( QString() )
return m_track->album() ? m_track->album()->imageLocation().toDisplayString() : QString();
}
QString
MetaTrackPrototype::url() const
{
CHECK_TRACK( QString() )
return m_track->playableUrl().url();
}
double
MetaTrackPrototype::bpm() const
{
CHECK_TRACK( 0.0 )
return m_track->bpm();
}
bool
MetaTrackPrototype::isLoaded() const
{
MetaProxy::TrackPtr proxyTrack = MetaProxy::TrackPtr::dynamicCast( m_track );
if( proxyTrack && !proxyTrack->isResolved() )
{
const_cast<MetaTrackPrototype*>( this )->Observer::subscribeTo( m_track );
return false;
}
return true;
}
QScriptValue
MetaTrackPrototype::imagePixmap( int size ) const
{
CHECK_TRACK( QScriptValue() )
return m_track->album() ? m_engine->toScriptValue( m_track->album()->image( size ) ) : QScriptValue();
}
bool
MetaTrackPrototype::isValid() const
{
return m_track;
}
bool
MetaTrackPrototype::isEditable()
{
CHECK_TRACK( false )
return m_track->editor(); // converts to bool nicely
}
QString
MetaTrackPrototype::lyrics() const
{
CHECK_TRACK( QString() )
return m_track->cachedLyrics();
}
void
MetaTrackPrototype::setScore( double score )
{
CHECK_TRACK()
m_track->statistics()->setScore( score );
}
void
MetaTrackPrototype::setRating( int rating )
{
CHECK_TRACK()
m_track->statistics()->setRating( rating );
}
void
MetaTrackPrototype::setTrackNumber( int number )
{
GET_TRACK_EC( ec->setTrackNumber( number ) )
}
void
MetaTrackPrototype::setDiscNumber( int number )
{
GET_TRACK_EC( ec->setDiscNumber( number ) )
}
void
MetaTrackPrototype::setAlbum( const QString &album )
{
GET_TRACK_EC( ec->setAlbum( album ) )
}
void
MetaTrackPrototype::setArtist( const QString &artist )
{
GET_TRACK_EC( ec->setArtist( artist ) )
}
void
MetaTrackPrototype::setComposer( const QString &composer )
{
GET_TRACK_EC( ec->setComposer( composer ) )
}
void
MetaTrackPrototype::setGenre( const QString &genre )
{
GET_TRACK_EC( ec->setGenre( genre ) )
}
void
MetaTrackPrototype::setYear( int year )
{
GET_TRACK_EC( ec->setYear( year ) )
}
void
MetaTrackPrototype::setComment( const QString &comment )
{
GET_TRACK_EC( ec->setComment( comment ) )
}
void
MetaTrackPrototype::setLyrics( const QString &lyrics )
{
CHECK_TRACK()
m_track->setCachedLyrics( lyrics );
}
void
MetaTrackPrototype::setTitle( const QString& title )
{
GET_TRACK_EC( ec->setTitle( title ) )
}
void
MetaTrackPrototype::setImageUrl( const QString& imageUrl )
{
CHECK_TRACK()
if( m_track->album() )
m_track->album()->setImage( QImage(imageUrl) );
}
void
MetaTrackPrototype::metadataChanged( Meta::TrackPtr track )
{
Observer::unsubscribeFrom( track );
debug() << "Loaded track: " << track->prettyName();
emit loaded( track );
}
void
MetaTrackPrototype::fromScriptTagMap( const QScriptValue &value, Meta::FieldHash &map )
{
QScriptValueIterator it( value );
while( it.hasNext() )
{
it.next();
map[Meta::fieldForName( it.name() )] = it.value().toVariant();
}
}
QScriptValue
MetaTrackPrototype::toScriptTagMap( QScriptEngine *engine, const Meta::FieldHash &map )
{
QScriptValue scriptMap = engine->newObject();
for( typename Meta::FieldHash::const_iterator it( map.constBegin() ); it != map.constEnd(); ++it )
scriptMap.setProperty( Meta::nameForField( it.key() ), engine->toScriptValue( it.value() ) );
return scriptMap;
}
void
MetaTrackPrototype::changeTags( const Meta::FieldHash &changes, bool respectConfig )
{
if( !isLoadedAndLocal() )
return;
if( changes.isEmpty() )
return;
WriteTagsJob *job = new WriteTagsJob( m_track->playableUrl().path(), changes, respectConfig );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &WriteTagsJob::done, job, &QObject::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
QImage
MetaTrackPrototype::embeddedCover() const
{
if( isLoadedAndLocal() )
return QImage();
return Meta::Tag::embeddedCover( m_track->playableUrl().path() );
}
void
MetaTrackPrototype::setEmbeddedCover( const QImage &image )
{
if( image.isNull() )
return;
}
bool
MetaTrackPrototype::isLoadedAndLocal() const
{
CHECK_TRACK( false );
if( !isLoaded() )
{
debug() << "Track for url " << m_track->prettyUrl() << " not loaded yet!";
return false;
}
if( !m_track->playableUrl().isLocalFile() )
{
debug() << m_track->prettyName() + " is not a local file!";
return false;
}
return true;
}
#undef GET_TRACK
#undef CHECK_TRACK
#undef GET_TRACK_EC
diff --git a/src/scripting/scriptengine/exporters/QueryMakerExporter.cpp b/src/scripting/scriptengine/exporters/QueryMakerExporter.cpp
index 86437f0cc5..f3b9074d8c 100644
--- a/src/scripting/scriptengine/exporters/QueryMakerExporter.cpp
+++ b/src/scripting/scriptengine/exporters/QueryMakerExporter.cpp
@@ -1,112 +1,112 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "QueryMakerExporter.h"
#include "core-impl/collections/support/TextualQueryFilter.h"
#include "core/collections/QueryMaker.h"
#include "scripting/scriptengine/ScriptingDefines.h"
#include <QScriptEngine>
#include <QScriptValue>
#include <QEventLoop>
using namespace AmarokScript;
using Collections::QueryMaker;
void
QueryMakerPrototype::init( QScriptEngine *engine )
{
qScriptRegisterMetaType<QueryMaker*>( engine, toScriptValue<QueryMaker*,QueryMakerPrototype>, fromScriptValue<QueryMaker*,QueryMakerPrototype> );
}
// script invokable
void
QueryMakerPrototype::addFilter( const QString &filter )
{
if( !m_querymaker )
return;
Collections::addTextualFilter( m_querymaker.data(), filter );
m_filter += filter + " ";
}
void
QueryMakerPrototype::run()
{
if( !m_querymaker )
return;
m_querymaker.data()->setQueryType( Collections::QueryMaker::Track );
m_querymaker.data()->run();
}
Meta::TrackList
QueryMakerPrototype::blockingRun()
{
if( !m_querymaker )
return Meta::TrackList();
QEventLoop loop;
- connect( m_querymaker.data(), SIGNAL(newResultReady(Meta::TrackList)), SLOT(slotResult(Meta::TrackList)) );
- connect( m_querymaker.data(), SIGNAL(queryDone()), &loop, SLOT(quit()) );
+ connect( m_querymaker.data(), &Collections::QueryMaker::newTracksReady, this, &QueryMakerPrototype::slotResult );
+ connect( m_querymaker.data(), &Collections::QueryMaker::queryDone, &loop, &QEventLoop::quit );
run();
loop.exec();
return m_result;
}
void
QueryMakerPrototype::abort()
{
if( m_querymaker )
m_querymaker.data()->abortQuery();
}
//private
QueryMakerPrototype::QueryMakerPrototype( QueryMaker *queryMaker )
: QObject( 0 ) //engine ownership
, m_querymaker( queryMaker )
{
if( !queryMaker )
return;
- connect( queryMaker, SIGNAL(newResultReady(Meta::TrackList)), SIGNAL(newResultReady(Meta::TrackList)) );
- connect( queryMaker, SIGNAL(queryDone()), SIGNAL(queryDone()) );
+ connect( queryMaker, &Collections::QueryMaker::newTracksReady, this, &QueryMakerPrototype::newResultReady );
+ connect( queryMaker, &Collections::QueryMaker::queryDone, this, &QueryMakerPrototype::queryDone );
queryMaker->setAutoDelete( true );
}
QString
QueryMakerPrototype::filter() const
{
return m_filter;
}
bool
QueryMakerPrototype::isValid() const
{
return m_querymaker;
}
void
QueryMakerPrototype::slotResult( const Meta::TrackList &tracks )
{
m_result << tracks;
}
QueryMakerPrototype::~QueryMakerPrototype()
{
if( m_querymaker )
m_querymaker.data()->deleteLater();
}
diff --git a/src/scripting/scriptengine/exporters/ScriptableBiasExporter.cpp b/src/scripting/scriptengine/exporters/ScriptableBiasExporter.cpp
index ae7b140f83..013383cf89 100644
--- a/src/scripting/scriptengine/exporters/ScriptableBiasExporter.cpp
+++ b/src/scripting/scriptengine/exporters/ScriptableBiasExporter.cpp
@@ -1,559 +1,559 @@
/****************************************************************************************
* Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ScriptableBias"
#include "ScriptableBiasExporter.h"
#include "core/support/Debug.h"
#include "core/meta/Meta.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <QApplication>
#include <QCoreApplication>
#include <QScriptEngine>
#include <QThread>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
using namespace AmarokScript;
void
ScriptableBiasFactory::init( QScriptEngine *engine )
{
TrackSetExporter::init( engine );
engine->globalObject().setProperty( "BiasFactory", engine->newFunction( biasCtor ),
QScriptValue:: Undeletable | QScriptValue::ReadOnly );
engine->globalObject().setProperty( "GroupBiasFactory", engine->newFunction( groupBiasCtor ),
QScriptValue:: Undeletable | QScriptValue::ReadOnly );
}
QScriptValue
ScriptableBiasFactory::biasCtor( QScriptContext *context, QScriptEngine *engine )
{
Q_UNUSED( context )
const QScriptValue biasFactoryObject = engine->newQObject( new ScriptableBiasFactory( engine )
, QScriptEngine::ScriptOwnership
, QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeChildObjects );
return biasFactoryObject;
}
QScriptValue
ScriptableBiasFactory::groupBiasCtor( QScriptContext *context, QScriptEngine *engine )
{
Q_UNUSED( context )
ScriptableBiasFactory *factory = new ScriptableBiasFactory( engine, true );
QScriptValue biasFactoryObject = engine->newQObject( factory
, QScriptEngine::ScriptOwnership
, QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeChildObjects );
return biasFactoryObject;
}
ScriptableBiasFactory::ScriptableBiasFactory( QScriptEngine *engine, bool groupBias )
: QObject( engine )
, m_groupBias( groupBias )
, m_engine( engine )
, m_enabled( false )
{}
ScriptableBiasFactory::~ScriptableBiasFactory()
{
Dynamic::BiasFactory::instance()->removeBiasFactory( this );
}
Dynamic::BiasPtr
ScriptableBiasFactory::createBias()
{
ScriptableBias *bias;
//if( m_groupBias )
// return new ScriptableGroupBias( this );
//else
bias = new ScriptableBias( this );
Dynamic::BiasPtr biasPtr = Dynamic::BiasPtr( bias );
QScriptValue biasObject = bias->scriptObject();
if( m_initFunction.isFunction() )
m_initFunction.call( biasObject, QScriptValueList() << biasObject );
return biasPtr;
}
// private
QScriptEngine*
ScriptableBiasFactory::engine() const
{
return m_engine;
}
void
ScriptableBiasFactory::setEnabled( bool enabled )
{
if( enabled )
{
if( !m_enabled )
Dynamic::BiasFactory::instance()->registerNewBiasFactory( this );
}
else
Dynamic::BiasFactory::instance()->removeBiasFactory( this );
m_enabled = enabled;
}
bool
ScriptableBiasFactory::enabled() const
{
return m_enabled;
}
void
ScriptableBiasFactory::setName( const QString &name )
{
m_name = name;
}
QString
ScriptableBiasFactory::name() const
{
return m_name;
}
QString
ScriptableBiasFactory::i18nName() const
{
return m_i18nName;
}
QString
ScriptableBiasFactory::i18nDescription() const
{
return m_description;
}
QScriptValue
ScriptableBiasFactory::initFunction() const
{
return m_initFunction;
}
void
ScriptableBiasFactory::setInitFunction( const QScriptValue &value )
{
m_initFunction = value;
}
void
ScriptableBiasFactory::setI18nDescription( const QString &description )
{
m_description = description;
}
void
ScriptableBiasFactory::setI18nName( const QString &i18nName )
{
m_i18nName = i18nName;
}
QScriptValue
ScriptableBiasFactory::widgetFunction() const
{
return m_widgetFunction;
}
void
ScriptableBiasFactory::setWidgetFunction( const QScriptValue &value )
{
// throw exception?
//if( !value.isFunction() )
m_widgetFunction = value;
}
void
ScriptableBiasFactory::setFromXmlFunction( const QScriptValue &value )
{
m_fromXmlFunction = value;
}
void
ScriptableBiasFactory::setToXmlFunction( const QScriptValue &value )
{
m_toXmlFunction = value;
}
QScriptValue
ScriptableBiasFactory::fromXmlFunction() const
{
return m_fromXmlFunction;
}
QScriptValue
ScriptableBiasFactory::toXmlFunction() const
{
return m_toXmlFunction;
}
QScriptValue
ScriptableBiasFactory::matchingTracksFunction() const
{
return m_matchingTracksFunction;
}
void
ScriptableBiasFactory::setMatchingTracksFunction( const QScriptValue &value )
{
m_matchingTracksFunction = value;
}
void
ScriptableBiasFactory::setTrackMatchesFunction( const QScriptValue &value )
{
m_trackMatchesFunction = value;
}
QScriptValue
ScriptableBiasFactory::trackMatchesFunction() const
{
return m_trackMatchesFunction;
}
void
ScriptableBiasFactory::setToStringFunction( const QScriptValue &value )
{
m_toStringFunction = value;
}
QScriptValue
ScriptableBiasFactory::toStringFunction() const
{
return m_toStringFunction;
}
/*********************************************************************************
// ScriptableBias
**********************************************************************************/
void
ScriptableBias::toXml( QXmlStreamWriter *writer ) const
{
if( m_scriptBias.data()->toXmlFunction().isFunction() )
m_scriptBias.data()->fromXmlFunction().call( m_biasObject,
QScriptValueList() << m_engine->toScriptValue<QXmlStreamWriter*>( writer ) );
else
Dynamic::AbstractBias::toXml( writer );
}
void
ScriptableBias::fromXml( QXmlStreamReader *reader )
{
if( m_scriptBias.data()->fromXmlFunction().isFunction() )
m_scriptBias.data()->fromXmlFunction().call( m_biasObject,
QScriptValueList() << m_engine->toScriptValue<QXmlStreamReader*>( reader ) );
else
Dynamic::AbstractBias::fromXml( reader );
}
QWidget*
ScriptableBias::widget( QWidget *parent )
{
QWidget *widget = dynamic_cast<QWidget*>( m_scriptBias.data()->widgetFunction().call( m_biasObject,
m_scriptBias.data()->engine()->newQObject( parent ) ).toQObject() );
if( widget )
return widget;
return Dynamic::AbstractBias::widget( parent );
}
void
ScriptableBias::invalidate()
{
Dynamic::AbstractBias::invalidate();
}
Dynamic::TrackSet
ScriptableBias::matchingTracks( const Meta::TrackList &playlist, int contextCount, int finalCount, const Dynamic::TrackCollectionPtr universe ) const
{
DEBUG_BLOCK
if( QThread::currentThread() == QCoreApplication::instance()->thread() )
return slotMatchingTracks( playlist, contextCount, finalCount, universe );
Dynamic::TrackSet retVal;
Q_ASSERT( QMetaObject::invokeMethod( const_cast<ScriptableBias*>( this ), "slotMatchingTracks", Qt::BlockingQueuedConnection,
Q_RETURN_ARG( Dynamic::TrackSet, retVal),
Q_ARG( Meta::TrackList, playlist ),
Q_ARG( int, contextCount ),
Q_ARG( int, finalCount ),
Q_ARG( Dynamic::TrackCollectionPtr, universe )
) );
debug() << "Returning trackSet, trackCount " << retVal.trackCount() << ", isOutstanding " << retVal.isOutstanding();
return retVal;
}
Dynamic::TrackSet
ScriptableBias::slotMatchingTracks( const Meta::TrackList &playlist, int contextCount, int finalCount, const Dynamic::TrackCollectionPtr universe ) const
{
Q_ASSERT( QThread::currentThread() == QCoreApplication::instance()->thread() );
if( m_scriptBias.data()->matchingTracksFunction().isFunction() )
{
QScriptValue trackSetVal = m_scriptBias.data()->matchingTracksFunction().call( m_biasObject,
QScriptValueList() << m_engine->toScriptValue<Meta::TrackList>( playlist )
<< contextCount
<< finalCount
<< m_engine->toScriptValue<QStringList>( universe->uids() ) );
TrackSetExporter *trackSetExporter = dynamic_cast<TrackSetExporter*>( trackSetVal.toQObject() );
if( trackSetExporter )
return Dynamic::TrackSet( *trackSetExporter );
}
debug() << "Invalid trackSet received";
return Dynamic::TrackSet( universe, false );
}
QString
ScriptableBias::name() const
{
QString name;
if( m_scriptBias )
name = m_scriptBias.data()->name();
return name.isEmpty() ? Dynamic::AbstractBias::name() : name;
}
void
ScriptableBias::ready( const Dynamic::TrackSet &trackSet )
{
debug() << "Received trackset, count: " << trackSet.trackCount() << "Is outstanding:" << trackSet.isOutstanding();
emit resultReady( trackSet );
}
void
ScriptableBias::paintOperator( QPainter *painter, const QRect &rect, Dynamic::AbstractBias *bias )
{
Dynamic::AbstractBias::paintOperator( painter, rect, bias );
}
void
ScriptableBias::replace( Dynamic::BiasPtr newBias )
{
Dynamic::AbstractBias::replace( newBias );
}
QString
ScriptableBias::toString() const
{
return m_scriptBias.data()->toStringFunction().call( m_biasObject ).toString();
}
bool
ScriptableBias::trackMatches( int position, const Meta::TrackList& playlist, int contextCount ) const
{
if( m_scriptBias.data()->trackMatchesFunction().isFunction() )
return m_scriptBias.data()->trackMatchesFunction().call( m_biasObject,
QScriptValueList() << position
<< m_engine->toScriptValue<Meta::TrackList>( playlist )
<< contextCount
).toBool();
return true;
}
ScriptableBias::ScriptableBias( ScriptableBiasFactory *biasProto )
: m_scriptBias( biasProto )
, m_engine( biasProto->engine() )
{
m_biasObject = m_engine->newQObject( this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater );
- connect( m_engine, SIGNAL(destroyed(QObject*)), SLOT(removeBias()) );
+ connect( m_engine, &QObject::destroyed, this, &ScriptableBias::removeBias );
}
ScriptableBias::~ScriptableBias()
{}
void
ScriptableBias::removeBias()
{
replace( Dynamic::BiasPtr( new Dynamic::ReplacementBias( name() ) ) );
}
/////////////////////////////////////////////////////////////////////////////////////////
// TrackSetExporter
/////////////////////////////////////////////////////////////////////////////////////////
void
TrackSetExporter::init( QScriptEngine *engine )
{
qScriptRegisterMetaType<Dynamic::TrackSet>( engine, toScriptValue, fromScriptValue );
engine->globalObject().setProperty( "TrackSet", engine->newFunction( trackSetConstructor ),
QScriptValue:: Undeletable | QScriptValue::ReadOnly );
}
void
TrackSetExporter::fromScriptValue( const QScriptValue &obj, Dynamic::TrackSet &trackSet )
{
DEBUG_BLOCK
TrackSetExporter *trackSetProto = dynamic_cast<TrackSetExporter*>( obj.toQObject() );
if( !trackSetProto )
trackSet = Dynamic::TrackSet( Dynamic::TrackCollectionPtr( new Dynamic::TrackCollection( QStringList() ) ), false );
else
trackSet = *trackSetProto;
}
QScriptValue
TrackSetExporter::toScriptValue( QScriptEngine *engine, const Dynamic::TrackSet &trackSet )
{
DEBUG_BLOCK
TrackSetExporter *trackProto = new TrackSetExporter( trackSet );
QScriptValue val = engine->newQObject( trackProto, QScriptEngine::ScriptOwnership,
QScriptEngine::ExcludeSuperClassContents );
return val;
}
bool
TrackSetExporter::containsUid( const QString &uid ) const
{
return Dynamic::TrackSet::contains( uid );
}
QScriptValue
TrackSetExporter::trackSetConstructor( QScriptContext *context, QScriptEngine *engine )
{
DEBUG_BLOCK
// if( !context->isCalledAsConstructor() ) throw exception?
Dynamic::TrackSet trackSet;
bool invalid = false;
switch( context->argumentCount() )
{
case 0:
break;
case 1:
{
TrackSetExporter *trackSetPrototype = dynamic_cast<TrackSetExporter*>( context->argument( 0 ).toQObject() );
if( trackSetPrototype )
trackSet = Dynamic::TrackSet( *trackSetPrototype );
else
invalid = true;
break;
}
case 2:
if( context->argument( 1 ).isBool() )
{
bool isFull = context->argument( 1 ).toBool();
QScriptValue arg0 = context->argument( 0 );
QStringList uidList;
Meta::TrackList trackList;
if( arg0.toVariant().canConvert<QStringList>() )
{
uidList = arg0.toVariant().toStringList();
Q_ASSERT( !arg0.toVariant().canConvert<Meta::TrackList>() );
trackSet = Dynamic::TrackSet( Dynamic::TrackCollectionPtr( new Dynamic::TrackCollection( uidList ) ), isFull );
}
else if( arg0.toVariant().canConvert<Meta::TrackList>() )
{
debug() << "In Meta::Tracklist TrackSet ctor";
trackList = qscriptvalue_cast<Meta::TrackList>( arg0 );
foreach( const Meta::TrackPtr &track, trackList )
{
if( track )
uidList << track->uidUrl();
}
trackSet = Dynamic::TrackSet( Dynamic::TrackCollectionPtr( new Dynamic::TrackCollection( uidList ) ), isFull );
}
else
invalid = true;
break;
}
default:
invalid = true;
}
if( invalid )
{
context->throwError( QScriptContext::SyntaxError, "Invalid arguments for TrackSet!" );
return engine->undefinedValue();
}
const QScriptValue trackSetObject = engine->newQObject( new TrackSetExporter( trackSet )
, QScriptEngine::ScriptOwnership
, QScriptEngine::ExcludeSuperClassContents );
return trackSetObject;
}
void
TrackSetExporter::reset( bool value )
{
Dynamic::TrackSet::reset( value );
}
void
TrackSetExporter::intersectTrackSet( const Dynamic::TrackSet &trackSet)
{
Dynamic::TrackSet::intersect( trackSet );
}
void
TrackSetExporter::intersectUids( const QStringList &uids )
{
Dynamic::TrackSet::intersect( uids );
}
void
TrackSetExporter::subtractTrack( const Meta::TrackPtr &track )
{
Dynamic::TrackSet::subtract( track );
}
void
TrackSetExporter::subtractTrackSet( const Dynamic::TrackSet &trackSet )
{
Dynamic::TrackSet::subtract( trackSet );
}
void
TrackSetExporter::subtractUids( const QStringList &uids )
{
Dynamic::TrackSet::subtract( uids );
}
void
TrackSetExporter::uniteTrack( const Meta::TrackPtr &track )
{
Dynamic::TrackSet::unite( track );
}
void
TrackSetExporter::uniteTrackSet( const Dynamic::TrackSet &trackSet )
{
Dynamic::TrackSet::unite( trackSet );
}
void
TrackSetExporter::uniteUids( const QStringList &uids )
{
Dynamic::TrackSet::unite( uids );
}
Meta::TrackPtr
TrackSetExporter::getRandomTrack() const
{
return CollectionManager::instance()->trackForUrl( QUrl( Dynamic::TrackSet::getRandomTrack() ) );
}
bool
TrackSetExporter::containsTrack( const Meta::TrackPtr track ) const
{
return Dynamic::TrackSet::contains( track );
}
// private
TrackSetExporter::TrackSetExporter( const Dynamic::TrackSet &trackSet )
: QObject( 0 )
, TrackSet( trackSet )
{}
diff --git a/src/scripting/scriptmanager/ScriptItem.cpp b/src/scripting/scriptmanager/ScriptItem.cpp
index 7de029b85d..0247e32b5b 100644
--- a/src/scripting/scriptmanager/ScriptItem.cpp
+++ b/src/scripting/scriptmanager/ScriptItem.cpp
@@ -1,333 +1,335 @@
/****************************************************************************************
* Copyright (c) 2004-2010 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2005-2007 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2006 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2006 Martin Ellis <martin.ellis@kdemail.net> *
* Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com> *
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* Copyright (c) 2009 Jakob Kummerow <jakob.kummerow@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ScriptItem"
#include "ScriptItem.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "core/interfaces/Logger.h"
#include "MainWindow.h"
#include "amarokconfig.h"
#include "config.h"
#include "services/scriptable/ScriptableServiceManager.h"
#include "scripting/scriptengine/AmarokCollectionScript.h"
#include "scripting/scriptengine/AmarokScriptConfig.h"
#include "scripting/scriptengine/AmarokEngineScript.h"
#include "scripting/scriptengine/AmarokInfoScript.h"
#include "scripting/scriptengine/AmarokKNotifyScript.h"
#include "scripting/scriptengine/AmarokLyricsScript.h"
#include "scripting/scriptengine/AmarokNetworkScript.h"
#include "scripting/scriptengine/AmarokOSDScript.h"
#include "scripting/scriptengine/AmarokPlaylistScript.h"
#include "scripting/scriptengine/AmarokScript.h"
#include "scripting/scriptengine/AmarokScriptableServiceScript.h"
#include "scripting/scriptengine/AmarokServicePluginManagerScript.h"
#include "scripting/scriptengine/AmarokStatusbarScript.h"
#include "scripting/scriptengine/AmarokStreamItemScript.h"
#include "scripting/scriptengine/AmarokWindowScript.h"
#include "scripting/scriptengine/exporters/CollectionTypeExporter.h"
#include "scripting/scriptengine/exporters/MetaTypeExporter.h"
#include "scripting/scriptengine/exporters/QueryMakerExporter.h"
#include "scripting/scriptengine/exporters/ScriptableBiasExporter.h"
#include "scripting/scriptengine/ScriptImporter.h"
#include "scripting/scriptengine/ScriptingDefines.h"
#include "ScriptManager.h"
#include <KStandardDirs>
#include <KPushButton>
#include <QToolTip>
#include <QFileInfo>
#include <QScriptEngine>
////////////////////////////////////////////////////////////////////////////////
// ScriptTerminatorWidget
////////////////////////////////////////////////////////////////////////////////
ScriptTerminatorWidget::ScriptTerminatorWidget( const QString &message )
: PopupWidget( 0 )
{
setFrameStyle( QFrame::StyledPanel | QFrame::Raised );
setContentsMargins( 4, 4, 4, 4 );
setMinimumWidth( 26 );
setMinimumHeight( 26 );
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
QPalette p = QToolTip::palette();
setPalette( p );
QLabel *alabel = new QLabel( message, this );
alabel->setWordWrap( true );
alabel->setTextFormat( Qt::RichText );
alabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
alabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
alabel->setPalette( p );
KPushButton *button = new KPushButton( i18n( "Terminate" ), this );
button->setPalette(p);;
- connect( button, SIGNAL(clicked()), SIGNAL(terminate()) );
+ connect( button, &QAbstractButton::clicked, this, &ScriptTerminatorWidget::terminate );
button = new KPushButton( KStandardGuiItem::close(), this );
button->setPalette(p);
- connect( button, SIGNAL(clicked()), SLOT(hide()) );
+ connect( button, &QAbstractButton::clicked, this, &ScriptTerminatorWidget::hide );
reposition();
}
////////////////////////////////////////////////////////////////////////////////
// ScriptItem
////////////////////////////////////////////////////////////////////////////////
ScriptItem::ScriptItem( QObject *parent, const QString &name, const QString &path, const KPluginInfo &info )
: QObject( parent )
, m_name( name )
, m_url( path )
, m_info( info )
, m_running( false )
, m_evaluating( false )
, m_runningTime( 0 )
, m_timerId( 0 )
{}
void
ScriptItem::pause()
{
DEBUG_BLOCK
if( !m_engine )
{
warning() << "Script has no script engine attached:" << m_name;
return;
}
killTimer( m_timerId );
if( m_popupWidget )
{
m_popupWidget.data()->hide();
m_popupWidget.data()->deleteLater();;
}
//FIXME: Sometimes a script can be evaluating and cannot be abort? or can be reevaluating for some reason?
if( m_engine.data()->isEvaluating() )
{
m_engine.data()->abortEvaluation();
m_evaluating = false;
return;
}
if( m_info.category() == "Scriptable Service" )
The::scriptableServiceManager()->removeRunningScript( m_name );
if( m_info.isPluginEnabled() )
{
debug() << "Disabling script" << m_info.pluginName();
m_info.setPluginEnabled( false );
m_info.save();
}
m_log << QString( "%1 Script ended" ).arg( QTime::currentTime().toString() );
m_running = false;
m_evaluating = false;
}
QString
ScriptItem::specPath() const
{
QFileInfo info( m_url.path() );
const QString specPath = QString( "%1/%2.spec" ).arg( info.path(), info.completeBaseName() );
return specPath;
}
void
ScriptItem::timerEvent( QTimerEvent* event )
{
Q_UNUSED( event )
if( m_engine && m_engine.data()->isEvaluating() )
{
m_runningTime += 100;
if( m_runningTime >= 5000 )
{
debug() << "5 seconds passed evaluating" << m_name;
m_runningTime = 0;
if( !m_popupWidget )
m_popupWidget = new ScriptTerminatorWidget(
i18n( "Script %1 has been evaluating for over"
" 5 seconds now, terminate?"
, m_name ) );
- connect( m_popupWidget.data(), SIGNAL(terminate()), SLOT(stop()) );
+ connect( m_popupWidget.data(), &ScriptTerminatorWidget::terminate,
+ this, &ScriptItem::stop );
m_popupWidget.data()->show();
}
}
else
{
if( m_popupWidget )
m_popupWidget.data()->deleteLater();
m_runningTime = 0;
}
}
QString
ScriptItem::handleError( QScriptEngine *engine )
{
QString errorString = QString( "Script Error: %1 (line: %2)" )
.arg( engine->uncaughtException().toString() )
.arg( engine->uncaughtExceptionLineNumber() );
error() << errorString;
engine->clearExceptions();
stop();
return errorString;
}
bool
ScriptItem::start( bool silent )
{
DEBUG_BLOCK
//load the wrapper classes
m_output.clear();
initializeScriptEngine();
QFile scriptFile( m_url.path() );
scriptFile.open( QIODevice::ReadOnly );
m_running = true;
m_evaluating = true;
m_log << QString( "%1 Script started" ).arg( QTime::currentTime().toString() );
m_timerId = startTimer( 100 );
Q_ASSERT( m_engine );
m_output << m_engine.data()->evaluate( scriptFile.readAll() ).toString();
debug() << "After Evaluation "<< m_name;
emit evaluated( m_output.join( "\n" ) );
scriptFile.close();
if ( m_evaluating )
{
m_evaluating = false;
if ( m_engine.data()->hasUncaughtException() )
{
m_log << handleError( m_engine.data() );
if( !silent )
{
debug() << "The Log For the script that is the borked: " << m_log;
}
return false;
}
if( m_info.category() == QLatin1String("Scriptable Service") )
m_service.data()->slotCustomize( m_name );
}
else
stop();
return true;
}
void
ScriptItem::initializeScriptEngine()
{
DEBUG_BLOCK
if( m_engine )
return;
m_engine = new AmarokScript::AmarokScriptEngine( this );
- connect( m_engine.data(), SIGNAL(deprecatedCall(QString)), this, SLOT(slotDeprecatedCall(QString)) );
- connect( m_engine.data(), SIGNAL(signalHandlerException(QScriptValue)), this,
- SIGNAL(signalHandlerException(QScriptValue)));
+ connect( m_engine.data(), &AmarokScript::AmarokScriptEngine::deprecatedCall,
+ this, &ScriptItem::slotDeprecatedCall );
+ connect( m_engine.data(), &AmarokScript::AmarokScriptEngine::signalHandlerException,
+ this, &ScriptItem::signalHandlerException );
m_engine.data()->setProcessEventsInterval( 50 );
debug() << "starting script engine:" << m_name;
// first create the Amarok global script object
new AmarokScript::AmarokScript( m_name, m_engine.data() );
// common utils
new AmarokScript::ScriptImporter( m_engine.data(), m_url );
new AmarokScript::AmarokScriptConfig( m_name, m_engine.data() );
new AmarokScript::InfoScript( m_url, m_engine.data() );
//new AmarokNetworkScript( m_engine.data() );
new AmarokScript::Downloader( m_engine.data() );
// backend
new AmarokScript::AmarokCollectionScript( m_engine.data() );
new AmarokScript::AmarokEngineScript( m_engine.data() );
// UI
new AmarokScript::AmarokWindowScript( m_engine.data() );
new AmarokScript::AmarokPlaylistScript( m_engine.data() );
new AmarokScript::AmarokStatusbarScript( m_engine.data() );
new AmarokScript::AmarokKNotifyScript( m_engine.data() );
new AmarokScript::AmarokOSDScript( m_engine.data() );
AmarokScript::CollectionPrototype::init( m_engine.data() );
AmarokScript::QueryMakerPrototype::init( m_engine.data() );
const QString &category = m_info.category();
if( category.contains( QLatin1String("Lyrics") ) )
{
new AmarokScript::AmarokLyricsScript( m_engine.data() );
}
if( category.contains( QLatin1String("Scriptable Service") ) )
{
new StreamItem( m_engine.data() );
m_service = new AmarokScript::ScriptableServiceScript( m_engine.data() );
new AmarokScript::AmarokServicePluginManagerScript( m_engine.data() );
}
AmarokScript::MetaTrackPrototype::init( m_engine.data() );
AmarokScript::ScriptableBiasFactory::init( m_engine.data() );
}
void
ScriptItem::stop()
{
pause();
m_engine.data()->deleteLater();
}
void
ScriptItem::slotDeprecatedCall( const QString &call )
{
Q_UNUSED( call )
disconnect( sender(), SIGNAL(deprecatedCall(QString)), this, 0 );
if( !AmarokConfig::enableDeprecationWarnings() )
return;
QString message = i18nc( "%1 is the name of the offending script, %2 the name of the script author, and %3 the author's email"
, "The script %1 uses deprecated scripting API calls. Please contact the script"
" author, %2 at %3, and ask him to upgrade it before the next Amarok release."
, m_info.name(), m_info.author(), m_info.email() );
Amarok::Components::logger()->longMessage( message );
}
void
ScriptItem::uninstall()
{
emit uninstalled();
deleteLater();
}
ScriptItem::~ScriptItem()
{
stop();
}
diff --git a/src/scripting/scriptmanager/ScriptManager.cpp b/src/scripting/scriptmanager/ScriptManager.cpp
index 30fdfdf97e..d5d4bf5986 100644
--- a/src/scripting/scriptmanager/ScriptManager.cpp
+++ b/src/scripting/scriptmanager/ScriptManager.cpp
@@ -1,426 +1,426 @@
/****************************************************************************************
* Copyright (c) 2004-2010 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2005-2007 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2006 Alexandre Pereira de Oliveira <aleprj@gmail.com> *
* Copyright (c) 2006 Martin Ellis <martin.ellis@kdemail.net> *
* Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com> *
* Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> *
* Copyright (c) 2009 Jakob Kummerow <jakob.kummerow@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ScriptManager"
#include "ScriptManager.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "core/interfaces/Logger.h"
#include "MainWindow.h"
#include "amarokconfig.h"
#include <config.h> // for the compile flags
#include "services/scriptable/ScriptableServiceManager.h"
#include "ScriptItem.h"
#include "ScriptUpdater.h"
#include <KMessageBox>
#include <KStandardDirs>
#include <KGlobal>
#include <QFileInfo>
#include <QScriptEngine>
#include <sys/stat.h>
#include <sys/types.h>
ScriptManager* ScriptManager::s_instance = 0;
ScriptManager::ScriptManager( QObject* parent )
: QObject( parent )
{
DEBUG_BLOCK
setObjectName( "ScriptManager" );
s_instance = this;
if( AmarokConfig::enableScripts() == false )
{
#pragma message("PORTEME KF5: not sure if this is relevant in QT5")
/*if( !minimumBindingsAvailable() )
{
KMessageBox::error( 0,
i18n( "Scripts have been disabled since you are missing the QtScriptQtBindings "
"package. Please install the package and restart Amarok for scripts to work." ),
i18n( "Scripts Disabled!" ) );
return;
}*/
AmarokConfig::setEnableScripts( true );
}
// Delay this call via eventloop, because it's a bit slow and would block
- QTimer::singleShot( 0, this, SLOT(updateAllScripts()) );
+ QTimer::singleShot( 0, this, &ScriptManager::updateAllScripts );
}
bool
ScriptManager::minimumBindingsAvailable()
{
QStringList minimumBindings;
minimumBindings << "qt.core" << "qt.gui" << "qt.sql" << "qt.xml" << "qt.uitools" << "qt.network";
QScriptEngine engine;
foreach( const QString &binding, minimumBindings )
{
// simply compare with availableExtensions()? Or can import still fail?
QScriptValue error = engine.importExtension( binding );
if( error.isUndefined() )
continue; // undefined indicates success
debug() << "Extension" << binding << "not found:" << error.toString();
debug() << "Available extensions:" << engine.availableExtensions();
return false;
}
return true;
}
ScriptManager::~ScriptManager()
{}
void
ScriptManager::destroy() {
if (s_instance) {
delete s_instance;
s_instance = 0;
}
}
ScriptManager*
ScriptManager::instance()
{
return s_instance ? s_instance : new ScriptManager( The::mainWindow() );
}
////////////////////////////////////////////////////////////////////////////////
// public
////////////////////////////////////////////////////////////////////////////////
bool
ScriptManager::runScript( const QString& name, bool silent )
{
if( !m_scripts.contains( name ) )
return false;
return slotRunScript( name, silent );
}
bool
ScriptManager::stopScript( const QString& name )
{
if( name.isEmpty() )
return false;
if( !m_scripts.contains( name ) )
return false;
m_scripts[name]->stop();
return true;
}
QStringList
ScriptManager::listRunningScripts() const
{
QStringList runningScripts;
foreach( const ScriptItem *item, m_scripts )
{
if( item->running() )
runningScripts << item->info().pluginName();
}
return runningScripts;
}
QString
ScriptManager::specForScript( const QString& name ) const
{
if( !m_scripts.contains( name ) )
return QString();
return m_scripts[name]->specPath();
}
bool
ScriptManager::lyricsScriptRunning() const
{
return !m_lyricsScript.isEmpty();
}
void
ScriptManager::notifyFetchLyrics( const QString& artist, const QString& title, const QString& url, Meta::TrackPtr track )
{
DEBUG_BLOCK
emit fetchLyrics( artist, title, url, track );
}
////////////////////////////////////////////////////////////////////////////////
// private slots (script updater stuff)
////////////////////////////////////////////////////////////////////////////////
void
ScriptManager::updateAllScripts() // SLOT
{
DEBUG_BLOCK
// find all scripts (both in $KDEHOME and /usr)
QStringList foundScripts = KGlobal::dirs()->findAllResources( "data", "amarok/scripts/*/main.js",
KStandardDirs::Recursive |
KStandardDirs::NoDuplicates );
// remove deleted scripts
foreach( ScriptItem *item, m_scripts )
{
const QString specPath = QString( "%1/script.spec" ).arg( QFileInfo( item->url().path() ).path() );
if( !QFile::exists( specPath ) )
{
debug() << "Removing script " << item->info().pluginName();
item->uninstall();
m_scripts.remove( item->info().pluginName() );
}
}
m_nScripts = foundScripts.count();
// get timestamp of the last update check
KConfigGroup config = Amarok::config( "ScriptManager" );
const uint lastCheck = config.readEntry( "LastUpdateCheck", QVariant( 0 ) ).toUInt();
const uint now = QDateTime::currentDateTime().toTime_t();
bool autoUpdateScripts = AmarokConfig::autoUpdateScripts();
// note: we can't update scripts without the QtCryptoArchitecture, so don't even try
#ifndef QCA2_FOUND
autoUpdateScripts = false;
#endif
// last update was at least 7 days ago -> check now if auto update is enabled
if( autoUpdateScripts && (now - lastCheck > 7*24*60*60) )
{
debug() << "ScriptUpdater: Performing script update check now!";
for( int i = 0; i < m_nScripts; ++i )
{
ScriptUpdater *updater = new ScriptUpdater( this );
// all the ScriptUpdaters are now started in parallel.
// tell them which script to work on
updater->setScriptPath( foundScripts.at( i ) );
// tell them whom to signal when they're finished
- connect( updater, SIGNAL(finished(QString)), SLOT(updaterFinished(QString)) );
+ connect( updater, &ScriptUpdater::finished, this, &ScriptManager::updaterFinished );
// and finally tell them to get to work
- QTimer::singleShot( 0, updater, SLOT(updateScript()) );
+ QTimer::singleShot( 0, updater, &ScriptUpdater::updateScript );
}
// store current timestamp
config.writeEntry( "LastUpdateCheck", QVariant( now ) );
config.sync();
}
// last update was pretty recent, don't check again
else
{
debug() << "ScriptUpdater: Skipping update check";
for ( int i = 0; i < m_nScripts; i++ )
{
loadScript( foundScripts.at( i ) );
}
configChanged( true );
}
}
void
ScriptManager::updaterFinished( const QString &scriptPath ) // SLOT
{
DEBUG_BLOCK
// count this event
m_updateSemaphore.release();
loadScript( scriptPath );
if ( m_updateSemaphore.tryAcquire(m_nScripts) )
{
configChanged( true );
}
sender()->deleteLater();
}
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
bool
ScriptManager::slotRunScript( const QString &name, bool silent )
{
ScriptItem *item = m_scripts.value( name );
- connect( item, SIGNAL(signalHandlerException(QScriptValue)),
- SLOT(handleException(QScriptValue)));
+ connect( item, &ScriptItem::signalHandlerException,
+ this, &ScriptManager::handleException );
if( item->info().category() == "Lyrics" )
{
m_lyricsScript = name;
debug() << "lyrics script started:" << name;
emit lyricsScriptStarted();
}
return item->start( silent );
}
void
ScriptManager::handleException(const QScriptValue& value)
{
DEBUG_BLOCK
QScriptEngine *engine = value.engine();
if (!engine)
return;
Amarok::Components::logger()->longMessage( i18n( "Script error reported by: %1\n%2", scriptNameForEngine( engine ), value.toString() ), Amarok::Logger::Error );
}
void
ScriptManager::ServiceScriptPopulate( const QString &name, int level, int parent_id,
const QString &path, const QString &filter )
{
if( m_scripts.value( name )->service() )
m_scripts.value( name )->service()->slotPopulate( name, level, parent_id, path, filter );
}
void
ScriptManager::ServiceScriptCustomize( const QString &name )
{
if( m_scripts.value( name )->service() )
m_scripts.value( name )->service()->slotCustomize( name );
}
void
ScriptManager::ServiceScriptRequestInfo( const QString &name, int level, const QString &callbackString )
{
if( m_scripts.value( name )->service() )
m_scripts.value( name )->service()->slotRequestInfo( name, level, callbackString );
}
void
ScriptManager::configChanged( bool changed )
{
emit scriptsChanged();
if( !changed )
return;
//evil scripts may prevent the config dialog from dismissing, delay execution
- QTimer::singleShot( 0, this, SLOT(slotConfigChanged()) );
+ QTimer::singleShot( 0, this, &ScriptManager::slotConfigChanged );
}
////////////////////////////////////////////////////////////////////////////////
// private
////////////////////////////////////////////////////////////////////////////////
void
ScriptManager::slotConfigChanged()
{
foreach( ScriptItem *item, m_scripts )
{
const QString name = item->info().pluginName();
bool enabledByDefault = item->info().isPluginEnabledByDefault();
bool enabled = Amarok::config( "Plugins" ).readEntry( name + "Enabled", enabledByDefault );
if( !item->running() && enabled )
{
slotRunScript( name );
}
else if( item->running() && !enabled )
{
item->stop();
}
}
}
bool
ScriptManager::loadScript( const QString& path )
{
if( path.isEmpty() )
return false;
QStringList SupportAPIVersion;
SupportAPIVersion << QLatin1String("API V1.0.0") << QLatin1String("API V1.0.1");
QString ScriptVersion;
QFileInfo info( path );
const QString specPath = QString( "%1/script.spec" ).arg( info.path() );
if( !QFile::exists( specPath ) )
{
error() << "script.spec for "<< path << " is missing!";
return false;
}
KPluginInfo pluginInfo( specPath );
if( !pluginInfo.isValid() )
{
error() << "PluginInfo invalid for" << specPath;
return false;
}
const QString pluginName = pluginInfo.pluginName();
const QString category = pluginInfo.category();
const QString version = pluginInfo.version();
if( pluginName.isEmpty() || category.isEmpty() || version.isEmpty() )
{
error() << "PluginInfo has empty values for" << specPath;
return false;
}
ScriptItem *item;
if( !m_scripts.contains( pluginName ) )
{
item = new ScriptItem( this, pluginName, path, pluginInfo );
m_scripts[ pluginName ] = item;
}
else if( m_scripts[pluginName]->info().version() < pluginInfo.version() )
{
m_scripts[ pluginName ]->deleteLater();
item = new ScriptItem( this, pluginName, path, pluginInfo );
m_scripts[ pluginName ] = item;
}
else
item = m_scripts.value( pluginName );
//assume it is API V1.0.0 if there is no "API V" prefix found
if( !item->info().dependencies().at(0).startsWith("API V") )
ScriptVersion = QLatin1String("API V1.0.0");
else
ScriptVersion = item->info().dependencies().at(0);
if( !SupportAPIVersion.contains( ScriptVersion ) )
{
warning() << "script API version not compatible with Amarok.";
return false;
}
debug() << "found script:" << category << pluginName << version << item->info().dependencies();
return true;
}
KPluginInfo::List
ScriptManager::scripts( const QString &category ) const
{
KPluginInfo::List scripts;
foreach( const ScriptItem *script, m_scripts )
{
if( script->info().category() == category )
scripts << script->info();
}
return scripts;
}
QString
ScriptManager::scriptNameForEngine( const QScriptEngine *engine ) const
{
foreach( const QString &name, m_scripts.keys() )
{
ScriptItem *script = m_scripts[name];
if( script->engine() == engine )
return name;
}
return QString();
}
diff --git a/src/scripting/scriptmanager/ScriptUpdater.cpp b/src/scripting/scriptmanager/ScriptUpdater.cpp
index 77917fd59b..6a1d33026f 100644
--- a/src/scripting/scriptmanager/ScriptUpdater.cpp
+++ b/src/scripting/scriptmanager/ScriptUpdater.cpp
@@ -1,276 +1,276 @@
/****************************************************************************************
* Copyright (c) 2009 Jakob Kummerow <jakob.kummerow@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ScriptUpdater"
#include "ScriptUpdater.h"
#include "core/support/Debug.h"
#include <KIO/Job>
#include <KPluginInfo>
#include <KStandardDirs>
#include <KTar>
#include <KGlobal>
#include <QDir>
#include <QFileInfo>
#ifdef QCA2_FOUND
#include <QtCrypto>
#endif
ScriptUpdater::ScriptUpdater( QObject *parent ) : QObject( parent )
{
}
ScriptUpdater::~ScriptUpdater()
{
}
void
ScriptUpdater::setScriptPath( const QString& scriptPath )
{
m_scriptPath = scriptPath;
}
void
ScriptUpdater::updateScript()
{
DEBUG_BLOCK
// 1a. detect currently installed version
QFileInfo info( m_scriptPath );
const QString specPath = info.path() + '/' + "script.spec";
if( !QFile::exists( specPath ) )
{
// no .spec file found, can't continue
emit finished( m_scriptPath );
return;
}
KPluginInfo pInfo( specPath );
if ( !pInfo.isValid() || pInfo.name().isEmpty() || pInfo.version().isEmpty() )
{
// invalid or unusable .spec file, can't continue
emit finished( m_scriptPath );
return;
}
m_scriptversion = pInfo.version();
// 1b. detect script name
QFile file( m_scriptPath );
m_fileName = file.fileName();
QRegExp rxname( "amarok/scripts/(.+)/main.js" );
rxname.indexIn( m_fileName );
m_scriptname = rxname.cap( 1 );
// 2. check if there are updates: get 'version' file from server
QUrl versionUrl( updateBaseUrl );
versionUrl = versionUrl.adjusted(QUrl::StripTrailingSlash);
versionUrl.setPath(versionUrl.path() + '/' + ( m_scriptname ));
versionUrl = versionUrl.adjusted(QUrl::StripTrailingSlash);
versionUrl.setPath(versionUrl.path() + '/' + ( '/' + versionFilename ));
m_versionFile.open();
debug() << m_scriptname << ": Accessing " << versionUrl.toDisplayString() << " ...";
QUrl versionDest( m_versionFile.fileName() );
KIO::FileCopyJob *versionJob = KIO::file_copy( versionUrl, versionDest, -1, KIO::Overwrite | KIO::HideProgressInfo );
- connect ( versionJob, SIGNAL(result(KJob*)), this, SLOT(phase2(KJob*)) );
+ connect ( versionJob, &KIO::FileCopyJob::result, this, &ScriptUpdater::phase2 );
}
void
ScriptUpdater::phase2( KJob * job )
{
DEBUG_BLOCK
if ( job->error() )
{
// if no 'version' file was found, cancel the update
emit finished( m_scriptPath );
return;
}
QFile file( m_versionFile.fileName() );
if ( !file.open( QIODevice::ReadOnly ) ) {
debug() << m_scriptname << ": Failed to open version file for reading!";
emit finished( m_scriptPath );
return;
}
QString response( file.readAll() );
file.close();
debug() << m_scriptname << ": online version: " << response;
if ( !isNewer( response, m_scriptversion ) )
{
// if no newer version is available, cancel update
emit finished( m_scriptPath );
return;
}
debug() << m_scriptname << ": newer version found, starting update :-)";
// 3. get the update archive, download it to a temporary file
QUrl archiveSrc( updateBaseUrl );
archiveSrc = archiveSrc.adjusted(QUrl::StripTrailingSlash);
archiveSrc.setPath(archiveSrc.path() + '/' + ( m_scriptname ));
archiveSrc = archiveSrc.adjusted(QUrl::StripTrailingSlash);
archiveSrc.setPath(archiveSrc.path() + '/' + ( '/' + archiveFilename ));
m_archiveFile.open(); // temporary files only have a fileName() after they've been opened
QUrl archiveDest( m_archiveFile.fileName() );
KIO::FileCopyJob *archiveJob = KIO::file_copy( archiveSrc, archiveDest, -1, KIO::Overwrite | KIO::HideProgressInfo );
- connect ( archiveJob, SIGNAL(result(KJob*)), this, SLOT(phase3(KJob*)) );
+ connect ( archiveJob, &KIO::FileCopyJob::result, this, &ScriptUpdater::phase3 );
}
void ScriptUpdater::phase3( KJob * job )
{
if ( job->error() )
{
// if the file wasn't found, cancel the update
emit finished( m_scriptPath );
return;
}
// 4. get the archive's signature, download it to a temporary file as well
QUrl sigSrc( updateBaseUrl );
sigSrc = sigSrc.adjusted(QUrl::StripTrailingSlash);
sigSrc.setPath(sigSrc.path() + '/' + ( m_scriptname ));
sigSrc = sigSrc.adjusted(QUrl::StripTrailingSlash);
sigSrc.setPath(sigSrc.path() + '/' + ( '/' + signatureFilename ));
m_sigFile.open();
QUrl sigDest( m_sigFile.fileName() );
KIO::FileCopyJob *sigJob = KIO::file_copy( sigSrc, sigDest, -1, KIO::Overwrite | KIO::HideProgressInfo );
- connect ( sigJob, SIGNAL(result(KJob*)), this, SLOT(phase4(KJob*)) );
+ connect ( sigJob, &KIO::FileCopyJob::result, this, &ScriptUpdater::phase4 );
}
void
ScriptUpdater::phase4( KJob * job )
{
if ( job->error() )
{
// if the signature couldn't be downloaded, cancel the update
emit finished( m_scriptPath );
return;
}
// 5. compare the signature to the archive's hash
#ifdef QCA2_FOUND
QCA::Initializer init;
QCA::ConvertResult conversionResult;
QCA::PublicKey pubkey = QCA::PublicKey::fromPEM( publicKey, &conversionResult );
if ( !( QCA::ConvertGood == conversionResult ) )
{
debug() << m_scriptname << ": Failed to read public key!";
emit finished( m_scriptPath );
return;
}
QFile file( m_archiveFile.fileName() );
if ( !file.open( QIODevice::ReadOnly ) )
{
debug() << m_scriptname << ": Failed to open archive file for reading!";
emit finished( m_scriptPath );
return;
}
QCA::Hash hash( "sha1" );
hash.update( &file );
file.close();
QFile versionFile( m_versionFile.fileName() );
if ( !versionFile.open( QIODevice::ReadOnly ) )
{
debug() << m_scriptname << ": Failed to open version file for reading!";
emit finished( m_scriptPath );
return;
}
QCA::Hash versionHash( "sha1" );
versionHash.update( &versionFile );
versionFile.close();
QFile sigFile( m_sigFile.fileName() );
if ( !sigFile.open( QIODevice::ReadOnly ) )
{
debug() << m_scriptname << ": Failed to open signature file for reading!";
emit finished( m_scriptPath );
return;
}
QByteArray signature = QByteArray::fromBase64( sigFile.readAll() );
sigFile.close();
pubkey.startVerify( QCA::EMSA3_SHA1 );
pubkey.update( hash.final() );
pubkey.update( versionHash.final() );
if ( !pubkey.validSignature( signature ) )
{
debug() << m_scriptname << ": Invalid signature, no update performed.";
emit finished( m_scriptPath );
return;
}
debug() << m_scriptname << ": Signature matches. Performing update now.";
#else
debug() << m_scriptname << ": Amarok build without QtCrypto. Cannot verify signature";
return;
#endif
// 6. everything OK, perform the update by extracting the archive
KTar archive( m_archiveFile.fileName() );
if( !archive.open( QIODevice::ReadOnly ) )
{
// in case of errors: bad luck, cancel the update
debug() << m_scriptname << ": Error opening the update package.";
emit finished( m_scriptPath );
return;
}
const QString relativePath = KGlobal::dirs()->relativeLocation( "data", m_fileName );
const QFileInfo fileinfo( relativePath );
const QString destination = KGlobal::dirs()->saveLocation( "data", fileinfo.path(), false );
const QDir dir;
if( !dir.exists( destination ) )
{
dir.mkpath( destination );
}
const KArchiveDirectory* const archiveDir = archive.directory();
archiveDir->copyTo( destination );
// update m_scriptPath so that the updated version of the script will be loaded
m_scriptPath = destination + "/main.js";
debug() << m_scriptname << ": Updating finished successfully :-)";
// all done, temporary files are deleted automatically by Qt
emit finished( m_scriptPath );
}
// decide whether a version string 'update' is newer than 'installed'
bool
ScriptUpdater::isNewer( const QString & update, const QString & installed )
{
// only dots are supported as separators, and only integers are supported
// between the dots (so no fancy stuff like 2.1-1 or 2.1.beta2)
QStringList uList = update.split( '.' );
QStringList iList = installed.split( '.' );
int i = 0;
// stop working if the end of both lists is reached
while ( i < uList.length() || i < iList.length() ) {
// read current number, or use 0 if it isn't present (so that "2" == "2.0" == "2.0.0.0.0.0" == "2...0")
int up = ( ( uList.length() > i && ( !uList[i].isEmpty() ) ) ? uList[i].toInt() : 0 );
int in = ( ( iList.length() > i && ( !iList[i].isEmpty() ) ) ? iList[i].toInt() : 0 );
if ( up > in )
{
return true;
}
else if ( up < in )
{
return false;
}
else
{
// both strings are equal up to this point -> look at the next pair of numbers
i++;
}
}
// if we reach this point, both versions are considered equal
return false;
}
diff --git a/src/services/ServiceSqlQueryMaker.cpp b/src/services/ServiceSqlQueryMaker.cpp
index 58250205c6..0265a7a957 100644
--- a/src/services/ServiceSqlQueryMaker.cpp
+++ b/src/services/ServiceSqlQueryMaker.cpp
@@ -1,881 +1,881 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ServiceSqlQueryMaker"
#include "ServiceSqlQueryMaker.h"
#include <core/storage/SqlStorage.h>
#include "core/meta/support/MetaConstants.h"
#include "core/support/Debug.h"
#include "core-impl/storage/StorageManager.h"
#include "ServiceSqlCollection.h"
#include <QStack>
#include <QSharedPointer>
#include <ThreadWeaver/QObjectDecorator>
using namespace Collections;
class ServiceSqlWorkerThread : public QObject, public ThreadWeaver::Job
{
Q_OBJECT
public:
ServiceSqlWorkerThread( ServiceSqlQueryMaker *queryMaker )
: QObject()
, ThreadWeaver::Job()
, m_queryMaker( queryMaker )
, m_aborted( false )
{
//nothing to do
}
virtual void requestAbort()
{
m_aborted = true;
}
Q_SIGNALS:
/** This signal is emitted when this job is being processed by a thread. */
void started(ThreadWeaver::JobPointer);
/** This signal is emitted when the job has been finished (no matter if it succeeded or not). */
void done(ThreadWeaver::JobPointer);
/** This job has failed.
* This signal is emitted when success() returns false after the job is executed. */
void failed(ThreadWeaver::JobPointer);
protected:
virtual void run(ThreadWeaver::JobPointer self = QSharedPointer<ThreadWeaver::Job>(), ThreadWeaver::Thread *thread = 0)
{
Q_UNUSED(self);
Q_UNUSED(thread);
QString query = m_queryMaker->query();
QStringList result = m_queryMaker->runQuery( query );
if( !m_aborted )
m_queryMaker->handleResult( result );
if( m_aborted )
setStatus(Status_Aborted);
else
setStatus(Status_Running);
}
void defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
private:
ServiceSqlQueryMaker *m_queryMaker;
bool m_aborted;
};
struct ServiceSqlQueryMaker::Private
{
enum QueryType { NONE, TRACK, ARTIST, ALBUM, ALBUMARTIST, GENRE, COMPOSER, YEAR, CUSTOM };
enum {TRACKS_TABLE = 1, ALBUMS_TABLE = 2, ARTISTS_TABLE = 4, GENRE_TABLE = 8, ALBUMARTISTS_TABLE = 16 };
int linkedTables;
QueryType queryType;
QString query;
QString queryReturnValues;
QString queryFrom;
QString queryMatch;
QString queryFilter;
QString queryOrderBy;
//bool includedBuilder;
//bool collectionRestriction;
AlbumQueryMode albumMode;
bool withoutDuplicates;
int maxResultSize;
ServiceSqlWorkerThread *worker;
QStack<bool> andStack;
};
ServiceSqlQueryMaker::ServiceSqlQueryMaker( ServiceSqlCollection* collection, ServiceMetaFactory * metaFactory, ServiceSqlRegistry * registry )
: QueryMaker()
, m_collection( collection )
, m_registry( registry )
, m_metaFactory( metaFactory )
, d( new Private )
{
//d->includedBuilder = true;
//d->collectionRestriction = false;
d->worker = NULL;
d->queryType = Private::NONE;
d->linkedTables = 0;
d->withoutDuplicates = false;
d->maxResultSize = -1;
d->andStack.push( true );
}
ServiceSqlQueryMaker::~ServiceSqlQueryMaker()
{
// what about d->worker?
delete d;
}
void
ServiceSqlQueryMaker::abortQuery()
{
if( d->worker )
d->worker->requestAbort();
}
void
ServiceSqlQueryMaker::run()
{
if( d->queryType == Private::NONE )
return; //better error handling?
if( d->worker && !d->worker->isFinished() )
{
//the worker thread seems to be running
//TODO: wait or job to complete
}
else
{
d->worker = new ServiceSqlWorkerThread( this );
connect( d->worker, SIGNAL(done(ThreadWeaver::JobPointer)),SLOT(done(ThreadWeaver::JobPointer)) );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(d->worker) );
}
}
void
ServiceSqlQueryMaker::done( ThreadWeaver::JobPointer job )
{
ThreadWeaver::Queue::instance()->dequeue( job );
ThreadWeaver::QObjectDecorator *qs = new ThreadWeaver::QObjectDecorator(job.data());
qs->deleteLater();
d->worker = NULL;
emit queryDone();
}
QueryMaker*
ServiceSqlQueryMaker::setQueryType( QueryType type)
{
switch( type ) {
case QueryMaker::Track:
//make sure to keep this method in sync with handleTracks(QStringList) and the SqlTrack ctor
if( d->queryType == Private::NONE )
{
QString prefix = m_metaFactory->tablePrefix();
//d->queryFrom = ' ' + prefix + "_tracks";
d->withoutDuplicates = true;
d->queryFrom = ' ' + prefix + "_tracks";
d->queryType = Private::TRACK;
d->queryReturnValues = m_metaFactory->getTrackSqlRows() + ',' +
m_metaFactory->getAlbumSqlRows() + ',' +
m_metaFactory->getArtistSqlRows() + ',' +
m_metaFactory->getGenreSqlRows();
d->linkedTables |= Private::GENRE_TABLE;
d->linkedTables |= Private::ARTISTS_TABLE;
d->linkedTables |= Private::ALBUMS_TABLE;
d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres
if ( d->linkedTables & Private::ARTISTS_TABLE )
{
d->queryOrderBy += " ORDER BY " + prefix + "_tracks.album_id"; //make sure items are added as album groups
}
}
return this;
case QueryMaker::Artist:
if( d->queryType == Private::NONE )
{
QString prefix = m_metaFactory->tablePrefix();
d->queryFrom = ' ' + prefix + "_tracks";
d->linkedTables |= Private::ARTISTS_TABLE;
d->linkedTables |= Private::ALBUMS_TABLE;
d->queryType = Private::ARTIST;
d->withoutDuplicates = true;
d->queryReturnValues = m_metaFactory->getArtistSqlRows();
d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres
}
return this;
case QueryMaker::AlbumArtist:
if( d->queryType == Private::NONE )
{
QString prefix = m_metaFactory->tablePrefix();
d->queryFrom = ' ' + prefix + "_tracks";
d->linkedTables |= Private::ALBUMARTISTS_TABLE;
d->queryType = Private::ALBUMARTIST;
d->withoutDuplicates = true;
d->queryReturnValues = QString( "albumartists.id, " ) +
"albumartists.name, " +
"albumartists.description ";
d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres
}
return this;
case QueryMaker::Album:
if( d->queryType == Private::NONE )
{
QString prefix = m_metaFactory->tablePrefix();
d->queryFrom = ' ' + prefix + "_tracks";
d->queryType = Private::ALBUM;
d->linkedTables |= Private::ALBUMS_TABLE;
d->linkedTables |= Private::ARTISTS_TABLE;
d->withoutDuplicates = true;
d->queryReturnValues = m_metaFactory->getAlbumSqlRows() + ',' +
m_metaFactory->getArtistSqlRows();
d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres
}
return this;
case QueryMaker::Composer:
/* if( d->queryType == Private::NONE )
{
d->queryType = Private::COMPOSER;
d->withoutDuplicates = true;
d->linkedTables |= Private::COMPOSER_TAB;
d->queryReturnValues = "composer.name, composer.id";
}*/
return this;
case QueryMaker::Genre:
if( d->queryType == Private::NONE )
{
QString prefix = m_metaFactory->tablePrefix();
d->queryFrom = ' ' + prefix + "_genre";
d->queryType = Private::GENRE;
//d->linkedTables |= Private::ALBUMS_TABLE;
//d->linkedTables |= Private::GENRE_TABLE;
d->withoutDuplicates = true;
d->queryReturnValues = m_metaFactory->getGenreSqlRows();
d->queryOrderBy = " GROUP BY " + prefix +"_genre.name"; // HAVING COUNT ( " + prefix +"_genre.name ) > 10 ";
}
return this;
case QueryMaker::Year:
/*if( d->queryType == Private::NONE )
{
d->queryType = Private::YEAR;
d->withoutDuplicates = true;
d->linkedTables |= Private::YEAR_TAB;
d->queryReturnValues = "year.name, year.id";
}*/
return this;
case QueryMaker::Custom:
/* if( d->queryType == Private::NONE )
d->queryType = Private::CUSTOM;*/
return this;
case QueryMaker::Label:
case QueryMaker::None:
return this;
}
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::TrackPtr &track )
{
//DEBUG_BLOCK
Q_UNUSED( track );
//TODO still pondereing this one...
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::ArtistPtr &artist, QueryMaker::ArtistMatchBehaviour behaviour )
{
QString prefix = m_metaFactory->tablePrefix();
if( !d )
return this;
if( behaviour == AlbumArtists || behaviour == AlbumOrTrackArtists )
d->linkedTables |= Private::ALBUMARTISTS_TABLE;
//this should NOT be made into a static cast as this might get called with an incompatible type!
const Meta::ServiceArtist * serviceArtist = dynamic_cast<const Meta::ServiceArtist *>( artist.data() );
d->linkedTables |= Private::ARTISTS_TABLE;
if( serviceArtist )
{
switch( behaviour )
{
case TrackArtists:
d->queryMatch += QString( " AND " + prefix + "_artists.id= '%1'" ).arg( serviceArtist->id() );
break;
case AlbumArtists:
d->queryMatch += QString( " AND albumartists.id= '%1'" ).arg( serviceArtist->id() );
break;
case AlbumOrTrackArtists:
d->queryMatch += QString( " AND ( " + prefix + "_artists.id= '%1' OR albumartists.id= '%1' )" ).arg( serviceArtist->id() );
break;
}
}
else
{
switch( behaviour )
{
case TrackArtists:
d->queryMatch += QString( " AND " + prefix + "_artists.name= '%1'" ).arg( escape( artist->name() ) );
break;
case AlbumArtists:
d->queryMatch += QString( " AND albumartists.name= '%1'" ).arg( escape( artist->name() ) );
break;
case AlbumOrTrackArtists:
d->queryMatch += QString( " AND ( " + prefix + "_artists.name= '%1' OR albumartists.name= '%1' )" ).arg( escape( artist->name() ) );
break;
}
}
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::AlbumPtr &album )
{
QString prefix = m_metaFactory->tablePrefix();
if( !d )
return this;
//this should NOT be made into a static cast as this might get called with an incompatible type!
const Meta::ServiceAlbumPtr serviceAlbum = Meta::ServiceAlbumPtr::dynamicCast( album );
d->linkedTables |= Private::ALBUMS_TABLE;
d->linkedTables |= Private::ARTISTS_TABLE;
if( d->queryType == Private::GENRE )
d->linkedTables |= Private::GENRE_TABLE;
if( serviceAlbum )
{
d->queryMatch += QString( " AND " + prefix + "_albums.id = '%1'" ).arg( serviceAlbum->id() );
}
else
{
d->queryMatch += QString( " AND " + prefix + "_albums.name='%1'" ).arg( escape( album->name() ) );
}
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
QString prefix = m_metaFactory->tablePrefix();
//this should NOT be made into a static cast as this might get called with an incompatible type!
const Meta::ServiceGenre* serviceGenre = static_cast<const Meta::ServiceGenre *>( genre.data() );
if( !d || !serviceGenre )
return this;
//genres link to albums in the database, so we need to start from here unless soig a track query
// if ( d->queryType == Private::TRACK ) {
//d->queryFrom = ' ' + prefix + "_tracks";
d->linkedTables |= Private::ALBUMS_TABLE;
//} else
//d->queryFrom = ' ' + prefix + "_albums";
//if ( d->queryType == Private::ARTIST )
//d->linkedTables |= Private::ARTISTS_TABLE;
d->linkedTables |= Private::GENRE_TABLE;
d->queryMatch += QString( " AND " + prefix + "_genre.name = '%1'" ).arg( serviceGenre->name() );
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::ComposerPtr &composer )
{
Q_UNUSED( composer );
//TODO
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::YearPtr &year )
{
Q_UNUSED( year );
//TODO
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addMatch( const Meta::LabelPtr &label )
{
Q_UNUSED( label );
//TODO
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
if( !isValidValue( value ) )
{
return this;
}
//a few hacks needed by some of the speedup code:
if ( d->queryType == Private::GENRE )
{
QString prefix = m_metaFactory->tablePrefix();
d->queryFrom = ' ' + prefix + "_tracks";
d->linkedTables |= Private::ALBUMS_TABLE;
d->linkedTables |= Private::ARTISTS_TABLE;
d->linkedTables |= Private::GENRE_TABLE;
}
QString like = likeCondition( filter, !matchBegin, !matchEnd );
d->queryFilter += QString( " %1 %2 %3 " ).arg( andOr(), nameForValue( value ), like );
return this;
}
QueryMaker*
ServiceSqlQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd )
{
if( isValidValue( value ) )
{
QString like = likeCondition( filter, !matchBegin, !matchEnd );
d->queryFilter += QString( " %1 NOT %2 %3 " ).arg( andOr(), nameForValue( value ), like );
}
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
AMAROK_NOTIMPLEMENTED
Q_UNUSED( value )
Q_UNUSED( filter )
Q_UNUSED( compare )
return this;
}
QueryMaker*
ServiceSqlQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare )
{
AMAROK_NOTIMPLEMENTED
Q_UNUSED( value )
Q_UNUSED( filter )
Q_UNUSED( compare )
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addReturnValue( qint64 value )
{
Q_UNUSED( value );
/*if( d->queryType == Private::CUSTOM )
{
if ( !d->queryReturnValues.isEmpty() )
d->queryReturnValues += ',';
d->queryReturnValues += nameForValue( value );
}*/
return this;
}
QueryMaker*
ServiceSqlQueryMaker::addReturnFunction( ReturnFunction function, qint64 value )
{
Q_UNUSED( value )
Q_UNUSED( function )
return this;
}
QueryMaker*
ServiceSqlQueryMaker::orderBy( qint64 value, bool descending )
{
Q_UNUSED( value );
if ( d->queryOrderBy.isEmpty() )
d->queryOrderBy = " ORDER BY name "; //TODO FIX!!
d->queryOrderBy += QString( " %1 " ).arg( descending ? "DESC" : "ASC" );
return this;
}
QueryMaker*
ServiceSqlQueryMaker::limitMaxResultSize( int size )
{
d->maxResultSize = size;
return this;
}
void
ServiceSqlQueryMaker::linkTables()
{
if( !d->linkedTables )
return;
QString prefix = m_metaFactory->tablePrefix();
//d->queryFrom = ' ' + prefix + "_tracks";
if( d->linkedTables & Private::ALBUMS_TABLE )
d->queryFrom += " LEFT JOIN " + prefix + "_albums ON " + prefix + "_tracks.album_id = " + prefix + "_albums.id";
if( d->linkedTables & Private::ARTISTS_TABLE )
d->queryFrom += " LEFT JOIN " + prefix + "_artists ON " + prefix + "_albums.artist_id = " + prefix + "_artists.id";
if( d->linkedTables & Private::ALBUMARTISTS_TABLE )
d->queryFrom += " LEFT JOIN " + prefix + "_artists AS albumartists ON " + prefix + "_albums.artist_id = albumartists.id";
if( d->linkedTables & Private::GENRE_TABLE )
d->queryFrom += " LEFT JOIN " + prefix + "_genre ON " + prefix + "_genre.album_id = " + prefix + "_albums.id";
}
void
ServiceSqlQueryMaker::buildQuery()
{
if( d->albumMode == OnlyCompilations )
return;
linkTables();
QString query = "SELECT ";
if ( d->withoutDuplicates )
query += "DISTINCT ";
query += d->queryReturnValues;
query += " FROM ";
query += d->queryFrom;
query += " WHERE 1 "; // oh... to not have to bother with the leadig "AND"
// that may or may not be needed
query += d->queryMatch;
if ( !d->queryFilter.isEmpty() )
{
query += " AND ( 1 ";
query += d->queryFilter;
query += " ) ";
}
//query += d->queryFilter;
query += d->queryOrderBy;
if ( d->maxResultSize > -1 )
query += QString( " LIMIT %1 OFFSET 0 " ).arg( d->maxResultSize );
query += ';';
d->query = query;
}
QString
ServiceSqlQueryMaker::query()
{
if ( d->query.isEmpty() )
buildQuery();
return d->query;
}
QStringList
ServiceSqlQueryMaker::runQuery( const QString &query )
{
if( d->albumMode == OnlyCompilations )
return QStringList();
return m_collection->query( query );
}
void
ServiceSqlQueryMaker::handleResult( const QStringList &result )
{
if( !result.isEmpty() )
{
switch( d->queryType ) {
/*case Private::CUSTOM:
emit newResultReady( result );
break;*/
case Private::TRACK:
handleTracks( result );
break;
case Private::ARTIST:
case Private::ALBUMARTIST:
handleArtists( result );
break;
case Private::ALBUM:
handleAlbums( result );
break;
case Private::GENRE:
handleGenres( result );
break;
/* case Private::COMPOSER:
handleComposers( result );
break;
case Private::YEAR:
handleYears( result );
break;*/
case Private::NONE:
debug() << "Warning: queryResult with queryType == NONE";
default:
break;
}
}
else
{
switch( d->queryType ) {
case QueryMaker::Custom:
emit newResultReady( QStringList() );
break;
case QueryMaker::Track:
- emit newResultReady( Meta::TrackList() );
+ emit newTracksReady( Meta::TrackList() );
break;
case QueryMaker::Artist:
- emit newResultReady( Meta::ArtistList() );
+ emit newArtistsReady( Meta::ArtistList() );
break;
case QueryMaker::Album:
- emit newResultReady( Meta::AlbumList() );
+ emit newAlbumsReady( Meta::AlbumList() );
break;
case QueryMaker::AlbumArtist:
- emit newResultReady( Meta::ArtistList() );
+ emit newArtistsReady( Meta::ArtistList() );
break;
case QueryMaker::Genre:
- emit newResultReady( Meta::GenreList() );
+ emit newGenresReady( Meta::GenreList() );
break;
case QueryMaker::Composer:
- emit newResultReady( Meta::ComposerList() );
+ emit newComposersReady( Meta::ComposerList() );
break;
case QueryMaker::Year:
- emit newResultReady( Meta::YearList() );
+ emit newYearsReady( Meta::YearList() );
break;
case QueryMaker::None:
debug() << "Warning: queryResult with queryType == NONE";
}
}
//queryDone will be emitted in done(Job*)
}
bool
ServiceSqlQueryMaker::isValidValue( qint64 value )
{
return value == Meta::valTitle ||
value == Meta::valArtist ||
value == Meta::valAlbum ||
value == Meta::valGenre;
}
QString
ServiceSqlQueryMaker::nameForValue( qint64 value )
{
QString prefix = m_metaFactory->tablePrefix();
switch( value )
{
case Meta::valTitle:
d->linkedTables |= Private::TRACKS_TABLE;
return prefix + "_tracks.name";
case Meta::valArtist:
d->linkedTables |= Private::ARTISTS_TABLE;
return prefix + "_artists.name";
case Meta::valAlbum:
d->linkedTables |= Private::ALBUMS_TABLE;
return prefix + "_albums.name";
case Meta::valGenre:
d->queryFrom = prefix + "_tracks";
d->linkedTables |= Private::ALBUMS_TABLE;
d->linkedTables |= Private::GENRE_TABLE;
return prefix + "_genre.name";
default:
debug() << "ERROR: unknown value in ServiceSqlQueryMaker::nameForValue( qint64 ): value=" + QString::number( value );
return QString();
}
}
void
ServiceSqlQueryMaker::handleTracks( const QStringList &result )
{
Meta::TrackList tracks;
//SqlRegistry* reg = m_collection->registry();
int rowCount = ( m_metaFactory->getTrackSqlRowCount() +
m_metaFactory->getAlbumSqlRowCount() +
m_metaFactory->getArtistSqlRowCount() +
m_metaFactory->getGenreSqlRowCount() );
int resultRows = result.count() / rowCount;
for( int i = 0; i < resultRows; i++ )
{
QStringList row = result.mid( i*rowCount, rowCount );
Meta::TrackPtr trackptr = m_registry->getTrack( row );
tracks.append( trackptr );
}
- emit newResultReady( tracks );
+ emit newTracksReady( tracks );
}
void
ServiceSqlQueryMaker::handleArtists( const QStringList &result )
{
Meta::ArtistList artists;
// SqlRegistry* reg = m_collection->registry();
int rowCount = m_metaFactory->getArtistSqlRowCount();
int resultRows = result.size() / rowCount;
for( int i = 0; i < resultRows; i++ )
{
QStringList row = result.mid( i*rowCount, rowCount );
artists.append( m_registry->getArtist( row ) );
}
- emit newResultReady( artists );
+ emit newArtistsReady( artists );
}
void
ServiceSqlQueryMaker::handleAlbums( const QStringList &result )
{
Meta::AlbumList albums;
int rowCount = m_metaFactory->getAlbumSqlRowCount() + m_metaFactory->getArtistSqlRowCount();
int resultRows = result.size() / rowCount;
for( int i = 0; i < resultRows; i++ )
{
QStringList row = result.mid( i*rowCount, rowCount );
albums.append( m_registry->getAlbum( row ) );
}
- emit newResultReady( albums );
+ emit newAlbumsReady( albums );
}
void
ServiceSqlQueryMaker::handleGenres( const QStringList &result )
{
Meta::GenreList genres;
int rowCount = m_metaFactory->getGenreSqlRowCount();
int resultRows = result.size() / rowCount;
for( int i = 0; i < resultRows; i++ )
{
QStringList row = result.mid( i*rowCount, rowCount );
genres.append( m_registry->getGenre( row ) );
}
- emit newResultReady( genres );
+ emit newGenresReady( genres );
}
/*void
ServiceSqlQueryMaker::handleComposers( const QStringList &result )
{
Meta::ComposerList composers;
SqlRegistry* reg = m_collection->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
composers.append( reg->getComposer( name, id.toInt() ) );
}
emit newResultReady( composers );
}
void
ServiceSqlQueryMaker::handleYears( const QStringList &result )
{
Meta::YearList years;
SqlRegistry* reg = m_collection->registry();
for( QStringListIterator iter( result ); iter.hasNext(); )
{
QString name = iter.next();
QString id = iter.next();
years.append( reg->getYear( name, id.toInt() ) );
}
emit newResultReady( years );
}*/
QString
ServiceSqlQueryMaker::escape( QString text ) const //krazy2:exclude=constref
{
SqlStorage *storage = StorageManager::instance()->sqlStorage();
if( storage )
return storage->escape( text );
else
return QString();
}
QString
ServiceSqlQueryMaker::likeCondition( const QString &text, bool anyBegin, bool anyEnd ) const
{
if( anyBegin || anyEnd )
{
QString escaped = text;
escaped = escape( escaped );
//see comments in SqlQueryMaker::likeCondition
escaped.replace( '%', "/%" ).replace( '_', "/_" );
QString ret = " LIKE ";
ret += '\'';
if ( anyBegin )
ret += '%';
ret += escaped;
if ( anyEnd )
ret += '%';
ret += '\'';
//Case insensitive collation for queries
ret += " COLLATE utf8_unicode_ci ";
//Use / as the escape character
ret += " ESCAPE '/' ";
return ret;
}
else
{
return QString( " = '%1' " ).arg( escape( text ) );
}
}
QueryMaker*
ServiceSqlQueryMaker::beginAnd()
{
d->queryFilter += andOr();
d->queryFilter += " ( 1 ";
d->andStack.push( true );
return this;
}
QueryMaker*
ServiceSqlQueryMaker::beginOr()
{
d->queryFilter += andOr();
d->queryFilter += " ( 0 ";
d->andStack.push( false );
return this;
}
QueryMaker*
ServiceSqlQueryMaker::endAndOr()
{
d->queryFilter += ')';
d->andStack.pop();
return this;
}
QString
ServiceSqlQueryMaker::andOr() const
{
return d->andStack.top() ? " AND " : " OR ";
}
QueryMaker *
ServiceSqlQueryMaker::setAlbumQueryMode(AlbumQueryMode mode)
{
d->albumMode = mode;
return this;
}
#include "ServiceSqlQueryMaker.moc"
diff --git a/src/services/ServiceSqlRegistry.cpp b/src/services/ServiceSqlRegistry.cpp
index 3b409a4a29..dd406132ff 100644
--- a/src/services/ServiceSqlRegistry.cpp
+++ b/src/services/ServiceSqlRegistry.cpp
@@ -1,297 +1,297 @@
/****************************************************************************************
* Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ServiceSqlRegistry"
#include "ServiceSqlRegistry.h"
#include "core/support/Debug.h"
#include <QMutableHashIterator>
#include <QMutexLocker>
using namespace Meta;
ServiceSqlRegistry::ServiceSqlRegistry( ServiceMetaFactory * metaFactory )
: QObject( 0 )
, m_metaFactory( metaFactory )
{
setObjectName( "ServiceSqlRegistry" );
/* m_timer = new QTimer( this );
m_timer->setInterval( 60000 ); //try to clean up every 60 seconds, change if necessary
m_timer->setSingleShot( false );
- connect( m_timer, SIGNAL(timeout()), this, SLOT(emptyCache()) );
+ connect( m_timer, &QTimer::timeout, this, SLOT(emptyCache()) );
m_timer->start();*/
}
ServiceSqlRegistry::~ServiceSqlRegistry()
{
//don't delete m_collection
}
TrackPtr
ServiceSqlRegistry::getTrack( const QStringList &rowData )
{
//test if rowData is the correct length
int correctLength = m_metaFactory->getTrackSqlRowCount() + m_metaFactory->getAlbumSqlRowCount() + m_metaFactory->getArtistSqlRowCount() + m_metaFactory->getGenreSqlRowCount();
if ( rowData.size() != correctLength )
return Meta::TrackPtr();
int id = rowData[0].toInt();
QMutexLocker locker( &m_trackMutex );
if( m_trackMap.contains( id ) )
{
return m_trackMap.value( id );
}
else
{
int index = 0;
TrackPtr trackPtr = m_metaFactory->createTrack( rowData.mid(index, m_metaFactory->getTrackSqlRowCount() ) );
index += m_metaFactory->getTrackSqlRowCount();
ServiceTrack * track = static_cast<ServiceTrack *> ( trackPtr.data() );
AlbumPtr albumPtr;
if ( m_albumMap.contains( track->albumId() ) )
albumPtr = m_albumMap.value( track->albumId() );
else
albumPtr = getAlbum( rowData.mid( index, rowData.count() -1 ) );
index += m_metaFactory->getAlbumSqlRowCount();
ServiceAlbum * album = static_cast<ServiceAlbum *> ( albumPtr.data() );
album->addTrack( trackPtr );
track->setAlbumPtr( albumPtr );
m_albumMap.insert( track->albumId(), albumPtr );
ArtistPtr artistPtr;
if ( m_artistMap.contains( track->artistId() ) )
artistPtr = m_artistMap.value( track->artistId() );
else
{
QStringList subRows = rowData.mid(index, m_metaFactory->getArtistSqlRowCount() );
artistPtr = m_metaFactory->createArtist( subRows );
}
index += m_metaFactory->getArtistSqlRowCount();
ServiceArtist * artist = static_cast<ServiceArtist *> ( artistPtr.data() );
artist->addTrack( trackPtr );
track->setArtist( artistPtr );
m_artistMap.insert( track->artistId(), artistPtr );
GenrePtr genrePtr;
int genreId = rowData[index].toInt();
if( m_genreMap.contains( genreId ) )
genrePtr = m_genreMap.value( genreId );
else
genrePtr = m_metaFactory->createGenre( rowData.mid(index, m_metaFactory->getGenreSqlRowCount() ) );
ServiceGenre * genre = dynamic_cast<ServiceGenre *> ( genrePtr.data() );
Q_ASSERT( genre );
if( genre )
genre->addTrack( trackPtr );
track->setGenre( genrePtr );
m_genreMap.insert( genreId, genrePtr );
m_trackMap.insert( id, trackPtr );
return trackPtr;
}
}
ArtistPtr
ServiceSqlRegistry::getArtist( const QStringList &rowData )
{
int id = rowData[0].toInt();
QMutexLocker locker( &m_artistMutex );
if( m_artistMap.contains( id ) )
return m_artistMap.value( id );
else
{
ArtistPtr artist = m_metaFactory->createArtist( rowData );
m_artistMap.insert( id, artist );
return artist;
}
}
GenrePtr
ServiceSqlRegistry::getGenre( const QStringList &rowData )
{
int id = rowData[0].toInt();
QMutexLocker locker( &m_genreMutex );
if( m_genreMap.contains( id ) )
return m_genreMap.value( id );
else
{
GenrePtr genre = m_metaFactory->createGenre( rowData );
m_genreMap.insert( id, genre );
return genre;
}
}
/*ComposerPtr
ServiceSqlRegistry::getComposer( const QString &name, int id )
{
QMutexLocker locker( &m_composerMutex );
if( m_composerMap.contains( name ) )
return m_composerMap.value( name );
else
{
ComposerPtr composer( new SqlComposer( m_collection, id, name ) );
m_composerMap.insert( name, composer );
return composer;
}
}*/
/*YearPtr
ServiceSqlRegistry::getYear( const QString &name, int id )
{
QMutexLocker locker( &m_yearMutex );
if( m_yearMap.contains( name ) )
return m_yearMap.value( name );
else
{
YearPtr year( new SqlYear( m_collection, id, name ) );
m_yearMap.insert( name, year );
return year;
}
}*/
AlbumPtr
ServiceSqlRegistry::getAlbum( const QStringList &rowData )
{
int id = rowData[0].toInt();
QMutexLocker locker( &m_albumMutex );
if( m_albumMap.contains( id ) )
return m_albumMap.value( id );
else
{
int index = 0;
QStringList testString = rowData.mid( index, m_metaFactory->getAlbumSqlRowCount() );
AlbumPtr albumPtr = m_metaFactory->createAlbum( rowData.mid(index, m_metaFactory->getAlbumSqlRowCount() ) );
m_albumMap.insert( id, albumPtr );
index += m_metaFactory->getAlbumSqlRowCount();
ServiceAlbum* album = static_cast<ServiceAlbum *> ( albumPtr.data() );
ArtistPtr artistPtr;
// we need to set the artist for this album
if ( m_artistMap.contains( album->artistId() ) )
artistPtr = m_artistMap.value( album->artistId() );
else
{
QStringList subRows = rowData.mid(index, m_metaFactory->getArtistSqlRowCount() );
artistPtr = m_metaFactory->createArtist( subRows );
}
index += m_metaFactory->getArtistSqlRowCount();
ServiceArtist * artist = static_cast<ServiceArtist *> ( artistPtr.data() );
album->setAlbumArtist( artistPtr );
m_artistMap.insert( artist->id(), artistPtr );
return albumPtr;
}
}
/*
void
ServiceSqlRegistry::emptyCache()
{
DEBUG_BLOCK
bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer;
hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = false;
//try to avoid possible deadlocks by aborting when we can't get all locks
if ( ( hasTrack = m_trackMutex.tryLock() )
&& ( hasAlbum = m_albumMutex.tryLock() )
&& ( hasArtist = m_artistMutex.tryLock() )
&& ( hasYear = m_yearMutex.tryLock() )
&& ( hasGenre = m_genreMutex.tryLock() )
&& ( hasComposer = m_composerMutex.tryLock() ) )
{
//this very simple garbage collector doesn't handle cyclic object graphs
//so care has to be taken to make sure that we are not dealing with a cyclic graph
//by invalidating the tracks cache on all objects
#define foreachInvalidateCache( Type, RealType, x ) \
for( QMutableHashIterator<QString,Type > iter(x); iter.hasNext(); ) \
RealType::staticCast( iter.next().value() )->invalidateCache()
foreachInvalidateCache( AlbumPtr, KSharedPtr<SqlAlbum>, m_albumMap );
foreachInvalidateCache( ArtistPtr, KSharedPtr<SqlArtist>, m_artistMap );
foreachInvalidateCache( GenrePtr, KSharedPtr<SqlGenre>, m_genreMap );
foreachInvalidateCache( ComposerPtr, KSharedPtr<SqlComposer>, m_composerMap );
foreachInvalidateCache( YearPtr, KSharedPtr<SqlYear>, m_yearMap );
//elem.count() == 2 is correct because elem is one pointer to the object
//and the other is stored in the hash map
#define foreachCollectGarbage( Key, Type, x ) \
for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
{ \
Type elem = iter.next().value(); \
if( elem.count() == 2 ) \
iter.remove(); \
}
foreachCollectGarbage( TrackId, TrackPtr, m_trackMap )
//run before artist so that album artist pointers can be garbage collected
foreachCollectGarbage( QString, AlbumPtr, m_albumMap )
foreachCollectGarbage( QString, ArtistPtr, m_artistMap )
foreachCollectGarbage( QString, GenrePtr, m_genreMap )
foreachCollectGarbage( QString, ComposerPtr, m_composerMap )
foreachCollectGarbage( QString, YearPtr, m_yearMap )
}
//make sure to unlock all necessary locks
//important: calling unlock() on an unlocked mutex gives an undefined result
//unlocking a mutex locked by another thread results in an error, so be careful
if( hasTrack ) m_trackMutex.unlock();
if( hasAlbum ) m_albumMutex.unlock();
if( hasArtist ) m_artistMutex.unlock();
if( hasYear ) m_yearMutex.unlock();
if( hasGenre ) m_genreMutex.unlock();
if( hasComposer ) m_composerMutex.unlock();
}*/
ServiceMetaFactory * ServiceSqlRegistry::factory()
{
return m_metaFactory;
}
diff --git a/src/services/scriptable/ScriptableServiceQueryMaker.cpp b/src/services/scriptable/ScriptableServiceQueryMaker.cpp
index 4117a6f7f5..af43c2ad64 100644
--- a/src/services/scriptable/ScriptableServiceQueryMaker.cpp
+++ b/src/services/scriptable/ScriptableServiceQueryMaker.cpp
@@ -1,461 +1,462 @@
/****************************************************************************************
* Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "ScriptableServiceQueryMaker"
#include "ScriptableServiceQueryMaker.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Debug.h"
#include "core-impl/collections/support/MemoryMatcher.h"
#include "scripting/scriptmanager/ScriptManager.h"
#include "services/scriptable/ScriptableServiceMeta.h"
#include <QTimer>
using namespace Collections;
struct ScriptableServiceQueryMaker::Private {
//don't change the order of items in this enum
enum QueryType { TRACK=1, ALBUM=2, ARTIST=3, GENRE=4, NONE=5 };
QueryType type;
QueryType closestParent;
int maxsize;
QString callbackString;
int parentId;
AlbumQueryMode albumMode;
QString filter;
QString lastFilter;
};
ScriptableServiceQueryMaker::ScriptableServiceQueryMaker( ScriptableServiceCollection * collection, QString name )
: DynamicServiceQueryMaker()
, d( new Private )
, m_convertToMultiTracks( false )
{
setParent( collection );
m_collection = collection;
m_name = name;
- connect( collection, SIGNAL(updateComplete()), this, SLOT(slotScriptComplete()) );
+ connect( collection, &Collections::ScriptableServiceCollection::updateComplete,
+ this, &ScriptableServiceQueryMaker::slotScriptComplete );
d->type = Private::NONE;
d->closestParent = Private::NONE;
d->maxsize = -1;
d->parentId = -1;
d->albumMode = AllAlbums;
}
ScriptableServiceQueryMaker::~ScriptableServiceQueryMaker()
{
delete d;
}
void ScriptableServiceQueryMaker::run()
{
if ( d->albumMode == OnlyCompilations )
return;
if ( d->type == Private::NONE )
//TODO error handling
return;
if ( d->callbackString.isEmpty() )
d->callbackString = "none";
if ( d->type == Private::GENRE ) {
if ( ( m_collection->levels() == 4 ) && ( m_collection->lastFilter() != d->filter ) )
{
m_collection->clear();
}
- QTimer::singleShot( 0, this, SLOT(fetchGenre()) );
+ QTimer::singleShot( 0, this, &ScriptableServiceQueryMaker::fetchGenre );
}
else if ( d->type == Private::ARTIST )
{
if ( ( m_collection->levels() == 3 ) && ( m_collection->lastFilter() != d->filter ) )
{
m_collection->clear();
}
- QTimer::singleShot( 0, this, SLOT(fetchArtists()) );
+ QTimer::singleShot( 0, this, &ScriptableServiceQueryMaker::fetchArtists );
}
else if ( d->type == Private::ALBUM )
{
if ( ( m_collection->levels() == 2 ) && ( m_collection->lastFilter() != d->filter ) )
{
m_collection->clear();
}
- QTimer::singleShot( 0, this, SLOT(fetchAlbums()) );
+ QTimer::singleShot( 0, this, &ScriptableServiceQueryMaker::fetchAlbums );
}
else if ( d->type == Private::TRACK )
{
if ( ( m_collection->levels() == 1 ) && ( m_collection->lastFilter() != d->filter ) )
{
m_collection->clear();
}
- QTimer::singleShot( 0, this, SLOT(fetchTracks()) );
+ QTimer::singleShot( 0, this, &ScriptableServiceQueryMaker::fetchTracks );
}
}
void ScriptableServiceQueryMaker::abortQuery()
{
}
QueryMaker * ScriptableServiceQueryMaker::setQueryType( QueryType type )
{
switch( type ) {
case QueryMaker::Artist:
case QueryMaker::AlbumArtist:
d->type = Private::ARTIST;
return this;
case QueryMaker::Album:
d->type = Private::ALBUM;
return this;
case QueryMaker::Track:
d->type = Private::TRACK;
return this;
case QueryMaker::Genre:
d->type = Private::GENRE;
return this;
case QueryMaker::Composer:
case QueryMaker::Year:
case QueryMaker::Custom:
case QueryMaker::Label:
case QueryMaker::None:
//TODO: Implement.
return this;
}
return this;
}
QueryMaker * ScriptableServiceQueryMaker::addMatch( const Meta::GenrePtr &genre )
{
if ( d->closestParent > Private::GENRE )
{
d->closestParent = Private::GENRE;
const Meta::ScriptableServiceGenre * scriptableGenre = static_cast< const Meta::ScriptableServiceGenre * >( genre.data() );
d->callbackString = scriptableGenre->callbackString();
d->parentId = scriptableGenre->id();
}
return this;
}
QueryMaker * ScriptableServiceQueryMaker::addMatch( const Meta::ArtistPtr & artist, QueryMaker::ArtistMatchBehaviour behaviour )
{
Q_UNUSED( behaviour );
const Meta::ScriptableServiceArtist *scriptableArtist = dynamic_cast<const Meta::ScriptableServiceArtist *>( artist.data() );
if ( scriptableArtist && d->closestParent > Private::ARTIST )
{
d->closestParent = Private::ARTIST;
d->callbackString = scriptableArtist->callbackString();
d->parentId = scriptableArtist->id();
}
return this;
}
QueryMaker * ScriptableServiceQueryMaker::addMatch( const Meta::AlbumPtr & album )
{
if ( d->closestParent > Private::ALBUM )
{
d->closestParent = Private::ALBUM;
debug() << "Here!";
const Meta::ScriptableServiceAlbum * scriptableAlbum = static_cast< const Meta::ScriptableServiceAlbum * >( album.data() );
d->callbackString = scriptableAlbum->callbackString();
d->parentId = scriptableAlbum->id();
}
return this;
}
void
ScriptableServiceQueryMaker::setConvertToMultiTracks( bool convert )
{
m_convertToMultiTracks = convert;
}
void ScriptableServiceQueryMaker::handleResult( const Meta::GenreList & genres )
{
if ( d->maxsize >= 0 && genres.count() > d->maxsize )
- emit newResultReady( genres.mid( 0, d->maxsize ) );
+ emit newGenresReady( genres.mid( 0, d->maxsize ) );
else
- emit newResultReady( genres );
+ emit newGenresReady( genres );
}
void ScriptableServiceQueryMaker::handleResult( const Meta::AlbumList & albums )
{
if ( d->maxsize >= 0 && albums.count() > d->maxsize )
- emit newResultReady( albums.mid( 0, d->maxsize ) );
+ emit newAlbumsReady( albums.mid( 0, d->maxsize ) );
else
- emit newResultReady( albums );
+ emit newAlbumsReady( albums );
}
void ScriptableServiceQueryMaker::handleResult( const Meta::ArtistList & artists )
{
if ( d->maxsize >= 0 && artists.count() > d->maxsize )
- emit newResultReady( artists.mid( 0, d->maxsize ) );
+ emit newArtistsReady( artists.mid( 0, d->maxsize ) );
else
- emit newResultReady( artists );
+ emit newArtistsReady( artists );
}
void ScriptableServiceQueryMaker::handleResult( const Meta::TrackList &tracks )
{
Meta::TrackList ret;
if( m_convertToMultiTracks )
{
foreach( const Meta::TrackPtr &track, tracks )
{
using namespace Meta;
const ScriptableServiceTrack *serviceTrack =
dynamic_cast<const ScriptableServiceTrack *>( track.data() );
if( !serviceTrack )
{
error() << "failed to convert generic track" << track.data() << "to ScriptableServiceTrack";
continue;
}
ret << serviceTrack->playableTrack();
}
}
else
ret = tracks;
if ( d->maxsize >= 0 && ret.count() > d->maxsize )
- emit newResultReady( ret.mid( 0, d->maxsize ) );
+ emit newTracksReady( ret.mid( 0, d->maxsize ) );
else
- emit newResultReady( ret );
+ emit newTracksReady( ret );
}
void ScriptableServiceQueryMaker::fetchGenre()
{
DEBUG_BLOCK
Meta::GenreList genre = m_collection->genreMap().values();
if ( genre.count() > 0 )
{
handleResult( genre );
emit( queryDone() );
}
else
//this is where we call the script to get it to add more stuff!
ScriptManager::instance()->ServiceScriptPopulate( m_name, 3, d->parentId, d->callbackString, d->filter );
}
void ScriptableServiceQueryMaker::fetchArtists()
{
DEBUG_BLOCK
Meta::ArtistList artists;
if ( d->parentId != -1 )
{
Meta::GenrePtr genrePtr = m_collection->genreById( d->parentId );
Meta::ScriptableServiceGenre * scGenre = dynamic_cast<Meta::ScriptableServiceGenre *> ( genrePtr.data() );
if ( scGenre )
{
Meta::ArtistList allArtists = m_collection->artistMap().values();
foreach ( Meta::ArtistPtr artistPtr, allArtists )
{
Meta::ScriptableServiceArtist *scArtist = dynamic_cast<Meta::ScriptableServiceArtist *> ( artistPtr.data() );
if ( scArtist && scArtist->genreId() == d->parentId )
artists.append( artistPtr );
}
}
}
if ( artists.count() > 0 )
{
handleResult( artists );
emit( queryDone() );
}
else
//this is where we call the script to get it to add more stuff!
ScriptManager::instance()->ServiceScriptPopulate( m_name, 2, d->parentId, d->callbackString, d->filter );
}
void ScriptableServiceQueryMaker::fetchAlbums()
{
DEBUG_BLOCK
debug() << "parent id: " << d->parentId;
if ( d->albumMode == OnlyCompilations)
return;
Meta::AlbumList albums;
if ( d->parentId != -1 )
{
albums = matchAlbums( m_collection, m_collection->artistById( d->parentId ) );
}
else
albums = m_collection->albumMap().values();
if ( albums.count() > 0 )
{
handleResult( albums );
emit( queryDone() );
}
else
//this is where we call the script to get it to add more stuff!
ScriptManager::instance()->ServiceScriptPopulate( m_name, 1, d->parentId, d->callbackString, d->filter );
}
void ScriptableServiceQueryMaker::fetchTracks()
{
DEBUG_BLOCK
Meta::TrackList tracks;
debug() << "parent id: " << d->parentId;
Meta::AlbumPtr album;
if ( d->parentId != -1 && ( album = m_collection->albumById( d->parentId ) ) )
{
AlbumMatcher albumMatcher( album );
tracks = albumMatcher.match( m_collection->trackMap().values() );
}
else
tracks = m_collection->trackMap().values();
if ( tracks.count() > 0 ) {
handleResult( tracks );
emit( queryDone() );
}
else
//this is where we call the script to get it to add more stuff!
{
debug() << "i am sending signals!";
ScriptManager::instance()->ServiceScriptPopulate( m_name, 0, d->parentId, d->callbackString, d->filter );
}
}
void ScriptableServiceQueryMaker::slotScriptComplete()
{
DEBUG_BLOCK
if ( d->type == Private::GENRE )
{
Meta::GenreList genre = m_collection->genreMap().values();
handleResult( genre );
}
else if ( d->type == Private::ARTIST )
{
Meta::ArtistList artists;
if ( d->parentId != -1 )
{
Meta::GenrePtr genrePtr = m_collection->genreById( d->parentId );
Meta::ScriptableServiceGenre * scGenre = dynamic_cast<Meta::ScriptableServiceGenre *> ( genrePtr.data() );
if ( scGenre )
{
Meta::ArtistList allArtists = m_collection->artistMap().values();
foreach ( Meta::ArtistPtr artistPtr, allArtists )
{
Meta::ScriptableServiceArtist *scArtist = dynamic_cast<Meta::ScriptableServiceArtist *> ( artistPtr.data() );
if ( scArtist && scArtist->genreId() == d->parentId )
artists.append( artistPtr );
}
}
}
else
artists = m_collection->artistMap().values();
debug() << "there are " << artists.count() << " artists";
handleResult( artists );
}
else if ( d->type == Private::ALBUM )
{
Meta::AlbumList albums;
if ( d->parentId != -1 )
{
albums = matchAlbums( m_collection, m_collection->artistById( d->parentId ) );
}
else
albums = m_collection->albumMap().values();
debug() << "there are " << albums.count() << " albums";
handleResult( albums );
}
else if ( d->type == Private::TRACK )
{
Meta::TrackList tracks;
if ( d->parentId != -1 )
{
Meta::AlbumPtr album = m_collection->albumById( d->parentId );
if( album )
{
AlbumMatcher albumMatcher( album );
tracks = albumMatcher.match( m_collection->trackMap().values() );
}
}
else
tracks = m_collection->trackMap().values();
debug() << "there are " << tracks.count() << " tracks";
handleResult( tracks );
}
emit( queryDone() );
}
QueryMaker * ScriptableServiceQueryMaker::setAlbumQueryMode( AlbumQueryMode mode )
{
d->albumMode = mode;
return this;
}
QueryMaker * ScriptableServiceQueryMaker::addFilter( qint64 value, const QString & filter, bool matchBegin, bool matchEnd )
{
Q_UNUSED( matchBegin )
Q_UNUSED( matchEnd )
DEBUG_BLOCK
if ( value == Meta::valTitle )
{
//I am sure there is a really good reason to add this space, as nothing works if it is removed, but WHY?!?
d->filter += filter + ' ';
d->filter = d->filter.replace( ' ', "%20" );
}
int level = 0;
if ( d->type == Private::GENRE )
level = 4;
if ( d->type == Private::ARTIST )
level = 3;
else if ( d->type == Private::ALBUM )
level = 2;
else if ( d->type == Private::TRACK )
level = 1;
// should only clear all if we are querying for a top level item
if ( m_collection->levels() == level )
{
//we need to clear everything as we have no idea what the scripts wants to do...
//TODO: with KSharedPointers in use, does this leak!?
debug() << "clear all!!!!!!!!!!!!!!";
m_collection->clear();
}
d->lastFilter = d->filter;
m_collection->setLastFilter( d->filter );
return this;
}
diff --git a/src/statsyncing/Controller.cpp b/src/statsyncing/Controller.cpp
index 06b90121cd..5fd80b3be7 100644
--- a/src/statsyncing/Controller.cpp
+++ b/src/statsyncing/Controller.cpp
@@ -1,447 +1,449 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "Controller.h"
#include "EngineController.h"
#include "MainWindow.h"
#include "ProviderFactory.h"
#include "amarokconfig.h"
+#include "core/collections/Collection.h"
#include "core/interfaces/Logger.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "statsyncing/Config.h"
#include "statsyncing/Process.h"
#include "statsyncing/ScrobblingService.h"
#include "statsyncing/collection/CollectionProvider.h"
#include "statsyncing/ui/CreateProviderDialog.h"
#include "statsyncing/ui/ConfigureProviderDialog.h"
#include "MetaValues.h"
#include <KMessageBox>
#include <QTimer>
using namespace StatSyncing;
const int Controller::s_syncingTriggerTimeout( 5000 );
Controller::Controller( QObject* parent )
: QObject( parent )
, m_startSyncingTimer( new QTimer( this ) )
, m_config( new Config( this ) )
, m_updateNowPlayingTimer( new QTimer( this ) )
{
qRegisterMetaType<ScrobblingServicePtr>();
m_startSyncingTimer->setSingleShot( true );
- connect( m_startSyncingTimer, SIGNAL(timeout()), SLOT(startNonInteractiveSynchronization()) );
+ connect( m_startSyncingTimer, &QTimer::timeout, this, &Controller::startNonInteractiveSynchronization );
CollectionManager *manager = CollectionManager::instance();
Q_ASSERT( manager );
- connect( manager, SIGNAL(collectionAdded(Collections::Collection*,CollectionManager::CollectionStatus)),
- SLOT(slotCollectionAdded(Collections::Collection*,CollectionManager::CollectionStatus)) );
- connect( manager, SIGNAL(collectionRemoved(QString)), SLOT(slotCollectionRemoved(QString)) );
+ connect( manager, &CollectionManager::collectionAdded, this, &Controller::slotCollectionAdded );
+ connect( manager, &CollectionManager::collectionRemoved, this, &Controller::slotCollectionRemoved );
delayedStartSynchronization();
EngineController *engine = Amarok::Components::engineController();
Q_ASSERT( engine );
- connect( engine, SIGNAL(trackFinishedPlaying(Meta::TrackPtr,double)),
- SLOT(slotTrackFinishedPlaying(Meta::TrackPtr,double)) );
+ connect( engine, &EngineController::trackFinishedPlaying,
+ this, &Controller::slotTrackFinishedPlaying );
m_updateNowPlayingTimer->setSingleShot( true );
m_updateNowPlayingTimer->setInterval( 10000 ); // wait 10s before updating
// We connect the signals to (re)starting the timer to postpone the submission a
// little to prevent frequent updates of rapidly - changing metadata
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
- m_updateNowPlayingTimer, SLOT(start()) );
+ connect( engine, &EngineController::trackChanged,
+ m_updateNowPlayingTimer, QOverload<>::of(&QTimer::start) );
// following is needed for streams that don't emit newTrackPlaying on song change
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- m_updateNowPlayingTimer, SLOT(start()) );
- connect( m_updateNowPlayingTimer, SIGNAL(timeout()),
- SLOT(slotUpdateNowPlayingWithCurrentTrack()) );
+ connect( engine, &EngineController::trackMetadataChanged,
+ m_updateNowPlayingTimer, QOverload<>::of(&QTimer::start) );
+ connect( m_updateNowPlayingTimer, &QTimer::timeout,
+ this, &Controller::slotUpdateNowPlayingWithCurrentTrack );
// we need to reset m_lastSubmittedNowPlayingTrack when a track is played twice
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- SLOT(slotResetLastSubmittedNowPlayingTrack()) );
+ connect( engine, &EngineController::trackPlaying,
+ this, &Controller::slotResetLastSubmittedNowPlayingTrack );
}
Controller::~Controller()
{
}
QList<qint64>
Controller::availableFields()
{
// when fields are changed, please update translations in MetadataConfig::MetadataConfig()
return QList<qint64>() << Meta::valRating << Meta::valFirstPlayed
<< Meta::valLastPlayed << Meta::valPlaycount << Meta::valLabel;
}
void
Controller::registerProvider( const ProviderPtr &provider )
{
QString id = provider->id();
bool enabled = false;
if( m_config->providerKnown( id ) )
enabled = m_config->providerEnabled( id, false );
else
{
switch( provider->defaultPreference() )
{
case Provider::Never:
case Provider::NoByDefault:
enabled = false;
break;
case Provider::Ask:
{
QString text = i18nc( "%1 is collection name", "%1 has an ability to "
"synchronize track meta-data such as play count or rating "
"with other collections. Do you want to keep %1 synchronized?\n\n"
"You can always change the decision in Amarok configuration.",
provider->prettyName() );
enabled = KMessageBox::questionYesNo( The::mainWindow(), text ) == KMessageBox::Yes;
break;
}
case Provider::YesByDefault:
enabled = true;
break;
}
}
// don't tell config about Never-by-default providers
if( provider->defaultPreference() != Provider::Never )
{
m_config->updateProvider( id, provider->prettyName(), provider->icon(), true, enabled );
m_config->save();
}
m_providers.append( provider );
- connect( provider.data(), SIGNAL(updated()), SLOT(slotProviderUpdated()) );
+ connect( provider.data(), &StatSyncing::Provider::updated, this, &Controller::slotProviderUpdated );
if( enabled )
delayedStartSynchronization();
}
void
Controller::unregisterProvider( const ProviderPtr &provider )
{
disconnect( provider.data(), 0, this, 0 );
if( m_config->providerKnown( provider->id() ) )
{
m_config->updateProvider( provider->id(), provider->prettyName(),
provider->icon(), /* online */ false );
m_config->save();
}
m_providers.removeAll( provider );
}
void
Controller::setFactories( const QList<Plugins::PluginFactory*> &factories )
{
foreach( Plugins::PluginFactory *pFactory, factories )
{
ProviderFactory *factory = qobject_cast<ProviderFactory*>( pFactory );
if( !factory )
continue;
if( m_providerFactories.contains( factory->type() ) ) // we have it already
continue;
m_providerFactories.insert( factory->type(), factory );
}
}
bool
Controller::hasProviderFactories() const
{
return !m_providerFactories.isEmpty();
}
bool
Controller::providerIsConfigurable( const QString &id ) const
{
ProviderPtr provider = findRegisteredProvider( id );
return provider ? provider->isConfigurable() : false;
}
QWidget*
Controller::providerConfigDialog( const QString &id ) const
{
ProviderPtr provider = findRegisteredProvider( id );
if( !provider || !provider->isConfigurable() )
return 0;
ConfigureProviderDialog *dialog
= new ConfigureProviderDialog( id, provider->configWidget(),
The::mainWindow() );
- connect( dialog, SIGNAL(providerConfigured(QString,QVariantMap)),
- SLOT(reconfigureProvider(QString,QVariantMap)) );
- connect( dialog, SIGNAL(finished()), dialog, SLOT(deleteLater()) );
+ connect( dialog, &StatSyncing::ConfigureProviderDialog::providerConfigured,
+ this, &Controller::reconfigureProvider );
+ connect( dialog, &StatSyncing::ConfigureProviderDialog::finished,
+ dialog, &StatSyncing::ConfigureProviderDialog::deleteLater );
return dialog;
}
QWidget*
Controller::providerCreationDialog() const
{
CreateProviderDialog *dialog = new CreateProviderDialog( The::mainWindow() );
foreach( ProviderFactory * const factory, m_providerFactories )
dialog->addProviderType( factory->type(), factory->prettyName(),
factory->icon(), factory->createConfigWidget() );
- connect( dialog, SIGNAL(providerConfigured(QString,QVariantMap)),
- SLOT(createProvider(QString,QVariantMap)) );
- connect( dialog, SIGNAL(finished()), dialog, SLOT(deleteLater()) );
+ connect( dialog, &StatSyncing::CreateProviderDialog::providerConfigured,
+ this, &Controller::createProvider );
+ connect( dialog, &StatSyncing::CreateProviderDialog::finished,
+ dialog, &StatSyncing::CreateProviderDialog::deleteLater );
return dialog;
}
void
Controller::createProvider( QString type, QVariantMap config )
{
Q_ASSERT( m_providerFactories.contains( type ) );
m_providerFactories[type]->createProvider( config );
}
void
Controller::reconfigureProvider( QString id, QVariantMap config )
{
ProviderPtr provider = findRegisteredProvider( id );
if( provider )
provider->reconfigure( config );
}
void
Controller::registerScrobblingService( const ScrobblingServicePtr &service )
{
if( m_scrobblingServices.contains( service ) )
{
warning() << __PRETTY_FUNCTION__ << "scrobbling service" << service << "already registered";
return;
}
m_scrobblingServices << service;
}
void
Controller::unregisterScrobblingService( const ScrobblingServicePtr &service )
{
m_scrobblingServices.removeAll( service );
}
QList<ScrobblingServicePtr>
Controller::scrobblingServices() const
{
return m_scrobblingServices;
}
Config *
Controller::config()
{
return m_config;
}
void
Controller::synchronize()
{
- synchronize( Process::Interactive );
+ synchronizeWithMode( Process::Interactive );
}
void
Controller::scrobble( const Meta::TrackPtr &track, double playedFraction, const QDateTime &time )
{
foreach( ScrobblingServicePtr service, m_scrobblingServices )
{
ScrobblingService::ScrobbleError error = service->scrobble( track, playedFraction, time );
if( error == ScrobblingService::NoError )
emit trackScrobbled( service, track );
else
emit scrobbleFailed( service, track, error );
}
}
void
Controller::slotProviderUpdated()
{
QObject *updatedProvider = sender();
Q_ASSERT( updatedProvider );
foreach( const ProviderPtr &provider, m_providers )
{
if( provider.data() == updatedProvider )
{
m_config->updateProvider( provider->id(), provider->prettyName(),
provider->icon(), true );
m_config->save();
}
}
}
void
Controller::delayedStartSynchronization()
{
if( m_startSyncingTimer->isActive() )
m_startSyncingTimer->start( s_syncingTriggerTimeout ); // reset the timeout
else
{
m_startSyncingTimer->start( s_syncingTriggerTimeout );
// we could as well connect to all m_providers updated signals, but this serves
// for now
CollectionManager *manager = CollectionManager::instance();
Q_ASSERT( manager );
- connect( manager, SIGNAL(collectionDataChanged(Collections::Collection*)),
- SLOT(delayedStartSynchronization()) );
+ connect( manager, &CollectionManager::collectionDataChanged,
+ this, &Controller::delayedStartSynchronization );
}
}
void
Controller::slotCollectionAdded( Collections::Collection *collection,
CollectionManager::CollectionStatus status )
{
if( status != CollectionManager::CollectionEnabled )
return;
ProviderPtr provider( new CollectionProvider( collection ) );
registerProvider( provider );
}
void
Controller::slotCollectionRemoved( const QString &id )
{
// here we depend on StatSyncing::CollectionProvider returning identical id
// as collection
ProviderPtr provider = findRegisteredProvider( id );
if( provider )
unregisterProvider( provider );
}
void
Controller::startNonInteractiveSynchronization()
{
CollectionManager *manager = CollectionManager::instance();
Q_ASSERT( manager );
- disconnect( manager, SIGNAL(collectionDataChanged(Collections::Collection*)),
- this, SLOT(delayedStartSynchronization()) );
- synchronize( Process::NonInteractive );
+ disconnect( manager, &CollectionManager::collectionDataChanged,
+ this, &Controller::delayedStartSynchronization );
+ synchronizeWithMode( Process::NonInteractive );
}
-void Controller::synchronize( int intMode )
+void Controller::synchronizeWithMode( int intMode )
{
Process::Mode mode = Process::Mode( intMode );
if( m_currentProcess )
{
if( mode == StatSyncing::Process::Interactive )
m_currentProcess.data()->raise();
return;
}
// read saved config
qint64 fields = m_config->checkedFields();
if( mode == Process::NonInteractive && fields == 0 )
return; // nothing to do
ProviderPtrSet checkedProviders;
foreach( ProviderPtr provider, m_providers )
{
if( m_config->providerEnabled( provider->id(), false ) )
checkedProviders.insert( provider );
}
ProviderPtrList usedProviders;
switch( mode )
{
case Process::Interactive:
usedProviders = m_providers;
break;
case Process::NonInteractive:
usedProviders = checkedProviders.toList();
break;
}
if( usedProviders.isEmpty() )
return; // nothing to do
if( usedProviders.count() == 1 && usedProviders.first()->id() == "localCollection" )
{
if( mode == StatSyncing::Process::Interactive )
{
QString text = i18n( "You only seem to have the Local Collection. Statistics "
"synchronization only makes sense if there is more than one collection." );
Amarok::Components::logger()->longMessage( text );
}
return;
}
m_currentProcess = new Process( m_providers, checkedProviders, fields, mode, this );
m_currentProcess.data()->start();
}
void
Controller::slotTrackFinishedPlaying( const Meta::TrackPtr &track, double playedFraction )
{
if( !AmarokConfig::submitPlayedSongs() )
return;
Q_ASSERT( track );
scrobble( track, playedFraction );
}
void
Controller::slotResetLastSubmittedNowPlayingTrack()
{
m_lastSubmittedNowPlayingTrack = Meta::TrackPtr();
}
void
Controller::slotUpdateNowPlayingWithCurrentTrack()
{
EngineController *engine = Amarok::Components::engineController();
if( !engine )
return;
Meta::TrackPtr track = engine->currentTrack(); // null track is okay
if( tracksVirtuallyEqual( track, m_lastSubmittedNowPlayingTrack ) )
{
debug() << __PRETTY_FUNCTION__ << "this track already recently submitted, ignoring";
return;
}
foreach( ScrobblingServicePtr service, m_scrobblingServices )
{
service->updateNowPlaying( track );
}
m_lastSubmittedNowPlayingTrack = track;
}
ProviderPtr
Controller::findRegisteredProvider( const QString &id ) const
{
foreach( const ProviderPtr &provider, m_providers )
if( provider->id() == id )
return provider;
return ProviderPtr( 0 );
}
bool
Controller::tracksVirtuallyEqual( const Meta::TrackPtr &first, const Meta::TrackPtr &second )
{
if( !first && !second )
return true; // both null
if( !first || !second )
return false; // exactly one is null
const QString firstAlbum = first->album() ? first->album()->name() : QString();
const QString secondAlbum = second->album() ? second->album()->name() : QString();
const QString firstArtist = first->artist() ? first->artist()->name() : QString();
const QString secondArtist = second->artist() ? second->artist()->name() : QString();
return first->name() == second->name() &&
firstAlbum == secondAlbum &&
firstArtist == secondArtist;
}
diff --git a/src/statsyncing/Controller.h b/src/statsyncing/Controller.h
index 5728df3547..9f7550fd79 100644
--- a/src/statsyncing/Controller.h
+++ b/src/statsyncing/Controller.h
@@ -1,242 +1,242 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef STATSYNCING_CONTROLLER_H
#define STATSYNCING_CONTROLLER_H
#include "amarok_export.h"
// for CollectionManager::CollectionStatus that cannont be fwd-declared
#include "core-impl/collections/support/CollectionManager.h"
#include <QWeakPointer>
#include <QDateTime>
#include <QMap>
#include <KLocalizedString>
class QTimer;
namespace StatSyncing
{
class Config;
class CreateProviderDialog;
class Process;
class Provider;
typedef QExplicitlySharedDataPointer<Provider> ProviderPtr;
typedef QList<ProviderPtr> ProviderPtrList;
class ProviderFactory;
class ScrobblingService;
typedef QExplicitlySharedDataPointer<ScrobblingService> ScrobblingServicePtr;
/**
* A singleton class that controls statistics synchronization and related tasks.
*/
class AMAROK_EXPORT Controller : public QObject
{
Q_OBJECT
public:
explicit Controller( QObject *parent = 0 );
~Controller();
/**
* Return a list of Meta::val* fields that statistics synchronization can
* actually synchronize.
*/
static QList<qint64> availableFields();
/**
* Register a StatSyncing::Provider with StatSyncing controller. This makes
* it possible to synchronize provider with other providers. You don't need
* to call this for Collections that are registered through CollectionManager
* (and marked as enabled there) as it is done automatically.
*/
virtual void registerProvider( const ProviderPtr &provider );
/**
* Forget about StatSyncing::Provider @param provider.
*/
virtual void unregisterProvider( const ProviderPtr &provider );
/**
* Handle plugin factories derived from ProviderFactory, used for creating
* multiple provider instances. This method is called by Amarok's plugin
* infrastructure.
*/
void setFactories( const QList<Plugins::PluginFactory*> &factories );
/**
* Returns true if any instantiable provider types are registered with the
* controller.
*/
bool hasProviderFactories() const;
/**
* Returns true if the provider identified by @param id is configurable
*/
bool providerIsConfigurable( const QString &id ) const;
/**
* Returns a configuration dialog for a provider identified by @param id .
* @returns 0 if there's no provider identified by id or the provider is not
* configurable, otherwise a pointer to the dialog constructed as a child of
* The::mainWindow
*/
QWidget *providerConfigDialog( const QString &id ) const;
/**
* Returns a provider creation dialog, prepopulated with registered provider
* types.
* @returns a pointer to the dialog constructed as a child of The::mainWindow,
* and is a subclass of KAssistantDialog.
*
* @see StatSyncing::CreateProviderDialog
*/
QWidget *providerCreationDialog() const;
/**
* Register ScrobblingService with StatSyncing controller. Controller then
* listens to EngineController and calls scrobble() etc. when user plays
* tracks. Also allows scrobbling for tracks played on just connected iPods.
*
* @param service
*/
void registerScrobblingService( const ScrobblingServicePtr &service );
/**
* Forget about ScrobblingService @param service
*/
void unregisterScrobblingService( const ScrobblingServicePtr &service );
/**
* Return a list of currently registered scrobbling services (in arbitrary
* order).
*/
QList<ScrobblingServicePtr> scrobblingServices() const;
/**
* Return StatSyncing configuration object that describes enabled and
* disabled statsyncing providers. You may not cache the pointer.
*/
Config *config();
public Q_SLOTS:
/**
* Start the whole synchronization machinery. This call returns quickly,
* way before the synchronization is finished.
*/
void synchronize();
/**
* Scrobble a track using all registered scrobbling services. They may check
* certain criteria such as track length and refuse to scrobble the track.
*
* @param track track to scrobble
* @param playedFraction fraction which has been actually played, or a number
* greater than 1 if the track was played multiple times
* (for example on a media device)
* @param time time when it was played, invalid QDateTime signifies that the
* track has been played just now. This is the default when the
* parameter is omitted.
*/
void scrobble( const Meta::TrackPtr &track, double playedFraction = 1.0,
const QDateTime &time = QDateTime() );
Q_SIGNALS:
/**
* Emitted when a track passed to scrobble() is successfully queued for
* scrobbling submission. This signal is emitted for every scrobbling service.
* For each service, you either get this or scrobbleFailed().
*/
void trackScrobbled( const ScrobblingServicePtr &service, const Meta::TrackPtr &track );
/**
* Emitted when a scrobbling service @service was unable to scrobble() a track.
*
* @param error is a ScrobblingService::ScrobbleError enum value.
*/
void scrobbleFailed( const ScrobblingServicePtr &service, const Meta::TrackPtr &track, int error );
private Q_SLOTS:
/**
* Creates new instance of provider type identified by @param type
* with configuration stored in @param config.
*/
void createProvider( QString type, QVariantMap config );
/**
* Reconfigures provider identified by @param id with configuration
* stored in @param config.
*/
void reconfigureProvider( QString id, QVariantMap config );
/**
* Can only be connected to provider changed() signal
*/
void slotProviderUpdated();
/**
* Wait a few seconds and if no collectionUpdate() signal arrives until then,
* start synchronization. Otherwise postpone the synchronization for a few
* seconds.
*/
void delayedStartSynchronization();
void slotCollectionAdded( Collections::Collection* collection,
CollectionManager::CollectionStatus status );
void slotCollectionRemoved( const QString &id );
void startNonInteractiveSynchronization();
- void synchronize( int mode );
+ void synchronizeWithMode( int mode );
void slotTrackFinishedPlaying( const Meta::TrackPtr &track, double playedFraction );
void slotResetLastSubmittedNowPlayingTrack();
void slotUpdateNowPlayingWithCurrentTrack();
private:
Q_DISABLE_COPY( Controller )
ProviderPtr findRegisteredProvider( const QString &id ) const;
/**
* Return true if important metadata of both tracks is equal.
*/
bool tracksVirtuallyEqual( const Meta::TrackPtr &first, const Meta::TrackPtr &second );
QMap<QString, ProviderFactory*> m_providerFactories;
// synchronization-related
ProviderPtrList m_providers;
QWeakPointer<Process> m_currentProcess;
QTimer *m_startSyncingTimer;
Config *m_config;
/**
* When a new collection appears, StatSyncing::Controller will automatically
* trigger synchronization. It however waits s_syncingTriggerTimeout
* milliseconds to let the collection settle down. Moreover, if the newly
* added collection emits updated(), the timeout will run from start again.
*
* (reason: e.g. iPod Collectin appears quickly, but with no tracks, which
* are added gradually as they are parsed. This "ensures" we only start
* syncing as soon as all tracks are parsed.)
*/
static const int s_syncingTriggerTimeout;
// scrobbling-related
QList<ScrobblingServicePtr> m_scrobblingServices;
QTimer *m_updateNowPlayingTimer;
Meta::TrackPtr m_lastSubmittedNowPlayingTrack;
};
} // namespace StatSyncing
#endif // STATSYNCING_CONTROLLER_H
diff --git a/src/statsyncing/Process.cpp b/src/statsyncing/Process.cpp
index fe8470c1d3..4ebcb9a4ca 100644
--- a/src/statsyncing/Process.cpp
+++ b/src/statsyncing/Process.cpp
@@ -1,297 +1,301 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "Process.h"
#include "MainWindow.h"
#include "MetaValues.h"
#include "core/interfaces/Logger.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/support/Components.h"
#include "statsyncing/Config.h"
#include "statsyncing/Controller.h"
#include "statsyncing/jobs/MatchTracksJob.h"
#include "statsyncing/jobs/SynchronizeTracksJob.h"
#include "statsyncing/models/MatchedTracksModel.h"
#include "statsyncing/models/ProvidersModel.h"
#include "statsyncing/models/SingleTracksModel.h"
#include "statsyncing/ui/ChooseProvidersPage.h"
#include "statsyncing/ui/MatchedTracksPage.h"
using namespace StatSyncing;
Process::Process( const ProviderPtrList &providers, const ProviderPtrSet &preSelectedProviders,
qint64 checkedFields, Process::Mode mode, QObject *parent )
: QObject( parent )
, m_mode( mode )
, m_providersModel( new ProvidersModel( providers, preSelectedProviders, this ) )
, m_checkedFields( checkedFields )
, m_matchedTracksModel( 0 )
, m_dialog( new KDialog() )
{
m_dialog.data()->setCaption( i18n( "Synchronize Statistics" ) );
m_dialog.data()->setButtons( KDialog::None );
m_dialog.data()->setInitialSize( QSize( 860, 500 ) );
m_dialog.data()->restoreDialogSize( Amarok::config( "StatSyncingDialog" ) );
// delete this process when user hits the close button
- connect( m_dialog.data(), SIGNAL(finished()), SLOT(slotSaveSizeAndDelete()) );
+ connect( m_dialog.data(), &KDialog::finished, this, &Process::slotSaveSizeAndDelete );
/* we need to delete all QWidgets on application exit well before QApplication
* is destroyed. We however don't set MainWindow as parent as this would make
* StatSyncing dialog share taskbar entry with Amarok main window */
- connect( The::mainWindow(), SIGNAL(destroyed(QObject*)), SLOT(slotDeleteDialog()) );
+ connect( The::mainWindow(), &MainWindow::destroyed, this, &Process::slotDeleteDialog );
}
Process::~Process()
{
delete m_dialog.data(); // we cannot deleteLater, dialog references m_matchedTracksModel
}
void
Process::start()
{
if( m_mode == Interactive )
{
m_providersPage = new ChooseProvidersPage();
m_providersPage.data()->setFields( Controller::availableFields(), m_checkedFields );
m_providersPage.data()->setProvidersModel( m_providersModel, m_providersModel->selectionModel() );
- connect( m_providersPage.data(), SIGNAL(accepted()), SLOT(slotMatchTracks()) );
- connect( m_providersPage.data(), SIGNAL(rejected()), SLOT(slotSaveSizeAndDelete()) );
+ connect( m_providersPage.data(), &StatSyncing::ChooseProvidersPage::accepted,
+ this, &Process::slotMatchTracks );
+ connect( m_providersPage.data(), &StatSyncing::ChooseProvidersPage::rejected,
+ this, &Process::slotSaveSizeAndDelete );
m_dialog.data()->mainWidget()->hide(); // otherwise it may last as a ghost image
m_dialog.data()->setMainWidget( m_providersPage.data() ); // takes ownership
raise();
}
else if( m_checkedFields )
slotMatchTracks();
}
void
Process::raise()
{
if( m_providersPage || m_tracksPage )
{
m_dialog.data()->show();
m_dialog.data()->activateWindow();
m_dialog.data()->raise();
}
else
m_mode = Interactive; // schedule dialog should be shown when something happens
}
void
Process::slotMatchTracks()
{
MatchTracksJob *job = new MatchTracksJob( m_providersModel->selectedProviders() );
QString text = i18n( "Matching Tracks for Statistics Synchronization" );
if( m_providersPage )
{
ChooseProvidersPage *page = m_providersPage.data(); // too lazy to type
m_checkedFields = page->checkedFields();
page->disableControls();
page->setProgressBarText( text );
- connect( job, SIGNAL(totalSteps(int)), page, SLOT(setProgressBarMaximum(int)) );
- connect( job, SIGNAL(incrementProgress()), page, SLOT(progressBarIncrementProgress()) );
- connect( page, SIGNAL(rejected()), job, SLOT(abort()) );
- connect( m_dialog.data(), SIGNAL(finished()), job, SLOT(abort()) );
+ connect( job, &StatSyncing::MatchTracksJob::totalSteps,
+ page, &StatSyncing::ChooseProvidersPage::setProgressBarMaximum );
+ connect( job, &StatSyncing::MatchTracksJob::incrementProgress, page,
+ &StatSyncing::ChooseProvidersPage::progressBarIncrementProgress );
+ connect( page, &StatSyncing::ChooseProvidersPage::rejected, job, &StatSyncing::MatchTracksJob::abort );
+ connect( m_dialog.data(), &KDialog::finished, job, &StatSyncing::MatchTracksJob::abort );
}
else // background operation
{
Amarok::Components::logger()->newProgressOperation( job, text, 100, job, SLOT(abort()) );
}
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(slotTracksMatched(ThreadWeaver::JobPointer)) );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &StatSyncing::MatchTracksJob::done, this, &Process::slotTracksMatched );
+ connect( job, &StatSyncing::MatchTracksJob::done, job, &StatSyncing::MatchTracksJob::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
void
Process::slotTracksMatched( ThreadWeaver::JobPointer job )
{
MatchTracksJob *matchJob = dynamic_cast<MatchTracksJob *>( job.data() );
if( !matchJob )
{
error() << __PRETTY_FUNCTION__ << "Failed cast, should never happen";
deleteLater();
return;
}
if( !matchJob->success() )
{
warning() << __PRETTY_FUNCTION__ << "MatchTracksJob failed";
deleteLater();
return;
}
StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
if( !controller )
{
warning() << __PRETTY_FUNCTION__ << "StatSyncing::Controller disappeared";
deleteLater();
return;
}
// remove fields that are not writable:
qint64 usedFields = m_checkedFields & m_providersModel->writableTrackStatsDataUnion();
m_options.setSyncedFields( usedFields );
m_options.setExcludedLabels( controller->config()->excludedLabels() );
QList<qint64> columns = QList<qint64>() << Meta::valTitle;
foreach( qint64 field, Controller::availableFields() )
{
if( field & usedFields )
columns << field;
}
m_matchedTracksModel = new MatchedTracksModel( matchJob->matchedTuples(), columns,
m_options, this );
QList<ScrobblingServicePtr> services = controller->scrobblingServices();
// only fill in m_tracksToScrobble if there are actual scrobbling services available
m_tracksToScrobble = services.isEmpty() ? TrackList() : matchJob->tracksToScrobble();
if( m_matchedTracksModel->hasConflict() || m_mode == Interactive )
{
m_tracksPage = new MatchedTracksPage();
MatchedTracksPage *page = m_tracksPage.data(); // convenience
page->setProviders( matchJob->providers() );
page->setMatchedTracksModel( m_matchedTracksModel );
foreach( ProviderPtr provider, matchJob->providers() )
{
if( !matchJob->uniqueTracks().value( provider ).isEmpty() )
page->addUniqueTracksModel( provider, new SingleTracksModel(
matchJob->uniqueTracks().value( provider ), columns, m_options, page ) );
if( !matchJob->excludedTracks().value( provider ).isEmpty() )
page->addExcludedTracksModel( provider, new SingleTracksModel(
matchJob->excludedTracks().value( provider ), columns, m_options, page ) );
}
page->setTracksToScrobble( m_tracksToScrobble, services );
- connect( page, SIGNAL(back()), SLOT(slotBack()) );
- connect( page, SIGNAL(accepted()), SLOT(slotSynchronize()) );
- connect( page, SIGNAL(rejected()), SLOT(slotSaveSizeAndDelete()) );
+ connect( page, &StatSyncing::MatchedTracksPage::back, this, &Process::slotBack );
+ connect( page, &StatSyncing::MatchedTracksPage::accepted, this, &Process::slotSynchronize );
+ connect( page, &StatSyncing::MatchedTracksPage::rejected, this, &Process::slotSaveSizeAndDelete );
m_dialog.data()->mainWidget()->hide(); // otherwise it may last as a ghost image
m_dialog.data()->setMainWidget( page ); // takes ownership
raise();
}
else // NonInteractive mode without conflict
slotSynchronize();
}
void
Process::slotBack()
{
m_mode = Interactive; // reset mode, we're interactive from this point
start();
}
void
Process::slotSynchronize()
{
// disconnect, otherwise we prematurely delete Process and thus m_matchedTracksModel
- disconnect( m_dialog.data(), SIGNAL(finished()), this, SLOT(slotSaveSizeAndDelete()) );
+ disconnect( m_dialog.data(), &KDialog::finished, this, &Process::slotSaveSizeAndDelete );
m_dialog.data()->close();
SynchronizeTracksJob *job = new SynchronizeTracksJob(
m_matchedTracksModel->matchedTuples(), m_tracksToScrobble, m_options );
QString text = i18n( "Synchronizing Track Statistics" );
Amarok::Components::logger()->newProgressOperation( job, text, 100, job, SLOT(abort()) );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(slotLogSynchronization(ThreadWeaver::JobPointer)) );
- connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), job, SLOT(deleteLater()) );
+ connect( job, &StatSyncing::SynchronizeTracksJob::done, this, &Process::slotLogSynchronization );
+ connect( job, &StatSyncing::SynchronizeTracksJob::done, job, &StatSyncing::SynchronizeTracksJob::deleteLater );
ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
}
void
Process::slotLogSynchronization( ThreadWeaver::JobPointer job )
{
deleteLater(); // our work is done
SynchronizeTracksJob *syncJob = dynamic_cast<SynchronizeTracksJob *>( job.data() );
if( !syncJob )
{
warning() << __PRETTY_FUNCTION__ << "syncJob is null";
return;
}
int updatedTracksCount = syncJob->updatedTracksCount();
QMap<ScrobblingServicePtr, QMap<ScrobblingService::ScrobbleError, int> > scrobbles =
syncJob->scrobbles();
QStringList providerNames;
foreach( ProviderPtr provider, m_providersModel->selectedProviders() )
providerNames << "<b>" + provider->prettyName() + "</b>";
QString providers = providerNames.join( i18nc( "comma between list words", ", " ) );
QStringList text = QStringList() << i18ncp( "%2 is a list of collection names",
"Synchronization of %2 done. <b>One</b> track was updated.",
"Synchronization of %2 done. <b>%1</b> tracks were updated.",
updatedTracksCount, providers );
QMap<ScrobblingService::ScrobbleError, int> scrobbleErrorCounts;
foreach( const ScrobblingServicePtr &provider, scrobbles.keys() )
{
QString name = "<b>" + provider->prettyName() + "</b>";
QMap<ScrobblingService::ScrobbleError, int> &providerScrobbles = scrobbles[ provider ];
QMapIterator<ScrobblingService::ScrobbleError, int> it( providerScrobbles );
while( it.hasNext() )
{
it.next();
if( it.key() == ScrobblingService::NoError )
text << i18np( "<b>One</b> track was queued for scrobbling to %2.",
"<b>%1</b> tracks were queued for scrobbling to %2.", it.value(), name );
else
scrobbleErrorCounts[ it.key() ] += it.value();
}
}
if( scrobbleErrorCounts.value( ScrobblingService::TooShort ) )
text << i18np( "<b>One</b> track's played time was too short to be scrobbled.",
"<b>%1</b> tracks' played time was too short to be scrobbled.",
scrobbleErrorCounts[ ScrobblingService::TooShort ] );
if( scrobbleErrorCounts.value( ScrobblingService::BadMetadata ) )
text << i18np( "<b>One</b> track had insufficient metadata to be scrobbled.",
"<b>%1</b> tracks had insufficient metadata to be scrobbled.",
scrobbleErrorCounts[ ScrobblingService::BadMetadata ] );
if( scrobbleErrorCounts.value( ScrobblingService::FromTheFuture ) )
text << i18np( "<b>One</b> track was reported to have been played in the future.",
"<b>%1</b> tracks were reported to have been played in the future.",
scrobbleErrorCounts[ ScrobblingService::FromTheFuture ] );
if( scrobbleErrorCounts.value( ScrobblingService::FromTheDistantPast ) )
text << i18np( "<b>One</b> track was last played in too distant past to be scrobbled.",
"<b>%1</b> tracks were last played in too distant past to be scrobbled.",
scrobbleErrorCounts[ ScrobblingService::FromTheDistantPast ] );
if( scrobbleErrorCounts.value( ScrobblingService::SkippedByUser ) )
text << i18np( "Scrobbling of <b>one</b> track was skipped as configured by the user.",
"Scrobbling of <b>%1</b> tracks was skipped as configured by the user.",
scrobbleErrorCounts[ ScrobblingService::SkippedByUser ] );
Amarok::Components::logger()->longMessage( text.join( "<br>\n" ) );
}
void
Process::slotSaveSizeAndDelete()
{
if( m_dialog )
{
KConfigGroup group = Amarok::config( "StatSyncingDialog" );
m_dialog.data()->saveDialogSize( group );
}
deleteLater();
}
void
Process::slotDeleteDialog()
{
// we cannot use deleteLater(), we don't have spare eventloop iteration
delete m_dialog.data();
}
diff --git a/src/statsyncing/collection/CollectionProvider.cpp b/src/statsyncing/collection/CollectionProvider.cpp
index 2fd56ad16d..6041f0e24e 100644
--- a/src/statsyncing/collection/CollectionProvider.cpp
+++ b/src/statsyncing/collection/CollectionProvider.cpp
@@ -1,187 +1,187 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CollectionProvider.h"
#include "MetaValues.h"
#include "amarokconfig.h"
#include "core/collections/Collection.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "statsyncing/collection/CollectionTrack.h"
using namespace StatSyncing;
CollectionProvider::CollectionProvider( Collections::Collection *collection )
: m_coll( collection )
{
Q_ASSERT( m_coll );
- connect( collection, SIGNAL(updated()), SIGNAL(updated()) );
- connect( this, SIGNAL(startArtistSearch()), SLOT(slotStartArtistSearch()) );
- connect( this, SIGNAL(startTrackSearch(QString)), SLOT(slotStartTrackSearch(QString)) );
+ connect( collection, &Collections::Collection::updated, this, &CollectionProvider::updated );
+ connect( this, &CollectionProvider::startArtistSearch, this, &CollectionProvider::slotStartArtistSearch );
+ connect( this, &CollectionProvider::startTrackSearch, this, &CollectionProvider::slotStartTrackSearch );
}
CollectionProvider::~CollectionProvider()
{
}
QString
CollectionProvider::id() const
{
return m_coll ? m_coll.data()->collectionId() : QString();
}
QString
CollectionProvider::prettyName() const
{
return m_coll ? m_coll.data()->prettyName() : QString();
}
QIcon
CollectionProvider::icon() const
{
return m_coll ? m_coll.data()->icon() : QIcon();
}
qint64
CollectionProvider::reliableTrackMetaData() const
{
if( id().startsWith("amarok-nepomuk:") )
return Meta::valTitle | Meta::valArtist | Meta::valAlbum | Meta::valComposer |
Meta::valTrackNr;
else
return Meta::valTitle | Meta::valArtist | Meta::valAlbum |
Meta::valComposer | Meta::valYear | Meta::valTrackNr | Meta::valDiscNr;
}
qint64
CollectionProvider::writableTrackStatsData() const
{
// TODO: this is unreliable and hacky, but serves for now:
if( id() == "localCollection" )
return Meta::valRating | Meta::valFirstPlayed | Meta::valLastPlayed | Meta::valPlaycount | Meta::valLabel;
else
return Meta::valRating | Meta::valFirstPlayed | Meta::valLastPlayed | Meta::valPlaycount;
}
Provider::Preference
CollectionProvider::defaultPreference()
{
// currently only Local Collection and iPod one have good syncing capabilities
if( id() == "localCollection" )
return YesByDefault;
if( id().startsWith( "amarok-ipodtrackuid" ) )
return Ask;
return NoByDefault;
}
QSet<QString>
CollectionProvider::artists()
{
if( !m_coll )
return QSet<QString>();
m_foundArtists.clear();
emit startArtistSearch();
m_queryMakerSemaphore.acquire(); // blocks until slotQueryDone() releases the semaphore
QSet<QString> ret = m_foundArtists;
m_foundArtists.clear(); // don't waste memory
return ret;
}
TrackList
CollectionProvider::artistTracks( const QString &artistName )
{
if( !m_coll )
return TrackList();
m_foundTracks.clear();
emit startTrackSearch( artistName );
m_queryMakerSemaphore.acquire(); // blocks until slotQueryDone() releases the semaphore
TrackList ret = m_foundTracks;
m_foundTracks.clear(); // don't waste memory
m_currentArtistName.clear();
return ret;
}
void
CollectionProvider::slotStartArtistSearch()
{
if( !m_coll )
{
m_queryMakerSemaphore.release(); // prevent deadlock
return;
}
Collections::QueryMaker *qm = m_coll.data()->queryMaker();
qm->setAutoDelete( true );
qm->setQueryType( Collections::QueryMaker::Artist );
- connect( qm, SIGNAL(newResultReady(Meta::ArtistList)),
- SLOT(slotNewResultReady(Meta::ArtistList)) );
- connect( qm, SIGNAL(queryDone()), SLOT(slotQueryDone()) );
+ connect( qm, &Collections::QueryMaker::newArtistsReady,
+ this, &CollectionProvider::slotNewArtistsReady );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionProvider::slotQueryDone );
qm->run();
}
void
CollectionProvider::slotStartTrackSearch( QString artistName )
{
if( !m_coll )
{
m_queryMakerSemaphore.release(); // prevent deadlock
return;
}
Collections::QueryMaker *qm = m_coll.data()->queryMaker();
qm->setAutoDelete( true );
qm->setQueryType( Collections::QueryMaker::Track );
m_currentArtistName = artistName;
qm->addFilter( Meta::valArtist, m_currentArtistName, true, true );
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)),
- SLOT(slotNewResultReady(Meta::TrackList)) );
- connect( qm, SIGNAL(queryDone()), SLOT(slotQueryDone()) );
+ connect( qm, &Collections::QueryMaker::newTracksReady,
+ this, &CollectionProvider::slotNewTracksReady );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionProvider::slotQueryDone );
qm->run();
}
void
-CollectionProvider::slotNewResultReady( Meta::ArtistList list )
+CollectionProvider::slotNewArtistsReady( Meta::ArtistList list )
{
foreach( const Meta::ArtistPtr &artist, list )
{
m_foundArtists.insert( artist->name() );
}
}
void
-CollectionProvider::slotNewResultReady( Meta::TrackList list )
+CollectionProvider::slotNewTracksReady( Meta::TrackList list )
{
foreach( Meta::TrackPtr track, list )
{
Meta::ArtistPtr artistPtr = track->artist();
QString artist = artistPtr ? artistPtr->name() : QString();
// QueryMaker interface is case-insensitive and cannot be configured otherwise.
// StatSyncing::Provicer interface is case-sensitive, so we must filter here
if( artist == m_currentArtistName )
m_foundTracks.append( TrackPtr( new CollectionTrack( track ) ) );
}
}
void
CollectionProvider::slotQueryDone()
{
m_queryMakerSemaphore.release(); // unblock method in a worker thread
}
diff --git a/src/statsyncing/collection/CollectionProvider.h b/src/statsyncing/collection/CollectionProvider.h
index f377a578e2..651b9d0066 100644
--- a/src/statsyncing/collection/CollectionProvider.h
+++ b/src/statsyncing/collection/CollectionProvider.h
@@ -1,88 +1,88 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef STATSYNCING_COLLECTIONPROVIDER_H
#define STATSYNCING_COLLECTIONPROVIDER_H
#include "core/meta/forward_declarations.h"
#include "statsyncing/Provider.h"
#include <QSemaphore>
namespace Collections {
class Collection;
}
namespace StatSyncing
{
/**
* Provider that has Collections::Colections as a back-end.
*/
class CollectionProvider : public Provider
{
Q_OBJECT
public:
/**
* Construct provider that has @param collection as a back-end.
*/
CollectionProvider( Collections::Collection *collection );
virtual ~CollectionProvider();
virtual QString id() const;
virtual QString prettyName() const;
virtual QIcon icon() const;
virtual qint64 reliableTrackMetaData() const;
virtual qint64 writableTrackStatsData() const;
virtual Preference defaultPreference();
virtual QSet<QString> artists();
virtual TrackList artistTracks( const QString &artistName );
Q_SIGNALS:
/// hacks to create and start QueryMaker in main eventloop
void startArtistSearch();
void startTrackSearch( QString artistName );
private Q_SLOTS:
/// @see startArtistSearch
void slotStartArtistSearch();
void slotStartTrackSearch( QString artistName );
- void slotNewResultReady( Meta::ArtistList list );
- void slotNewResultReady( Meta::TrackList list );
+ void slotNewArtistsReady( Meta::ArtistList list );
+ void slotNewTracksReady( Meta::TrackList list );
void slotQueryDone();
private:
Q_DISABLE_COPY(CollectionProvider)
/// collection can disappear at any time, use weak pointer to notice it
QWeakPointer<Collections::Collection> m_coll;
QSet<QString> m_foundArtists;
QString m_currentArtistName;
TrackList m_foundTracks;
/**
* Semaphore for the simplified producer-consumer pattern, where
* slotNewResultReady( ArtistList ) along with slotQueryDone() is producer
* and artists() is consumer, or
* slotNewResultReady( TrackList ) along with slotQueryDone() is producer
* and artistTracks() is consumer.
*/
QSemaphore m_queryMakerSemaphore;
};
} // namespace StatSyncing
#endif // STATSYNCING_COLLECTIONPROVIDER_H
diff --git a/src/statsyncing/jobs/SynchronizeTracksJob.cpp b/src/statsyncing/jobs/SynchronizeTracksJob.cpp
index 897887bef3..70b6801af2 100644
--- a/src/statsyncing/jobs/SynchronizeTracksJob.cpp
+++ b/src/statsyncing/jobs/SynchronizeTracksJob.cpp
@@ -1,169 +1,167 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SynchronizeTracksJob.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "core/support/Components.h"
#include "core/support/Debug.h"
#include "statsyncing/Controller.h"
#include "statsyncing/TrackTuple.h"
#include <ThreadWeaver/Thread>
using namespace StatSyncing;
static const int denom = 20; // emit incementProgress() signal each N tracks
static const int fuzz = denom / 2;
SynchronizeTracksJob::SynchronizeTracksJob( const QList<TrackTuple> &tuples,
const TrackList &tracksToScrobble,
const Options &options, QObject *parent )
: QObject( parent )
, ThreadWeaver::Job()
, m_abort( false )
, m_tuples( tuples )
, m_tracksToScrobble( tracksToScrobble )
, m_updatedTracksCount( 0 )
, m_options( options )
{
}
void
SynchronizeTracksJob::abort()
{
m_abort = true;
}
void
SynchronizeTracksJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
Q_UNUSED(self);
Q_UNUSED(thread);
emit totalSteps( ( m_tuples.size() + fuzz ) / denom );
Controller *controller = Amarok::Components::statSyncingController();
if( controller )
{
- connect( this, SIGNAL(scrobble(Meta::TrackPtr,double,QDateTime)),
- controller, SLOT(scrobble(Meta::TrackPtr,double,QDateTime)) );
+ connect( this, &SynchronizeTracksJob::scrobble,
+ controller, &StatSyncing::Controller::scrobble );
// we don't run an event loop, we must use direct connection for controller to talk to us
- connect( controller, SIGNAL(trackScrobbled(ScrobblingServicePtr,Meta::TrackPtr)),
- SLOT(slotTrackScrobbled(ScrobblingServicePtr,Meta::TrackPtr)),
- Qt::DirectConnection );
- connect( controller, SIGNAL(scrobbleFailed(ScrobblingServicePtr,Meta::TrackPtr,int)),
- SLOT(slotScrobbleFailed(ScrobblingServicePtr,Meta::TrackPtr,int)),
- Qt::DirectConnection );
+ connect( controller, &StatSyncing::Controller::trackScrobbled,
+ this, &SynchronizeTracksJob::slotTrackScrobbled, Qt::DirectConnection );
+ connect( controller, &StatSyncing::Controller::scrobbleFailed,
+ this, &SynchronizeTracksJob::slotScrobbleFailed, Qt::DirectConnection );
}
else
warning() << __PRETTY_FUNCTION__ << "StatSyncing::Controller not available!";
// first, queue tracks for scrobbling, because after syncing their recent playcount is
// reset
foreach( const TrackPtr &track, m_tracksToScrobble )
{
Meta::TrackPtr metaTrack = track->metaTrack();
int playcount = track->recentPlayCount();
if( metaTrack && playcount > 0 )
{
m_scrobbledTracks << metaTrack;
emit scrobble( metaTrack, playcount, track->lastPlayed() );
}
}
ProviderPtrSet updatedProviders;
int i = 0;
foreach( const TrackTuple &tuple, m_tuples )
{
if( m_abort )
break;
// no point in checking for hasUpdate() here, synchronize() is witty enough
const ProviderPtrSet tupleUpdatedProviders = tuple.synchronize( m_options );
updatedProviders |= tupleUpdatedProviders;
m_updatedTracksCount += tupleUpdatedProviders.count();
if( ( i + fuzz ) % denom == 0 )
emit incrementProgress();
i++;
}
foreach( ProviderPtr provider, updatedProviders )
provider->commitTracks();
// we need to reset playCount of scrobbled tracks to reset their recent play count
foreach( Meta::TrackPtr track, m_scrobbledTracks )
{
Meta::StatisticsPtr statistics = track->statistics();
statistics->setPlayCount( statistics->playCount() );
}
if( !m_tracksToScrobble.isEmpty() )
// wait 3 seconds so that we have chance to catch slotTrackScrobbled()..
QObject::thread()->msleep( 3000 );
if( controller )
- disconnect( controller, SIGNAL(trackScrobbled(ScrobblingServicePtr,Meta::TrackPtr)), this, 0 );
- disconnect( controller, SIGNAL(scrobbleFailed(ScrobblingServicePtr,Meta::TrackPtr,int)), this, 0 );
+ disconnect( controller, &StatSyncing::Controller::trackScrobbled, this, 0 );
+ disconnect( controller, &StatSyncing::Controller::scrobbleFailed, this, 0 );
emit endProgressOperation( this );
}
void SynchronizeTracksJob::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
Q_EMIT started(self);
ThreadWeaver::Job::defaultBegin(self, thread);
}
void SynchronizeTracksJob::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
{
ThreadWeaver::Job::defaultEnd(self, thread);
if (!self->success()) {
Q_EMIT failed(self);
}
Q_EMIT done(self);
}
void
SynchronizeTracksJob::slotTrackScrobbled( const ScrobblingServicePtr &service,
const Meta::TrackPtr &track )
{
slotScrobbleFailed( service, track, ScrobblingService::NoError );
}
void
SynchronizeTracksJob::slotScrobbleFailed( const ScrobblingServicePtr &service,
const Meta::TrackPtr &track, int error )
{
// only count tracks scrobbled by us. Still chance for false-positives, though
if( m_scrobbledTracks.contains( track ) )
{
ScrobblingService::ScrobbleError errorEnum = ScrobblingService::ScrobbleError( error );
m_scrobbles[ service ][ errorEnum ]++;
}
}
int
SynchronizeTracksJob::updatedTracksCount() const
{
return m_updatedTracksCount;
}
QMap<ScrobblingServicePtr, QMap<ScrobblingService::ScrobbleError, int> >
SynchronizeTracksJob::scrobbles()
{
return m_scrobbles;
}
diff --git a/src/statsyncing/models/ProvidersModel.cpp b/src/statsyncing/models/ProvidersModel.cpp
index d321097ff7..9ca07ad305 100644
--- a/src/statsyncing/models/ProvidersModel.cpp
+++ b/src/statsyncing/models/ProvidersModel.cpp
@@ -1,142 +1,142 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ProvidersModel.h"
#include "core/meta/support/MetaConstants.h"
#include "statsyncing/Provider.h"
#include <KLocalizedString>
#include <QItemSelectionModel>
using namespace StatSyncing;
ProvidersModel::ProvidersModel( const ProviderPtrList &providers,
const ProviderPtrSet &preSelectedProviders, QObject *parent )
: QAbstractListModel( parent )
, m_providers( providers )
, m_selectionModel( new QItemSelectionModel( this, this ) )
{
// TODO: sort providers
// selection defaults to model's tick state
for( int i = 0; i < m_providers.count(); i++ )
{
if( preSelectedProviders.contains( m_providers.at( i ) ) )
{
QModelIndex idx = index( i );
m_selectionModel->select( idx, QItemSelectionModel::Select );
}
}
- connect( m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
- SIGNAL(selectedProvidersChanged()) );
+ connect( m_selectionModel, &QItemSelectionModel::selectionChanged,
+ this, &ProvidersModel::selectedProvidersChanged );
}
ProvidersModel::~ProvidersModel()
{
}
QVariant
ProvidersModel::data( const QModelIndex &index, int role ) const
{
if( !index.isValid() || index.column() != 0 ||
index.row() < 0 || index.row() >= m_providers.count() )
{
return QVariant();
}
ProviderPtr provider = m_providers.at( index.row() );
switch( role )
{
case Qt::DisplayRole:
if( provider->description().isEmpty() )
return provider->prettyName();
return i18nc( "%1: name, %2: description", "%1 (%2)", provider->prettyName(),
provider->description() );
case Qt::DecorationRole:
return provider->icon();
case Qt::ToolTipRole:
return i18n( "Can match tracks by: %1\nCan synchronize: %2",
fieldsToString( provider->reliableTrackMetaData() ),
fieldsToString( provider->writableTrackStatsData() ) );
}
return QVariant();
}
int
ProvidersModel::rowCount( const QModelIndex &parent ) const
{
return parent.isValid() ? 0 : m_providers.count();
}
ProviderPtrList
ProvidersModel::selectedProviders() const
{
ProviderPtrList ret;
// preserve order, so do it the hard way
for( int i = 0; i < rowCount(); i++ )
{
QModelIndex idx = index( i, 0 );
if( m_selectionModel->isSelected( idx ) )
ret << m_providers.at( i );
}
return ret;
}
qint64
ProvidersModel::reliableTrackMetadataIntersection() const
{
if( selectedProviders().isEmpty() )
return 0;
QListIterator<ProviderPtr> it( selectedProviders() );
qint64 fields = it.next()->reliableTrackMetaData();
while( it.hasNext() )
fields &= it.next()->reliableTrackMetaData();
return fields;
}
qint64
ProvidersModel::writableTrackStatsDataUnion() const
{
qint64 fields = 0;
foreach( const ProviderPtr &provider, selectedProviders() )
{
fields |= provider->writableTrackStatsData();
}
return fields;
}
QItemSelectionModel *
ProvidersModel::selectionModel() const
{
return m_selectionModel;
}
QString
ProvidersModel::fieldsToString( qint64 fields ) const
{
QStringList fieldNames;
for( qint64 i = 0; i < 64; i++ )
{
qint64 field = 1LL << i;
if( !( field & fields ) )
continue;
QString name = Meta::i18nForField( field );
if( !name.isEmpty() )
fieldNames << name;
}
return fieldNames.join( i18nc( "comma between list words", ", " ) );
}
diff --git a/src/statsyncing/ui/ChooseProvidersPage.cpp b/src/statsyncing/ui/ChooseProvidersPage.cpp
index 3862cc42f1..b330360f5b 100644
--- a/src/statsyncing/ui/ChooseProvidersPage.cpp
+++ b/src/statsyncing/ui/ChooseProvidersPage.cpp
@@ -1,184 +1,186 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ChooseProvidersPage.h"
#include "App.h"
#include "core/meta/support/MetaConstants.h"
#include "statsyncing/models/ProvidersModel.h"
#include <KPushButton>
#include <QCheckBox>
using namespace StatSyncing;
ChooseProvidersPage::ChooseProvidersPage( QWidget *parent, Qt::WindowFlags f )
: QWidget( parent, f )
, m_providersModel( 0 )
{
setupUi( this );
KGuiItem configure = KStandardGuiItem::configure();
configure.setText( i18n( "Configure Synchronization..." ) );
buttonBox->addButton( configure, QDialogButtonBox::ActionRole, this, SLOT(openConfiguration()) );
buttonBox->addButton( KGuiItem( i18n( "Next" ), "go-next" ), QDialogButtonBox::AcceptRole );
- connect( buttonBox, SIGNAL(accepted()), SIGNAL(accepted()) );
- connect( buttonBox, SIGNAL(rejected()), SIGNAL(rejected()) );
+ connect( buttonBox, &QDialogButtonBox::accepted, this, &ChooseProvidersPage::accepted );
+ connect( buttonBox, &QDialogButtonBox::rejected, this, &ChooseProvidersPage::rejected );
progressBar->hide();
}
ChooseProvidersPage::~ChooseProvidersPage()
{
}
void
ChooseProvidersPage::setFields( const QList<qint64> &fields, qint64 checkedFields )
{
QLayout *fieldsLayout = fieldsBox->layout();
foreach( qint64 field, fields )
{
QString name = Meta::i18nForField( field );
QCheckBox *checkBox = new QCheckBox( name );
fieldsLayout->addWidget( checkBox );
checkBox->setCheckState( ( field & checkedFields ) ? Qt::Checked : Qt::Unchecked );
checkBox->setProperty( "field", field );
- connect( checkBox, SIGNAL(stateChanged(int)), SIGNAL(checkedFieldsChanged()) );
+ connect( checkBox, &QCheckBox::stateChanged, this, &ChooseProvidersPage::checkedFieldsChanged );
}
fieldsLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
- connect( this, SIGNAL(checkedFieldsChanged()), SLOT(updateEnabledFields()) );
+ connect( this, &ChooseProvidersPage::checkedFieldsChanged, this, &ChooseProvidersPage::updateEnabledFields );
updateEnabledFields();
}
qint64
ChooseProvidersPage::checkedFields() const
{
qint64 ret = 0;
QLayout *fieldsLayout = fieldsBox->layout();
for( int i = 0; i < fieldsLayout->count(); i++ )
{
QCheckBox *checkBox = qobject_cast<QCheckBox *>( fieldsLayout->itemAt( i )->widget() );
if( !checkBox )
continue;
if( checkBox->isChecked() && checkBox->property( "field" ).canConvert<qint64>() )
ret |= checkBox->property( "field" ).value<qint64>();
}
return ret;
}
void
ChooseProvidersPage::setProvidersModel( ProvidersModel *model, QItemSelectionModel *selectionModel )
{
m_providersModel = model;
providersView->setModel( model );
providersView->setSelectionModel( selectionModel );
- connect( model, SIGNAL(selectedProvidersChanged()), SLOT(updateMatchedLabel()) );
- connect( model, SIGNAL(selectedProvidersChanged()), SLOT(updateEnabledFields()) );
+ connect( model, &StatSyncing::ProvidersModel::selectedProvidersChanged,
+ this, &ChooseProvidersPage::updateMatchedLabel );
+ connect( model, &StatSyncing::ProvidersModel::selectedProvidersChanged,
+ this, &ChooseProvidersPage::updateEnabledFields );
updateMatchedLabel();
updateEnabledFields();
}
void
ChooseProvidersPage::disableControls()
{
// disable checkboxes
QLayout *fieldsLayout = fieldsBox->layout();
for( int i = 0; i < fieldsLayout->count(); i++ )
{
QWidget *widget = fieldsLayout->itemAt( i )->widget();
if( widget )
widget->setEnabled( false );
}
// disable view
providersView->setEnabled( false );
// disable all but Cancel button
foreach( QAbstractButton *button, buttonBox->buttons() )
{
if( buttonBox->buttonRole( button ) != QDialogButtonBox::RejectRole )
button->setEnabled( false );
}
}
void
ChooseProvidersPage::setProgressBarText( const QString &text )
{
progressBar->setFormat( text );
progressBar->show();
}
void
ChooseProvidersPage::setProgressBarMaximum( int maximum )
{
progressBar->setMaximum( maximum );
progressBar->show();
}
void
ChooseProvidersPage::progressBarIncrementProgress()
{
progressBar->setValue( progressBar->value() + 1 );
progressBar->show();
}
void
ChooseProvidersPage::updateMatchedLabel()
{
qint64 fields = m_providersModel->reliableTrackMetadataIntersection();
QString fieldNames = m_providersModel->fieldsToString( fields );
matchLabel->setText( i18n( "Tracks matched by: %1", fieldNames ) );
}
void
ChooseProvidersPage::updateEnabledFields()
{
if( !m_providersModel )
return;
qint64 writableFields = m_providersModel->writableTrackStatsDataUnion();
QLayout *fieldsLayout = fieldsBox->layout();
for( int i = 0; i < fieldsLayout->count(); i++ )
{
QWidget *checkBox = fieldsLayout->itemAt( i )->widget();
if( !checkBox || !checkBox->property( "field" ).canConvert<qint64>() )
continue;
qint64 field = checkBox->property( "field" ).value<qint64>();
bool enabled = writableFields & field;
checkBox->setEnabled( enabled );
QString text = i18nc( "%1 is field name such as Rating", "No selected collection "
"supports writing %1 - it doesn't make sense to synchronize it.",
Meta::i18nForField( field ) );
checkBox->setToolTip( enabled ? QString() : text );
}
QAbstractButton *nextButton = 0;
foreach( QAbstractButton *button, buttonBox->buttons() )
{
if( buttonBox->buttonRole( button ) == QDialogButtonBox::AcceptRole )
nextButton = button;
}
if( nextButton )
nextButton->setEnabled( writableFields != 0 );
}
void ChooseProvidersPage::openConfiguration()
{
App *app = App::instance();
if( app )
app->slotConfigAmarok( "MetadataConfig" );
}
diff --git a/src/statsyncing/ui/ConfigureProviderDialog.cpp b/src/statsyncing/ui/ConfigureProviderDialog.cpp
index ecfeb8baa5..f65d0d8648 100644
--- a/src/statsyncing/ui/ConfigureProviderDialog.cpp
+++ b/src/statsyncing/ui/ConfigureProviderDialog.cpp
@@ -1,60 +1,60 @@
/****************************************************************************************
* Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ConfigureProviderDialog.h"
#include "statsyncing/Provider.h"
#include <KLocale>
#include <QVBoxLayout>
#include <QRadioButton>
#include <QDebug>
#include <KConfigGroup>
#include <QPushButton>
namespace StatSyncing
{
ConfigureProviderDialog::ConfigureProviderDialog( const QString &providerId,
QWidget *configWidget, QWidget *parent,
Qt::WindowFlags f )
: KPageDialog( parent, f )
, m_providerId( providerId )
{
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
setWindowTitle( i18n( "Configure Synchronization Target" ) );
setModal( true );
buttonBox()->button(QDialogButtonBox::Help)->setVisible(false);
mainWidget = configWidget;
- connect( this, SIGNAL(accepted()), SLOT(slotAccepted()) );
+ connect( this, &ConfigureProviderDialog::accepted, this, &ConfigureProviderDialog::slotAccepted );
}
ConfigureProviderDialog::~ConfigureProviderDialog()
{
}
void
ConfigureProviderDialog::slotAccepted()
{
const ProviderConfigWidget *configWidget = qobject_cast<ProviderConfigWidget*>(mainWidget);
emit providerConfigured( m_providerId, configWidget->config() );
}
} // namespace StatSyncing
diff --git a/src/statsyncing/ui/CreateProviderDialog.cpp b/src/statsyncing/ui/CreateProviderDialog.cpp
index fb8774699e..b860864698 100644
--- a/src/statsyncing/ui/CreateProviderDialog.cpp
+++ b/src/statsyncing/ui/CreateProviderDialog.cpp
@@ -1,136 +1,136 @@
/****************************************************************************************
* Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CreateProviderDialog.h"
#include "statsyncing/Controller.h"
#include "statsyncing/Provider.h"
#include "core/support/Components.h"
#include <KLocale>
#include <QLabel>
#include <QVBoxLayout>
#include <QRadioButton>
#include <KConfigGroup>
#include <QPushButton>
namespace StatSyncing
{
CreateProviderDialog::CreateProviderDialog( QWidget *parent, Qt::WindowFlags f )
: KAssistantDialog( parent, f )
{
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum );
setWindowTitle( i18n( "Add Synchronization Target" ) );
setModal( true );
buttonBox()->button(QDialogButtonBox::Help)->setVisible(false);
m_providerButtons.setExclusive( true );
m_layout = new QVBoxLayout;
QWidget *providerTypeWidget = new QWidget;
QVBoxLayout *mainLayout = new QVBoxLayout;
QLabel *warning = new QLabel( i18n( "<span style=\"color:red; font-weight:bold;\">"
"Important:</span> before synchronizing tracks with a "
"file-based target always make sure that "
"the database file is not currently in use!" ) );
warning->setWordWrap( true );
mainLayout->addLayout( m_layout );
mainLayout->addSpacing( 10 );
mainLayout->addStretch();
mainLayout->addWidget( warning );
providerTypeWidget->setLayout( mainLayout );
m_providerTypePage = new KPageWidgetItem( providerTypeWidget,
i18n( "Choose Target Type" ) );
providerTypeWidget->hide();
addPage( m_providerTypePage );
- connect( this, SIGNAL(accepted()), SLOT(slotAccepted()) );
+ connect( this, &CreateProviderDialog::accepted, this, &CreateProviderDialog::slotAccepted );
}
CreateProviderDialog::~CreateProviderDialog()
{
}
void
CreateProviderDialog::addProviderType( const QString &id, const QString &prettyName,
const QIcon &icon,
ProviderConfigWidget *configWidget )
{
QRadioButton *providerTypeButton = new QRadioButton;
providerTypeButton->setText( prettyName );
providerTypeButton->setIcon( icon );
m_providerButtons.addButton( providerTypeButton );
m_idForButton.insert( providerTypeButton, id );
m_layout->insertWidget( buttonInsertPosition( prettyName ), providerTypeButton );
KPageWidgetItem *configPage =
new KPageWidgetItem( configWidget, i18n( "Configure Target" ) );
m_configForButton.insert( providerTypeButton, configPage );
addPage( configPage );
setAppropriate( configPage, false );
- connect( providerTypeButton, SIGNAL(toggled(bool)),
- SLOT(providerButtonToggled(bool)) );
+ connect( providerTypeButton, &QAbstractButton::toggled,
+ this, &CreateProviderDialog::providerButtonToggled );
if( !m_providerButtons.checkedButton() )
providerTypeButton->setChecked( true );
}
int
CreateProviderDialog::buttonInsertPosition( const QString &prettyName )
{
for( int i = 0; i < m_layout->count(); ++i )
{
const QRadioButton * const button =
dynamic_cast<const QRadioButton*>( m_layout->itemAt( i )->widget() );
if( button != 0 && prettyName.localeAwareCompare( button->text() ) <= 0 )
return i;
}
// Nothing found, place the button at the end
return -1;
}
void
CreateProviderDialog::providerButtonToggled( bool checked )
{
KPageWidgetItem *configPage = m_configForButton[sender()];
setAppropriate( configPage, checked );
}
void
CreateProviderDialog::slotAccepted()
{
QAbstractButton *checkedButton = m_providerButtons.checkedButton();
if( !checkedButton ) return;
const QString id = m_idForButton[checkedButton];
KPageWidgetItem *configPage = m_configForButton[checkedButton];
const ProviderConfigWidget *configWidget =
qobject_cast<ProviderConfigWidget*>( configPage->widget() );
emit providerConfigured( id, configWidget->config() );
}
} // namespace StatSyncing
diff --git a/src/statsyncing/ui/MatchedTracksPage.cpp b/src/statsyncing/ui/MatchedTracksPage.cpp
index 4da83957eb..43b30ad2d3 100644
--- a/src/statsyncing/ui/MatchedTracksPage.cpp
+++ b/src/statsyncing/ui/MatchedTracksPage.cpp
@@ -1,528 +1,529 @@
/****************************************************************************************
* Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MatchedTracksPage.h"
#include "App.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Debug.h"
#include "statsyncing/TrackTuple.h"
#include "statsyncing/models/MatchedTracksModel.h"
#include "statsyncing/ui/TrackDelegate.h"
#include <KStandardGuiItem>
#include <KPushButton>
#include <QEvent>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
// needed for QCombobox payloads:
Q_DECLARE_METATYPE( StatSyncing::ProviderPtr )
namespace StatSyncing
{
class SortFilterProxyModel : public QSortFilterProxyModel
{
public:
SortFilterProxyModel( QObject *parent = 0 )
: QSortFilterProxyModel( parent )
, m_tupleFilter( -1 )
{
// filer all columns, accept when at least one column matches:
setFilterKeyColumn( -1 );
}
/**
* Filter tuples based on their MatchedTracksModel::TupleFlag flag. Set to -1
* to accept tuples with any flags.
*/
void setTupleFilter( int filter )
{
m_tupleFilter = filter;
invalidateFilter();
sort( sortColumn(), sortOrder() ); // this doesn't happen automatically
}
protected:
bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
{
if( source_parent.isValid() )
return true; // we match all child items, we filter only root ones
if( m_tupleFilter != -1 )
{
QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
int flags = sourceModel()->data( index, MatchedTracksModel::TupleFlagsRole ).toInt();
if( !(flags & m_tupleFilter) )
return false;
}
return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
}
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const
{
if( left.parent().isValid() ) // we are comparing childs, special mode:
{
// take providers, e.g. reset column to 0
QModelIndex l = sourceModel()->index( left.row(), 0, left.parent() );
QModelIndex r = sourceModel()->index( right.row(), 0, right.parent() );
QString leftProvider = sourceModel()->data( l, Qt::DisplayRole ).toString();
QString rightProvider = sourceModel()->data( r, Qt::DisplayRole ).toString();
// make this sorting ignore the sort order, always sort acsendingly:
if( sortOrder() == Qt::AscendingOrder )
return leftProvider.localeAwareCompare( rightProvider ) < 0;
else
return leftProvider.localeAwareCompare( rightProvider ) > 0;
}
return QSortFilterProxyModel::lessThan( left, right );
}
private:
int m_tupleFilter;
};
}
using namespace StatSyncing;
MatchedTracksPage::MatchedTracksPage( QWidget *parent, Qt::WindowFlags f )
: QWidget( parent, f )
, m_matchedTracksModel( 0 )
{
setupUi( this );
// this group box is only shown upon setTracksToScrobble() call
scrobblingGroupBox->hide();
m_matchedProxyModel = new SortFilterProxyModel( this );
m_uniqueProxyModel = new QSortFilterProxyModel( this );
m_excludedProxyModel = new QSortFilterProxyModel( this );
#define SETUP_MODEL( proxyModel, name, Name ) \
proxyModel->setSortLocaleAware( true ); \
proxyModel->setSortCaseSensitivity( Qt::CaseInsensitive ); \
proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); \
- connect( proxyModel, SIGNAL(modelReset()), SLOT(refresh##Name##StatusText()) ); \
- connect( proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(refresh##Name##StatusText()) ); \
- connect( proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(refresh##Name##StatusText()) ); \
+ connect( proxyModel, &QSortFilterProxyModel::modelReset, this, &MatchedTracksPage::refresh##Name##StatusText ); \
+ connect( proxyModel, &QSortFilterProxyModel::rowsInserted, this, &MatchedTracksPage::refresh##Name##StatusText ); \
+ connect( proxyModel, &QSortFilterProxyModel::rowsRemoved, this, &MatchedTracksPage::refresh##Name##StatusText ); \
name##TreeView->setModel( m_##name##ProxyModel ); \
name##TreeView->setItemDelegate( new TrackDelegate( name##TreeView ) ); \
connect( name##FilterLine, SIGNAL(textChanged(QString)), proxyModel, SLOT(setFilterFixedString(QString)) ); \
name##TreeView->header()->setStretchLastSection( false ); \
name##TreeView->header()->setDefaultSectionSize( 80 );
SETUP_MODEL( m_matchedProxyModel, matched, Matched )
SETUP_MODEL( m_uniqueProxyModel, unique, Unique )
SETUP_MODEL( m_excludedProxyModel, excluded, Excluded )
#undef SETUP_MODEL
- connect( uniqueFilterCombo, SIGNAL(currentIndexChanged(int)),
- SLOT(changeUniqueTracksProvider(int)) );
- connect( excludedFilterCombo, SIGNAL(currentIndexChanged(int)),
- SLOT(changeExcludedTracksProvider(int)) );
+ connect( uniqueFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &MatchedTracksPage::changeUniqueTracksProvider );
+ connect( excludedFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &MatchedTracksPage::changeExcludedTracksProvider );
KGuiItem configure = KStandardGuiItem::configure();
configure.setText( i18n( "Configure Synchronization..." ) );
buttonBox->addButton( configure, QDialogButtonBox::ActionRole, this, SLOT(openConfiguration()) );
KPushButton *back = buttonBox->addButton( KStandardGuiItem::back(),
QDialogButtonBox::ActionRole );
buttonBox->addButton( KGuiItem( i18n( "Synchronize" ), "document-save" ),
QDialogButtonBox::AcceptRole );
- connect( back, SIGNAL(clicked(bool)), SIGNAL(back()) );
- connect( buttonBox, SIGNAL(accepted()), SIGNAL(accepted()) );
- connect( buttonBox, SIGNAL(rejected()), SIGNAL(rejected()) );
+ connect( back, &QAbstractButton::clicked, this, &MatchedTracksPage::back );
+ connect( buttonBox, &QDialogButtonBox::accepted, this, &MatchedTracksPage::accepted );
+ connect( buttonBox, &QDialogButtonBox::rejected, this, &MatchedTracksPage::rejected );
tabWidget->setTabEnabled( 1, false );;
tabWidget->setTabToolTip( 1, i18n( "There are no tracks unique to one of the sources "
"participating in the synchronization" ) );
tabWidget->setTabEnabled( 2, false );
tabWidget->setTabToolTip( 2, i18n( "There are no tracks excluded from "
"synchronization" ) );
QMenu *menu = new QMenu( matchedExpandButton );
- menu->addAction( i18n( "Expand Tracks With Conflicts" ), this, SLOT(expand()) )->setData(
+ menu->addAction( i18n( "Expand Tracks With Conflicts" ), this, &MatchedTracksPage::expand )->setData(
MatchedTracksModel::HasConflict );
- menu->addAction( i18n( "Expand Updated" ), this, SLOT(expand()) )->setData(
+ menu->addAction( i18n( "Expand Updated" ), this, &MatchedTracksPage::expand )->setData(
MatchedTracksModel::HasUpdate );
- menu->addAction( i18n( "Expand All" ), this, SLOT(expand()) )->setData( 0 );
+ menu->addAction( i18n( "Expand All" ), this, &MatchedTracksPage::expand )->setData( 0 );
matchedExpandButton->setMenu( menu );
menu = new QMenu( matchedCollapseButton );
- menu->addAction( i18n( "Collapse Tracks Without Conflicts" ), this, SLOT(collapse()) )->setData(
+ menu->addAction( i18n( "Collapse Tracks Without Conflicts" ), this, &MatchedTracksPage::collapse )->setData(
MatchedTracksModel::HasConflict );
- menu->addAction( i18n( "Collapse Not Updated" ), this, SLOT(collapse()) )->setData(
+ menu->addAction( i18n( "Collapse Not Updated" ), this, &MatchedTracksPage::collapse )->setData(
MatchedTracksModel::HasUpdate );
- menu->addAction( i18n( "Collapse All" ), this, SLOT(collapse()) )->setData( 0 );
+ menu->addAction( i18n( "Collapse All" ), this, &MatchedTracksPage::collapse )->setData( 0 );
matchedCollapseButton->setMenu( menu );
}
MatchedTracksPage::~MatchedTracksPage()
{
}
void
MatchedTracksPage::setProviders( const ProviderPtrList &providers )
{
// populate menu of the "Take Ratings From" button
QMenu *takeRatingsMenu = new QMenu( matchedRatingsButton );
foreach( const ProviderPtr &provider, providers )
{
QAction *action = takeRatingsMenu->addAction( provider->icon(), provider->prettyName(),
- this, SLOT(takeRatingsFrom()) );
+ this, &MatchedTracksPage::takeRatingsFrom );
action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
}
- takeRatingsMenu->addAction( i18n( "Reset All Ratings to Undecided" ), this, SLOT(takeRatingsFrom()) );
+ takeRatingsMenu->addAction( i18n( "Reset All Ratings to Undecided" ), this, &MatchedTracksPage::takeRatingsFrom );
matchedRatingsButton->setMenu( takeRatingsMenu );
matchedRatingsButton->setIcon( QIcon::fromTheme( Meta::iconForField( Meta::valRating ) ) );
// populate menu of the "Labels" button
QMenu *labelsMenu = new QMenu( matchedLabelsButton );
foreach( const ProviderPtr &provider, providers )
{
QString text = i18nc( "%1 is collection name", "Include Labels from %1", provider->prettyName() );
- QAction *action = labelsMenu->addAction( provider->icon(), text, this, SLOT(includeLabelsFrom()) );
+ QAction *action = labelsMenu->addAction( provider->icon(), text, this, &MatchedTracksPage::includeLabelsFrom );
action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
text = i18nc( "%1 is collection name", "Exclude Labels from %1", provider->prettyName() );
- action = labelsMenu->addAction( provider->icon(), text, this, SLOT(excludeLabelsFrom()) );
+ action = labelsMenu->addAction( provider->icon(), text, this, &MatchedTracksPage::excludeLabelsFrom );
action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
}
labelsMenu->addAction( i18n( "Reset All Labels to Undecided (Don't Synchronize Them)" ),
- this, SLOT(excludeLabelsFrom()) );
+ this, &MatchedTracksPage::excludeLabelsFrom );
matchedLabelsButton->setMenu( labelsMenu );
matchedLabelsButton->setIcon( QIcon::fromTheme( Meta::iconForField( Meta::valLabel ) ) );
}
void
MatchedTracksPage::setMatchedTracksModel( MatchedTracksModel *model )
{
m_matchedTracksModel = model;
Q_ASSERT( m_matchedTracksModel );
m_matchedProxyModel->setSourceModel( m_matchedTracksModel );
setHeaderSizePoliciesFromModel( matchedTreeView->header(), m_matchedTracksModel );
m_matchedProxyModel->sort( 0, Qt::AscendingOrder );
// initially, expand tuples with conflicts:
expand( MatchedTracksModel::HasConflict );
- connect( m_matchedProxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
- SLOT(rememberExpandedState(QModelIndex,int,int)) );
- connect( m_matchedProxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
- SLOT(restoreExpandedState(QModelIndex,int,int)) );
+ connect( m_matchedProxyModel, &StatSyncing::SortFilterProxyModel::rowsAboutToBeRemoved,
+ this, &MatchedTracksPage::rememberExpandedState );
+ connect( m_matchedProxyModel, &StatSyncing::SortFilterProxyModel::rowsInserted,
+ this, &MatchedTracksPage::restoreExpandedState );
// re-fill combo box and disable choices without tracks
bool hasConflict = m_matchedTracksModel->hasConflict();
matchedFilterCombo->clear();
matchedFilterCombo->addItem( i18n( "All Tracks" ), -1 );
matchedFilterCombo->addItem( i18n( "Updated Tracks" ), int( MatchedTracksModel::HasUpdate ) );
matchedFilterCombo->addItem( i18n( "Tracks With Conflicts" ), int( MatchedTracksModel::HasConflict ) );
QStandardItemModel *comboModel = dynamic_cast<QStandardItemModel *>( matchedFilterCombo->model() );
int bestIndex = 0;
if( comboModel )
{
bestIndex = 2;
if( !hasConflict )
{
comboModel->item( 2 )->setFlags( Qt::NoItemFlags );
matchedFilterCombo->setItemData( 2, i18n( "There are no tracks with conflicts" ),
Qt::ToolTipRole );
bestIndex = 1;
if( !m_matchedTracksModel->hasUpdate() )
{
comboModel->item( 1 )->setFlags( Qt::NoItemFlags );
matchedFilterCombo->setItemData( 1, i18n( "There are no tracks going to be "
"updated" ), Qt::ToolTipRole );
bestIndex = 0; // no other possibility
}
}
}
matchedFilterCombo->setCurrentIndex( bestIndex );
changeMatchedTracksFilter( bestIndex );
- connect( matchedFilterCombo, SIGNAL(currentIndexChanged(int)), SLOT(changeMatchedTracksFilter(int)) );
+ connect( matchedFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &MatchedTracksPage::changeMatchedTracksFilter );
matchedRatingsButton->setEnabled( hasConflict );
matchedLabelsButton->setEnabled( hasConflict );
}
void
MatchedTracksPage::addUniqueTracksModel( ProviderPtr provider, QAbstractItemModel *model )
{
bool first = m_uniqueTracksModels.isEmpty();
m_uniqueTracksModels.insert( provider, model );
uniqueFilterCombo->addItem( provider->icon(), provider->prettyName(),
QVariant::fromValue<ProviderPtr>( provider ) );
if( first )
{
tabWidget->setTabEnabled( 1, true );
tabWidget->setTabToolTip( 1, i18n( "Tracks that are unique to their sources" ) );
setHeaderSizePoliciesFromModel( uniqueTreeView->header(), model );
uniqueFilterCombo->setCurrentIndex( 0 );
m_uniqueProxyModel->sort( 0, Qt::AscendingOrder );
}
}
void
MatchedTracksPage::addExcludedTracksModel( ProviderPtr provider, QAbstractItemModel *model )
{
bool first = m_excludedTracksModels.isEmpty();
m_excludedTracksModels.insert( provider, model );
excludedFilterCombo->addItem( provider->icon(), provider->prettyName(),
QVariant::fromValue<ProviderPtr>( provider ) );
if( first )
{
tabWidget->setTabEnabled( 2, true );
tabWidget->setTabToolTip( 2, i18n( "Tracks that have been excluded from "
"synchronization due to ambiguity" ) );
setHeaderSizePoliciesFromModel( excludedTreeView->header(), model );
excludedFilterCombo->setCurrentIndex( 0 );
m_excludedProxyModel->sort( 0, Qt::AscendingOrder );
}
}
void
MatchedTracksPage::setTracksToScrobble( const TrackList &tracksToScrobble,
const QList<ScrobblingServicePtr> &services )
{
int tracks = tracksToScrobble.count();
int plays = 0;
foreach( const TrackPtr &track, tracksToScrobble )
{
plays += track->recentPlayCount();
}
QStringList serviceNames;
foreach( const ScrobblingServicePtr &service, services )
{
serviceNames << "<b>" + service->prettyName() + "</b>";
}
if( plays )
{
QString playsText = i18np( "<b>One</b> play", "<b>%1</b> plays", plays );
QString text = i18ncp( "%2 is the 'X plays message above'",
"%2 of <b>one</b> track will be scrobbled to %3.",
"%2 of <b>%1</b> tracks will be scrobbled to %3.", tracks, playsText,
serviceNames.join( i18nc( "comma between list words", ", " ) ) );
scrobblingLabel->setText( text );
scrobblingGroupBox->show();
}
else
scrobblingGroupBox->hide();
}
void
MatchedTracksPage::changeMatchedTracksFilter( int index )
{
int filter = matchedFilterCombo->itemData( index ).toInt();
m_matchedProxyModel->setTupleFilter( filter );
}
void
MatchedTracksPage::changeUniqueTracksProvider( int index )
{
ProviderPtr provider = uniqueFilterCombo->itemData( index ).value<ProviderPtr>();
m_uniqueProxyModel->setSourceModel( m_uniqueTracksModels.value( provider ) );
// trigger re-sort, Qt doesn't do that automatically apparently
m_uniqueProxyModel->sort( m_uniqueProxyModel->sortColumn(), m_uniqueProxyModel->sortOrder() );
}
void
MatchedTracksPage::changeExcludedTracksProvider( int index )
{
ProviderPtr provider = excludedFilterCombo->itemData( index ).value<ProviderPtr>();
m_excludedProxyModel->setSourceModel( m_excludedTracksModels.value( provider ) );
// trigger re-sort, Qt doesn't do that automatically apparently
m_excludedProxyModel->sort( m_excludedProxyModel->sortColumn(), m_excludedProxyModel->sortOrder() );
}
void
MatchedTracksPage::refreshMatchedStatusText()
{
refreshStatusTextHelper( m_matchedProxyModel, matchedStatusBar );
}
void
MatchedTracksPage::refreshUniqueStatusText()
{
refreshStatusTextHelper( m_uniqueProxyModel, uniqueStatusBar );
}
void
MatchedTracksPage::refreshExcludedStatusText()
{
refreshStatusTextHelper( m_excludedProxyModel, excludedStatusBar );
}
void
MatchedTracksPage::refreshStatusTextHelper( QSortFilterProxyModel *topModel , QLabel *label )
{
int bottomModelRows = topModel->sourceModel() ?
topModel->sourceModel()->rowCount() : 0;
int topModelRows = topModel->rowCount();
QString bottomText = i18np( "%1 track", "%1 tracks", bottomModelRows );
if( topModelRows == bottomModelRows )
label->setText( bottomText );
else
{
QString text = i18nc( "%2 is the above '%1 track(s)' message", "Showing %1 out "
"of %2", topModelRows, bottomText );
label->setText( text );
}
}
void
MatchedTracksPage::rememberExpandedState( const QModelIndex &parent, int start, int end )
{
if( parent.isValid() )
return;
for( int topModelRow = start; topModelRow <= end; topModelRow++ )
{
QModelIndex topModelIndex = m_matchedProxyModel->index( topModelRow, 0 );
int bottomModelRow = m_matchedProxyModel->mapToSource( topModelIndex ).row();
if( matchedTreeView->isExpanded( topModelIndex ) )
m_expandedTuples.insert( bottomModelRow );
else
m_expandedTuples.remove( bottomModelRow );
}
}
void
MatchedTracksPage::restoreExpandedState( const QModelIndex &parent, int start, int end )
{
if( parent.isValid() )
return;
for( int topModelRow = start; topModelRow <= end; topModelRow++ )
{
QModelIndex topIndex = m_matchedProxyModel->index( topModelRow, 0 );
int bottomModelRow = m_matchedProxyModel->mapToSource( topIndex ).row();
if( m_expandedTuples.contains( bottomModelRow ) )
matchedTreeView->expand( topIndex );
}
}
void
MatchedTracksPage::takeRatingsFrom()
{
QAction *action = qobject_cast<QAction *>( sender() );
if( !action )
{
warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
return;
}
// provider may be null, it means "reset all ratings to undecided"
ProviderPtr provider = action->data().value<ProviderPtr>();
m_matchedTracksModel->takeRatingsFrom( provider );
}
void
MatchedTracksPage::includeLabelsFrom()
{
QAction *action = qobject_cast<QAction *>( sender() );
if( !action )
{
warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
return;
}
ProviderPtr provider = action->data().value<ProviderPtr>();
if( provider ) // no sense with null provider
m_matchedTracksModel->includeLabelsFrom( provider );
}
void
MatchedTracksPage::excludeLabelsFrom()
{
QAction *action = qobject_cast<QAction *>( sender() );
if( !action )
{
warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
return;
}
// provider may be null, it means "reset all labels to undecided"
ProviderPtr provider = action->data().value<ProviderPtr>();
m_matchedTracksModel->excludeLabelsFrom( provider );
}
void
MatchedTracksPage::expand( int onlyWithTupleFlags )
{
if( onlyWithTupleFlags < 0 )
{
QAction *action = qobject_cast<QAction *>( sender() );
if( action )
onlyWithTupleFlags = action->data().toInt();
else
onlyWithTupleFlags = 0;
}
for( int i = 0; i < m_matchedProxyModel->rowCount(); i++ )
{
QModelIndex idx = m_matchedProxyModel->index( i, 0 );
if( matchedTreeView->isExpanded( idx ) )
continue;
int flags = idx.data( MatchedTracksModel::TupleFlagsRole ).toInt();
if( ( flags & onlyWithTupleFlags ) == onlyWithTupleFlags )
matchedTreeView->expand( idx );
}
}
void
MatchedTracksPage::collapse()
{
int excludingFlags;
QAction *action = qobject_cast<QAction *>( sender() );
if( action )
excludingFlags = action->data().toInt();
else
excludingFlags = 0;
for( int i = 0; i < m_matchedProxyModel->rowCount(); i++ )
{
QModelIndex idx = m_matchedProxyModel->index( i, 0 );
if( !matchedTreeView->isExpanded( idx ) )
continue;
int flags = idx.data( MatchedTracksModel::TupleFlagsRole ).toInt();
if( ( flags & excludingFlags ) == 0 )
matchedTreeView->collapse( idx );
}
}
void
MatchedTracksPage::openConfiguration()
{
App *app = App::instance();
if( app )
app->slotConfigAmarok( "MetadataConfig" );
}
void
MatchedTracksPage::setHeaderSizePoliciesFromModel( QHeaderView *header, QAbstractItemModel *model )
{
for( int column = 0; column < model->columnCount(); column++ )
{
QVariant headerData = model->headerData( column, Qt::Horizontal,
CommonModel::ResizeModeRole );
QHeaderView::ResizeMode mode = QHeaderView::ResizeMode( headerData.toInt() );
header->setResizeMode( column, mode );
}
}
diff --git a/src/statusbar/CompoundProgressBar.cpp b/src/statusbar/CompoundProgressBar.cpp
index 2e681fd50b..5de49e92a5 100644
--- a/src/statusbar/CompoundProgressBar.cpp
+++ b/src/statusbar/CompoundProgressBar.cpp
@@ -1,272 +1,272 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CompoundProgressBar.h"
#include "core/support/Debug.h"
#include <QIcon>
#include <KLocale>
#include <QLayout>
#include <QMutexLocker>
CompoundProgressBar::CompoundProgressBar( QWidget *parent )
: ProgressBar( parent )
, m_mutex( QMutex::Recursive )
{
m_progressDetailsWidget = new PopupWidget( parent );
m_progressDetailsWidget->hide();
- connect( cancelButton(), SIGNAL(clicked()), this, SLOT(cancelAll()) );
+ connect( cancelButton(), &QAbstractButton::clicked, this, &CompoundProgressBar::cancelAll );
}
CompoundProgressBar::~CompoundProgressBar()
{
delete m_progressDetailsWidget;
m_progressDetailsWidget = 0;
}
void CompoundProgressBar::addProgressBar( ProgressBar *childBar, QObject *owner )
{
QMutexLocker locker( &m_mutex );
m_progressMap.insert( owner, childBar );
m_progressDetailsWidget->layout()->addWidget( childBar );
if( m_progressDetailsWidget->width() < childBar->width() )
m_progressDetailsWidget->setMinimumWidth( childBar->width() );
m_progressDetailsWidget->setMinimumHeight( childBar->height() * m_progressMap.count() + 8 );
m_progressDetailsWidget->reposition();
- connect( childBar, SIGNAL(percentageChanged(int)),
- SLOT(childPercentageChanged()) );
- connect( childBar, SIGNAL(cancelled(ProgressBar*)),
- SLOT(childBarCancelled(ProgressBar*)) );
- connect( childBar, SIGNAL(complete(ProgressBar*)),
- SLOT(childBarComplete(ProgressBar*)) );
- connect( owner, SIGNAL(destroyed(QObject*)), SLOT(slotObjectDestroyed(QObject*)) );
+ connect( childBar, &ProgressBar::percentageChanged,
+ this, &CompoundProgressBar::childPercentageChanged );
+ connect( childBar, &ProgressBar::cancelled,
+ this, &CompoundProgressBar::childBarCancelled );
+ connect( childBar, &ProgressBar::complete,
+ this, &CompoundProgressBar::childBarComplete );
+ connect( owner, &QObject::destroyed, this, &CompoundProgressBar::slotObjectDestroyed );
if( m_progressMap.count() == 1 )
{
setDescription( childBar->descriptionLabel()->text() );
cancelButton()->setToolTip( i18n( "Abort" ) );
}
else
{
setDescription( i18n( "Multiple background tasks running (click to show)" ) );
cancelButton()->setToolTip( i18n( "Abort all background tasks" ) );
}
cancelButton()->setHidden( false );
}
void CompoundProgressBar::endProgressOperation( QObject *owner )
{
QMutexLocker locker( &m_mutex );
if( !m_progressMap.contains( owner ) )
return ;
childBarComplete( m_progressMap.value( owner ) );
}
void
CompoundProgressBar::slotIncrementProgress()
{
incrementProgress( sender() );
}
void CompoundProgressBar::incrementProgress( const QObject *owner )
{
QMutexLocker locker( &m_mutex );
if( !m_progressMap.contains( owner ) )
return ;
m_progressMap.value( owner )->setValue( m_progressMap.value( owner )->value() + 1 );
}
void CompoundProgressBar::setProgress( const QObject *owner, int steps )
{
QMutexLocker locker( &m_mutex );
if( !m_progressMap.contains( owner ) )
return ;
m_progressMap.value( owner )->setValue( steps );
}
void
CompoundProgressBar::mousePressEvent( QMouseEvent *event )
{
QMutexLocker locker( &m_mutex );
if( m_progressDetailsWidget->isHidden() )
{
if( m_progressMap.count() )
showDetails();
}
else
{
hideDetails();
}
event->accept();
}
void CompoundProgressBar::setProgressTotalSteps( const QObject *owner, int value )
{
QMutexLocker locker( &m_mutex );
if( !m_progressMap.contains( owner ) )
return ;
m_progressMap.value( owner )->setMaximum( value );
}
void CompoundProgressBar::setParent( QWidget *parent )
{
QMutexLocker locker( &m_mutex );
delete m_progressDetailsWidget;
m_progressDetailsWidget = new PopupWidget( parent );
m_progressDetailsWidget->hide();
ProgressBar::setParent( parent );
}
void CompoundProgressBar::setProgressStatus( const QObject *owner, const QString &text )
{
QMutexLocker locker( &m_mutex );
if( !m_progressMap.contains( owner ) )
return ;
m_progressMap.value( owner )->setDescription( text );
}
void CompoundProgressBar::childPercentageChanged()
{
progressBar()->setValue( calcCompoundPercentage() );
}
void CompoundProgressBar::childBarCancelled( ProgressBar *childBar )
{
childBarFinished( childBar );
}
void CompoundProgressBar::childBarComplete( ProgressBar *childBar )
{
childBarFinished( childBar );
}
void CompoundProgressBar::slotObjectDestroyed( QObject *object )
{
QMutexLocker locker( &m_mutex );
if( m_progressMap.contains( object ) )
{
childBarFinished( m_progressMap.value( object ) );
}
}
void CompoundProgressBar::childBarFinished( ProgressBar *bar )
{
QMutexLocker locker( &m_mutex );
QObject *owner = const_cast<QObject *>( m_progressMap.key( bar ) );
owner->disconnect( this );
owner->disconnect( bar );
m_progressMap.remove( owner );
m_progressDetailsWidget->layout()->removeWidget( bar );
m_progressDetailsWidget->setFixedHeight( bar->height() * m_progressMap.count() + 8 );
m_progressDetailsWidget->reposition();
delete bar;
if( m_progressMap.count() == 1 )
{
//only one job still running, so no need to use the details widget any more.
//Also set the text to the description of
//the job instead of the "Multiple background tasks running" text.
setDescription( m_progressMap.values().at( 0 )->descriptionLabel()->text() );
cancelButton()->setToolTip( i18n( "Abort" ) );
hideDetails();
}
else if( m_progressMap.empty() )
{
progressBar()->setValue( 0 );
hideDetails();
emit( allDone() );
return;
}
else
{
setDescription( i18n( "Multiple background tasks running (click to show)" ) );
cancelButton()->setToolTip( i18n( "Abort all background tasks" ) );
}
progressBar()->setValue( calcCompoundPercentage() );
}
int CompoundProgressBar::calcCompoundPercentage()
{
QMutexLocker locker( &m_mutex );
int count = m_progressMap.count();
int total = 0;
foreach( ProgressBar *currentBar, m_progressMap )
total += currentBar->percentage();
return count == 0 ? 0 : total / count;
}
void CompoundProgressBar::cancelAll()
{
QMutexLocker locker( &m_mutex );
foreach( ProgressBar *currentBar, m_progressMap )
currentBar->cancel();
}
void CompoundProgressBar::showDetails()
{
QMutexLocker locker( &m_mutex );
m_progressDetailsWidget->raise();
//Hack to make sure it has the right height first time it is shown...
m_progressDetailsWidget->setFixedHeight(
m_progressMap.values().at( 0 )->height() * m_progressMap.count() + 8 );
m_progressDetailsWidget->reposition();
m_progressDetailsWidget->show();
}
void CompoundProgressBar::hideDetails()
{
m_progressDetailsWidget->hide();
}
void CompoundProgressBar::toggleDetails()
{
if( m_progressDetailsWidget->isVisible() )
hideDetails();
else
showDetails();
}
diff --git a/src/statusbar/KJobProgressBar.cpp b/src/statusbar/KJobProgressBar.cpp
index ea887a9b04..0b55e6e8b0 100644
--- a/src/statusbar/KJobProgressBar.cpp
+++ b/src/statusbar/KJobProgressBar.cpp
@@ -1,46 +1,46 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "KJobProgressBar.h"
KJobProgressBar::KJobProgressBar( QWidget *parent, KJob * job )
: ProgressBar( parent )
{
- connect( job, SIGNAL(percent(KJob*,ulong)), SLOT(updateJobStatus(KJob*,ulong)) );
- connect( job, SIGNAL(result(KJob*)), SLOT(delayedDone()) );
- connect( job, SIGNAL(infoMessage(KJob*,QString,QString)), SLOT(infoMessage(KJob*,QString,QString)) );
+ connect( job, SIGNAL(percent(KJob*, ulong)), this, SLOT(updateJobStatus(KJob*, ulong)) );
+ connect( job, &KJob::result, this, &KJobProgressBar::delayedDone );
+ connect( job, &KJob::infoMessage, this, &KJobProgressBar::infoMessage );
}
KJobProgressBar::~KJobProgressBar()
{
}
void KJobProgressBar::updateJobStatus( KJob * job, unsigned long value )
{
Q_UNUSED( job );
setValue( value );
emit( percentageChanged( percentage() ) );
}
void KJobProgressBar::infoMessage( KJob* job, QString plain, QString rich )
{
Q_UNUSED( job );
Q_UNUSED( rich );
setDescription( plain );
}
diff --git a/src/statusbar/LongMessageWidget.cpp b/src/statusbar/LongMessageWidget.cpp
index 5b65a6195b..06094220b7 100644
--- a/src/statusbar/LongMessageWidget.cpp
+++ b/src/statusbar/LongMessageWidget.cpp
@@ -1,152 +1,152 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2005 Max Howell <max.howell@methylblue.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "LongMessageWidget.h"
#include "core/support/Debug.h"
#include <KPushButton>
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include <QToolTip>
LongMessageWidget::LongMessageWidget( QWidget *anchor, const QString &message,
Amarok::Logger::MessageType type )
: PopupWidget( anchor )
, m_counter( 0 )
, m_timeout( 6000 )
{
DEBUG_BLOCK
Q_UNUSED( type )
setFrameStyle( QFrame::StyledPanel | QFrame::Raised );
setContentsMargins( 4, 4, 4, 4 );
setMinimumWidth( 26 );
setMinimumHeight( 26 );
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
QPalette p = QToolTip::palette();
setPalette( p );
KHBox *hbox = new KHBox( this );
layout()->addWidget( hbox );
hbox->setSpacing( 12 );
m_countdownFrame = new CountdownFrame( hbox );
m_countdownFrame->setObjectName( "counterVisual" );
m_countdownFrame->setFixedWidth( fontMetrics().width( "X" ) );
m_countdownFrame->setFrameStyle( QFrame::Plain | QFrame::Box );
QPalette pal;
pal.setColor( m_countdownFrame->foregroundRole(), p.dark().color() );
m_countdownFrame->setPalette( pal );
QLabel *alabel = new QLabel( message, hbox );
alabel->setWordWrap( true );
alabel->setOpenExternalLinks( true );
alabel->setObjectName( "label" );
alabel->setTextFormat( Qt::RichText );
alabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
alabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
alabel->setPalette( p );
hbox = new KHBox( this );
layout()->addWidget( hbox );
KPushButton *button = new KPushButton( KStandardGuiItem::close(), hbox );
button->setObjectName( "closeButton" );
- connect( button, SIGNAL(clicked()), SLOT(close()) );
+ connect( button, &QAbstractButton::clicked, this, &LongMessageWidget::close );
reposition();
show();
m_timerId = startTimer( m_timeout / m_countdownFrame->height() );
}
LongMessageWidget::~LongMessageWidget()
{}
void LongMessageWidget::close()
{
hide();
emit( closed() );
}
void LongMessageWidget::timerEvent( QTimerEvent* )
{
if( !m_timeout )
{
killTimer( m_timerId );
return;
}
CountdownFrame *&h = m_countdownFrame;
if( m_counter < h->height() - 3 )
{
h->setFilledRatio(( float ) m_counter / ( float ) h->height() );
h->repaint();
}
if( !testAttribute( Qt::WA_UnderMouse ) )
m_counter++;
if( m_counter > h->height() )
{
killTimer( m_timerId );
h->setFilledRatio( 1 );
h->repaint();
close();
}
else
{
killTimer( m_timerId );
m_timerId = startTimer( m_timeout / h->height() );
}
}
//////////////////////////////////////////////////////////////////////////////
// class CountdownFrame
//////////////////////////////////////////////////////////////////////////////
CountdownFrame::CountdownFrame( QWidget *parent )
: QFrame( parent )
, m_filled( 0.0 )
{}
void CountdownFrame::setFilledRatio( float filled )
{
m_filled = filled;
}
void CountdownFrame::paintEvent( QPaintEvent *e )
{
QFrame::paintEvent( e );
QPalette p = palette();
p.setCurrentColorGroup( QPalette::Active );
QPainter( this ).fillRect( 2, m_filled * height() , width() - 4, height()
- ( m_filled * height() ) , p.highlight()
);
}
diff --git a/src/statusbar/NetworkProgressBar.cpp b/src/statusbar/NetworkProgressBar.cpp
index 9c445e97e9..3ba03c671a 100644
--- a/src/statusbar/NetworkProgressBar.cpp
+++ b/src/statusbar/NetworkProgressBar.cpp
@@ -1,61 +1,62 @@
/****************************************************************************************
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "NetworkProgressBar.h"
NetworkProgressBar::NetworkProgressBar( QWidget *parent, QNetworkReply *reply )
: ProgressBar( parent )
{
- connect( reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(infoMessage(QNetworkReply::NetworkError)) );
- connect( reply, SIGNAL(finished()), SLOT(delayedDone()) );
- connect( reply, SIGNAL(destroyed()), SLOT(delayedDone()) );
+ connect( reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
+ this, &NetworkProgressBar::infoMessage );
+ connect( reply, &QNetworkReply::finished, this, &NetworkProgressBar::delayedDone );
+ connect( reply, &QNetworkReply::destroyed, this, &NetworkProgressBar::delayedDone );
switch( reply->operation() )
{
case QNetworkAccessManager::HeadOperation:
case QNetworkAccessManager::GetOperation:
- connect( reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(progressChanged(qint64,qint64)) );
+ connect( reply, &QNetworkReply::downloadProgress, this, &NetworkProgressBar::progressChanged );
break;
case QNetworkAccessManager::PutOperation:
case QNetworkAccessManager::PostOperation:
- connect( reply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(progressChanged(qint64,qint64)) );
+ connect( reply, &QNetworkReply::uploadProgress, this, &NetworkProgressBar::progressChanged );
break;
default:
break;
}
}
NetworkProgressBar::~NetworkProgressBar()
{
}
void NetworkProgressBar::progressChanged( qint64 bytesChanged, qint64 bytesTotal )
{
qreal percent = qreal(bytesChanged) / bytesTotal;
setValue( int(percent) * 100 );
}
void NetworkProgressBar::infoMessage( QNetworkReply::NetworkError code )
{
if( code != QNetworkReply::NoError )
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>( sender() );
setDescription( reply->errorString() );
}
}
diff --git a/src/statusbar/ProgressBar.cpp b/src/statusbar/ProgressBar.cpp
index ae2b57e302..852650442f 100644
--- a/src/statusbar/ProgressBar.cpp
+++ b/src/statusbar/ProgressBar.cpp
@@ -1,125 +1,124 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "statusbar/ProgressBar.h"
#include "core/support/Debug.h"
#include "MainWindow.h"
#include <QTimer>
#include <QIcon>
#include <KLocale>
ProgressBar::ProgressBar( QWidget *parent )
: QFrame( parent )
{
setFixedHeight( 30 );
setContentsMargins( 0, 0, 0, 4 );
QVBoxLayout *box = new QVBoxLayout;
box->setMargin( 0 );
box->setSpacing( 3 );
QHBoxLayout *descriptionLayout = new QHBoxLayout;
descriptionLayout->setMargin( 0 );
descriptionLayout->setSpacing( 2 );
m_descriptionLabel = new QLabel;
m_descriptionLabel->setWordWrap( true );
//add with stretchfactor 1 so it takes up more space then the cancel button
descriptionLayout->addWidget( m_descriptionLabel, 1 );
m_cancelButton = new QToolButton;
m_cancelButton->setIcon( QIcon::fromTheme( "dialog-cancel-amarok" ) );
m_cancelButton->setToolTip( i18n( "Abort" ) );
m_cancelButton->setHidden( true );
m_cancelButton->setFixedWidth( 16 );
m_cancelButton->setFixedHeight( 16 );
m_cancelButton->setAutoFillBackground( false );
m_cancelButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
descriptionLayout->addWidget( m_cancelButton );
descriptionLayout->setAlignment( m_cancelButton, Qt::AlignRight );
box->addLayout( descriptionLayout );
m_progressBar = new QProgressBar;
m_progressBar->setMinimum( 0 );
m_progressBar->setMaximum( 100 );
m_progressBar->setFixedHeight( 5 );
m_progressBar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
m_progressBar->setTextVisible( false );
box->addWidget( m_progressBar );
box->setAlignment( m_progressBar, Qt::AlignBottom );
setLayout( box );
}
ProgressBar::~ProgressBar()
{
}
void
ProgressBar::setDescription( const QString &description )
{
m_descriptionLabel->setText( description );
-
}
ProgressBar *
ProgressBar::setAbortSlot( QObject *receiver, const char *slot, Qt::ConnectionType type )
{
cancelButton()->setHidden( false );
if( receiver )
connect( this, SIGNAL(cancelled()), receiver, slot, type );
- connect( cancelButton(), SIGNAL(clicked()), this, SLOT(cancel()) );
+ connect( cancelButton(), &QAbstractButton::clicked, this, &ProgressBar::cancel );
return this;
}
void ProgressBar::cancel()
{
DEBUG_BLOCK
debug() << "cancelling operation: " << m_descriptionLabel->text();
- emit( cancelled() );
+// emit( cancelled() );
emit( cancelled( this ) );
}
void ProgressBar::setValue( int percentage )
{
progressBar()->setValue( percentage );
emit( percentageChanged( percentage ) );
//this safety check has to be removed as KJobs sometimes start out
//by showing 100%, thus removing the progress info before it even gets started
/*if ( percentage == m_progressBar->maximum() )
QTimer::singleShot( POST_COMPLETION_DELAY, this, SLOT(delayedDone()) );*/
}
void ProgressBar::delayedDone()
{
emit( complete( this ) );
}
int ProgressBar::percentage()
{
if( m_progressBar->maximum() == 100 )
return m_progressBar->value();
return (int)( ( (float) m_progressBar->value() / (float)m_progressBar->maximum() ) * 100.0 );
}
diff --git a/src/statusbar/ProgressBar.h b/src/statusbar/ProgressBar.h
index f0d3d67e66..6cf938c015 100644
--- a/src/statusbar/ProgressBar.h
+++ b/src/statusbar/ProgressBar.h
@@ -1,74 +1,86 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef PROGRESSBAR_H
#define PROGRESSBAR_H
#include "amarok_export.h"
#include <KHBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QToolButton>
#define POST_COMPLETION_DELAY 2000
/**
* A widget that encapsulates a progress bar, a description string and a cancel button.
*/
class AMAROK_EXPORT ProgressBar : public QFrame
{
Q_OBJECT
public:
ProgressBar( QWidget *parent );
~ProgressBar();
void setDescription( const QString &description );
+
ProgressBar *setAbortSlot( QObject *receiver, const char *slot,
- Qt::ConnectionType type = Qt::AutoConnection );
+ Qt::ConnectionType type = Qt::AutoConnection );
+ template<typename Func>
+ ProgressBar *setAbortSlot( const typename QtPrivate::FunctionPointer<Func>::Object *receiver, Func slot,
+ Qt::ConnectionType type = Qt::AutoConnection )
+ {
+ cancelButton()->setHidden( false );
+ if( receiver )
+ connect( this, &ProgressBar::cancelled, receiver, slot, type );
+ connect( cancelButton(), &QAbstractButton::clicked, this, &ProgressBar::cancel );
+
+ return this;
+ }
QToolButton *cancelButton() { return m_cancelButton; }
QProgressBar *progressBar() { return m_progressBar; }
QLabel *descriptionLabel() { return m_descriptionLabel; }
int maximum() { return m_progressBar->maximum(); }
void setMaximum( int max ) { m_progressBar->setMaximum( max ); }
int value() { return m_progressBar->value(); }
void setValue( int value );
int percentage();
public Q_SLOTS:
void cancel();
void delayedDone();
void slotTotalSteps( int steps ) { m_progressBar->setMaximum( steps ); }
Q_SIGNALS:
void cancelled( ProgressBar * );
- void cancelled();
+// void cancelled();
void complete( ProgressBar * );
void percentageChanged( int );
private:
QToolButton *m_cancelButton;
QProgressBar *m_progressBar;
QLabel *m_descriptionLabel;
};
#endif
diff --git a/src/synchronization/SynchronizationBaseJob.cpp b/src/synchronization/SynchronizationBaseJob.cpp
index caefa2bca8..f032cb1b15 100644
--- a/src/synchronization/SynchronizationBaseJob.cpp
+++ b/src/synchronization/SynchronizationBaseJob.cpp
@@ -1,518 +1,518 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SynchronizationBaseJob.h"
#include "core/collections/Collection.h"
#include "core/collections/QueryMaker.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaConstants.h"
#include "core/support/Debug.h"
#include <QMetaEnum>
#include <QMetaObject>
#include <QHashIterator>
//this class might cause SqlQueryMaker to generate very large SQL statements
//see http://dev.mysql.com/doc/refman/5.0/en/packet-too-large.html
//if it turns out that the generated SQL query is too large
SynchronizationBaseJob::SynchronizationBaseJob()
: QObject()
, m_state( NotStarted )
, m_currentResultCount( 0 )
, m_collectionA( 0 )
, m_collectionB( 0 )
{
- connect( &m_timer, SIGNAL(timeout()), this, SLOT(timeout()) );
+ connect( &m_timer, &QTimer::timeout, this, &SynchronizationBaseJob::timeout );
//abort syncing if both queries have not returned within 30 seconds for a given state
//probably needs to be adjusted during testing
m_timer.setInterval( 30000 );
m_timer.setSingleShot( true );
}
SynchronizationBaseJob::~SynchronizationBaseJob()
{
//nothing to do
}
void
SynchronizationBaseJob::setCollectionA( Collections::Collection *collection )
{
m_collectionA = collection;
}
void
SynchronizationBaseJob::setCollectionB( Collections::Collection *collection )
{
m_collectionB = collection;
}
void
SynchronizationBaseJob::setFilter( const QString &filter )
{
Q_UNUSED( filter )
}
Collections::QueryMaker*
SynchronizationBaseJob::createQueryMaker( Collections::Collection *collection )
{
//TODO: apply filters. This allows us to only sync a subset of a collection
Collections::QueryMaker *qm = collection->queryMaker();
qm->setAutoDelete( true );
m_queryMakers.insert( qm, collection );
return qm;
}
void
SynchronizationBaseJob::synchronize()
{
DEBUG_BLOCK
if( !m_collectionA || !m_collectionB )
{
debug() << "aborting synchronization, at least one collecton is missing";
deleteLater();
return;
}
m_state = ComparingArtists;
setupArtistQuery( m_collectionA )->run();
setupArtistQuery( m_collectionB )->run();
m_timer.start();
}
void
SynchronizationBaseJob::timeout()
{
const QMetaObject *mo = metaObject();
QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "State" ) );
debug() << "syncing aborted due to a timeout in state " << me.valueToKey( m_state );
deleteLater();
}
void
SynchronizationBaseJob::slotQueryDone()
{
DEBUG_BLOCK;
m_currentResultCount += 1;
if( m_currentResultCount < 2 )
return;
m_currentResultCount = 0;
m_timer.stop();
switch( m_state )
{
case ComparingArtists:
{
m_state = ComparingAlbums;
handleArtistResult();
break;
}
case ComparingAlbums:
{
m_state = ComparingTracks;
handleAlbumResult();
break;
}
case ComparingTracks:
{
m_state = Syncing;
handleTrackResult();
break;
}
default:
{
const QMetaObject *mo = metaObject();
QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "State" ) );
debug() << "detected state " << me.valueToKey( m_state ) << " in slotQueryDone(), do not know how to handle this. Aborting";
deleteLater();
break;
}
}
}
Collections::QueryMaker*
SynchronizationBaseJob::setupArtistQuery( Collections::Collection *coll )
{
Collections::QueryMaker *qm = createQueryMaker( coll );
qm->setQueryType( Collections::QueryMaker::Artist );
- connect( qm, SIGNAL(queryDone()), this, SLOT(slotQueryDone()), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::ArtistList)), this, SLOT(slotResultReady(Meta::ArtistList)), Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotQueryDone, Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::newArtistsReady, this, &SynchronizationBaseJob::slotArtistsReady, Qt::QueuedConnection );
return qm;
}
Collections::QueryMaker*
SynchronizationBaseJob::setupAlbumQuery( Collections::Collection *coll )
{
Collections::QueryMaker *qm = createQueryMaker( coll );
qm->setQueryType( Collections::QueryMaker::Album );
- connect( qm, SIGNAL(queryDone()), this, SLOT(slotQueryDone()), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::AlbumList)), this, SLOT(slotResultReady(Meta::AlbumList)), Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotQueryDone, Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &SynchronizationBaseJob::slotAlbumsReady, Qt::QueuedConnection );
return qm;
}
Collections::QueryMaker*
SynchronizationBaseJob::setupTrackQuery( Collections::Collection *coll )
{
Collections::QueryMaker *qm = createQueryMaker( coll );
qm->setQueryType( Collections::QueryMaker::Track );
- connect( qm, SIGNAL(queryDone()), this, SLOT(slotQueryDone()), Qt::QueuedConnection );
- connect( qm, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(slotResultReady(Meta::TrackList)), Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotQueryDone, Qt::QueuedConnection );
+ connect( qm, &Collections::QueryMaker::newTracksReady, this, &SynchronizationBaseJob::slotTracksReady, Qt::QueuedConnection );
return qm;
}
void
-SynchronizationBaseJob::slotResultReady( const Meta::ArtistList &artists )
+SynchronizationBaseJob::slotArtistsReady( const Meta::ArtistList &artists )
{
DEBUG_BLOCK;
Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) );
QSet<QString> artistSet;
foreach( const Meta::ArtistPtr &artist, artists )
{
if( artist )
artistSet.insert( artist->name() );
}
if( senderColl == m_collectionA )
{
m_artistsA.unite( artistSet );
}
else if( senderColl == m_collectionB )
{
m_artistsB.unite( artistSet );
}
else
{
//huh? how did we get here?
}
}
void
-SynchronizationBaseJob::slotResultReady( const Meta::AlbumList &albums )
+SynchronizationBaseJob::slotAlbumsReady( const Meta::AlbumList &albums )
{
DEBUG_BLOCK
Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) );
QSet<Meta::AlbumKey> albumSet;
foreach( const Meta::AlbumPtr &albumPtr, albums )
{
albumSet.insert( Meta::AlbumKey( albumPtr ) );
}
if( senderColl == m_collectionA )
{
m_albumsA.unite( albumSet );
}
else if( senderColl == m_collectionB )
{
m_albumsB.unite( albumSet );
}
else
{
//huh? how did we get here?
}
}
void
-SynchronizationBaseJob::slotResultReady( const Meta::TrackList &tracks )
+SynchronizationBaseJob::slotTracksReady( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) );
if( senderColl == m_collectionA )
{
foreach( const Meta::TrackPtr &track, tracks )
{
Meta::TrackKey key( track );
m_tracksA.insert( key );
m_keyToTrackA.insert( key, track );
}
}
else if( senderColl == m_collectionB )
{
foreach( const Meta::TrackPtr &track, tracks )
{
Meta::TrackKey key( track );
m_tracksB.insert( key );
m_keyToTrackB.insert( key, track );
}
}
else
{
//huh? how did we get here?
}
}
void
SynchronizationBaseJob::handleArtistResult()
{
DEBUG_BLOCK
QSet<QString> artistsOnlyInA = m_artistsA - m_artistsB;
QSet<QString> artistsOnlyInB = m_artistsB - m_artistsA;
QSet<QString> artistsInBoth = m_artistsA & m_artistsB;
foreach( const QString &artist, artistsOnlyInA )
{
m_artistResult.insert( artist, OnlyInA );
}
foreach( const QString &artist, artistsOnlyInB )
{
m_artistResult.insert( artist, OnlyInB );
}
foreach( const QString &artist, artistsInBoth )
{
m_artistResult.insert( artist, InBoth );
}
Collections::QueryMaker *qmA = setupAlbumQuery( m_collectionA );
Collections::QueryMaker *qmB = setupAlbumQuery( m_collectionB );
//we are going to exclude artists below, so make sure we exclude all of them by setting the QMs to And mode
qmA->beginAnd();
qmB->beginAnd();
QHashIterator<QString, InSet> iter( m_artistResult );
while( iter.hasNext() )
{
iter.next();
QString artist = iter.key();
InSet currentStatus = iter.value();
if( currentStatus == OnlyInA )
{
qmA->excludeFilter( Meta::valArtist, artist, true, true );
}
if( currentStatus == OnlyInB )
{
qmB->excludeFilter( Meta::valArtist, artist, true, true );
}
}
qmA->endAndOr();
qmB->endAndOr();
m_timer.start();
qmA->run();
qmB->run();
}
void
SynchronizationBaseJob::handleAlbumResult()
{
DEBUG_BLOCK
QSet<Meta::AlbumKey> albumsOnlyInA = m_albumsA - m_albumsB;
QSet<Meta::AlbumKey> albumsOnlyInB = m_albumsB - m_albumsA;
QSet<Meta::AlbumKey> albumsInBoth = m_albumsA & m_albumsB;
foreach( const Meta::AlbumKey &album, albumsOnlyInA )
{
m_albumResult.insert( album, OnlyInA );
}
foreach( const Meta::AlbumKey &album, albumsOnlyInB )
{
m_albumResult.insert( album, OnlyInB );
}
foreach( const Meta::AlbumKey &album, albumsInBoth )
{
m_albumResult.insert( album, InBoth );
}
Collections::QueryMaker *qmA = setupTrackQuery( m_collectionA );
Collections::QueryMaker *qmB = setupTrackQuery( m_collectionB );
qmA->beginAnd();
qmB->beginAnd();
{
QHashIterator<QString, InSet> iter( m_artistResult );
while( iter.hasNext() )
{
iter.next();
QString artist = iter.key();
InSet currentStatus = iter.value();
if( currentStatus == OnlyInA )
{
qmA->excludeFilter( Meta::valArtist, artist, true, true );
}
if( currentStatus == OnlyInB )
{
qmB->excludeFilter( Meta::valArtist, artist, true, true );
}
}
}
{
QHashIterator<Meta::AlbumKey, InSet> iter( m_albumResult );
while( iter.hasNext() )
{
iter.next();
Meta::AlbumKey album = iter.key();
InSet currentStatus = iter.value();
if( currentStatus == OnlyInA )
{
qmA->beginOr();
qmA->excludeFilter( Meta::valAlbum, album.albumName(), true, true );
qmA->excludeFilter( Meta::valAlbumArtist, album.artistName(), true, true );
qmA->endAndOr();
}
if( currentStatus == OnlyInB )
{
qmB->beginOr();
qmB->excludeFilter( Meta::valAlbum, album.albumName(), true, true );
qmB->excludeFilter( Meta::valAlbumArtist, album.artistName(), true, true );
qmB->endAndOr();
}
}
}
qmA->endAndOr();
qmB->endAndOr();
m_timer.start();
qmA->run();
qmB->run();
}
void
SynchronizationBaseJob::handleTrackResult()
{
DEBUG_BLOCK
QSet<Meta::TrackKey> tracksOnlyInA = m_tracksA - m_tracksB;
QSet<Meta::TrackKey> tracksOnlyInB = m_tracksB - m_tracksA;
foreach( const Meta::TrackKey &key, tracksOnlyInA )
{
m_trackResultOnlyInA << m_keyToTrackA.value( key );
}
foreach( const Meta::TrackKey &key, tracksOnlyInB )
{
m_trackResultOnlyInB << m_keyToTrackB.value( key );
}
//we have to make sure that we do not start queries that will return *all* tracks of the collection
//because we did not add any filter to it
bool haveToStartQueryA = false;
bool haveToStartQueryB = false;
//we do not care about tracks in both collections
Collections::QueryMaker *qmA = createQueryMaker( m_collectionA );
Collections::QueryMaker *qmB = createQueryMaker( m_collectionB );
qmA->setQueryType( Collections::QueryMaker::Track );
qmB->setQueryType( Collections::QueryMaker::Track );
qmA->beginOr();
qmB->beginOr();
{
QHashIterator<QString, InSet> iter( m_artistResult );
while( iter.hasNext() )
{
iter.next();
QString artist = iter.key();
InSet currentStatus = iter.value();
if( currentStatus == OnlyInA )
{
qmA->addFilter( Meta::valArtist, artist, true, true );
haveToStartQueryA = true;
}
if( currentStatus == OnlyInB )
{
qmB->addFilter( Meta::valArtist, artist, true, true );
haveToStartQueryB = true;
}
}
}
{
QHashIterator<Meta::AlbumKey, InSet> iter( m_albumResult );
while( iter.hasNext() )
{
iter.next();
Meta::AlbumKey album = iter.key();
InSet currentStatus = iter.value();
if( currentStatus == OnlyInA )
{
qmA->beginAnd();
qmA->addFilter( Meta::valAlbum, album.albumName(), true, true );
qmA->addFilter( Meta::valAlbumArtist, album.artistName(), true, true );
qmA->endAndOr();
haveToStartQueryA = true;
}
if( currentStatus == OnlyInB )
{
qmB->beginAnd();
qmB->addFilter( Meta::valAlbum, album.albumName(), true, true );
qmB->addFilter( Meta::valAlbumArtist, album.artistName(), true, true );
qmB->endAndOr();
haveToStartQueryB = true;
}
}
}
qmA->endAndOr();
qmB->endAndOr();
- connect( qmA, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(slotSyncTracks(Meta::TrackList)) );
- connect( qmB, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(slotSyncTracks(Meta::TrackList)) );
- connect( qmA, SIGNAL(queryDone()), this, SLOT(slotSyncQueryDone()) );
- connect( qmB, SIGNAL(queryDone()), this, SLOT(slotSyncQueryDone()) );
+ connect( qmA, &Collections::QueryMaker::newTracksReady, this, &SynchronizationBaseJob::slotSyncTracks );
+ connect( qmB, &Collections::QueryMaker::newTracksReady, this, &SynchronizationBaseJob::slotSyncTracks );
+ connect( qmA, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotSyncQueryDone );
+ connect( qmB, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotSyncQueryDone );
m_timer.start();
if( haveToStartQueryA )
{
qmA->run();
}
else
{
delete qmA;
m_currentResultCount += 1;
}
if( haveToStartQueryB )
{
qmB->run();
}
else
{
delete qmB;
m_currentResultCount += 1;
}
if( !( haveToStartQueryA || haveToStartQueryB ) )
{
slotSyncQueryDone();
}
}
void
SynchronizationBaseJob::slotSyncTracks( const Meta::TrackList &tracks )
{
DEBUG_BLOCK
Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) );
if( senderColl == m_collectionA )
{
m_trackResultOnlyInA << tracks;
}
else if( senderColl == m_collectionB )
{
m_trackResultOnlyInB << tracks;
}
else
{
//huh?
debug() << "received data from unknown collection";
}
}
void
SynchronizationBaseJob::slotSyncQueryDone()
{
DEBUG_BLOCK;
m_currentResultCount += 1;
if( m_currentResultCount < 2 )
return;
m_currentResultCount = 0;
m_timer.stop();
if( m_state == Syncing )
{
doSynchronization( m_trackResultOnlyInA, OnlyInA, m_collectionA, m_collectionB );
doSynchronization( m_trackResultOnlyInB, OnlyInB, m_collectionA, m_collectionB );
deleteLater();
}
else
{
const QMetaObject *mo = metaObject();
QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "State" ) );
debug() << "detected state " << me.valueToKey( m_state ) << " in slotSyncQueryDone(), do not know how to handle this. Aborting";
deleteLater();
}
}
diff --git a/src/synchronization/SynchronizationBaseJob.h b/src/synchronization/SynchronizationBaseJob.h
index c0483b862d..99b1a0b1c8 100644
--- a/src/synchronization/SynchronizationBaseJob.h
+++ b/src/synchronization/SynchronizationBaseJob.h
@@ -1,122 +1,122 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef SYNCHRONIZATIONBASEJOB_H
#define SYNCHRONIZATIONBASEJOB_H
#include "core/meta/forward_declarations.h"
#include "core/meta/support/MetaKeys.h"
#include <QHash>
#include <QObject>
#include <QPair>
#include <QSet>
#include <QString>
#include <QTimer>
namespace Collections {
class Collection;
class QueryMaker;
}
class SynchronizationBaseJob : public QObject
{
Q_OBJECT
Q_ENUMS( State )
Q_ENUMS( InSet )
public:
enum State
{
NotStarted,
ComparingArtists,
ComparingAlbums,
ComparingTracks,
Syncing
};
enum InSet
{
OnlyInA,
OnlyInB,
InBoth
};
SynchronizationBaseJob();
~SynchronizationBaseJob();
void setFilter( const QString &filter );
public Q_SLOTS:
virtual void synchronize();
private Q_SLOTS:
- void slotResultReady( const Meta::TrackList &artists );
- void slotResultReady( const Meta::AlbumList &albums );
- void slotResultReady( const Meta::ArtistList &tracks );
+ void slotTracksReady( const Meta::TrackList &artists );
+ void slotAlbumsReady( const Meta::AlbumList &albums );
+ void slotArtistsReady( const Meta::ArtistList &tracks );
void slotQueryDone();
void slotSyncTracks( const Meta::TrackList &tracks );
void slotSyncQueryDone();
void timeout();
protected:
void setCollectionA( Collections::Collection *collection );
void setCollectionB( Collections::Collection *collection );
/**
* perform the actual synchronization in this method.
* SynchronizationBaseJob will delete itself afterwards.
*/
virtual void doSynchronization( const Meta::TrackList &tracks, InSet syncDirection, Collections::Collection *collA, Collections::Collection *collB ) = 0;
private:
Collections::QueryMaker* createQueryMaker( Collections::Collection *collection );
void handleAlbumResult();
void handleArtistResult();
void handleTrackResult();
private:
Collections::QueryMaker* setupArtistQuery( Collections::Collection *coll );
Collections::QueryMaker* setupAlbumQuery( Collections::Collection *coll );
Collections::QueryMaker* setupTrackQuery( Collections::Collection *coll );
State m_state;
int m_currentResultCount;
Collections::Collection *m_collectionA;
Collections::Collection *m_collectionB;
/** All the query makers we created and the collection they belong to. */
QHash<Collections::QueryMaker*, Collections::Collection*> m_queryMakers;
QSet<QString> m_artistsA;
QSet<QString> m_artistsB;
QSet<Meta::AlbumKey> m_albumsA;
QSet<Meta::AlbumKey> m_albumsB;
QSet<Meta::TrackKey> m_tracksA;
QSet<Meta::TrackKey> m_tracksB;
QHash<Meta::TrackKey, Meta::TrackPtr> m_keyToTrackA;
QHash<Meta::TrackKey, Meta::TrackPtr> m_keyToTrackB;
QHash<QString, InSet> m_artistResult;
QHash<Meta::AlbumKey, InSet> m_albumResult;
Meta::TrackList m_trackResultOnlyInA;
Meta::TrackList m_trackResultOnlyInB;
QTimer m_timer;
};
#endif
diff --git a/src/toolbar/CurrentTrackToolbar.cpp b/src/toolbar/CurrentTrackToolbar.cpp
index 330f421cba..b20ce76911 100644
--- a/src/toolbar/CurrentTrackToolbar.cpp
+++ b/src/toolbar/CurrentTrackToolbar.cpp
@@ -1,70 +1,70 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "CurrentTrackToolbar.h"
#include "EngineController.h"
#include "GlobalCurrentTrackActions.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/capabilities/BookmarkThisCapability.h"
#include "core/meta/Meta.h"
CurrentTrackToolbar::CurrentTrackToolbar( QWidget * parent )
: QToolBar( parent )
{
setToolButtonStyle( Qt::ToolButtonIconOnly );
setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
//setIconDimensions( 16 );
setContentsMargins( 0, 0, 0, 0 );
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
- this, SLOT(handleAddActions()) );
+ connect( engine, &EngineController::trackChanged,
+ this, &CurrentTrackToolbar::handleAddActions );
}
CurrentTrackToolbar::~CurrentTrackToolbar()
{}
void CurrentTrackToolbar::handleAddActions()
{
clear();
Meta::TrackPtr track = The::engineController()->currentTrack();
foreach( QAction* action, The::globalCurrentTrackActions()->actions() )
addAction( action );
if( track )
{
QScopedPointer< Capabilities::ActionsCapability > ac( track->create<Capabilities::ActionsCapability>() );
if( ac )
{
QList<QAction *> currentTrackActions = ac->actions();
foreach( QAction *action, currentTrackActions )
{
if( !action->parent() )
action->setParent( this );
addAction( action );
}
}
QScopedPointer< Capabilities::BookmarkThisCapability > btc( track->create<Capabilities::BookmarkThisCapability>() );
if( btc && btc->bookmarkAction() )
addAction( btc->bookmarkAction() );
}
}
diff --git a/src/toolbar/MainToolbar.cpp b/src/toolbar/MainToolbar.cpp
index ac49f464b9..4db6b55cef 100644
--- a/src/toolbar/MainToolbar.cpp
+++ b/src/toolbar/MainToolbar.cpp
@@ -1,1028 +1,1028 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de> *
* Copyright (c) 2010 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "MainToolbar"
#include "MainToolbar.h"
#include "App.h"
#include "ActionClasses.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "GlobalCurrentTrackActions.h"
#include "MainWindow.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "amarokurls/AmarokUrl.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "core/capabilities/ActionsCapability.h"
#include "core/capabilities/BookmarkThisCapability.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core-impl/capabilities/timecode/TimecodeLoadCapability.h"
#include "playlist/PlaylistActions.h"
#include "playlist/PlaylistModelStack.h"
#include "playlist/PlaylistController.h"
#include "widgets/AnimatedLabelStack.h"
#include "widgets/PlayPauseButton.h"
#include "widgets/SliderWidget.h"
#include "widgets/TrackActionButton.h"
#include "widgets/VolumeDial.h"
#include <QEvent>
#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QPaintEvent>
#include <QResizeEvent>
#include <QSlider>
#include <QTimer>
#include <QVBoxLayout>
// #define prev_next_role QPalette::Link
#define prev_next_role foregroundRole()
static const int prevOpacity = 128;
static const int nextOpacity = 160;
static const int icnSize = 48;
static const int leftRightSpacer = 15;
static const int timeLabelMargin = 6;
static const int skipPadding = 6;
static const int skipMargin = 20;
static const int constant_progress_ratio_minimum_width = 640;
static const int space_between_tracks_and_slider = 2;
static const float track_fontsize_factor = 1.1f;
static const int track_action_spacing = 6;
MainToolbar::MainToolbar( QWidget *parent )
: QToolBar( i18n( "Main Toolbar" ), parent )
, m_lastTime( -1 )
, m_trackBarAnimationTimer( 0 )
{
DEBUG_BLOCK
setObjectName( "MainToolbar" );
m_promoString = i18n( "Rediscover Your Music" );
// control padding between buttons and labels, it's style controlled by default
layout()->setSpacing( 0 );
EngineController *engine = The::engineController();
setIconSize( QSize( icnSize, icnSize ) );
QWidget *spacerWidget = new QWidget(this);
spacerWidget->setFixedWidth( leftRightSpacer );
addWidget( spacerWidget );
m_playPause = new PlayPauseButton;
m_playPause->setPlaying( engine->isPlaying() );
m_playPause->setFixedSize( icnSize, icnSize );
addWidget( m_playPause );
- connect( m_playPause, SIGNAL(toggled(bool)), engine, SLOT(playPause()) );
+ connect( m_playPause, &PlayPauseButton::toggled, engine, &EngineController::playPause );
QWidget *info = new QWidget(this);
QVBoxLayout *vl = new QVBoxLayout( info );
QFont fnt = QApplication::font(); // don't use the toolbar font. Often small to support icons only.
if( fnt.pointSize() > 0 )
fnt.setPointSize( qRound(fnt.pointSize() * track_fontsize_factor) );
const int fntH = QFontMetrics( QApplication::font() ).height();
m_skip_left = The::svgHandler()->renderSvg( "tiny_skip_left", 80*fntH/128, fntH, "tiny_skip_left" );
m_skip_right = The::svgHandler()->renderSvg( "tiny_skip_right", 80*fntH/128, fntH, "tiny_skip_right" );
m_prev.key = 0;
m_prev.label = new AnimatedLabelStack(QStringList(), info);
m_prev.label->setFont( fnt );
if( layoutDirection() == Qt::LeftToRight )
m_prev.label->setPadding( m_skip_right.width() + skipPadding + skipMargin, 0 );
else
m_prev.label->setPadding( 0, m_skip_right.width() + skipPadding + skipMargin );
m_prev.label->setAlign( Qt::AlignLeft );
m_prev.label->setAnimated( false );
m_prev.label->setOpacity( prevOpacity );
m_prev.label->installEventFilter( this );
m_prev.label->setForegroundRole( prev_next_role );
- connect( m_prev.label, SIGNAL(clicked(QString)), The::playlistActions(), SLOT(back()) );
+ connect( m_prev.label, &AnimatedLabelStack::clicked, The::playlistActions(), &Playlist::Actions::back );
m_current.label = new AnimatedLabelStack( QStringList( m_promoString ), info );
m_current.label->setFont( fnt );
m_current.label->setPadding( 24, 24 );
m_current.label->setBold( true );
m_current.label->setLayout( new QHBoxLayout );
m_current.label->installEventFilter( this );
- connect( m_current.label, SIGNAL(clicked(QString)),
- Amarok::actionCollection()->action("show_active_track"), SLOT(trigger()) );
+ connect( m_current.label, &AnimatedLabelStack::clicked,
+ Amarok::actionCollection()->action("show_active_track"), &QAction::trigger );
m_next.key = 0;
m_next.label = new AnimatedLabelStack(QStringList(), info);
m_next.label->setFont( fnt );
if( layoutDirection() == Qt::LeftToRight )
m_next.label->setPadding( 0, m_skip_left.width() + skipPadding + skipMargin );
else
m_next.label->setPadding( m_skip_left.width() + skipPadding + skipMargin, 0 );
m_next.label->setAlign( Qt::AlignRight );
m_next.label->setAnimated( false );
m_next.label->setOpacity( nextOpacity );
m_next.label->installEventFilter( this );
m_next.label->setForegroundRole( prev_next_role );
- connect( m_next.label, SIGNAL(clicked(QString)), The::playlistActions(), SLOT(next()) );
+ connect( m_next.label, &AnimatedLabelStack::clicked, The::playlistActions(), &Playlist::Actions::next );
m_dummy.label = new AnimatedLabelStack(QStringList(), info);
m_next.label->setFont( fnt );
m_dummy.label->hide();
vl->addItem( m_trackBarSpacer = new QSpacerItem(0, m_current.label->minimumHeight(), QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
vl->addSpacing( space_between_tracks_and_slider );
- connect( m_prev.label, SIGNAL(pulsing(bool)), m_current.label, SLOT(setStill(bool)) );
- connect( m_next.label, SIGNAL(pulsing(bool)), m_current.label, SLOT(setStill(bool)) );
+ connect( m_prev.label, &AnimatedLabelStack::pulsing, m_current.label, &AnimatedLabelStack::setStill );
+ connect( m_next.label, &AnimatedLabelStack::pulsing, m_current.label, &AnimatedLabelStack::setStill );
m_timeLabel = new QLabel( info );
m_timeLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight );
m_slider = new Amarok::TimeSlider( info );
- connect( m_slider, SIGNAL(sliderReleased(int)), The::engineController(), SLOT(seekTo(int)) );
- connect( m_slider, SIGNAL(valueChanged(int)), SLOT(setLabelTime(int)) );
- connect( App::instance(), SIGNAL(settingsChanged()), SLOT(layoutProgressBar()) );
+ connect( m_slider, &Amarok::TimeSlider::sliderReleased, The::engineController(), &EngineController::seekTo );
+ connect( m_slider, &Amarok::TimeSlider::valueChanged, this, &MainToolbar::setLabelTime );
+ connect( App::instance(), &App::settingsChanged, this, &MainToolbar::layoutProgressBar );
m_remainingTimeLabel = new QLabel( info );
m_remainingTimeLabel->setAlignment( Qt::AlignVCenter | Qt::AlignLeft );
const int pbsH = qMax( m_timeLabel->sizeHint().height(), m_slider->sizeHint().height() );
vl->addItem( m_progressBarSpacer = new QSpacerItem(0, pbsH, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
addWidget( info );
m_volume = new VolumeDial( this );
m_volume->setRange( 0, 100);
m_volume->setValue( engine->volume() );
m_volume->setMuted( engine->isMuted() );
m_volume->setFixedSize( icnSize, icnSize );
m_volume->addWheelProxies( QList<QWidget*>() << this << info
<< m_prev.label << m_current.label << m_next.label
<< m_timeLabel << m_remainingTimeLabel );
addWidget( m_volume );
- connect( engine, SIGNAL(volumeChanged(int)), m_volume, SLOT(setValue(int)) );
- connect( m_volume, SIGNAL(valueChanged(int)), engine, SLOT(setVolume(int)) );
- connect( m_volume, SIGNAL(muteToggled(bool)), engine, SLOT(setMuted(bool)) );
+ connect( engine, &EngineController::volumeChanged, m_volume, &VolumeDial::setValue );
+ connect( m_volume, &VolumeDial::valueChanged, engine, &EngineController::setVolume );
+ connect( m_volume, &VolumeDial::muteToggled, engine, &EngineController::setMuted );
spacerWidget = new QWidget(this);
spacerWidget->setFixedWidth( leftRightSpacer );
addWidget( spacerWidget );
}
void
MainToolbar::addBookmark( const QString &name, int milliSeconds )
{
if( m_slider )
m_slider->drawTriangle( name, milliSeconds, false );
}
// Moves the label towards its target position by 2/3rds of the remaining distance
static void adjustLabelPos( QWidget *label, int targetX )
{
QRect r = label->geometry();
int d = targetX - r.x();
if( d )
{
r.translate( qMin( qAbs(d), r.width()/6 ) * (d > 0 ? 1 : -1), 0 );
label->setGeometry( r );
}
}
void
MainToolbar::animateTrackLabels()
{
bool done = true;
int off = -m_current.label->parentWidget()->geometry().x();
adjustLabelPos( m_prev.label, m_prev.rect.x() + off );
m_prev.label->setOpacity( prevOpacity );
if(done)
done = m_prev.label->geometry().x() == m_prev.rect.x() + off;
adjustLabelPos( m_current.label, m_current.rect.x() + off );
if(done)
done = m_current.label->geometry().x() == m_current.rect.x() + off;
adjustLabelPos( m_next.label, m_next.rect.x() + off );
m_next.label->setOpacity( nextOpacity );
if(done)
done = m_next.label->geometry().x() == m_next.rect.x() + off;
adjustLabelPos( m_dummy.label, m_dummy.targetX );
if( m_dummy.label->geometry().x() == m_dummy.targetX )
m_dummy.label->hide();
else
done = false;
if( done )
{
killTimer( m_trackBarAnimationTimer );
setCurrentTrackActionsVisible( true );
m_trackBarAnimationTimer = 0;
}
}
void
MainToolbar::stopped()
{
m_slider->setValue( m_slider->minimum() );
m_slider->update(); // necessary to clean the moodbar...
setLabelTime( -1 );
m_playPause->setPlaying( false );
}
void
MainToolbar::paused()
{
m_playPause->setPlaying( false );
}
void
MainToolbar::playing()
{
m_playPause->setPlaying( true );
}
void
MainToolbar::volumeChanged( int percent )
{
m_volume->setValue( percent );
}
void
MainToolbar::muteStateChanged( bool mute )
{
m_volume->setMuted( mute );
}
void
MainToolbar::layoutProgressBar()
{
const int limit = constant_progress_ratio_minimum_width;
const QRect r = m_progressBarSpacer->geometry();
const int bw = AmarokConfig::showMoodbarInSlider() ? 10 : 6;
int w = bw;
if( size().width() < limit )
{
w = (limit<<7)/size().width();
w = w*w*w*bw;
w /= (1<<21);
}
w = r.width() / w;
int tlW = m_timeLabel->width();
if( tlW + timeLabelMargin > w )
w = tlW;
int rtlW = m_remainingTimeLabel->width();
if( rtlW + timeLabelMargin > w )
w = rtlW;
QRect pb = r.adjusted( w, 0, -w, 0 );
m_slider->setGeometry( pb );
QRect tlR( 0, 0, tlW, r.height() );
QRect rtlR( 0, 0, rtlW, r.height() );
if( layoutDirection() == Qt::LeftToRight )
{
tlR.moveTopRight( pb.topLeft() - QPoint( timeLabelMargin, 0 ) );
rtlR.moveTopLeft( pb.topRight() + QPoint( timeLabelMargin, 0 ) );
}
else
{
rtlR.moveTopRight( pb.topLeft() - QPoint( timeLabelMargin, 0 ) );
tlR.moveTopLeft( pb.topRight() + QPoint( timeLabelMargin, 0 ) );
}
m_timeLabel->setGeometry( tlR );
m_remainingTimeLabel->setGeometry( rtlR );
}
void
MainToolbar::layoutTrackBar()
{
m_dummy.label->hide();
// this is the label parenting widge ("info") offset
const QPoint off = m_current.label->parentWidget()->geometry().topLeft();
QRect r = m_trackBarSpacer->geometry();
r.setWidth( r.width() / 3);
int d = r.width();
if( layoutDirection() == Qt::RightToLeft )
{
d = -d;
r.moveRight( m_trackBarSpacer->geometry().right() );
}
m_prev.rect = r.translated( off );
m_prev.label->setGeometry( r );
m_prev.label->setOpacity( prevOpacity );
r.translate( d, 0 );
m_current.rect = r.translated( off );
m_current.label->setGeometry( r );
r.translate( d, 0 );
m_next.rect = r.translated( off );
m_next.label->setGeometry( r );
m_next.label->setOpacity( nextOpacity );
setCurrentTrackActionsVisible( true );
}
void
MainToolbar::updateCurrentTrackActions()
{
// wipe layout ================
QLayoutItem *item;
while ( (item = m_current.label->layout()->takeAt(0)) )
{
delete item->widget();
delete item;
}
// collect actions ================
QList<QAction*> actions;
foreach( QAction* action, The::globalCurrentTrackActions()->actions() )
actions << action;
Meta::TrackPtr track = The::engineController()->currentTrack();
if( track )
{
QScopedPointer< Capabilities::ActionsCapability > ac( track->create<Capabilities::ActionsCapability>() );
if( ac )
actions << ac->actions();
QScopedPointer< Capabilities::BookmarkThisCapability > btc( track->create<Capabilities::BookmarkThisCapability>() );
if( btc && btc->bookmarkAction() )
actions << btc->bookmarkAction();
}
QHBoxLayout *hbl = static_cast<QHBoxLayout*>( m_current.label->layout() );
hbl->setContentsMargins( 0, 0, 0, 0 );
hbl->setSpacing( track_action_spacing );
TrackActionButton *btn;
const int n = actions.count() / 2;
for ( int i = 0; i < actions.count(); ++i )
{
if( i == n )
hbl->addStretch( 10 );
btn = new TrackActionButton( m_current.label, actions.at(i) );
if( !actions.at(i)->parent() ) // see documentation of ActionsCapability::actions
actions.at(i)->setParent(btn);
btn->installEventFilter( this );
hbl->addWidget( btn );
}
}
#define HAS_TAG(_TAG_) track->_TAG_() && !track->_TAG_()->name().isEmpty()
#define TAG(_TAG_) track->_TAG_()->prettyName()
#define CONTAINS_TAG(_TAG_) contains( TAG(_TAG_), Qt::CaseInsensitive )
static QStringList metadata( Meta::TrackPtr track )
{
QStringList list;
if( track )
{
bool noTags = false;
QString title = track->prettyName();
if(( noTags = title.isEmpty() )) // should not happen
title = track->prettyUrl();
if(( noTags = title.isEmpty() )) // should never happen
title = track->playableUrl().url();
if(( noTags = title.isEmpty() )) // sth's MEGA wrong ;-)
title = "???";
// no tags -> probably filename. try to strip the suffix
if( noTags || track->name().isEmpty() )
{
noTags = true;
int dot = title.lastIndexOf('.');
if( dot > 0 && title.length() - dot < 6 )
title = title.left( dot );
}
// the track has no tags, is long or contains tags other than the titlebar
// ==> separate
if( noTags || title.length() > 50 ||
(HAS_TAG(artist) && title.CONTAINS_TAG(artist)) ||
(HAS_TAG(composer) && title.CONTAINS_TAG(composer)) ||
(HAS_TAG(album) && title.CONTAINS_TAG(album)) )
{
// this will split "all-in-one" filename tags
QRegExp rx("(\\s+-\\s+|\\s*;\\s*|\\s*:\\s*)");
list << title.split( rx, QString::SkipEmptyParts );
QList<QString>::iterator i = list.begin();
bool ok;
while ( i != list.end() )
{
// check whether this entry is only a number, i.e. probably year or track #
i->toInt( &ok );
if( ok )
i = list.erase( i );
else
++i;
}
}
else // plain title
{
list << title;
}
if( HAS_TAG(artist) && !list.CONTAINS_TAG(artist) )
list << TAG(artist);
else if( HAS_TAG(composer) && !list.CONTAINS_TAG(composer) )
list << TAG(composer);
if( HAS_TAG(album) && !list.CONTAINS_TAG(album) )
list << TAG(album);
/* other tags
string year
string genre
double score
int rating
qint64 length // ms
int sampleRate
int bitrate
int trackNumber
int discNumber
uint lastPlayed
uint firstPlayed
int playCount
QString type
bool inCollection
*/
}
return list;
}
#undef HAS_TAG
#undef TAG
#undef CONTAINS_TAG
void
MainToolbar::updatePrevAndNext()
{
if( !The::engineController()->currentTrack() )
{
m_prev.key = 0L;
m_prev.label->setForegroundRole( foregroundRole() );
m_prev.label->setOpacity( 96 );
m_prev.label->setData( QStringList() ); // << "[ " + i18n("Previous") + " ]" );
m_prev.label->setCursor( Qt::ArrowCursor );
m_next.key = 0L;
m_next.label->setForegroundRole( foregroundRole() );
m_next.label->setOpacity( 96 );
m_next.label->setData( QStringList() ); // << "[ " + i18n("Next") + " ]" );
m_next.label->setCursor( Qt::ArrowCursor );
m_current.label->setUpdatesEnabled( true );
return;
}
// NOTICE: i don't like this, but the order is important.
// Reason is the (current) behaviour of the RandomTrackNavigator
// when the playlist is completed it will clear the history and reshuffle
// to be able to sneakpeak the next track, it's necessary to trigger this with this query
// if we'd query the previous track first, we'd get a track that's actually no more present after
// the next track query. by this order we'll get a 0L track, what's also the navigators opinion
// about its queue :-\ //
bool needUpdate = false;
bool hadKey = bool(m_next.key);
Meta::TrackPtr track = The::playlistActions()->likelyNextTrack();
m_next.key = track ? track.data() : 0L;
m_next.label->setForegroundRole( prev_next_role );
m_next.label->setOpacity( nextOpacity );
m_next.label->setData( metadata( track ) );
m_next.label->setCursor( track ? Qt::PointingHandCursor : Qt::ArrowCursor );
if( hadKey != bool(m_next.key) )
needUpdate = true;
hadKey = bool(m_prev.key);
track = The::playlistActions()->likelyPrevTrack();
m_prev.key = track ? track.data() : 0L;
m_prev.label->setForegroundRole( prev_next_role );
m_next.label->setOpacity( prevOpacity );
m_prev.label->setData( metadata( track ) );
m_prev.label->setCursor( track ? Qt::PointingHandCursor : Qt::ArrowCursor );
if( hadKey != bool(m_prev.key) )
needUpdate = true;
// we may have disabled it as otherwise the current label gets updated one eventcycle before prev & next
// see ::engineTrackChanged()
m_current.label->setUpdatesEnabled( true );
if( needUpdate )
update();
// unanimated change, probably by sliding the bar - fix label positions
if( !m_trackBarAnimationTimer )
layoutTrackBar();
}
void
MainToolbar::updateBookmarks( const QString *BookmarkName )
{
// DEBUG_BLOCK
m_slider->clearTriangles();
if( Meta::TrackPtr track = The::engineController()->currentTrack() )
{
if( track->has<Capabilities::TimecodeLoadCapability>() )
{
Capabilities::TimecodeLoadCapability *tcl = track->create<Capabilities::TimecodeLoadCapability>();
BookmarkList list = tcl->loadTimecodes();
// debug() << "found " << list.count() << " timecodes on this track";
foreach( AmarokUrlPtr url, list )
{
if( url->command() == "play" && url->args().keys().contains( "pos" ) )
{
int pos = url->args().value( "pos" ).toDouble() * 1000;
debug() << "showing timecode: " << url->name() << " at " << pos ;
m_slider->drawTriangle( url->name(), pos, ( BookmarkName && BookmarkName == url->name() ) );
}
}
delete tcl;
}
}
}
void
MainToolbar::trackChanged( Meta::TrackPtr track )
{
if( !isVisible() || (m_trackBarAnimationTimer && track && track.data() == m_current.key) )
return;
if( m_trackBarAnimationTimer )
{
killTimer( m_trackBarAnimationTimer );
m_trackBarAnimationTimer = 0;
}
if( track )
{
m_current.key = track.data();
m_current.uidUrl = track->uidUrl();
m_current.label->setUpdatesEnabled( false );
m_current.label->setData( metadata( track ) );
m_current.label->setCursor( Qt::PointingHandCursor );
// If all labels are in position and this is a single step for or back, we perform a slide
// on the other two labels, i.e. e.g. move the prev to current label position and current
// to the next and the animate the move into their target positions
QRect r = m_trackBarSpacer->geometry();
r.setWidth( r.width() / 3 );
int d = r.width();
if( layoutDirection() == Qt::RightToLeft )
{
d = -d;
r.moveRight( m_trackBarSpacer->geometry().right() );
}
if( isVisible() && m_current.label->geometry().x() == r.x() + d )
{
if( m_current.key == m_next.key && m_current.key != m_prev.key )
{
setCurrentTrackActionsVisible( false );
// left
m_dummy.targetX = r.x() - d;
// if( d < 0 ) // rtl
// m_dummy.targetX -= d;
m_dummy.label->setGeometry( r );
m_dummy.label->setData( m_prev.label->data() );
m_dummy.label->show();
// center
r.translate( d, 0 );
m_prev.label->setGeometry( r );
// right
r.translate( d, 0 );
m_current.label->setGeometry( r );
m_next.label->setGeometry( r );
m_next.label->setOpacity( 0 );
m_next.label->raise();
animateTrackLabels();
m_trackBarAnimationTimer = startTimer( 40 );
}
else if( m_current.key == m_prev.key )
{
setCurrentTrackActionsVisible( false );
// left
m_prev.label->setGeometry( r );
m_current.label->setGeometry( r );
// center
r.translate( d, 0 );
m_next.label->setGeometry( r );
// right
r.translate( d, 0 );
m_dummy.targetX = r.x() + d;
m_dummy.label->setGeometry( r );
m_dummy.label->setData( m_next.label->data() );
m_dummy.label->show();
m_prev.label->setOpacity( 0 );
m_prev.label->raise();
animateTrackLabels();
m_trackBarAnimationTimer = startTimer( 40 );
}
}
trackLengthChanged( The::engineController()->trackLength() );
}
else
{
// no track
setLabelTime( -1 );
m_slider->setValue( m_slider->minimum() );
m_current.key = 0L;
m_current.uidUrl.clear();
m_current.label->setData( QStringList( m_promoString ) );
m_current.label->setCursor( Qt::ArrowCursor );
}
updateCurrentTrackActions();
m_trackBarSpacer->changeSize(0, m_current.label->minimumHeight(), QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
const int pbsH = qMax( m_timeLabel->sizeHint().height(), m_slider->sizeHint().height() );
m_progressBarSpacer->changeSize(0, pbsH, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
- QTimer::singleShot( 0, this, SLOT(updatePrevAndNext()) );
+ QTimer::singleShot( 0, this, &MainToolbar::updatePrevAndNext );
}
void
MainToolbar::trackLengthChanged( qint64 ms )
{
m_slider->setRange( 0, ms );
m_slider->setEnabled( ms > 0 );
// get the urlid of the current track as the engine might stop and start several times
// when skipping last.fm tracks, so we need to know if we are still on the same track...
if( Meta::TrackPtr track = The::engineController()->currentTrack() )
m_current.uidUrl = track->uidUrl();
updateBookmarks( 0 );
}
void
MainToolbar::trackPositionChanged( qint64 position, bool /*userSeek*/ )
{
if( m_slider->isEnabled() )
m_slider->setSliderValue( position );
else
setLabelTime( position );
}
void
MainToolbar::showEvent( QShowEvent *ev )
{
EngineController *engine = The::engineController();
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped()) );
- connect( engine, SIGNAL(paused()),
- this, SLOT(paused()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(playing()) );
-
- connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
- this, SLOT(trackChanged(Meta::TrackPtr)) );
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- this, SLOT(trackChanged(Meta::TrackPtr)) );
- connect( engine, SIGNAL(trackLengthChanged(qint64)),
- this, SLOT(trackLengthChanged(qint64)) );
- connect( engine, SIGNAL(trackPositionChanged(qint64,bool)),
- this, SLOT(trackPositionChanged(qint64,bool)) );
- connect( engine, SIGNAL(volumeChanged(int)),
- this, SLOT(volumeChanged(int)) );
- connect( engine, SIGNAL(muteStateChanged(bool)),
- this, SLOT(muteStateChanged(bool)) );
+ connect( engine, &EngineController::stopped,
+ this, &MainToolbar::stopped );
+ connect( engine, &EngineController::paused,
+ this, &MainToolbar::paused );
+ connect( engine, &EngineController::trackPlaying,
+ this, &MainToolbar::playing );
+
+ connect( engine, &EngineController::trackChanged,
+ this, &MainToolbar::trackChanged );
+ connect( engine, &EngineController::trackMetadataChanged,
+ this, &MainToolbar::trackChanged );
+ connect( engine, &EngineController::trackLengthChanged,
+ this, &MainToolbar::trackLengthChanged );
+ connect( engine, &EngineController::trackPositionChanged,
+ this, &MainToolbar::trackPositionChanged );
+ connect( engine, &EngineController::volumeChanged,
+ this, &MainToolbar::volumeChanged );
+ connect( engine, &EngineController::muteStateChanged,
+ this, &MainToolbar::muteStateChanged );
// We need the three changed signals:
// 1. the playlist changes (PlaylistController)
// 2. the sorting of the playlist changed (Playlist)
// 3. The navigator changed to e.g. dynamic mode (PlaylistActions)
- connect( The::playlistController(), SIGNAL(changed()),
- this, SLOT(updatePrevAndNext()) );
+ connect( The::playlistController(), &Playlist::Controller::changed,
+ this, &MainToolbar::updatePrevAndNext );
- connect( The::playlist()->qaim(), SIGNAL(queueChanged()),
- this, SLOT(updatePrevAndNext()) );
+ connect( The::playlist()->qaim(), &QAbstractItemModel::dataChanged, //FIXME: dataChanged used to be queueChanged
+ this, &MainToolbar::updatePrevAndNext );
- connect( The::playlistActions(), SIGNAL(navigatorChanged()),
- this, SLOT(updatePrevAndNext()) );
+ connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
+ this, &MainToolbar::updatePrevAndNext );
- connect( The::amarokUrlHandler(), SIGNAL(timecodesUpdated(const QString*)),
- this, SLOT(updateBookmarks(const QString*)) );
- connect( The::amarokUrlHandler(), SIGNAL(timecodeAdded(QString,int)),
- this, SLOT(addBookmark(QString,int)) );
+ connect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodesUpdated,
+ this, &MainToolbar::updateBookmarks );
+ connect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodeAdded,
+ this, &MainToolbar::addBookmark );
QToolBar::showEvent( ev );
trackChanged( The::engineController()->currentTrack() );
layoutTrackBar();
layoutProgressBar();
m_playPause->setPlaying( The::engineController()->isPlaying() );
trackPositionChanged( engine->trackPositionMs(), false ); // Refresh slider on restore
}
void
MainToolbar::hideEvent( QHideEvent *ev )
{
QToolBar::hideEvent( ev );
disconnect( The::engineController(), 0, this, 0 );
- disconnect( The::playlistController(), SIGNAL(changed()),
- this, SLOT(updatePrevAndNext()) );
+ disconnect( The::playlistController(), &Playlist::Controller::changed,
+ this, &MainToolbar::updatePrevAndNext );
- disconnect( The::playlist()->qaim(), SIGNAL(queueChanged()),
- this, SLOT(updatePrevAndNext()) );
+ disconnect( The::playlist()->qaim(), &QAbstractItemModel::dataChanged, //FIXME: dataChanged used to be queueChanged
+ this, &MainToolbar::updatePrevAndNext );
- disconnect( The::playlistActions(), SIGNAL(navigatorChanged()),
- this, SLOT(updatePrevAndNext()) );
+ disconnect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
+ this, &MainToolbar::updatePrevAndNext );
- disconnect( The::amarokUrlHandler(), SIGNAL(timecodesUpdated(const QString*)),
- this, SLOT(updateBookmarks(const QString*)) );
- disconnect( The::amarokUrlHandler(), SIGNAL(timecodeAdded(QString,int)),
- this, SLOT(addBookmark(QString,int)) );
+ disconnect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodesUpdated,
+ this, &MainToolbar::updateBookmarks );
+ disconnect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodeAdded,
+ this, &MainToolbar::addBookmark );
}
void
MainToolbar::paintEvent( QPaintEvent *ev )
{
// let the style paint draghandles and in doubt override the shadow from above
QToolBar::paintEvent( ev );
// skip icons
QPainter p( this );
Skip *left = &m_prev;
Skip *right = &m_next;
if( layoutDirection() == Qt::RightToLeft )
{
left = &m_next;
right = &m_prev;
}
if( left->key )
p.drawPixmap( left->rect.left() + skipMargin,
left->rect.y() + ( left->rect.height() - m_skip_left.height() ) /2,
m_skip_left );
if( right->key )
p.drawPixmap( right->rect.right() - ( m_skip_right.width() + skipMargin ),
right->rect.y() + ( right->rect.height() - m_skip_right.height() ) /2,
m_skip_right );
p.end();
}
void
MainToolbar::resizeEvent( QResizeEvent *ev )
{
if( ev->size().width() > 0 && ev->size().width() != ev->oldSize().width() )
{
layoutProgressBar();
layoutTrackBar();
}
}
void
MainToolbar::setCurrentTrackActionsVisible( bool vis )
{
if( m_current.actionsVisible == vis )
return;
m_current.actionsVisible = vis;
QLayoutItem *item;
for ( int i = 0; i < m_current.label->layout()->count(); ++i )
{
item = m_current.label->layout()->itemAt( i );
if( item->widget() )
item->widget()->setVisible( vis );
}
}
const char * timeString[4] = { "3:33", "33:33", "3:33:33", "33:33:33" };
static inline int
timeFrame( int secs )
{
if( secs < 10*60 ) // 9:59
return 0;
if( secs < 60*60 ) // 59:59
return 1;
if( secs < 10*60*60 ) // 9:59:59
return 2;
return 3; // 99:59:59
}
void
MainToolbar::setLabelTime( int ms )
{
bool relayout = false;
if( ms < 0 ) // clear
{
m_timeLabel->hide();
m_remainingTimeLabel->hide();
m_lastTime = -1;
m_lastRemainingTime = -1;
relayout = true;
}
else if( isVisible() ) // no need to do expensive stuff - it's updated every second anyway
{
const int secs = ms/1000;
const int remainingSecs = m_slider->maximum() > 0 ? (m_slider->maximum() - ms) / 1000 : 0;
if( secs == m_lastTime && remainingSecs == m_lastRemainingTime )
return;
m_timeLabel->setText( Meta::secToPrettyTime( secs ) );
// -- determine fix the timeLabel width at a sensible maximum
int tf = timeFrame( secs );
if( m_lastTime < 0 || tf != timeFrame( m_lastTime ) )
{
const int w = QFontMetrics( m_timeLabel->font() ).width( timeString[tf] );
m_timeLabel->setFixedWidth( w );
relayout = true;
}
m_timeLabel->show();
if( remainingSecs > 0 )
{
m_remainingTimeLabel->setText( '-' + Meta::secToPrettyTime( remainingSecs ) );
tf = timeFrame( remainingSecs );
if( m_lastRemainingTime < 0 || tf != timeFrame( m_lastRemainingTime ) )
{
const int w = QFontMetrics( m_remainingTimeLabel->font() ).width( QString("-") + timeString[tf] );
m_remainingTimeLabel->setFixedWidth( w );
relayout = true;
}
m_remainingTimeLabel->show();
}
else
m_remainingTimeLabel->hide();
m_lastTime = secs;
m_lastRemainingTime = remainingSecs;
}
if(relayout)
layoutProgressBar();
}
void
MainToolbar::timerEvent( QTimerEvent *ev )
{
if( ev->timerId() == m_trackBarAnimationTimer )
animateTrackLabels();
else
QToolBar::timerEvent( ev );
}
bool
MainToolbar::eventFilter( QObject *o, QEvent *ev )
{
if( ev->type() == QEvent::MouseMove )
{
QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
if( mev->buttons() & Qt::LeftButton )
if( o == m_current.label || o == m_prev.label || o == m_next.label )
{
setCurrentTrackActionsVisible( false );
const int x = mev->globalPos().x();
int d = x - m_drag.lastX;
m_drag.lastX = x;
const int globalDist = qAbs( x - m_drag.startX );
if( globalDist > m_drag.max )
m_drag.max = globalDist;
if( globalDist > m_prev.label->width() )
return false; // constrain to one item width
m_current.label->setGeometry( m_current.label->geometry().translated( d, 0 ) );
m_prev.label->setGeometry( m_prev.label->geometry().translated( d, 0 ) );
m_next.label->setGeometry( m_next.label->geometry().translated( d, 0 ) );
}
return false;
}
if( ev->type() == QEvent::MouseButtonPress )
{
QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
if( mev->button() == Qt::LeftButton )
if( o == m_current.label || o == m_prev.label || o == m_next.label )
{
static_cast<QWidget*>(o)->setCursor( Qt::SizeHorCursor );
m_drag.max = 0;
m_drag.lastX = m_drag.startX = mev->globalPos().x();
}
return false;
}
if( ev->type() == QEvent::MouseButtonRelease )
{
QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
if( mev->button() == Qt::LeftButton )
if( o == m_current.label || o == m_prev.label || o == m_next.label )
{
const int x = mev->globalPos().x();
const int d = m_drag.startX - x;
QRect r = m_trackBarSpacer->geometry();
const int limit = r.width()/5; // 1/3 is too much, 1/6 to few
// reset cursor
AnimatedLabelStack *l = static_cast<AnimatedLabelStack*>(o);
l->setCursor( l->data().isEmpty() ? Qt::ArrowCursor : Qt::PointingHandCursor );
// if this was a _real_ drag, silently release the mouse
const bool silentRelease = m_drag.max > 25;
if( silentRelease )
{ // this is a drag, release secretly
o->blockSignals( true );
o->removeEventFilter( this );
QMouseEvent mre( QEvent::MouseButtonRelease, mev->pos(), mev->globalPos(),
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
QCoreApplication::sendEvent( o, &mre );
o->installEventFilter( this );
o->blockSignals( false );
}
// if moved "far enough" jump to prev/next track
// NOTICE the labels shall snap back _after_ the track has changed (in case)
// as this is not reliable, we force a timered snapback sa well
if( d > limit )
{
The::playlistActions()->next();
- QTimer::singleShot(500, this, SLOT(layoutTrackBar()) );
+ QTimer::singleShot(500, this, &MainToolbar::layoutTrackBar );
}
else if( d < -limit )
{
The::playlistActions()->back();
- QTimer::singleShot(500, this, SLOT(layoutTrackBar()) );
+ QTimer::singleShot(500, this, &MainToolbar::layoutTrackBar );
}
else
layoutTrackBar();
return silentRelease;
}
return false;
}
if( ev->type() == QEvent::Enter )
{
if(o == m_next.label && m_next.key)
m_next.label->setOpacity( 255 );
else if(o == m_prev.label && m_prev.key)
m_prev.label->setOpacity( 255 );
else if( o->parent() == m_current.label ) // trackaction
{
QEvent e( QEvent::Leave );
QCoreApplication::sendEvent( m_current.label, &e );
}
return false;
}
if( ev->type() == QEvent::Leave )
{
if(o == m_next.label && m_next.key)
m_next.label->setOpacity( nextOpacity );
else if(o == m_prev.label && m_prev.key)
m_prev.label->setOpacity( prevOpacity );
else if( o->parent() == m_current.label && // trackaction
m_current.label->rect().contains( m_current.label->mapFromGlobal(QCursor::pos()) ) )
{
QEvent e( QEvent::Enter );
QCoreApplication::sendEvent( m_current.label, &e );
}
return false;
}
return false;
}
diff --git a/src/toolbar/VolumePopupButton.cpp b/src/toolbar/VolumePopupButton.cpp
index 103bdc92b4..1eae4c067e 100644
--- a/src/toolbar/VolumePopupButton.cpp
+++ b/src/toolbar/VolumePopupButton.cpp
@@ -1,162 +1,162 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "VolumePopupButton.h"
#include "ActionClasses.h"
#include "EngineController.h"
#include "core/support/Amarok.h"
#include "widgets/SliderWidget.h"
#include <KLocale>
#include <KVBox>
#include <QAction>
#include <QLabel>
#include <QMenu>
#include <QToolBar>
#include <QWheelEvent>
#include <QWidgetAction>
VolumePopupButton::VolumePopupButton( QWidget * parent )
: QToolButton( parent )
{
//create the volume popup
m_volumeMenu = new QMenu( this );
KVBox * mainBox = new KVBox( this );
m_volumeLabel= new QLabel( mainBox );
m_volumeLabel->setAlignment( Qt::AlignHCenter );
KHBox * sliderBox = new KHBox( mainBox );
m_volumeSlider = new Amarok::VolumeSlider( Amarok::VOLUME_MAX, sliderBox, false );
m_volumeSlider->setFixedHeight( 170 );
mainBox->setMargin( 0 );
mainBox->setSpacing( 0 );
sliderBox->setSpacing( 0 );
sliderBox->setMargin( 0 );
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, SIGNAL(sliderMoved(int)), ec, SLOT(setVolume(int)) );
- connect( m_volumeSlider, SIGNAL(sliderReleased(int)), ec, SLOT(setVolume(int)) );
+ 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, SIGNAL(toggled(bool)), ec, SLOT(setMuted(bool)) );
+ 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, SIGNAL(volumeChanged(int)),
- this, SLOT(volumeChanged(int)) );
+ connect( ec, &EngineController::volumeChanged,
+ this, &VolumePopupButton::volumeChanged );
- connect( ec, SIGNAL(muteStateChanged(bool)),
- this, SLOT(muteStateChanged(bool)) );
+ connect( ec, &EngineController::muteStateChanged,
+ this, &VolumePopupButton::muteStateChanged );
}
void
VolumePopupButton::volumeChanged( int newVolume )
{
if ( newVolume < 34 )
setIcon( QIcon::fromTheme( "audio-volume-low" ) );
else if ( newVolume < 67 )
setIcon( QIcon::fromTheme( "audio-volume-medium" ) );
else
setIcon( QIcon::fromTheme( "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 );
const KLocalizedString tip = m_muteAction->isChecked() ? ki18n( "Volume: %1% (muted)" ) : ki18n( "Volume: %1%" );
setToolTip( tip.subs( newVolume ).toString() );
}
void
VolumePopupButton::muteStateChanged( bool muted )
{
const int volume = The::engineController()->volume();
if ( muted )
{
setIcon( QIcon::fromTheme( "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/transcoding/TranscodingAssistantDialog.cpp b/src/transcoding/TranscodingAssistantDialog.cpp
index a5aa6d7ba0..3c2d920e7c 100644
--- a/src/transcoding/TranscodingAssistantDialog.cpp
+++ b/src/transcoding/TranscodingAssistantDialog.cpp
@@ -1,230 +1,230 @@
/****************************************************************************************
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TranscodingAssistantDialog.h"
#include "TranscodingJob.h"
#include "core/transcoding/TranscodingController.h"
#include <QIcon>
#include <KPushButton>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
using namespace Transcoding;
AssistantDialog::AssistantDialog( const QStringList &playableFileTypes, bool saveSupported,
Collections::CollectionLocationDelegate::OperationType operation,
const QString &destCollectionName,
const Configuration &prevConfiguration,
QWidget *parent )
: KPageDialog( parent, Qt::Dialog )
, m_configuration( JUST_COPY )
, m_save( false )
, m_playableFileTypes( playableFileTypes )
{
DEBUG_BLOCK
Q_UNUSED( destCollectionName ) // keep it in signature, may be useful in future
QWidget *uiBase = new QWidget( this );
ui.setupUi( uiBase );
setModal( true );
setWindowTitle( i18n( "Transcode Tracks" ) );
setMinimumSize( 590, 340 );
setMaximumWidth( 590 );
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &AssistantDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &AssistantDialog::reject);
mainLayout->addWidget(uiBase);
okButton->setText( i18n( "Transc&ode" ) );
okButton->setEnabled( false );
mainLayout->addWidget(buttonBox);
QString explanatoryText;
QIcon justCopyIcon;
QString justCopyText;
QString justCopyDescription;
switch( operation )
{
case Collections::CollectionLocationDelegate::Copy:
explanatoryText = i18n(
"While copying, you can choose to transcode your music files into another "
"format with an encoder (codec). This can be done to save space or to "
"make your files readable by a portable music player or a particular "
"software program." );
justCopyIcon = QIcon::fromTheme( "edit-copy" );
justCopyText = i18n( "&Copy" );
justCopyDescription = i18n( "Just copy the tracks without transcoding them." );
break;
case Collections::CollectionLocationDelegate::Move:
explanatoryText = i18n(
"While moving, you can choose to transcode your music files into another "
"format with an encoder (codec). This can be done to save space or to "
"make your files readable by a portable music player or a particular "
"software program. Only successfully transcoded files will be removed "
"from their original location." );
justCopyIcon = QIcon::fromTheme( "go-jump" ); // Dolphin uses this icon for "move"
justCopyText = i18n( "&Move" );
justCopyDescription = i18n( "Just move the tracks without transcoding them." );
break;
}
ui.explanatoryTextLabel->setText( explanatoryText );
ui.justCopyButton->setIcon( justCopyIcon );
ui.justCopyButton->setText( justCopyText );
ui.justCopyButton->setDescription( justCopyDescription );
ui.justCopyButton->setMinimumHeight( ui.justCopyButton->iconSize().height() + 2*10 ); //we make room for the pretty icon
- connect( ui.justCopyButton, SIGNAL(clicked()),
- this, SLOT(onJustCopyClicked()) );
+ connect( ui.justCopyButton, &QAbstractButton::clicked,
+ this, &AssistantDialog::onJustCopyClicked );
//Let's set up the codecs page...
populateFormatList();
- connect( ui.formatListWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
- this, SLOT(onFormatSelect(QListWidgetItem*)) );
+ connect( ui.formatListWidget, &QListWidget::currentItemChanged,
+ this, &AssistantDialog::onFormatSelect );
ui.formatIconLabel->hide();
ui.formatNameLabel->hide();
- connect( buttonBox->button(QDialogButtonBox::Ok) , SIGNAL(clicked()),
- this, SLOT(onTranscodeClicked()) );
+ connect( buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked,
+ this, &AssistantDialog::onTranscodeClicked );
ui.rememberCheckBox->setChecked( m_save );
ui.rememberCheckBox->setEnabled( saveSupported );
- connect( ui.rememberCheckBox, SIGNAL(toggled(bool)),
- this, SLOT(onRememberToggled(bool)) );
+ connect( ui.rememberCheckBox, &QCheckBox::toggled,
+ this, &AssistantDialog::onRememberToggled );
switch( prevConfiguration.trackSelection() ) //restore the previously selected TrackSelection radio button
{
case Configuration::TranscodeUnlessSameType:
ui.transcodeUnlessSameTypeRadioButton->setChecked( true );
break;
case Configuration::TranscodeOnlyIfNeeded:
ui.transcodeOnlyIfNeededRadioButton->setChecked( true );
break;
case Configuration::TranscodeAll:
ui.transcodeAllRadioButton->setChecked( true );
}
ui.transcodeAllRadioButton->setEnabled( false );
ui.transcodeUnlessSameTypeRadioButton->setEnabled( false );
ui.transcodeOnlyIfNeededRadioButton->setEnabled( false );
}
void
AssistantDialog::populateFormatList()
{
QSet<Encoder> available = Amarok::Components::transcodingController()->availableEncoders();
// Add a note if no encoder is found
ui.groupBox->setEnabled( !available.isEmpty() );
ui.encoderNotFoundLabel->setVisible( available.isEmpty() );
foreach( Encoder encoder, Amarok::Components::transcodingController()->allEncoders() )
{
if( encoder == INVALID || encoder == JUST_COPY )
continue; // skip "fake" encoders
Format *format = Amarok::Components::transcodingController()->format( encoder );
QListWidgetItem *item = new QListWidgetItem( format->icon(), format->prettyName() );
item->setToolTip( format->description() );
item->setData( Qt::UserRole, encoder );
// can be disabled due to unavailabilty
bool enabled = available.contains( encoder );
if( !enabled )
item->setToolTip( i18nc( "Tooltip of a disabled transcoding encoder option",
"Not currently available on your system." ) );
// or because not supported by target collection
if( enabled && !m_playableFileTypes.isEmpty() )
{
enabled = m_playableFileTypes.contains( format->fileExtension() );
if( !enabled )
item->setToolTip( i18n( "Target collection indicates this format would not be playable." ) );
}
Qt::ItemFlags flags = item->flags();
if( !enabled )
item->setFlags( flags & ~Qt::ItemIsEnabled );
ui.formatListWidget->addItem( item );
}
}
void
AssistantDialog::onJustCopyClicked() //SLOT
{
QDialog::done( QDialog::Accepted );
}
void
AssistantDialog::onTranscodeClicked() //SLOT
{
m_configuration = ui.transcodingOptionsStackedWidget->configuration( trackSelection() );
QDialog::done( QDialog::Accepted );
}
void
AssistantDialog::onFormatSelect( QListWidgetItem *item ) //SLOT
{
if( item )
{
ui.formatIconLabel->show();
ui.formatNameLabel->show();
Encoder encoder = static_cast< Encoder >( item->data( Qt::UserRole ).toInt() );
const Format *format = Amarok::Components::transcodingController()->format( encoder );
ui.formatIconLabel->setPixmap( format->icon().pixmap( 32, 32 ) );
ui.formatNameLabel->setText( format->prettyName() );
ui.formatIconLabel->setToolTip( format->description() );
ui.formatIconLabel->setWhatsThis( format->description() );
ui.formatNameLabel->setToolTip( format->description() );
ui.formatNameLabel->setWhatsThis( format->description() );
ui.transcodingOptionsStackedWidget->switchPage( encoder );
ui.transcodeAllRadioButton->setEnabled( true );
ui.transcodeUnlessSameTypeRadioButton->setEnabled( true );
ui.transcodeOnlyIfNeededRadioButton->setEnabled( true );
buttonBox()->button(QDialogButtonBox::Ok)->setEnabled(true);
}
}
void
AssistantDialog::onRememberToggled( bool checked ) //SLOT
{
m_save = checked;
}
Configuration::TrackSelection
AssistantDialog::trackSelection() const
{
if( ui.transcodeOnlyIfNeededRadioButton->isChecked() )
return Configuration::TranscodeOnlyIfNeeded;
else if( ui.transcodeUnlessSameTypeRadioButton->isChecked() )
return Configuration::TranscodeUnlessSameType;
else
return Configuration::TranscodeAll;
}
diff --git a/src/transcoding/TranscodingJob.cpp b/src/transcoding/TranscodingJob.cpp
index b9096b2cb9..627bd0490a 100644
--- a/src/transcoding/TranscodingJob.cpp
+++ b/src/transcoding/TranscodingJob.cpp
@@ -1,179 +1,185 @@
/****************************************************************************************
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TranscodingJob.h"
#include "core/support/Debug.h"
#include "core/transcoding/TranscodingController.h"
#include <KProcess>
#include <QFile>
#include <QTimer>
namespace Transcoding
{
Job::Job( const QUrl &src,
const QUrl &dest,
const Transcoding::Configuration &configuration,
QObject *parent )
: KJob( parent )
, m_src( src )
, m_dest( dest )
, m_configuration( configuration )
, m_duration( -1 )
{
init();
}
Job::Job( QUrl &src,
const Transcoding::Configuration &configuration,
QObject *parent )
: KJob( parent )
, m_src( src )
, m_dest( src )
, m_configuration( configuration )
, m_duration( -1 )
{
QString fileExtension = Amarok::Components::transcodingController()->format( configuration.encoder() )->fileExtension();
if( !( fileExtension.isEmpty() ) )
{
QString destPath = src.path();
destPath.truncate( destPath.lastIndexOf( '.' ) + 1 );
destPath.append( fileExtension );
m_dest.setPath( destPath );
}
init();
}
void
Job::init()
{
m_transcoder = new KProcess( this );
m_transcoder->setOutputChannelMode( KProcess::MergedChannels );
//First the executable...
m_transcoder->setProgram( "ffmpeg" );
//... prevent ffmpeg from being interactive when destination file already exists. We
// would use -n to exit immediatelly, but libav's ffmpeg doesn't support it, so we
// check for destination file existence manually and pass -y (overwrite) to avoid
// race condition
*m_transcoder << QString( "-y" );
//... then we'd have the infile configuration followed by "-i" and the infile path...
*m_transcoder << QString( "-i" )
<< m_src.path();
//... and finally, outfile configuration followed by the outfile path.
const Transcoding::Format *format = Amarok::Components::transcodingController()->format( m_configuration.encoder() );
*m_transcoder << format->ffmpegParameters( m_configuration )
<< m_dest.path();
- connect( m_transcoder, SIGNAL(readyRead()),
- this, SLOT(processOutput()) );
- connect( m_transcoder, SIGNAL(finished(int,QProcess::ExitStatus)),
- this, SLOT(transcoderDone(int,QProcess::ExitStatus)) );
+ connect( m_transcoder, &KProcess::readyRead,
+ this, &Job::processOutput );
+ connect( m_transcoder, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished),
+ this, &Job::transcoderDone );
}
void
Job::start()
{
DEBUG_BLOCK
if( QFile::exists( m_dest.path() ) )
{
debug() << "Not starting ffmpeg encoder, file already exists:" << m_dest.path();
- QTimer::singleShot( 0, this, SLOT(transcoderDone()) );
+ QTimer::singleShot( 0, this, &Job::transcoderDoneDefault );
}
else
{
QString commandline = QString( "'" ) + m_transcoder->program().join("' '") + QString( "'" );
debug()<< "Calling" << commandline.toLocal8Bit().constData();
m_transcoder->start();
}
}
void
Job::transcoderDone( int exitCode, QProcess::ExitStatus exitStatus ) //SLOT
{
if( exitCode == 0 && exitStatus == QProcess::NormalExit )
debug() << "YAY, transcoding done!";
else
{
debug() << "NAY, transcoding fail!";
setError( KJob::UserDefinedError );
setErrorText( QString( "Calling `" ) + m_transcoder->program().join(" ") + "` failed" );
}
emitResult();
}
+void
+Job::transcoderDoneDefault()
+{
+ transcoderDone( -1, QProcess::CrashExit );
+}
+
void
Job::processOutput()
{
QString output = QString::fromLocal8Bit( m_transcoder->readAllStandardOutput().data() );
if( output.simplified().isEmpty() )
return;
foreach( const QString &line, output.split( QChar( '\n' ) ) )
debug() << "ffmpeg:" << line.toLocal8Bit().constData();
if( m_duration == -1 )
{
m_duration = computeDuration( output );
if( m_duration >= 0 )
setTotalAmount( KJob::Bytes, m_duration ); //Nothing better than bytes I can think of
}
qint64 progress = computeProgress( output );
if( progress > -1 )
setProcessedAmount( KJob::Bytes, progress );
}
inline qint64
Job::computeDuration( const QString &output )
{
//We match something like "Duration: 00:04:33.60"
QRegExp matchDuration( "Duration: (\\d{2,}):(\\d{2}):(\\d{2})\\.(\\d{2})" );
if( output.contains( matchDuration ) )
{
//duration is in csec
qint64 duration = matchDuration.cap( 1 ).toLong() * 60 * 60 * 100 +
matchDuration.cap( 2 ).toInt() * 60 * 100 +
matchDuration.cap( 3 ).toInt() * 100 +
matchDuration.cap( 4 ).toInt();
return duration;
}
else
return -1;
}
inline qint64
Job::computeProgress( const QString &output )
{
//Output is like size= 323kB time=18.10 bitrate= 146.0kbits/s
//We're going to use the "time" column, which counts the elapsed time in seconds.
QRegExp matchTime( "time=(\\d+)\\.(\\d{2})" );
if( output.contains( matchTime ) )
{
qint64 time = matchTime.cap( 1 ).toLong() * 100 +
matchTime.cap( 2 ).toInt();
return time;
}
else
return -1;
}
} //namespace Transcoding
diff --git a/src/transcoding/TranscodingJob.h b/src/transcoding/TranscodingJob.h
index effaaaf8f5..d178a44093 100644
--- a/src/transcoding/TranscodingJob.h
+++ b/src/transcoding/TranscodingJob.h
@@ -1,110 +1,107 @@
/****************************************************************************************
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef TRANSCODING_JOB_H
#define TRANSCODING_JOB_H
#include "amarok_transcoding_export.h"
#include "core/transcoding/TranscodingConfiguration.h"
#include <KJob>
#include <QUrl>
#include <KProcess>
namespace Transcoding
{
/**
* A KJob that transcodes an audio stream from a file into another file with a different
* codec and container format.
* @author Téo Mrnjavac <teo@kde.org>
*/
class AMAROK_TRANSCODING_EXPORT Job : public KJob
{
Q_OBJECT
public:
/**
* Constructor. Creates a Transcoding::Job and fills in the source, destination and
* encoder parameters. The job does not start automatically.
* @param src the path of the source file.
* @param dest the path of the destination file, to be created.
* @param configuration the string of parameters to be fed to the encoder. This implementation
* uses the FFmpeg executable, @see http://ffmpeg.org/ffmpeg-doc.html#SEC6
* @param the parent QObject.
*/
explicit Job( const QUrl &src,
const QUrl &dest,
const Transcoding::Configuration &configuration,
QObject *parent = 0 );
/**
* Convenience constructor. Creates a Transcoding::Job with the destination file to be
* placed in the same directory as the source.
*/
explicit Job( QUrl &src,
const Transcoding::Configuration &configuration,
QObject *parent = 0 );
/**
* Sets the path of the source file.
* @param src the path of the source file.
*/
void setSource( const QUrl &src );
/**
* Sets the path of the destination file, to be created.
* @param dest the path of the destination file.
*/
void setDestination( const QUrl &dest );
/**
* Starts the transcoding job.
*/
void start();
/**
* Get the source url.
*/
QUrl srcUrl() const { return m_src; }
/**
* Get the destination url.
*/
QUrl destUrl() const { return m_dest; }
private Q_SLOTS:
void processOutput();
- /**
- * Default arguments are for convenience (read: lazyness) so that this can be
- * connected to QTimer::singleShot()
- */
- void transcoderDone( int exitCode = -1, QProcess::ExitStatus exitStatus = QProcess::CrashExit );
+ void transcoderDone( int exitCode, QProcess::ExitStatus exitStatus );
+ void transcoderDoneDefault();
void init();
private:
inline qint64 computeDuration( const QString &output );
inline qint64 computeProgress( const QString &output );
QUrl m_src;
QUrl m_dest;
Transcoding::Configuration m_configuration;
KProcess *m_transcoder;
qint64 m_duration; //in csec
};
} //namespace Transcoding
#endif //TRANSCODING_JOB_H
diff --git a/src/transcoding/TranscodingPropertySliderWidget.cpp b/src/transcoding/TranscodingPropertySliderWidget.cpp
index 44944c6561..9c296fbc1f 100644
--- a/src/transcoding/TranscodingPropertySliderWidget.cpp
+++ b/src/transcoding/TranscodingPropertySliderWidget.cpp
@@ -1,111 +1,111 @@
/****************************************************************************************
* Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TranscodingPropertySliderWidget.h"
#include "core/support/Debug.h"
#include "KLocalizedString"
#include <QHBoxLayout>
namespace Transcoding
{
PropertySliderWidget::PropertySliderWidget( Property property, QWidget * parent )
: QWidget( parent )
, m_property( property )
{
m_name = property.name();
QBoxLayout *mainLayout;
m_mainLabel = new QLabel( m_property.prettyName(), this );
m_mainLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
mainLayout = new QVBoxLayout( this );
QBoxLayout *secondaryTopLayout = new QHBoxLayout( this );
QBoxLayout *secondaryBotLayout = new QHBoxLayout( this );
mainLayout->addWidget( m_mainLabel );
mainLayout->addLayout( secondaryTopLayout );
mainLayout->addLayout( secondaryBotLayout );
secondaryTopLayout->addSpacing( 5 );
m_mainEdit = new QSlider( this );
m_mainEdit->setOrientation( Qt::Horizontal );
m_mainEdit->setRange( m_property.min(), m_property.max() );
m_mainEdit->setValue( m_property.defaultValue().toInt() );
m_mainEdit->setTickPosition( QSlider::TicksBelow );
m_mainEdit->setTickInterval( 1 );
m_mainEdit->setPageStep( 2 );
m_mainEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
secondaryTopLayout->addWidget( m_mainEdit, 3 );
secondaryTopLayout->addSpacing( 5 );
QLabel *leftLabel = new QLabel( m_property.endLabels().at( 0 ), this );
secondaryBotLayout->addWidget( leftLabel, 1 );
m_midLabel = new QLabel( QString::number( m_mainEdit->value() ), this );
{
QFont font = m_midLabel->font();
font.setBold( true );
m_midLabel->setFont( font );
}
- connect( m_mainEdit, SIGNAL(valueChanged(int)),
- this, SLOT(onSliderChanged(int)) );
+ connect( m_mainEdit, &QSlider::valueChanged,
+ this, &PropertySliderWidget::onSliderChanged );
QLabel *rightLabel = new QLabel( m_property.endLabels().at( 1 ), this );
rightLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
secondaryBotLayout->addWidget( rightLabel, 1 );
mainLayout->addWidget( m_midLabel );
onSliderChanged( m_property.defaultValue().toInt() );
QString description = m_property.description();
m_mainEdit->setToolTip( description );
m_mainLabel->setToolTip( description );
m_mainEdit->setWhatsThis( description );
m_mainLabel->setWhatsThis( description );
m_mainLabel->setBuddy( m_mainEdit );
m_midLabel->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter );
}
void
PropertySliderWidget::onSliderChanged( int value ) //SLOT
{
QString newText;
if( !m_property.valueLabels().isEmpty() &&
m_property.valueLabels().size() == qAbs( m_property.max() - m_property.min() ) + 1 )
newText = m_property.valueLabels().at( value - qMin( m_property.min(), m_property.max() ) );
else
newText = QString::number( value );
if( value == m_property.defaultValue().toInt() )
newText += i18n( " (recommended)" );
m_midLabel->setText( newText );
}
QVariant
PropertySliderWidget::value() const
{
return m_mainEdit->value();
}
} //namespace Transcoding
diff --git a/src/widgets/AlbumBreadcrumbWidget.cpp b/src/widgets/AlbumBreadcrumbWidget.cpp
index d699f7d5a7..eee679eb30 100644
--- a/src/widgets/AlbumBreadcrumbWidget.cpp
+++ b/src/widgets/AlbumBreadcrumbWidget.cpp
@@ -1,77 +1,77 @@
/****************************************************************************************
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AlbumBreadcrumbWidget.h"
#include "core/meta/Meta.h"
#include "widgets/BreadcrumbItemButton.h"
#include <QIcon>
#include <KLocale>
AlbumBreadcrumbWidget::AlbumBreadcrumbWidget( const Meta::AlbumPtr album, QWidget *parent )
: KHBox( parent )
, m_album( album )
{
const QIcon artistIcon = QIcon::fromTheme( "filename-artist-amarok" );
const QIcon albumIcon = QIcon::fromTheme( "filename-album-amarok" );
new BreadcrumbItemMenuButton( this );
m_artistButton = new BreadcrumbItemButton( artistIcon, QString(), this );
new BreadcrumbItemMenuButton( this );
m_albumButton = new BreadcrumbItemButton( albumIcon, QString(), this );
QWidget *spacer = new QWidget( this );
setStretchFactor( m_artistButton, 1 );
setStretchFactor( m_albumButton, 1 );
setStretchFactor( spacer, 1 );
- connect( m_artistButton, SIGNAL(clicked()), SLOT(artistClicked()) );
- connect( m_albumButton, SIGNAL(clicked()), SLOT(albumClicked()) );
+ connect( m_artistButton, &BreadcrumbItemButton::clicked, this, &AlbumBreadcrumbWidget::slotArtistClicked );
+ connect( m_albumButton, &BreadcrumbItemButton::clicked, this, &AlbumBreadcrumbWidget::slotAlbumClicked );
updateBreadcrumbs();
}
AlbumBreadcrumbWidget::~AlbumBreadcrumbWidget()
{
}
void AlbumBreadcrumbWidget::setAlbum( const Meta::AlbumPtr album )
{
m_album = album;
updateBreadcrumbs();
}
void AlbumBreadcrumbWidget::updateBreadcrumbs()
{
const QString &album = m_album->prettyName();
const QString &artist = m_album->hasAlbumArtist() ? m_album->albumArtist()->prettyName()
: i18n( "Various Artists" );
m_artistButton->setText( artist );
m_albumButton->setText( album );
}
-void AlbumBreadcrumbWidget::artistClicked()
+void AlbumBreadcrumbWidget::slotArtistClicked()
{
if( m_album->hasAlbumArtist() )
emit artistClicked( m_album->albumArtist()->name() );
}
-void AlbumBreadcrumbWidget::albumClicked()
+void AlbumBreadcrumbWidget::slotAlbumClicked()
{
emit albumClicked( m_album->name() );
}
diff --git a/src/widgets/AlbumBreadcrumbWidget.h b/src/widgets/AlbumBreadcrumbWidget.h
index efb61e1294..932cf34e31 100644
--- a/src/widgets/AlbumBreadcrumbWidget.h
+++ b/src/widgets/AlbumBreadcrumbWidget.h
@@ -1,67 +1,67 @@
/****************************************************************************************
* Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_ALBUMBREADCRUMBWIDGET_H
#define AMAROK_ALBUMBREADCRUMBWIDGET_H
#include "core/meta/forward_declarations.h"
#include <KHBox>
class BreadcrumbItemButton;
/**
* This simple widget shows a breadcrumb-like display of one artist and one
* album. Clicking on the artist or album emits signals containing their meta
* objects and names.
*
* It looks like this:
* \code
* +-------------------------------+
* | > X artist > Y album |
* +-------------------------------+
* where X and Y are generic artist and album icons respectively.
* \endcode
*
* TODO: list artists/albums when clicking on the '>' to be more useful
*/
class AlbumBreadcrumbWidget : public KHBox
{
Q_OBJECT
public:
explicit AlbumBreadcrumbWidget( const Meta::AlbumPtr album, QWidget *parent = 0 );
~AlbumBreadcrumbWidget();
void setAlbum( const Meta::AlbumPtr album );
Q_SIGNALS:
void artistClicked( const QString& );
void albumClicked( const QString& );
private Q_SLOTS:
- void artistClicked();
- void albumClicked();
+ void slotArtistClicked();
+ void slotAlbumClicked();
private:
Meta::AlbumPtr m_album;
BreadcrumbItemButton *m_artistButton;
BreadcrumbItemButton *m_albumButton;
void updateBreadcrumbs();
};
#endif /* AMAROK_ALBUMBREADCRUMBWIDGET_H */
diff --git a/src/widgets/AmarokDockWidget.cpp b/src/widgets/AmarokDockWidget.cpp
index e1035ab25c..8fba867288 100644
--- a/src/widgets/AmarokDockWidget.cpp
+++ b/src/widgets/AmarokDockWidget.cpp
@@ -1,57 +1,57 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AmarokDockWidget.h"
AmarokDockWidget::AmarokDockWidget( const QString & title, QWidget * parent, Qt::WindowFlags flags )
: QDockWidget( title, parent, flags )
, m_polished( false )
{
m_dummyTitleBarWidget = new QWidget( this );
- connect( this, SIGNAL(visibilityChanged(bool)), SLOT(slotVisibilityChanged(bool)) );
+ connect( this, &AmarokDockWidget::visibilityChanged, this, &AmarokDockWidget::slotVisibilityChanged );
}
void AmarokDockWidget::slotVisibilityChanged( bool visible )
{
if( visible )
ensurePolish();
}
void AmarokDockWidget::ensurePolish()
{
if( !m_polished )
{
polish();
m_polished = true;
}
}
void AmarokDockWidget::setMovable( bool movable )
{
if( movable )
{
const QFlags<QDockWidget::DockWidgetFeature> features = QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable;
setTitleBarWidget( 0 );
setFeatures( features );
}
else
{
const QFlags<QDockWidget::DockWidgetFeature> features = QDockWidget::NoDockWidgetFeatures;
setTitleBarWidget( m_dummyTitleBarWidget );
setFeatures( features );
}
}
diff --git a/src/widgets/AnimatedLabelStack.cpp b/src/widgets/AnimatedLabelStack.cpp
index 77339356b8..8b9bffb379 100644
--- a/src/widgets/AnimatedLabelStack.cpp
+++ b/src/widgets/AnimatedLabelStack.cpp
@@ -1,402 +1,402 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de> *
* Copyright (c) 2010 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "AnimatedLabelStack.h"
#include <QPainter>
#include <QPaintEvent>
#include <QTimer>
static const int frameTime = 50;
static const int normalDisplayTime = 7000;
AnimatedLabelStack::AnimatedLabelStack( const QStringList &data, QWidget *p, Qt::WindowFlags f ): QWidget(p, f)
, m_align(Qt::AlignCenter)
, m_animTimer(0)
, m_sleepTimer(0)
, m_time(0)
, m_fadeTime(300)
, m_displayTime(normalDisplayTime)
, m_index(0)
, m_visibleIndex(0)
, m_opacity(255)
, m_targetOpacity(255)
, m_animated(true)
, m_pulsating(false)
, m_pulseRequested(false)
, m_explicit(false)
, m_isClick(false)
{
setContentsMargins( 0, 0, 0, 0 );
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
setData( data );
}
void
AnimatedLabelStack::activateOnEnter()
{
if ( m_data.isEmpty() || !underMouse() || m_pulsating || m_explicit )
return;
if ( m_animated )
{
m_pulseRequested = true;
if ( m_time > m_fadeTime && m_time < (m_displayTime - m_fadeTime) )
m_time = m_displayTime - m_fadeTime;
wakeUp();
}
else
setPulsating( true );
}
void
AnimatedLabelStack::ensureAnimationStatus()
{
if ( m_data.count() > 1 && ( m_animated || m_pulsating ) )
{
wakeUp();
}
else
{
if ( m_animTimer )
{
killTimer( m_animTimer );
m_animTimer = 0;
}
if ( m_sleepTimer )
{
killTimer( m_sleepTimer );
m_sleepTimer = 0;
}
m_opacity = m_targetOpacity;
update();
}
}
void
AnimatedLabelStack::enterEvent( QEvent * )
{
// wait a short time, then pulse through entries
m_explicit = false;
- QTimer::singleShot(300, this, SLOT(activateOnEnter()) );
+ QTimer::singleShot(300, this, &AnimatedLabelStack::activateOnEnter );
}
void
AnimatedLabelStack::hideEvent( QHideEvent *e )
{
QWidget::hideEvent( e );
if ( m_animTimer )
{
killTimer( m_animTimer );
m_animTimer = 0;
}
if ( m_sleepTimer )
{
killTimer( m_sleepTimer );
m_sleepTimer = 0;
}
m_opacity = m_targetOpacity;
}
void
AnimatedLabelStack::leaveEvent( QEvent * )
{
m_explicit = false;
m_pulseRequested = false;
}
void
AnimatedLabelStack::mousePressEvent( QMouseEvent *me )
{
if ( me->button() != Qt::LeftButton || m_data.isEmpty() )
return;
m_isClick = true;
me->accept();
}
void
AnimatedLabelStack::mouseReleaseEvent( QMouseEvent *me )
{
if ( me->button() != Qt::LeftButton || m_data.isEmpty() )
return;
me->accept();
if ( m_isClick && underMouse() )
{
m_isClick = false;
if ( !m_data.isEmpty() )
emit clicked ( m_data.at( m_visibleIndex ) );
}
}
void
AnimatedLabelStack::paintEvent( QPaintEvent * pe )
{
if ( m_data.isEmpty() )
return;
QPainter p(this);
p.setClipRegion( pe->region() );
QColor c( palette().color( foregroundRole() ) );
c.setAlpha( m_targetOpacity );
if ( m_animTimer ) // currently animated
{
if ( m_opacity != m_targetOpacity ) // we're in transition period
{
if ( !m_pulsating )
{
c.setAlpha( qAbs(m_targetOpacity - m_opacity) );
p.setPen( c );
int index = m_visibleIndex - 1;
if (index < 0)
index = m_data.count() - 1;
p.drawText( textRect(), m_align | Qt::TextSingleLine, elidedText( m_data.at( index ) ) );
}
c.setAlpha( m_opacity );
}
}
p.setPen( c );
p.drawText( textRect(), m_align | Qt::TextSingleLine, elidedText( m_data.at( m_visibleIndex ) ) );
p.end();
}
void
AnimatedLabelStack::showEvent( QShowEvent *e )
{
ensureAnimationStatus();
QWidget::showEvent( e );
}
QString
AnimatedLabelStack::elidedText( const QString& text ) const
{
const QFontMetrics fontMetrics( font() );
QString newText = fontMetrics.elidedText( text, Qt::ElideRight, textRect().width() - 2 );
// Insert a whitespace between text and "..." (looks nicer)
if( newText != text )
newText.insert( newText.length() -1, ' ' );
return newText;
}
void
AnimatedLabelStack::pulse( int /*cycles*/, int /*minimum*/ )
{
//TODO: handle parameters...
activateOnEnter();
}
void
AnimatedLabelStack::setAlign( Qt::Alignment align )
{
m_align = Qt::AlignVCenter;
if ( align & Qt::AlignLeft )
m_align |= Qt::AlignLeft;
else if ( align & Qt::AlignRight )
m_align |= Qt::AlignRight;
else
m_align = Qt::AlignCenter;
}
void
AnimatedLabelStack::setAnimated( bool on )
{
m_animated = on;
ensureAnimationStatus();
}
void
AnimatedLabelStack::setBold( bool bold )
{
QFont fnt = font();
fnt.setBold(bold);
setFont(fnt);
setMinimumHeight( QFontMetrics(fnt).height() + 4 );
}
void
AnimatedLabelStack::setData( const QStringList &data )
{
if ( data == m_data )
return;
m_data = data;
m_time = 0;
m_index = 0;
m_visibleIndex = 0;
ensureAnimationStatus();
update();
}
void
AnimatedLabelStack::setPadding( int left, int right )
{
m_padding[0] = left;
m_padding[1] = right;
update();
}
void
AnimatedLabelStack::setPulsating( bool on )
{
if ( m_pulseRequested == on && m_pulsating == on )
return;
m_pulseRequested = on;
m_pulsating = on;
if ( m_pulsating )
{
m_displayTime = 1200;
m_fadeTime = 300;
if ( m_time > m_fadeTime && m_time < m_displayTime - m_fadeTime )
m_time = m_displayTime - m_fadeTime + 1; // for instant reaction
}
else
{
m_displayTime = normalDisplayTime;
m_fadeTime = 300;
if ( !m_animated )
m_time = m_fadeTime + 1;
}
ensureAnimationStatus();
emit pulsing( on );
}
void
AnimatedLabelStack::sleep( int ms )
{
if ( m_animTimer )
{
killTimer( m_animTimer );
m_animTimer = 0;
}
if ( !m_sleepTimer )
m_sleepTimer = startTimer( ms );
}
void
AnimatedLabelStack::wakeUp()
{
if ( m_sleepTimer )
{
killTimer( m_sleepTimer );
m_sleepTimer = 0;
}
if ( !m_animTimer )
m_animTimer = startTimer( frameTime );
}
void
AnimatedLabelStack::timerEvent( QTimerEvent * te )
{
if ( !isVisible() )
return;
if ( te->timerId() == m_sleepTimer )
wakeUp();
else if ( te->timerId() != m_animTimer )
return;
if ( m_explicit )
return; // the user explicitly altered content by wheeling, don't take it away
if ( m_time < m_fadeTime || m_time > (m_displayTime - m_fadeTime) )
update();
m_time += frameTime;
if ( m_time > m_displayTime )
{
m_time = 0;
if ( m_pulsating && !m_pulseRequested )
m_visibleIndex = m_index;
else
{
++m_visibleIndex;
if ( m_visibleIndex >= m_data.count() )
m_visibleIndex = 0;
}
if ( !m_pulsating )
m_index = m_visibleIndex;
}
if ( m_time < m_fadeTime ) // fade in
{
if ( m_pulseRequested && !m_pulsating )
setPulsating( true );
m_opacity = m_targetOpacity*m_time/m_fadeTime;
wakeUp();
}
else if ( m_pulsating && m_time > (m_displayTime - m_fadeTime) ) // fade out
{
m_opacity = m_targetOpacity*(m_displayTime - m_time)/m_fadeTime;
wakeUp();
}
else // (ensure) no fade
{
if ( !m_pulsating && m_time < (m_displayTime - m_fadeTime) )
{
m_time = m_displayTime - m_fadeTime + 1;
sleep( m_time );
}
m_opacity = m_targetOpacity; // to be sure
if ( m_pulsating && !m_pulseRequested && m_index == m_visibleIndex )
setPulsating( false );
}
}
void
AnimatedLabelStack::wheelEvent( QWheelEvent * we )
{
if ( we->modifiers() & Qt::ControlModifier )
{
we->accept();
if ( m_data.count() < 2 )
return;
setPulsating( false );
if ( we->delta() < 0 )
{
++m_visibleIndex;
if ( m_visibleIndex >= m_data.count() )
m_visibleIndex = 0;
}
else
{
--m_visibleIndex;
if ( m_visibleIndex < 0 )
m_visibleIndex = m_data.count() - 1;
}
m_index = m_visibleIndex;
m_time = m_fadeTime + 1;
m_explicit = true;
update();
}
else
we->ignore();
}
diff --git a/src/widgets/BreadcrumbItemButton.cpp b/src/widgets/BreadcrumbItemButton.cpp
index e5dd33010b..0366a5d944 100644
--- a/src/widgets/BreadcrumbItemButton.cpp
+++ b/src/widgets/BreadcrumbItemButton.cpp
@@ -1,329 +1,329 @@
/****************************************************************************************
* Copyright (c) 2006 Peter Penz <peter.penz@gmx.at> *
* Copyright (c) 2006 Aaron Seigo <aseigo@kde.org> *
* Copyright (c) 2009 Seb Ruiz <ruiz@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "BreadcrumbItemButton.h"
#include "amarokurls/AmarokUrlAction.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "core/support/Amarok.h"
#include <KColorScheme>
#include <QIcon>
#include <KLocale>
#include <QMenu>
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <QPainter>
#include <QStyle>
#include <QStyleOptionFocusRect>
BreadcrumbItemButton::BreadcrumbItemButton( QWidget *parent )
: Amarok::ElidingButton( parent )
, m_displayHint( 0 )
{
init();
}
BreadcrumbItemButton::BreadcrumbItemButton( const QString &text, QWidget *parent )
: Amarok::ElidingButton( text, parent )
, m_displayHint( 0 )
{
init();
}
BreadcrumbItemButton::BreadcrumbItemButton( const QIcon &icon, const QString &text, QWidget *parent )
: Amarok::ElidingButton( icon, text, parent )
, m_displayHint( 0 )
{
init();
}
void
BreadcrumbItemButton::init()
{
setFocusPolicy( Qt::NoFocus );
setDisplayHintEnabled( HoverHint, false );
}
BreadcrumbItemButton::~BreadcrumbItemButton()
{
}
void
BreadcrumbItemButton::setActive( const bool active )
{
setDisplayHintEnabled( ActiveHint, active );
QFont f = font();
f.setBold( active );
setFont( f );
}
void
BreadcrumbItemButton::setDisplayHintEnabled( DisplayHint hint, bool enable )
{
if( enable )
m_displayHint = m_displayHint | hint;
else
m_displayHint = m_displayHint & ~hint;
update();
}
bool
BreadcrumbItemButton::isDisplayHintEnabled( DisplayHint hint ) const
{
return (m_displayHint & hint) > 0;
}
void
BreadcrumbItemButton::enterEvent( QEvent* event )
{
QPushButton::enterEvent( event );
setDisplayHintEnabled( HoverHint, true );
update();
}
void
BreadcrumbItemButton::leaveEvent( QEvent* event )
{
QPushButton::leaveEvent( event );
setDisplayHintEnabled( HoverHint, false );
update();
}
void
BreadcrumbItemButton::paintEvent( QPaintEvent* event )
{
Q_UNUSED(event);
QPainter painter(this);
const int buttonHeight = height();
int buttonWidth = width();
int preferredWidth = sizeHint().width();
if (preferredWidth < minimumWidth()) {
preferredWidth = minimumWidth();
}
if (buttonWidth > preferredWidth) {
buttonWidth = preferredWidth;
}
drawHoverBackground(&painter);
int left, top, right, bottom;
getContentsMargins ( &left, &top, &right, &bottom );
const int padding = 2;
int xoffset;
if( !icon().isNull() )
{
const int iconWidth = iconSize().width();
const int iconHeight = iconSize().height();
const int iconTop = ( (buttonHeight - top - bottom) - iconHeight ) / 2;
const QRect iconRect( left + padding, iconTop, iconWidth, iconHeight );
painter.drawPixmap( iconRect, icon().pixmap( iconSize() ) );
xoffset = left + (padding * 2) + iconWidth;
}
else
xoffset = left + (padding * 2);
const QRect textRect( xoffset, top, buttonWidth, buttonHeight);
painter.drawText(textRect, Qt::AlignVCenter, text());
}
void
BreadcrumbItemButton::drawHoverBackground(QPainter* painter)
{
const bool isHovered = isDisplayHintEnabled( HoverHint );
if( isHovered )
{
// QColor backgroundColor = palette().color(QPalette::Highlight);
// TODO: the backgroundColor should be applied to the style
QStyleOptionViewItemV4 option;
option.initFrom(this);
option.state = QStyle::State_Enabled | QStyle::State_MouseOver;
option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter, this );
}
}
QColor
BreadcrumbItemButton::foregroundColor() const
{
const bool isHighlighted = isDisplayHintEnabled( HoverHint );
const bool isActive = isDisplayHintEnabled( ActiveHint );
QColor foregroundColor = palette().color( foregroundRole() );
if( !isActive && !isHighlighted )
foregroundColor.setAlpha( 180 );
return foregroundColor;
}
QSize
BreadcrumbItemButton::sizeHint() const
{
QSize size = Amarok::ElidingButton::sizeHint();
int width = 8;
if( !icon().isNull() )
{
width += iconSize().width();
}
if( !text().isEmpty() )
{
QFontMetrics fm( font() );
width += fm.width( text() );
}
size.setWidth( width );
return size;
}
BreadcrumbItemMenuButton::BreadcrumbItemMenuButton( QWidget* parent )
: BreadcrumbItemButton( parent )
{
setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
}
void
BreadcrumbItemMenuButton::paintEvent( QPaintEvent* event )
{
Q_UNUSED(event);
QPainter painter(this);
drawHoverBackground(&painter);
const QColor fgColor = foregroundColor();
QStyleOption option;
option.initFrom(this);
option.rect = QRect(0, 0, width(), height());
option.palette = palette();
option.palette.setColor(QPalette::Text, fgColor);
option.palette.setColor(QPalette::WindowText, fgColor);
option.palette.setColor(QPalette::ButtonText, fgColor);
if (layoutDirection() == Qt::LeftToRight) {
style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this);
} else {
style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
}
}
BreadcrumbUrlMenuButton::BreadcrumbUrlMenuButton( const QString &urlsCommand, QWidget *parent )
: BreadcrumbItemButton( QIcon::fromTheme( "bookmark-new-list" ), QString(), parent )
, m_urlsCommand( urlsCommand )
, m_copyToClipboardAction( 0 )
{
setToolTip( i18n( "List and run bookmarks, or create new ones" ) );
- connect( this, SIGNAL(clicked(bool)), this, SLOT(showMenu()) );
+ connect( this, &QAbstractButton::clicked, this, &BreadcrumbUrlMenuButton::showMenu );
}
BreadcrumbUrlMenuButton::~BreadcrumbUrlMenuButton()
{
}
void
BreadcrumbUrlMenuButton::generateMenu( const QPoint &pos )
{
DEBUG_BLOCK
BookmarkList list = The::amarokUrlHandler()->urlsByCommand( m_urlsCommand );
QMenu * menu = new QMenu();
menu->setTitle( i18n("Amarok Bookmarks" ) );
if( m_urlsCommand == "navigate" )
menu->addAction( Amarok::actionCollection()->action( "bookmark_browser" ) );
else if( m_urlsCommand == "playlist" )
{
menu->addAction( Amarok::actionCollection()->action( "bookmark_playlistview" ) );
debug()<<"Adding bookmark playlist action";
}
else if( m_urlsCommand == "context" )
{
menu->addAction( Amarok::actionCollection()->action( "bookmark_contextview" ) );
debug()<<"Adding bookmark context view action";
}
else
warning()<<"Bad URL command.";
if( !m_copyToClipboardAction )
{
m_copyToClipboardAction = new QAction( QIcon::fromTheme( "klipper" ), i18n( "Copy Current View Bookmark to Clipboard" ), this );
- connect( m_copyToClipboardAction, SIGNAL(triggered(bool)), this, SLOT(copyCurrentToClipboard()) );
+ connect( m_copyToClipboardAction, &QAction::triggered, this, &BreadcrumbUrlMenuButton::copyCurrentToClipboard );
}
menu->addAction( m_copyToClipboardAction );
menu->addAction( Amarok::actionCollection()->action( "bookmark_manager" ) );
menu->addSeparator();
foreach( AmarokUrlPtr url, list )
{
menu->addAction( new AmarokUrlAction( url, menu ) );
}
debug() << "showing menu at " << pos;
menu->exec( pos );
delete menu;
}
void
BreadcrumbUrlMenuButton::showMenu()
{
QPoint pos( 0, height() );
generateMenu( mapToGlobal( pos ) );
}
void
BreadcrumbUrlMenuButton::copyCurrentToClipboard()
{
QString urlString;
if( m_urlsCommand == "navigate" )
{
AmarokUrl url = The::amarokUrlHandler()->createBrowserViewBookmark();
urlString = url.url();
}
else if( m_urlsCommand == "playlist" )
{
AmarokUrl url = The::amarokUrlHandler()->createPlaylistViewBookmark();
urlString = url.url();
}
else if( m_urlsCommand == "context" )
{
AmarokUrl url = The::amarokUrlHandler()->createContextViewBookmark();
urlString = url.url();
}
QApplication::clipboard()->setText( urlString );
}
diff --git a/src/widgets/FilenameLayoutWidget.cpp b/src/widgets/FilenameLayoutWidget.cpp
index 6d017436e4..e8b7738a6c 100644
--- a/src/widgets/FilenameLayoutWidget.cpp
+++ b/src/widgets/FilenameLayoutWidget.cpp
@@ -1,461 +1,463 @@
/****************************************************************************************
* Copyright (c) 2008 Téo Mrnjavac <teo@kde.org> *
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Daniel Dewald <Daniel.Dewald@time.shift.de> *
* Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "FilenameLayoutWidget.h"
#include "TokenDropTarget.h"
#include "TokenPool.h"
#include "amarokconfig.h"
#include "MetaValues.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "core/meta/support/MetaConstants.h"
#include <KInputDialog>
#include <KLineEdit>
#include <KLocalizedString>
#include <QComboBox>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QBoxLayout>
#include <QStackedWidget>
// the order of these strings depends on the order of the
// Type enum.
static const QStringList typeElements = ( QStringList()
<< QString()
<< QLatin1String("%ignore%")
<< QLatin1String("%track%")
<< QLatin1String("%title%")
<< QLatin1String("%artist%")
<< QLatin1String("%composer%")
<< QLatin1String("%year%")
<< QLatin1String("%album%")
<< QLatin1String("%albumartist%")
<< QLatin1String("%comment%")
<< QLatin1String("%genre%")
<< QLatin1String("%filetype%")
<< QLatin1String("%folder%")
<< QLatin1String("%initial%")
<< QLatin1String("%discnumber%")
<< QLatin1String(" ")
<< QLatin1String("/")
<< QLatin1String(".")
<< QLatin1String("-")
<< QLatin1String("_")
<< QLatin1String("%collectionroot%") );
using namespace Meta;
// ------------------------- FilenameLayoutWidget -------------------
FilenameLayoutWidget::FilenameLayoutWidget( QWidget *parent )
: QWidget( parent )
, m_advancedMode( false )
{
m_mainLayout = new QVBoxLayout( this );
m_mainLayout->setContentsMargins( 0, 0, 0, 0 );
QGroupBox* schemeGroup = new QGroupBox( i18n("Scheme"), this );
QVBoxLayout* schemeGroupLayout = new QVBoxLayout( schemeGroup );
// --- presets
QHBoxLayout* presetLayout1 = new QHBoxLayout();
QLabel* presetLabel = new QLabel( i18n("Preset:"), this );
presetLayout1->addWidget( presetLabel, 0 );
m_presetCombo = new QComboBox( this );
m_presetCombo->setWhatsThis( i18n("A list of selectable filename scheme/format presets." ) );
presetLayout1->addWidget( m_presetCombo, 1 );
// - the preset buttons
m_addPresetButton = new QPushButton( i18n("Add preset"), this );
m_addPresetButton->setToolTip( i18n("Saves the current scheme/format as new preset.") );
presetLayout1->addWidget( m_addPresetButton, 0 );
m_updatePresetButton = new QPushButton( i18n("Update preset"), this );
m_updatePresetButton->setToolTip( i18n("Updates the preset with the current scheme/format.") );
presetLayout1->addWidget( m_updatePresetButton, 0 );
m_removePresetButton = new QPushButton( i18n("Remove preset"), this );
m_removePresetButton->setToolTip( i18n("Removes the currently selected preset.") );
presetLayout1->addWidget( m_removePresetButton, 0 );
schemeGroupLayout->addLayout( presetLayout1 );
// --- stacked widget
m_schemeStack = new QStackedWidget( this );
// -- simple schema
QWidget* simpleLayoutWidget = new QWidget( this );
QVBoxLayout *simpleLayout = new QVBoxLayout( simpleLayoutWidget );
// a token pool
m_tokenPool = new TokenPool( this );
simpleLayout->addWidget( m_tokenPool, 1 );
// token drop target inside a frame
QFrame* dropTargetFrame = new QFrame( this );
dropTargetFrame->setFrameShape(QFrame::StyledPanel);
dropTargetFrame->setFrameShadow(QFrame::Sunken);
m_dropTarget = new TokenDropTarget( this );
m_dropTarget->setRowLimit( 1 );
m_schemaLineLayout = new QHBoxLayout();
m_schemaLineLayout->setSpacing( 0 );
m_schemaLineLayout->setContentsMargins( 0, 0, 0, 0 );
m_schemaLineLayout->addWidget( m_dropTarget );
dropTargetFrame->setLayout( m_schemaLineLayout );
simpleLayout->addWidget( dropTargetFrame, 0 );
m_schemeStack->addWidget( simpleLayoutWidget );
// -- advanced schema
QWidget* advancedLayoutWidget = new QWidget( this );
QVBoxLayout *advancedLayout = new QVBoxLayout( advancedLayoutWidget );
m_syntaxLabel = new QLabel( this ); // placeholder for format description
advancedLayout->addWidget( m_syntaxLabel );
m_filenameLayoutEdit = new KLineEdit( this );
advancedLayout->addWidget( m_filenameLayoutEdit );
m_schemeStack->addWidget( advancedLayoutWidget );
schemeGroupLayout->addWidget( m_schemeStack );
m_advancedButton = new QPushButton( i18n("Advanced"), this );
schemeGroupLayout->addWidget( m_advancedButton );
// --
m_mainLayout->addWidget( schemeGroup );
- connect( m_tokenPool, SIGNAL(onDoubleClick(Token*)),
- m_dropTarget, SLOT(insertToken(Token*)) );
- connect( m_advancedButton, SIGNAL(clicked()),
- this, SLOT(toggleAdvancedMode()) );
- connect( m_dropTarget, SIGNAL(changed()),
- this, SIGNAL(schemeChanged()) );
- connect( m_dropTarget, SIGNAL(changed()),
- this, SLOT(slotUpdatePresetButton()) );
- connect( m_addPresetButton, SIGNAL(clicked(bool)),
- this, SLOT(slotAddFormat()) );
- connect( m_removePresetButton, SIGNAL(clicked(bool)),
- this, SLOT(slotRemoveFormat()) );
- connect( m_updatePresetButton, SIGNAL(clicked(bool)),
- this, SLOT(slotUpdateFormat()) );
-
- connect( m_filenameLayoutEdit, SIGNAL(textChanged(QString)),
- this, SIGNAL(schemeChanged()) );
- connect( m_filenameLayoutEdit, SIGNAL(textChanged(QString)),
- this, SLOT(slotUpdatePresetButton()) );
+ connect( m_tokenPool, &TokenPool::onDoubleClick,
+ m_dropTarget, &TokenDropTarget::appendToken );
+ connect( m_advancedButton, &QAbstractButton::clicked,
+ this, &FilenameLayoutWidget::toggleAdvancedMode );
+ connect( m_dropTarget, &TokenDropTarget::changed,
+ this, &FilenameLayoutWidget::schemeChanged );
+ connect( m_dropTarget, &TokenDropTarget::changed,
+ this, &FilenameLayoutWidget::slotUpdatePresetButton );
+ connect( m_addPresetButton, &QPushButton::clicked,
+ this, &FilenameLayoutWidget::slotAddFormat );
+ connect( m_removePresetButton, &QPushButton::clicked,
+ this, &FilenameLayoutWidget::slotRemoveFormat );
+ connect( m_updatePresetButton, &QPushButton::clicked,
+ this, &FilenameLayoutWidget::slotUpdateFormat );
+
+ connect( m_filenameLayoutEdit, &KLineEdit::textChanged,
+ this, &FilenameLayoutWidget::schemeChanged );
+ connect( m_filenameLayoutEdit, &KLineEdit::textChanged,
+ this, &FilenameLayoutWidget::slotUpdatePresetButton );
}
Token*
FilenameLayoutWidget::createToken(qint64 value) const
{
struct TokenDefinition
{
QString name;
QString iconName;
Type value;
};
static const TokenDefinition tokenDefinitions[] = {
{ i18nForField( valTrackNr ), iconForField( valTrackNr ), TrackNumber },
{ i18nForField( valDiscNr ), iconForField( valDiscNr ), DiscNumber },
{ i18nForField( valTitle ), iconForField( valTitle ), Title },
{ i18nForField( valArtist ), iconForField( valArtist ), Artist },
{ i18nForField( valComposer ), iconForField( valComposer ), Composer },
{ i18nForField( valYear ), iconForField( valYear ), Year },
{ i18nForField( valAlbum ), iconForField( valAlbum ), Album },
{ i18nForField( valAlbumArtist ), iconForField( valAlbumArtist ), AlbumArtist },
{ i18nForField( valComment ), iconForField( valComment ), Comment },
{ i18nForField( valGenre ), iconForField( valGenre ), Genre },
{ i18nForField( valFormat ), iconForField( valFormat ), FileType },
{ i18n( "Ignore" ), "filename-ignore-amarok", Ignore },
{ i18n( "Folder" ), "filename-folder-amarok", Folder },
{ i18nc( "Artist's Initial", "Initial" ), "filename-initial-amarok", Initial },
{ "/", "filename-slash-amarok", Slash },
{ "_", "filename-underscore-amarok", Underscore },
{ "-", "filename-dash-amarok", Dash },
{ ".", "filename-dot-amarok", Dot },
{ " ", "filename-space-amarok", Space },
{ i18n( "Collection root" ), "drive-harddisk", CollectionRoot },
{ QString(), 0, Space }
};
for( int i = 0; !tokenDefinitions[i].name.isNull(); ++i )
{
if( value == tokenDefinitions[i].value )
{
return new Token( tokenDefinitions[i].name,
tokenDefinitions[i].iconName,
static_cast<qint64>(tokenDefinitions[i].value) );
}
}
return 0;
}
Token*
FilenameLayoutWidget::createStaticToken(qint64 value) const
{
Token* token = createToken( value );
token->setEnabled( false );
token->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
return token;
}
//Stores the configuration when the dialog is accepted.
void
FilenameLayoutWidget::onAccept() //SLOT
{
QString custom = getParsableScheme();
// Custom scheme is stored per dialog
debug() << "--- saving custom scheme for" << m_configCategory << custom;
Amarok::config( m_configCategory ).writeEntry( "Custom Scheme", custom );
}
QString
FilenameLayoutWidget::getParsableScheme() const
{
return m_advancedMode ? m_filenameLayoutEdit->text() : dropTargetScheme();
}
// attempts to set the scheme
void FilenameLayoutWidget::setScheme(const QString& scheme)
{
m_filenameLayoutEdit->setText( scheme );
inferScheme( scheme );
slotUpdatePresetButton();
emit schemeChanged();
}
//Handles the modifications to the dialog to toggle between advanced and basic editing mode.
void
FilenameLayoutWidget::toggleAdvancedMode()
{
setAdvancedMode( !m_advancedMode );
}
//handles switching between basic and advanced mode
void
FilenameLayoutWidget::setAdvancedMode( bool isAdvanced )
{
setScheme( getParsableScheme() ); // setScheme set's both the edit and the drop target
m_advancedMode = isAdvanced;
if( isAdvanced )
{
m_advancedButton->setText( i18n( "&Basic..." ) );
m_schemeStack->setCurrentIndex( 1 );
}
else // set Basic mode
{
m_advancedButton->setText( i18n( "&Advanced..." ) );
m_schemeStack->setCurrentIndex( 0 );
}
QString entryValue = m_advancedMode ? "Advanced" : "Basic";
Amarok::config( m_configCategory ).writeEntry( "Mode", entryValue );
}
QString
FilenameLayoutWidget::dropTargetScheme() const
{
QString parsableScheme = "";
QList< Token *> list = m_dropTarget->tokensAtRow();
foreach( Token *token, list )
{
parsableScheme += typeElements[token->value()];
}
return parsableScheme;
}
void
FilenameLayoutWidget::inferScheme( const QString &s ) //SLOT
{
DEBUG_BLOCK
debug() << "infering scheme: " << s;
m_dropTarget->clear();
for( int i = 0; i < s.size(); )
{
// - search if there is a type with the matching string
// representation.
bool found = false;
for( int j = 1; j < typeElements.size() && !found; j++ )
{
int typeNameLength = typeElements[j].length();
Type type = static_cast<Type>(j);
if( s.midRef( i, typeNameLength ) == typeElements[j] )
{
- m_dropTarget->insertToken( createToken( type ) );
+ m_dropTarget->appendToken( createToken( type ) );
i += typeNameLength;
found = true;
}
}
if( !found )
{
debug() << "'" << s.at(i) << "' can't be represented as TokenLayoutWidget Token";
++i; // skip junk
}
}
}
void
FilenameLayoutWidget::populateConfiguration()
{
QString mode = Amarok::config( m_configCategory ).readEntry( "Mode" );
setAdvancedMode( mode == QLatin1String( "Advanced" ) );
// Custom scheme is stored per dialog
QString custom = Amarok::config( m_configCategory ).readEntryUntranslated( "Custom Scheme" );
debug() << "--- got custom scheme for" << m_configCategory << custom;
populateFormatList( custom );
setScheme( custom );
}
void
FilenameLayoutWidget::populateFormatList( const QString& custom )
{
DEBUG_BLOCK
// Configuration is not symmetric: dialog-specific settings are saved
// using m_configCategory, that is different per dialog. The presets are saved
// only in one single place, so these can be shared. This place is the "default" one,
// that is the configuration for OrganizeCollectionDialog.
// items are stored in the config list in the following format:
// Label#DELIM#format string
QStringList presets_raw;
int selected_index = -1;
m_presetCombo->clear();
presets_raw = AmarokConfig::formatPresets(); // Always use the one in OrganizeCollectionDialog
// presets_raw = Amarok::config( m_configCategory ).readEntry( QString::fromLatin1( "Format Presets" ), QStringList() );
debug() << "--- got presets" << presets_raw;
foreach( const QString &str, presets_raw )
{
QStringList items;
items = str.split( "#DELIM#", QString::SkipEmptyParts );
if( items.size() < 2 )
continue;
m_presetCombo->addItem( items.at( 0 ), items.at( 1 ) ); // Label, format string
if( items.at( 1 ) == custom )
selected_index = m_presetCombo->findData( items.at( 1 ) );
}
if( selected_index >= 0 )
m_presetCombo->setCurrentIndex( selected_index );
- connect( m_presetCombo, SIGNAL(activated(int)), this, SLOT(slotFormatPresetSelected(int)) );
- connect( m_presetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFormatPresetSelected(int)) );
+ connect( m_presetCombo, QOverload<int>::of(&QComboBox::activated),
+ this, &FilenameLayoutWidget::slotFormatPresetSelected );
+ connect( m_presetCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &FilenameLayoutWidget::slotFormatPresetSelected );
}
void
FilenameLayoutWidget::saveFormatList() const
{
DEBUG_BLOCK
QStringList presets_raw;
int n = m_presetCombo->count();
for( int i = 0; i < n; ++i )
{
QString item = "%1#DELIM#%2";
QString scheme = m_presetCombo->itemData( i ).toString();
QString label = m_presetCombo->itemText( i );
item = item.arg( label, scheme );
presets_raw.append( item );
}
debug() << "--- saving presets" << presets_raw;
AmarokConfig::setFormatPresets( presets_raw ); // Always use the one in OrganizeCollectionDialog
// Amarok::config( m_configCategory ).writeEntry( QString::fromLatin1( "Format Presets" ), presets_raw );
}
void
FilenameLayoutWidget::slotUpdatePresetButton()
{
QString comboScheme = m_presetCombo->itemData( m_presetCombo->currentIndex() ).toString();
m_updatePresetButton->setEnabled( comboScheme != getParsableScheme() );
}
void
FilenameLayoutWidget::slotFormatPresetSelected( int index )
{
QString scheme = m_presetCombo->itemData( index ).toString();
setScheme( scheme );
}
void
FilenameLayoutWidget::slotAddFormat()
{
bool ok = false;
QString name = KInputDialog::getText( i18n( "New Preset" ), i18n( "Preset Name" ), i18n( "New Preset" ), &ok, this );
if( !ok )
return; // user canceled.
QString format = getParsableScheme();
m_presetCombo->addItem( name, format );
m_presetCombo->setCurrentIndex( m_presetCombo->count() - 1 );
saveFormatList();
}
void
FilenameLayoutWidget::slotRemoveFormat()
{
int idx = m_presetCombo->currentIndex();
m_presetCombo->removeItem( idx );
saveFormatList();
}
void
FilenameLayoutWidget::slotUpdateFormat()
{
int idx = m_presetCombo->currentIndex();
QString formatString = getParsableScheme();
m_presetCombo->setItemData( idx, formatString );
m_updatePresetButton->setEnabled( false );
saveFormatList();
}
diff --git a/src/widgets/IconButton.cpp b/src/widgets/IconButton.cpp
index 3789e1cd03..c18ea51a33 100644
--- a/src/widgets/IconButton.cpp
+++ b/src/widgets/IconButton.cpp
@@ -1,162 +1,162 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "IconButton.h"
#include "SvgHandler.h"
#include <QMouseEvent>
#include <QPainter>
#include <QSizePolicy>
#include <QTimerEvent>
#include <QToolBar>
IconButton::IconButton( QWidget *parent ) : QWidget( parent )
, m_isClick( false )
{
m_anim.step = 0;
m_anim.timer = 0;
setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
// cannot use paletteChanged() from the palette handler directly since the
// svg handler also watches it for retinting. So upon palette change, we are
// called first and the old svg icons will be used.
- connect( The::svgHandler(), SIGNAL(retinted()), SLOT(svgRetinted()) );
+ connect( The::svgHandler(), &SvgHandler::retinted, this, &IconButton::svgRetinted );
}
void IconButton::setIcon( const QImage &img, int steps )
{
m_anim.step = 0;
m_anim.steps = steps;
m_icon = img;
m_oldIcon = steps ? m_buffer.image : QImage();
if ( m_anim.timer )
killTimer( m_anim.timer );
if ( steps )
m_anim.timer = startTimer( 40 );
else
updateIconBuffer();
repaint();
}
void IconButton::mousePressEvent( QMouseEvent *me )
{
me->accept();
m_isClick = true;
}
void IconButton::mouseReleaseEvent( QMouseEvent *me )
{
me->accept();
if ( m_isClick && rect().contains( me->pos() ) )
{
m_isClick = false;
emit clicked();
}
}
void IconButton::paintEvent( QPaintEvent * )
{
QPainter p(this);
p.drawPixmap( 0,0, m_buffer.pixmap );
p.end();
}
void IconButton::svgRetinted()
{
reloadContent( size() );
}
void IconButton::resizeEvent( QResizeEvent *re )
{
if( width() != height() )
resize( height(), height() );
else
{
reloadContent( re->size() );
QWidget::resizeEvent( re );
}
}
QSize IconButton::sizeHint() const
{
if ( QToolBar *toolBar = qobject_cast<QToolBar*>( parentWidget() ) )
return toolBar->iconSize();
return QSize( 32, 32 );
}
void IconButton::timerEvent( QTimerEvent *te )
{
if ( te->timerId() != m_anim.timer )
return;
++m_anim.step;
updateIconBuffer();
if ( m_anim.step >= m_anim.steps )
{
killTimer( m_anim.timer );
m_anim.timer = 0;
m_oldIcon = QImage();
}
repaint();
}
static QImage adjusted( QImage img, const QSize &sz )
{
if ( img.size() == sz )
return img;
QImage ret( sz, QImage::Format_ARGB32_Premultiplied );
ret.fill( Qt::transparent );
QPainter p( &ret );
p.drawImage( (ret.width() - img.width()) /2, (ret.height() - img.height()) /2, img );
p.end();
return ret;
}
static inline QImage interpolated( const QImage &img1, const QImage &img2, int a1, int a2 )
{
const int a = a1 + a2;
if (!a)
return img1.copy();
QImage img( img1.size(), img1.format() );
const uchar *src[2] = { img1.bits(), img2.bits() };
uchar *dst = img.bits();
const int n = img.width()*img.height()*4;
for ( int i = 0; i < n; ++i )
{
*dst = ((*src[0]*a1 + *src[1]*a2)/a) & 0xff;
++dst; ++src[0]; ++src[1];
}
return img;
}
void IconButton::updateIconBuffer()
{
if ( m_anim.step >= m_anim.steps )
m_buffer.image = adjusted( m_icon, size() );
else
m_buffer.image = interpolated( adjusted( m_oldIcon, size() ), adjusted( m_icon, size() ),
m_anim.steps - m_anim.step, m_anim.step );
m_buffer.pixmap = QPixmap::fromImage( m_buffer.image );
}
diff --git a/src/widgets/MetaQueryWidget.cpp b/src/widgets/MetaQueryWidget.cpp
index d48dcd97d8..536215648d 100644
--- a/src/widgets/MetaQueryWidget.cpp
+++ b/src/widgets/MetaQueryWidget.cpp
@@ -1,1212 +1,1204 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "core-impl/collections/support/CollectionManager.h"
#include "core/collections/MetaQueryMaker.h"
#include "core/collections/QueryMaker.h"
#include "widgets/MetaQueryWidget.h"
#include "widgets/kdatecombo.h"
#include "FileType.h"
#include <typeinfo>
#include <QWidget>
#include <QLineEdit>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QListView>
#include <QTimeEdit>
-#include <KComboBox>
#include <QIcon>
#include <KLocale>
#include <klocalizeddate.h>
-#include <KNumInput>
#include <KRatingWidget>
using namespace Amarok;
static const int maxHours = 24;
TimeDistanceWidget::TimeDistanceWidget( QWidget *parent )
: QWidget( parent )
{
m_timeEdit = new KIntSpinBox(this);
m_timeEdit->setMinimum( 0 );
m_timeEdit->setMaximum( 600 );
m_unitSelection = new KComboBox(this);
- connect( m_timeEdit, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateComboBoxLabels(int)) );
+ connect( m_timeEdit, QOverload<int>::of(&KIntSpinBox::valueChanged),
+ this, &TimeDistanceWidget::slotUpdateComboBoxLabels );
for (int i = 0; i < 7; ++i) {
m_unitSelection->addItem( QString() );
}
slotUpdateComboBoxLabels( 0 );
QHBoxLayout *hLayout = new QHBoxLayout(this);
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->addWidget( m_timeEdit );
hLayout->addWidget( m_unitSelection );
}
qint64 TimeDistanceWidget::timeDistance() const
{
qint64 time = m_timeEdit->value();
switch( m_unitSelection->currentIndex() )
{
case 6:
time *= 365*24*60*60; // years
break;
case 5:
time *= 30*24*60*60; // months
break;
case 4:
time *= 7*24*60*60; // weeks
break;
case 3:
time *= 24; // days
case 2:
time *= 60; // hours
case 1:
time *= 60; // minutes
}
return time;
}
void TimeDistanceWidget::setTimeDistance( qint64 value )
{
// as we don't store the time unit we try to reconstuct it
int unit = 0;
if( value > 600 || !(value % 60) ) {
unit = 1;
value /= 60;
if( value > 600 || !(value % 60) ) {
unit = 2;
value /= 60;
if( value > 72 || !(value % 24) ) {
unit = 3;
value /= 24;
if( !(value % 365) ) {
unit = 6;
value /= 365;
} else if( !(value % 30) ) {
unit = 5;
value /= 30;
} else if( !(value % 7) ) {
unit = 4;
value /= 7;
}
}
}
}
m_unitSelection->setCurrentIndex( unit );
m_timeEdit->setValue( value );
}
-void TimeDistanceWidget::connectChanged( QObject *receiver, const char *slot )
-{
- connect( m_timeEdit, SIGNAL(valueChanged(QString)), receiver, slot );
- connect( m_unitSelection, SIGNAL(currentIndexChanged(int)), receiver, slot );
-}
-
-
void TimeDistanceWidget::slotUpdateComboBoxLabels( int value )
{
m_unitSelection->setItemText(0, i18np("second", "seconds", value));
m_unitSelection->setItemText(1, i18np("minute", "minutes", value));
m_unitSelection->setItemText(2, i18np("hour", "hours", value));
m_unitSelection->setItemText(3, i18np("day", "days", value));
m_unitSelection->setItemText(4, i18np("week", "weeks", value));
m_unitSelection->setItemText(5, i18np("month", "months", value));
m_unitSelection->setItemText(6, i18np("year", "years", value));
}
void
MetaQueryWidget::Filter::setField( qint64 newField )
{
if( m_field == newField )
return;
// -- reset the value and the condition if the new filter has another type
if( MetaQueryWidget::isNumeric( m_field ) != MetaQueryWidget::isNumeric( newField ) )
{
value.clear();
if( MetaQueryWidget::isNumeric( newField ) )
condition = Equals;
else
condition = Contains;
}
if( !MetaQueryWidget::isDate( m_field ) && MetaQueryWidget::isDate( newField ) )
{
numValue = QDateTime::currentDateTime().toTime_t();
numValue2 = QDateTime::currentDateTime().toTime_t();
}
else
{
numValue = 0;
numValue2 = 0;
}
if (numValue < minimumValue( newField ) || numValue > maximumValue( newField ) )
numValue = defaultValue( newField );
if (numValue2 < minimumValue( newField ) || numValue2 > maximumValue( newField ) )
numValue2 = defaultValue( newField );
m_field = newField;
}
qint64
MetaQueryWidget::Filter::minimumValue( quint64 field )
{
switch( field )
{
case Meta::valYear: return 1900;
case Meta::valTrackNr: return 0;
case Meta::valDiscNr: return 0;
case Meta::valBpm: return 60;
case Meta::valBitrate: return 60;
case Meta::valSamplerate: return 8000;
case Meta::valFilesize: return 0;
case Meta::valScore: return 0;
case Meta::valPlaycount: return 0;
case Meta::valRating: return 0;
case Meta::valLength: return 0;
default: return 0;
}
}
qint64
MetaQueryWidget::Filter::maximumValue( quint64 field )
{
switch( field )
{
case Meta::valYear: return 2300;
case Meta::valTrackNr: return 100;
case Meta::valDiscNr: return 10;
case Meta::valBpm: return 200;
case Meta::valBitrate: return 2000;
case Meta::valSamplerate: return 48000;
case Meta::valFilesize: return 1000;
case Meta::valScore: return 100;
case Meta::valPlaycount: return 1000;
case Meta::valRating: return 10;
case Meta::valLength: return maxHours * 60 * 60 - 1;
default: return 0;
}
}
qint64
MetaQueryWidget::Filter::defaultValue( quint64 field )
{
switch( field )
{
case Meta::valYear: return 1976;
case Meta::valTrackNr: return 0;
case Meta::valDiscNr: return 0;
case Meta::valBpm: return 80;
case Meta::valBitrate: return 160;
case Meta::valSamplerate: return 44100;
case Meta::valFilesize: return 10;
case Meta::valScore: return 0;
case Meta::valPlaycount: return 00;
case Meta::valRating: return 0;
case Meta::valLength: return 3 * 60 + 59;
default: return 0;
}
}
MetaQueryWidget::MetaQueryWidget( QWidget* parent, bool onlyNumeric, bool noCondition )
: QWidget( parent )
, m_onlyNumeric( onlyNumeric )
, m_noCondition( noCondition )
, m_settingFilter( false )
, m_andLabel(0)
, m_compareSelection(0)
, m_valueSelection1(0)
, m_valueSelection2(0)
{
// note: we are using the strange layout structure because the KRatingWidget size depends on the height.
m_layoutMain = new QVBoxLayout( this );
m_layoutMain->setContentsMargins(0, 0, 0, 0);
makeFieldSelection();
m_layoutMain->addWidget( m_fieldSelection );
m_layoutValue = new QHBoxLayout();
m_layoutMain->addLayout(m_layoutValue);
m_layoutValueLabels = new QVBoxLayout();
m_layoutValue->addLayout(m_layoutValueLabels, 0);
m_layoutValueValues = new QVBoxLayout();
m_layoutValue->addLayout(m_layoutValueValues, 1);
if( m_onlyNumeric )
m_filter.setField( Meta::valYear );
else
m_filter.setField( 0 );
setFilter(m_filter);
}
MetaQueryWidget::~MetaQueryWidget()
{
}
MetaQueryWidget::Filter
MetaQueryWidget::filter() const
{
// special handling for between
if( m_filter.condition == Contains )
{
Filter f = m_filter;
f.numValue = qMin(m_filter.numValue, m_filter.numValue2) - 1;
f.numValue2 = qMax(m_filter.numValue, m_filter.numValue2) + 1;
}
return m_filter;
}
void
MetaQueryWidget::setFilter( const MetaQueryWidget::Filter &value )
{
m_settingFilter = true;
m_filter = value;
int index = m_fieldSelection->findData( int(m_filter.field()) );
m_fieldSelection->setCurrentIndex( index == -1 ? 0 : index );
if( !m_noCondition )
makeCompareSelection();
makeValueSelection();
setValueSelection();
m_settingFilter = false;
emit changed(m_filter);
}
static void addIconItem( KComboBox *box, qint64 field )
{
QString icon = Meta::iconForField( field );
QString text = Meta::i18nForField( field );
if( icon.isEmpty() )
box->addItem( text, field );
else
box->addItem( QIcon::fromTheme( icon ), text, field );
}
void
MetaQueryWidget::makeFieldSelection()
{
m_fieldSelection = new KComboBox( this );
if (!m_onlyNumeric)
{
m_fieldSelection->addItem( i18n( "Simple Search" ), 0 );
addIconItem( m_fieldSelection, Meta::valUrl );
// note: what about directory?
addIconItem( m_fieldSelection, Meta::valTitle );
addIconItem( m_fieldSelection, Meta::valArtist );
addIconItem( m_fieldSelection, Meta::valAlbumArtist );
addIconItem( m_fieldSelection, Meta::valAlbum );
addIconItem( m_fieldSelection, Meta::valGenre );
addIconItem( m_fieldSelection, Meta::valComposer );
}
addIconItem( m_fieldSelection, Meta::valYear );
if (!m_onlyNumeric)
addIconItem( m_fieldSelection, Meta::valComment );
addIconItem( m_fieldSelection, Meta::valTrackNr );
addIconItem( m_fieldSelection, Meta::valDiscNr );
addIconItem( m_fieldSelection, Meta::valBpm );
addIconItem( m_fieldSelection, Meta::valLength );
addIconItem( m_fieldSelection, Meta::valBitrate );
addIconItem( m_fieldSelection, Meta::valSamplerate );
addIconItem( m_fieldSelection, Meta::valFilesize );
if (!m_onlyNumeric)
addIconItem( m_fieldSelection, Meta::valFormat );
addIconItem( m_fieldSelection, Meta::valCreateDate );
addIconItem( m_fieldSelection, Meta::valScore );
addIconItem( m_fieldSelection, Meta::valRating );
addIconItem( m_fieldSelection, Meta::valFirstPlayed );
addIconItem( m_fieldSelection, Meta::valLastPlayed );
addIconItem( m_fieldSelection, Meta::valPlaycount );
if (!m_onlyNumeric)
addIconItem( m_fieldSelection, Meta::valLabel );
addIconItem( m_fieldSelection, Meta::valModified );
- connect( m_fieldSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldChanged(int)) );
+ connect( m_fieldSelection, QOverload<int>::of(&KComboBox::currentIndexChanged),
+ this, &MetaQueryWidget::fieldChanged );
}
void
MetaQueryWidget::fieldChanged( int i )
{
if( m_settingFilter )
return;
qint64 field = 0;
if( i<0 || i>=m_fieldSelection->count() )
field = m_fieldSelection->itemData( 0 ).toInt();
else
field = m_fieldSelection->itemData( i ).toInt();
m_filter.setField( field );
// in the fieldChanged slot we assume that the field was really changed,
// so we don't have a problem with throwing away all the old widgets
if( !m_noCondition )
makeCompareSelection();
makeValueSelection();
setValueSelection();
emit changed(m_filter);
}
void
MetaQueryWidget::compareChanged( int index )
{
FilterCondition condition = FilterCondition( m_compareSelection->itemData( index ).toInt() );
if( m_filter.condition == condition )
return; // nothing to do
if( m_filter.isDate() )
{
if( ( condition == OlderThan || condition == NewerThan )
&& m_filter.condition != OlderThan && m_filter.condition != NewerThan
)
{
// fix some inaccuracies caused by the conversion absoulte/relative time specifications
// this is actually just for visual consistency
int unit = 0;
qint64 value = QDateTime::currentDateTime().toTime_t() - m_filter.numValue;
if( value > 600 || !(value % 60) ) {
unit = 1;
value /= 60;
if( value > 600 || !(value % 60) ) {
unit = 2;
value /= 60;
if( value > 72 || !(value % 24) ) {
unit = 3;
value /= 24;
if( !(value % 365) ) {
unit = 6;
value /= 365;
} else if( !(value % 30) ) {
unit = 5;
value /= 30;
} else if( !(value % 7) ) {
unit = 4;
value /= 7;
}
}
}
}
switch( unit )
{
case 6:
value *= 365*24*60*60; // years
break;
case 5:
value *= 30*24*60*60; // months
break;
case 4:
value *= 7*24*60*60; // weeks
break;
case 3:
value *= 24; // days
case 2:
value *= 60; // hours
case 1:
value *= 60; // minutes
}
m_filter.numValue = value;
}
else if( condition != OlderThan && condition != NewerThan
&& ( m_filter.condition == OlderThan || m_filter.condition == NewerThan )
)
{
m_filter.numValue = QDateTime::currentDateTime().toTime_t() - m_filter.numValue;
}
}
m_filter.condition = condition;
// need to re-generate the value selection fields
makeValueSelection();
setValueSelection();
emit changed(m_filter);
}
void
MetaQueryWidget::valueChanged( const QString& value )
{
m_filter.value = value;
emit changed(m_filter);
}
void
MetaQueryWidget::numValueChanged( int value )
{
m_filter.numValue = value;
emit changed(m_filter);
}
void
MetaQueryWidget::numValue2Changed( int value )
{
m_filter.numValue2 = value;
emit changed(m_filter);
}
void
MetaQueryWidget::numValueChanged( qint64 value )
{
m_filter.numValue = value;
emit changed(m_filter);
}
void
MetaQueryWidget::numValue2Changed( qint64 value )
{
m_filter.numValue2 = value;
emit changed(m_filter);
}
void
MetaQueryWidget::numValueChanged( const QTime& value )
{
m_filter.numValue = qAbs( value.secsTo( QTime(0,0,0) ) );
emit changed(m_filter);
}
void
MetaQueryWidget::numValue2Changed( const QTime& value )
{
m_filter.numValue2 = qAbs( value.secsTo( QTime(0,0,0) ) );
emit changed(m_filter);
}
void
MetaQueryWidget::numValueDateChanged()
{
KDateCombo* dateSelection = qobject_cast<KDateCombo*>( sender() );
if( dateSelection )
{
QDate date;
dateSelection->getDate( &date );
m_filter.numValue = QDateTime( date ).toTime_t();
emit changed(m_filter);
}
}
void
MetaQueryWidget::numValue2DateChanged()
{
KDateCombo* dateSelection = qobject_cast<KDateCombo*>( sender() );
if( dateSelection )
{
QDate date;
dateSelection->getDate( &date );
m_filter.numValue2 = QDateTime( date ).toTime_t();
emit changed(m_filter);
}
}
void
MetaQueryWidget::numValueTimeDistanceChanged()
{
if( !sender() )
return;
// static_cast. Remember: the TimeDistanceWidget does not have a Q_OBJECT macro
TimeDistanceWidget* distanceSelection = static_cast<TimeDistanceWidget*>( sender()->parent() );
if( distanceSelection )
{
m_filter.numValue = distanceSelection->timeDistance();
emit changed(m_filter);
}
}
void
MetaQueryWidget::numValueFormatChanged(int index)
{
KComboBox* combo = static_cast<KComboBox*>(sender());
if( combo ) {
m_filter.numValue = combo->itemData( index ).toInt();
emit changed(m_filter);
}
}
void
MetaQueryWidget::setValueSelection()
{
if( m_compareSelection )
m_layoutValueLabels->addWidget( m_compareSelection );
if( m_filter.condition == Between )
{
delete m_andLabel; // delete the old label
m_andLabel = new QLabel( i18n( "and" ), this );
m_layoutValueLabels->addWidget( m_andLabel );
}
else
{
delete m_andLabel;
m_andLabel = 0;
}
if( m_valueSelection1 )
m_layoutValueValues->addWidget( m_valueSelection1 );
if( m_valueSelection2 )
m_layoutValueValues->addWidget( m_valueSelection2 );
}
void
MetaQueryWidget::makeCompareSelection()
{
delete m_compareSelection;
m_compareSelection = 0;
qint64 field = m_filter.field();
if( field == Meta::valFormat )
return; // the field is fixed
else if( isDate(field) )
{
m_compareSelection = new KComboBox();
m_compareSelection->addItem( conditionToString( Equals, field ), (int)Equals );
m_compareSelection->addItem( conditionToString( LessThan, field ), (int)LessThan );
m_compareSelection->addItem( conditionToString( GreaterThan, field ), (int)GreaterThan );
m_compareSelection->addItem( conditionToString( Between, field ), (int)Between );
m_compareSelection->addItem( conditionToString( OlderThan, field ), (int)OlderThan );
m_compareSelection->addItem( conditionToString( NewerThan, field ), (int)NewerThan );
}
else if( isNumeric(field) )
{
m_compareSelection = new KComboBox();
m_compareSelection->addItem( conditionToString( Equals, field ), (int)Equals );
m_compareSelection->addItem( conditionToString( LessThan, field ), (int)LessThan );
m_compareSelection->addItem( conditionToString( GreaterThan, field ), (int)GreaterThan );
m_compareSelection->addItem( conditionToString( Between, field ), (int)Between );
}
else
{
m_compareSelection = new KComboBox();
m_compareSelection->addItem( conditionToString( Contains, field ), (int)Contains );
m_compareSelection->addItem( conditionToString( Equals, field ), (int)Equals );
}
// -- select the correct entry (even if the condition is not one of the selection)
int index = m_compareSelection->findData( int(m_filter.condition) );
if( index == -1 )
{
index = 0;
m_filter.condition = FilterCondition(m_compareSelection->itemData( index ).toInt());
compareChanged(index);
}
m_compareSelection->setCurrentIndex( index == -1 ? 0 : index );
- connect( m_compareSelection, SIGNAL(currentIndexChanged(int)),
- SLOT(compareChanged(int)) );
+ connect( m_compareSelection, QOverload<int>::of(&KComboBox::currentIndexChanged),
+ this, &MetaQueryWidget::compareChanged );
}
void
MetaQueryWidget::makeValueSelection()
{
delete m_valueSelection1;
m_valueSelection1 = 0;
delete m_valueSelection2;
m_valueSelection2 = 0;
qint64 field = m_filter.field();
if( field == Meta::valUrl )
makeFilenameSelection();
else if( field == Meta::valTitle )
// We,re not going to populate this. There tends to be too many titles.
makeGenericComboSelection( true, 0 );
else if( field == Meta::valArtist ||
field == Meta::valAlbumArtist ||
field == Meta::valAlbum ||
field == Meta::valGenre ||
field == Meta::valComposer )
makeMetaComboSelection( field );
else if( field == Meta::valYear )
makeGenericNumberSelection( field );
else if( field == Meta::valComment )
makeGenericComboSelection( true, 0 );
else if( field == Meta::valTrackNr )
makeGenericNumberSelection( field );
else if( field == Meta::valDiscNr )
makeGenericNumberSelection( field );
else if( field == Meta::valBpm )
makeGenericNumberSelection( field );
else if( field == Meta::valLength )
makeLengthSelection();
else if( field == Meta::valBitrate )
makeGenericNumberSelection( field, i18nc("Unit for data rate kilo bit per seconds", "kbps") );
else if( field == Meta::valSamplerate )
makeGenericNumberSelection( field, i18nc("Unit for sample rate", "Hz") );
else if( field == Meta::valFilesize )
makeGenericNumberSelection( field, i18nc("Unit for file size in mega byte", "MiB") );
else if( field == Meta::valFormat )
makeFormatComboSelection();
else if( field == Meta::valCreateDate )
makeDateTimeSelection();
else if( field == Meta::valScore )
makeGenericNumberSelection( field );
else if( field == Meta::valRating )
makeRatingSelection();
else if( field == Meta::valFirstPlayed )
makeDateTimeSelection();
else if( field == Meta::valLastPlayed )
makeDateTimeSelection();
else if( field == Meta::valPlaycount )
makeGenericNumberSelection( field );
else if( field == Meta::valLabel )
makeGenericComboSelection( true, 0 );
else if( field == Meta::valModified )
makeDateTimeSelection();
else // e.g. the simple search
makeGenericComboSelection( true, 0 );
}
void
MetaQueryWidget::makeGenericComboSelection( bool editable, Collections::QueryMaker* populateQuery )
{
KComboBox* combo = new KComboBox( this );
combo->setEditable( editable );
if( populateQuery != 0 )
{
m_runningQueries.insert(populateQuery, QWeakPointer<KComboBox>(combo));
- connect( populateQuery, SIGNAL(newResultReady(QStringList)),
- SLOT(populateComboBox(QStringList)) );
- connect( populateQuery, SIGNAL(queryDone()),
- SLOT(comboBoxPopulated()) );
+ connect( populateQuery, &Collections::QueryMaker::newResultReady,
+ this, &MetaQueryWidget::populateComboBox );
+ connect( populateQuery, &Collections::QueryMaker::queryDone,
+ this, &MetaQueryWidget::comboBoxPopulated );
populateQuery->run();
}
combo->setEditText( m_filter.value );
- connect( combo, SIGNAL(editTextChanged(QString)),
- SLOT(valueChanged(QString)) );
+ connect( combo, &KComboBox::editTextChanged,
+ this, &MetaQueryWidget::valueChanged );
combo->completionObject()->setIgnoreCase( true );
combo->setCompletionMode( KCompletion::CompletionPopup );
combo->setInsertPolicy( QComboBox::InsertAtTop );
m_valueSelection1 = combo;
}
void
MetaQueryWidget::makeMetaComboSelection( qint64 field )
{
Collections::QueryMaker* qm = CollectionManager::instance()->queryMaker();
qm->setQueryType( Collections::QueryMaker::Custom );
qm->addReturnValue( field );
qm->setAutoDelete( true );
makeGenericComboSelection( true, qm );
}
void
MetaQueryWidget::populateComboBox( QStringList results )
{
QObject* query = sender();
if( !query )
return;
QWeakPointer<KComboBox> combo = m_runningQueries.value(query);
if( combo.isNull() )
return;
// note: adding items seems to reset the edit text, so we have
// to take care of that.
disconnect( combo.data(), 0, this, 0 );
// want the results unique and sorted
const QSet<QString> dataSet = results.toSet();
QStringList dataList = dataSet.toList();
dataList.sort();
combo.data()->addItems( dataList );
KCompletion* comp = combo.data()->completionObject();
comp->setItems( dataList );
// reset the text and re-enable the signal
combo.data()->setEditText( m_filter.value );
- connect( combo.data(), SIGNAL(editTextChanged(QString)),
- SLOT(valueChanged(QString)) );
+ connect( combo.data(), &KComboBox::editTextChanged,
+ this, &MetaQueryWidget::valueChanged );
}
void
MetaQueryWidget::makeFormatComboSelection()
{
KComboBox* combo = new KComboBox( this );
combo->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
QStringList filetypes = Amarok::FileTypeSupport::possibleFileTypes();
for (int listpos=0;listpos<filetypes.size();listpos++)
{
combo->addItem(filetypes.at(listpos),listpos);
}
int index = m_fieldSelection->findData( (int)m_filter.numValue );
combo->setCurrentIndex( index == -1 ? 0 : index );
- connect( combo,
- SIGNAL(currentIndexChanged(int)),
- SLOT(numValueFormatChanged(int)) );
+ connect( combo, QOverload<int>::of(&KComboBox::currentIndexChanged),
+ this, &MetaQueryWidget::numValueFormatChanged );
m_valueSelection1 = combo;
}
void
MetaQueryWidget::comboBoxPopulated()
{
QObject* query = sender();
if( !query )
return;
m_runningQueries.remove( query );
}
void
MetaQueryWidget::makeFilenameSelection()
{
// Don't populate the combobox. Too many urls.
makeGenericComboSelection( true, 0 );
}
void
MetaQueryWidget::makeRatingSelection()
{
KRatingWidget* ratingWidget = new KRatingWidget();
ratingWidget->setRating( (int)m_filter.numValue );
- connect( ratingWidget, SIGNAL(ratingChanged(int)),
- this, SLOT(numValueChanged(int)) );
+ connect( ratingWidget, QOverload<int>::of(&KRatingWidget::ratingChanged),
+ this, QOverload<int>::of(&MetaQueryWidget::numValueChanged) );
m_valueSelection1 = ratingWidget;
if( m_filter.condition != Between )
return;
// second KRatingWidget for the between selection
KRatingWidget* ratingWidget2 = new KRatingWidget();
ratingWidget2->setRating( (int)m_filter.numValue2 );
- connect( ratingWidget2, SIGNAL(ratingChanged(int)),
- this, SLOT(numValue2Changed(int)) );
+ connect( ratingWidget2, QOverload<int>::of(&KRatingWidget::ratingChanged),
+ this, QOverload<int>::of(&MetaQueryWidget::numValue2Changed) );
m_valueSelection2 = ratingWidget2;
}
void
MetaQueryWidget::makeLengthSelection()
{
QString displayFormat = i18nc( "time format for specifying track length - hours, minutes, seconds", "h:m:ss" );
QTimeEdit* timeSpin = new QTimeEdit();
timeSpin->setDisplayFormat( displayFormat );
timeSpin->setMinimumTime( QTime( 0, 0, 0 ) );
timeSpin->setMaximumTime( QTime( maxHours - 1, 59, 59 ) );
timeSpin->setTime( QTime().addSecs( m_filter.numValue ) );
- connect( timeSpin, SIGNAL(timeChanged(QTime)),
- SLOT(numValueChanged(QTime)) );
+ connect( timeSpin, &QTimeEdit::timeChanged,
+ this, QOverload<const QTime&>::of(&MetaQueryWidget::numValueChanged) );
m_valueSelection1 = timeSpin;
if( m_filter.condition != Between )
return;
QTimeEdit* timeSpin2 = new QTimeEdit();
timeSpin2->setDisplayFormat( displayFormat );
timeSpin2->setMinimumTime( QTime( 0, 0, 0 ) );
timeSpin2->setMaximumTime( QTime( maxHours - 1, 59, 59 ) );
timeSpin2->setTime( QTime().addSecs( m_filter.numValue2 ) );
- connect( timeSpin2, SIGNAL(timeChanged(QTime)),
- SLOT(numValue2Changed(QTime)) );
+ connect( timeSpin2, &QTimeEdit::timeChanged,
+ this, QOverload<const QTime&>::of(&MetaQueryWidget::numValue2Changed) );
m_valueSelection2 = timeSpin2;
}
void
MetaQueryWidget::makeGenericNumberSelection( qint64 field, const QString& unit )
{
KIntSpinBox* spin = new KIntSpinBox();
spin->setMinimum( Filter::minimumValue( field ) );
spin->setMaximum( Filter::maximumValue( field ) );
if( !unit.isEmpty() )
spin->setSuffix( ' ' + unit );
spin->setValue( m_filter.numValue );
- connect( spin, SIGNAL(valueChanged(int)),
- this, SLOT(numValueChanged(int)) );
+ connect( spin, QOverload<int>::of(&KIntSpinBox::valueChanged),
+ this, QOverload<int>::of(&MetaQueryWidget::numValueChanged) );
m_valueSelection1 = spin;
if( m_filter.condition != Between )
return;
// second spin box for the between selection
KIntSpinBox* spin2 = new KIntSpinBox();
spin2->setMinimum( Filter::minimumValue( field ) );
spin2->setMaximum( Filter::maximumValue( field ) );
if( !unit.isEmpty() )
spin2->setSuffix( ' ' + unit );
spin2->setValue( m_filter.numValue2 );
- connect( spin2, SIGNAL(valueChanged(int)),
- this, SLOT(numValue2Changed(int)) );
+ connect( spin2, QOverload<int>::of(&KIntSpinBox::valueChanged),
+ this, QOverload<int>::of(&MetaQueryWidget::numValue2Changed) );
m_valueSelection2 = spin2;
}
void
MetaQueryWidget::makeDateTimeSelection()
{
if( m_filter.condition == OlderThan || m_filter.condition == NewerThan )
{
TimeDistanceWidget* distanceSelection = new TimeDistanceWidget();
distanceSelection->setTimeDistance( m_filter.numValue );
- distanceSelection->connectChanged( this, SLOT(numValueTimeDistanceChanged()));
+ distanceSelection->connectChanged( this, &MetaQueryWidget::numValueTimeDistanceChanged);
m_valueSelection1 = distanceSelection;
}
else
{
KDateCombo* dateSelection = new KDateCombo();
QDateTime dt;
// if( m_filter.condition == Contains || m_filter.condition == Equals )
// dt = QDateTime::currentDateTime();
// else
// dt.setTime_t( m_filter.numValue );
dt.setTime_t( m_filter.numValue );
dateSelection->setDate( dt.date() );
- connect( dateSelection, SIGNAL(currentIndexChanged(int)),
- SLOT(numValueDateChanged()) );
+ connect( dateSelection, QOverload<int>::of(&KDateCombo::currentIndexChanged),
+ this, &MetaQueryWidget::numValueDateChanged );
m_valueSelection1 = dateSelection;
if( m_filter.condition != Between )
return;
// second KDateCombo for the between selection
KDateCombo* dateSelection2 = new KDateCombo();
dt.setTime_t( m_filter.numValue2 );
dateSelection2->setDate( dt.date() );
- connect( dateSelection2, SIGNAL(currentIndexChanged(int)),
- SLOT(numValue2DateChanged()) );
+ connect( dateSelection2, QOverload<int>::of(&KDateCombo::currentIndexChanged),
+ this, &MetaQueryWidget::numValue2DateChanged );
m_valueSelection2 = dateSelection2;
}
}
bool
MetaQueryWidget::isNumeric( qint64 field )
{
switch( field )
{
case Meta::valYear:
case Meta::valTrackNr:
case Meta::valDiscNr:
case Meta::valBpm:
case Meta::valLength:
case Meta::valBitrate:
case Meta::valSamplerate:
case Meta::valFilesize:
case Meta::valFormat:
case Meta::valCreateDate:
case Meta::valScore:
case Meta::valRating:
case Meta::valFirstPlayed:
case Meta::valLastPlayed:
case Meta::valPlaycount:
case Meta::valModified:
return true;
default:
return false;
}
}
bool
MetaQueryWidget::isDate( qint64 field )
{
switch( field )
{
case Meta::valCreateDate:
case Meta::valFirstPlayed:
case Meta::valLastPlayed:
case Meta::valModified:
return true;
default:
return false;
}
}
QString
MetaQueryWidget::conditionToString( FilterCondition condition, qint64 field )
{
if( isDate(field) )
{
switch( condition )
{
case LessThan:
return i18nc( "The date lies before the given fixed date", "before" );
case Equals:
return i18nc( "The date is the same as the given fixed date", "on" );
case GreaterThan:
return i18nc( "The date is after the given fixed date", "after" );
case Between:
return i18nc( "The date is between the given fixed dates", "between" );
case OlderThan:
return i18nc( "The date lies before the given time interval", "older than" );
case NewerThan:
return i18nc( "The date lies after the given time interval", "newer than" );
default:
; // fall through
}
}
else if( isNumeric(field) )
{
switch( condition )
{
case LessThan:
return i18n("less than");
case Equals:
return i18nc("a numerical tag (like year or track number) equals a value","equals");
case GreaterThan:
return i18n("greater than");
case Between:
return i18nc( "a numerical tag (like year or track number) is between two values", "between" );
default:
; // fall through
}
}
else
{
switch( condition )
{
case Equals:
return i18nc("an alphabetical tag (like title or artist name) equals some string","equals");
case Contains:
return i18nc("an alphabetical tag (like title or artist name) contains some string", "contains");
default:
; // fall through
}
}
return QString( i18n("unknown comparison") );
}
QString
MetaQueryWidget::Filter::fieldToString() const
{
return Meta::shortI18nForField( m_field );
}
QString MetaQueryWidget::Filter::toString( bool invert ) const
{
// this member is called when there is a keyword that needs numeric attributes
QString strValue1 = value;
QString strValue2 = value;
if( m_field == Meta::valFormat )
{
strValue1 = Amarok::FileTypeSupport::toString( Amarok::FileType( numValue ));
}
else if( m_field == Meta::valRating )
{
strValue1 = QString::number( (float)numValue / 2 );
strValue2 = QString::number( (float)numValue2 / 2 );
}
else if( isDate() )
{
if( condition == OlderThan || condition == NewerThan )
{
strValue1 = QString::number( numValue );
strValue2 = QString::number( numValue2 );
}
else
{
KLocalizedDate localizedDate1( QDateTime::fromTime_t(numValue).date() );
strValue1 = localizedDate1.formatDate( KLocale::ShortDate );
KLocalizedDate localizedDate2( QDateTime::fromTime_t(numValue2).date() );
strValue2 = localizedDate2.formatDate( KLocale::ShortDate );
}
}
else if( isNumeric() )
{
if ( condition != Between )
{
strValue1 = QString::number( numValue );
}
else if (numValue < numValue2) // two values are only used for "between". We want to order them by size
{
strValue1 = QString::number( numValue );
strValue2 = QString::number( numValue2 );
}
else
{
strValue1 = QString::number( numValue2 );
strValue2 = QString::number( numValue );
}
}
QString result;
if( m_field )
result = fieldToString() + ':';
switch( condition )
{
case Equals:
{
if( isNumeric() )
result += strValue1;
else
result += '=' + QString( "\"%1\"" ).arg( value );
if( invert )
result.prepend( QChar('-') );
break;
}
case GreaterThan:
{
result += '>' + strValue1;
if( invert )
result.prepend( QChar('-') );
break;
}
case LessThan:
{
result +='<' + strValue1;
if( invert )
result.prepend( QChar('-') );
break;
}
case Between:
{
if( invert )
result = QString( "%1<%2 OR %1>%3" ).arg( result, strValue1, strValue2 );
else
result = QString( "%1>%2 AND %1<%3" ).arg( result, strValue1, strValue2 );
break;
}
case OlderThan:
case NewerThan:
{
// a human readable time..
QChar strUnit = 's';
qint64 value = numValue;
if( !(value % 60) ) {
strUnit = 'M';
value /= 60;
if( !(value % 60) ) {
strUnit = 'h';
value /= 60;
if( !(value % 24) ) {
strUnit = 'd';
value /= 24;
if( !(value % 365) ) {
strUnit = 'y';
value /= 365;
} else if( !(value % 30) ) {
strUnit = 'm';
value /= 30;
} else if( !(value % 7) ) {
strUnit = 'w';
value /= 7;
}
}
}
}
if( condition == OlderThan )
result += '>' + QString::number(value) + strUnit;
else
result += '<' + QString::number(value) + strUnit;
if( invert )
result.prepend( QChar('-') );
break;
}
case Contains:
{
result += QString( "\"%1\"" ).arg( value );
if( invert )
result.prepend( QChar('-') );
break;
}
}
return result;
}
bool
MetaQueryWidget::isFieldSelectorHidden() const
{
return m_fieldSelection->isHidden();
}
void
MetaQueryWidget::setFieldSelectorHidden( const bool hidden )
{
m_fieldSelection->setVisible( !hidden );
}
void
MetaQueryWidget::setField( const qint64 field )
{
int index = m_fieldSelection->findData( field );
m_fieldSelection->setCurrentIndex( index == -1 ? 0 : index );
}
diff --git a/src/widgets/MetaQueryWidget.h b/src/widgets/MetaQueryWidget.h
index c3258c3c80..728d1a637c 100644
--- a/src/widgets/MetaQueryWidget.h
+++ b/src/widgets/MetaQueryWidget.h
@@ -1,223 +1,232 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_METAQUERY_H
#define AMAROK_METAQUERY_H
#include <QWidget>
#include <QWeakPointer>
#include "core/meta/forward_declarations.h"
#include "core/meta/support/MetaConstants.h"
+#include <KComboBox>
+#include <KNumInput>
+
class QFrame;
class QGridLayout;
class QHBoxLayout;
class QVBoxLayout;
class QLabel;
class QToolButton;
-class KComboBox;
-class KIntSpinBox;
class KToolBar;
class KVBox;
namespace Collections
{
class QueryMaker;
}
/**
* A class that allows to select a time distance.
*/
class TimeDistanceWidget : public QWidget
{
Q_OBJECT
public:
TimeDistanceWidget( QWidget *parent = 0 );
qint64 timeDistance() const;
void setTimeDistance( qint64 value );
- void connectChanged( QObject *receiver, const char *slot );
+
+ template<typename Func>
+ void connectChanged( typename QtPrivate::FunctionPointer<Func>::Object *receiver, Func slot )
+ {
+ connect( m_timeEdit, static_cast<void(KIntSpinBox::*)(int)>(&KIntSpinBox::valueChanged),
+ receiver, slot );
+ connect( m_unitSelection, static_cast<void(KComboBox::*)(int)>(&KComboBox::currentIndexChanged),
+ receiver, slot );
+ }
protected:
KIntSpinBox *m_timeEdit;
KComboBox *m_unitSelection;
private Q_SLOTS:
void slotUpdateComboBoxLabels( int value );
};
class MetaQueryWidget : public QWidget
{
Q_PROPERTY( bool hideFieldSelector READ isFieldSelectorHidden WRITE setFieldSelectorHidden )
Q_OBJECT
public:
/** Creates a MetaQueryWidget which can be used to select one meta query filter.
* @param onlyNumeric If set to true the widget will only display numeric fields.
* @param noCondition If set to true no condition can be selected.
*/
explicit MetaQueryWidget( QWidget* parent = 0, bool onlyNumeric = false, bool noCondition = false );
~MetaQueryWidget();
enum FilterCondition
{
Equals = 0,
GreaterThan = 1,
LessThan = 2,
Between = 3,
OlderThan = 4,
NewerThan = 5,
Contains = 6
};
struct Filter
{
Filter()
: m_field( 0 )
, numValue( 0 )
, numValue2( 0 )
, condition( Contains )
{}
qint64 field() const { return m_field; }
void setField( qint64 newField );
/** Returns a textual representation of the field.
*/
QString fieldToString() const;
/** Returns a textual representation of the filter.
* Used for the edit filter dialog (or for debugging)
*/
QString toString( bool invert = false ) const;
bool isNumeric() const
{ return MetaQueryWidget::isNumeric( m_field ); }
bool isDate() const
{ return MetaQueryWidget::isDate( m_field ); }
/** Returns the minimum allowed value for the field type */
static qint64 minimumValue( quint64 field );
static qint64 maximumValue( quint64 field );
static qint64 defaultValue( quint64 field );
private:
qint64 m_field;
public:
QString value;
qint64 numValue;
qint64 numValue2;
FilterCondition condition;
};
/** Returns the current filter value.
*/
Filter filter() const;
/** Returns true if the given field is a numeric field */
static bool isNumeric( qint64 field );
/** Returns true if the given field is a date field */
static bool isDate( qint64 field );
/** Returns a localized text of the condition.
@param field Needed in order to test whether the field is a date, numeric or a string since the texts differ slightly
*/
static QString conditionToString( FilterCondition condition, qint64 field );
public Q_SLOTS:
void setFilter(const MetaQueryWidget::Filter &value);
void setField( const qint64 field );
/** Field Selector combo box visibility state
*/
bool isFieldSelectorHidden() const;
void setFieldSelectorHidden( const bool hidden );
Q_SIGNALS:
void changed(const MetaQueryWidget::Filter &value);
private Q_SLOTS:
void fieldChanged( int );
void compareChanged( int );
void valueChanged( const QString& );
void numValueChanged( int );
void numValue2Changed( int );
void numValueChanged( qint64 );
void numValue2Changed( qint64 );
void numValueChanged( const QTime& );
void numValue2Changed( const QTime& );
void numValueDateChanged();
void numValue2DateChanged();
void numValueTimeDistanceChanged();
void numValueFormatChanged( int );
void populateComboBox( QStringList );
void comboBoxPopulated();
private:
void makeFieldSelection();
/** Adds the value selection widgets to the layout.
* Adds m_compareSelection, m_valueSelection1, m_valueSelection2 to the layout.
*/
void setValueSelection();
void makeCompareSelection();
void makeValueSelection();
void makeGenericComboSelection( bool editable, Collections::QueryMaker* populateQuery );
void makeMetaComboSelection( qint64 field );
void makeFormatComboSelection();
void makeGenericNumberSelection( qint64 field, const QString& unit = "" );
void makePlaycountSelection();
void makeRatingSelection();
void makeLengthSelection();
void makeDateTimeSelection();
void makeFilenameSelection();
bool m_onlyNumeric;
bool m_noCondition;
bool m_settingFilter; // if set to true we are just setting the filter
QVBoxLayout* m_layoutMain;
QHBoxLayout* m_layoutValue;
QVBoxLayout* m_layoutValueLabels;
QVBoxLayout* m_layoutValueValues;
KComboBox* m_fieldSelection;
QLabel* m_andLabel;
KComboBox* m_compareSelection;
QWidget* m_valueSelection1;
QWidget* m_valueSelection2;
Filter m_filter;
QMap< QObject*, QWeakPointer<KComboBox> > m_runningQueries;
};
#endif
diff --git a/src/widgets/Osd.cpp b/src/widgets/Osd.cpp
index 3539bac532..ada85128d9 100644
--- a/src/widgets/Osd.cpp
+++ b/src/widgets/Osd.cpp
@@ -1,866 +1,866 @@
/****************************************************************************************
* Copyright (c) 2004 Christian Muehlhaeuser <chris@chris.de> *
* Copyright (c) 2004-2006 Seb Ruiz <ruiz@kde.org> *
* Copyright (c) 2004,2005 Max Howell <max.howell@methylblue.com> *
* Copyright (c) 2005 Gabor Lehel <illissius@gmail.com> *
* Copyright (c) 2008-2013 Mark Kretschmann <kretschmann@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "OSD"
#include "Osd.h"
#include "EngineController.h"
#include "KNotificationBackend.h"
#include "PaletteHandler.h"
#include "SvgHandler.h"
#include "amarokconfig.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "core/meta/support/MetaUtility.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "widgets/StarManager.h"
#include <QApplication>
#include <QIcon>
#include <KLocale>
#include <KWindowSystem>
#include <KIconLoader>
#include <QDesktopWidget>
#include <QMouseEvent>
#include <QPainter>
#include <QPixmap>
#include <QRegExp>
#include <QTimeLine>
#include <QTimer>
namespace ShadowEngine
{
QImage makeShadow( const QPixmap &textPixmap, const QColor &bgColor );
}
namespace Amarok
{
inline QImage icon() { return QImage( KIconLoader::global()->iconPath( "amarok", -KIconLoader::SizeHuge ) ); }
}
OSDWidget::OSDWidget( QWidget *parent, const char *name )
: QWidget( parent )
, m_duration( 2000 )
, m_timer( new QTimer( this ) )
, m_alignment( Middle )
, m_screen( 0 )
, m_yOffset( MARGIN )
, m_rating( 0 )
, m_volume( The::engineController()->volume() )
, m_showVolume( false )
, m_hideWhenFullscreenWindowIsActive( false )
, m_fadeTimeLine( new QTimeLine( FADING_DURATION, this ) )
{
Qt::WindowFlags flags;
flags = Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint;
// The best of both worlds. On Windows, setting the widget as a popup avoids a task manager entry. On linux, a popup steals focus.
// Therefore we go need to do it platform specific :(
//This is no longer true. Qt::Window steals focus on X11, Qt:Tool does not. Not sure if we even need the ifdefs any more...
#ifdef Q_OS_WIN
flags |= Qt::Tool;
#else
flags |= Qt::Tool | Qt::X11BypassWindowManagerHint;
#endif
setWindowFlags( flags );
setObjectName( name );
setFocusPolicy( Qt::NoFocus );
#ifdef Q_WS_X11
KWindowSystem::setType( winId(), NET::Notification );
#endif
m_timer->setSingleShot( true );
- connect( m_timer, SIGNAL(timeout()), SLOT(hide()) );
+ connect( m_timer, &QTimer::timeout, this, &OSDWidget::hide );
m_fadeTimeLine->setUpdateInterval( 30 ); //~33 frames per second
- connect( m_fadeTimeLine, SIGNAL(valueChanged(qreal)), SLOT(setFadeOpacity(qreal)) );
+ connect( m_fadeTimeLine, &QTimeLine::valueChanged, this, &OSDWidget::setFadeOpacity );
//or crashes, KWindowSystem bug I think, crashes in QWidget::icon()
//kapp->setTopWidget( this );
}
OSDWidget::~OSDWidget()
{
DEBUG_BLOCK
}
void
OSDWidget::show( const QString &text, const QImage &newImage )
{
DEBUG_BLOCK
m_showVolume = false;
if ( !newImage.isNull() )
{
m_cover = newImage;
int w = m_scaledCover.width();
int h = m_scaledCover.height();
m_scaledCover = QPixmap::fromImage( m_cover.scaled( w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
}
else
m_cover = Amarok::icon();
m_text = text;
show();
}
void
OSDWidget::show()
{
if ( !isTemporaryDisabled() )
{
QWidget::show();
if( windowOpacity() == 0.0 && KWindowSystem::compositingActive() )
{
m_fadeTimeLine->setDirection( QTimeLine::Forward );
m_fadeTimeLine->start();
}
// Skip fading if OSD is already visible or if compositing is disabled
else
{
m_fadeTimeLine->stop();
setWindowOpacity( maxOpacity() );
}
}
}
void
OSDWidget::hide()
{
if( KWindowSystem::compositingActive() )
{
m_fadeTimeLine->setDirection( QTimeLine::Backward );
m_fadeTimeLine->start();
}
else
{
QWidget::hide();
}
}
bool
OSDWidget::isTemporaryDisabled() const
{
// Check if the OSD should not be shown,
// if a fullscreen window is focused.
if ( m_hideWhenFullscreenWindowIsActive )
{
return Amarok::KNotificationBackend::instance()->isFullscreenWindowActive();
}
return false;
}
void
OSDWidget::ratingChanged( const QString& path, int rating )
{
Meta::TrackPtr track = The::engineController()->currentTrack();
if( !track )
return;
if( track->playableUrl().isLocalFile() && track->playableUrl().path() == path )
ratingChanged( rating );
}
void
OSDWidget::ratingChanged( const short rating )
{
m_text = '\n' + i18n( "Rating changed" );
setRating( rating ); //Checks isEnabled() before doing anything
show();
}
void
OSDWidget::volumeChanged( int volume )
{
m_volume = volume;
if ( isEnabled() )
{
m_showVolume = true;
const KLocalizedString text = The::engineController()->isMuted() ? ki18n( "Volume: %1% (muted)" ) : ki18n( "Volume: %1%" );
m_text = text.subs( m_volume ).toString();
show();
}
}
void
OSDWidget::setVisible( bool visible )
{
if ( visible )
{
if ( !isEnabled() || m_text.isEmpty() )
return;
const uint margin = fontMetrics().width( 'x' );
const QRect newGeometry = determineMetrics( margin );
if( newGeometry.width() > 0 && newGeometry.height() > 0 )
{
m_margin = margin;
m_size = newGeometry.size();
setGeometry( newGeometry );
QWidget::setVisible( visible );
if( m_duration ) //duration 0 -> stay forever
m_timer->start( m_duration ); //calls hide()
}
else
warning() << "Attempted to make an invalid sized OSD\n";
update();
}
else
QWidget::setVisible( visible );
}
QRect
OSDWidget::determineMetrics( const int M )
{
// sometimes we only have a tiddly cover
const QSize minImageSize = m_cover.size().boundedTo( QSize( 100, 100 ) );
// determine a sensible maximum size, don't cover the whole desktop or cross the screen
const QSize margin( ( M + MARGIN ) * 2, ( M + MARGIN ) * 2 ); //margins
const QSize image = m_cover.isNull() ? QSize( 0, 0 ) : minImageSize;
const QSize max = QApplication::desktop()->screen( m_screen )->size() - margin;
// If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674)
m_text.replace( QRegExp( " +\n" ), "\n" );
// remove consecutive line breaks
m_text.replace( QRegExp( "\n+" ), "\n" );
// The osd cannot be larger than the screen
QRect rect = fontMetrics().boundingRect( 0, 0, max.width() - image.width(), max.height(),
Qt::AlignCenter, m_text );
rect.adjust( 0, 0, SHADOW_SIZE * 2, SHADOW_SIZE * 2 ); // the shadow needs some space
if( m_showVolume )
{
static const QString tmp = QString ("******").insert( 3,
( i18n("Volume: 100% (muted)" ) ) );
QRect tmpRect = fontMetrics().boundingRect( 0, 0,
max.width() - image.width(), max.height() - fontMetrics().height(),
Qt::AlignCenter, tmp );
tmpRect.setHeight( tmpRect.height() + fontMetrics().height() / 2 );
rect = tmpRect;
if ( The::engineController()->isMuted() )
m_cover = The::svgHandler()->renderSvg( "Muted", 100, 100, "Muted" ).toImage();
else if( m_volume > 66 )
m_cover = The::svgHandler()->renderSvg( "Volume", 100, 100, "Volume" ).toImage();
else if ( m_volume > 33 )
m_cover = The::svgHandler()->renderSvg( "Volume_mid", 100, 100, "Volume_mid" ).toImage();
else
m_cover = The::svgHandler()->renderSvg( "Volume_low", 100, 100, "Volume_low" ).toImage();
}
// Don't show both volume and rating
else if( m_rating )
{
QPixmap* star = StarManager::instance()->getStar( 1 );
if( rect.width() < star->width() * 5 )
rect.setWidth( star->width() * 5 ); //changes right edge position
rect.setHeight( rect.height() + star->height() + M ); //changes bottom edge pos
}
if( !m_cover.isNull() )
{
const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M)
m_scaledCover = QPixmap::fromImage(
m_cover.scaled(
qMin( availableWidth, m_cover.width() ),
qMin( rect.height(), m_cover.height() ),
Qt::KeepAspectRatio, Qt::SmoothTransformation
)
); //this will force us to be with our bounds
const int widthIncludingImage = rect.width()
+ m_scaledCover.width()
+ M; //margin between text + image
rect.setWidth( widthIncludingImage );
}
// expand in all directions by M
rect.adjust( -M, -M, M, M );
const QSize newSize = rect.size();
const QRect screen = QApplication::desktop()->screenGeometry( m_screen );
QPoint newPos( MARGIN, m_yOffset );
switch( m_alignment )
{
case Left:
break;
case Right:
newPos.rx() = screen.width() - MARGIN - newSize.width();
break;
case Center:
newPos.ry() = ( screen.height() - newSize.height() ) / 2;
//FALL THROUGH
case Middle:
newPos.rx() = ( screen.width() - newSize.width() ) / 2;
break;
}
//ensure we don't dip below the screen
if ( newPos.y() + newSize.height() > screen.height() - MARGIN )
newPos.ry() = screen.height() - MARGIN - newSize.height();
// correct for screen position
newPos += screen.topLeft();
return QRect( newPos, rect.size() );
}
void
OSDWidget::paintEvent( QPaintEvent *e )
{
QRect rect( QPoint(), m_size );
QColor shadowColor;
{
int h, s, v;
palette().color( QPalette::Normal, QPalette::WindowText ).getHsv( &h, &s, &v );
shadowColor = v > 128 ? Qt::black : Qt::white;
}
const int align = Qt::AlignCenter;
QPainter p( this );
p.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing );
p.setClipRect( e->rect() );
QPixmap background = The::svgHandler()->renderSvgWithDividers( "service_list_item", width(), height(), "service_list_item" );
p.drawPixmap( 0, 0, background );
//p.setPen( Qt::white ); // Revert this when the background can be colorized again.
rect.adjust( m_margin, m_margin, -m_margin, -m_margin ); // subtract margins
if( !m_cover.isNull() )
{
QRect r( rect );
r.setTop( ( m_size.height() - m_scaledCover.height() ) / 2 );
r.setSize( m_scaledCover.size() );
p.drawPixmap( r.topLeft(), m_scaledCover );
rect.setLeft( rect.left() + m_scaledCover.width() + m_margin );
}
int graphicsHeight = 0;
if( !m_showVolume && m_rating > 0 && !m_paused )
{
// TODO: Check if we couldn't use a KRatingPainter instead
QPixmap* star = StarManager::instance()->getStar( m_rating/2 );
QRect r( rect );
//Align to center...
r.setLeft( ( rect.left() + rect.width() / 2 ) - star->width() * m_rating / 4 );
r.setTop( rect.bottom() - star->height() );
graphicsHeight += star->height() + m_margin;
const bool half = m_rating % 2;
if( half )
{
QPixmap* halfStar = StarManager::instance()->getHalfStar( m_rating / 2 + 1 );
p.drawPixmap( r.left() + star->width() * ( m_rating / 2 ), r.top(), *halfStar );
star = StarManager::instance()->getStar( m_rating / 2 + 1 );
}
for( int i = 0; i < m_rating / 2; i++ )
{
p.drawPixmap( r.left() + i * star->width(), r.top(), *star );
}
}
rect.setBottom( rect.bottom() - graphicsHeight );
// Draw "shadow" text effect (black outline) (currently it's up to five pixel in every dir.)
QPixmap pixmap( rect.size() );
pixmap.fill( Qt::black );
QPainter p2( &pixmap );
p2.setFont( font() );
p2.setPen( Qt::white );
p2.setBrush( Qt::white );
p2.drawText( QRect( QPoint( SHADOW_SIZE, SHADOW_SIZE ),
QSize( rect.size().width() - SHADOW_SIZE * 2,
rect.size().height() - SHADOW_SIZE * 2 ) ),
align, m_text );
p2.end();
p.drawImage( rect.topLeft(), ShadowEngine::makeShadow( pixmap, shadowColor ) );
p.setPen( palette().color( QPalette::Active, QPalette::WindowText ) );
p.drawText( rect.adjusted( SHADOW_SIZE, SHADOW_SIZE,
-SHADOW_SIZE, -SHADOW_SIZE ), align, m_text );
}
void
OSDWidget::changeEvent( QEvent *event )
{
QWidget::changeEvent( event );
if( event->type() == QEvent::PaletteChange )
if( !AmarokConfig::osdUseCustomColors() )
unsetColors(); // Use new palette's colors
}
void
OSDWidget::mousePressEvent( QMouseEvent* )
{
hide();
}
void
OSDWidget::unsetColors()
{
setPalette( The::paletteHandler()->palette() );
}
void
OSDWidget::setTextColor(const QColor& color)
{
QPalette palette = this->palette();
palette.setColor( QPalette::Active, QPalette::WindowText, color );
setPalette(palette);
}
void
OSDWidget::setScreen( int screen )
{
const int n = QApplication::desktop()->numScreens();
m_screen = ( screen >= n ) ? n - 1 : screen;
}
void
OSDWidget::setFadeOpacity( qreal value )
{
setWindowOpacity( value * maxOpacity() );
if( value == 0.0 )
{
QWidget::hide();
}
}
void
OSDWidget::setFontScale( int scale )
{
double fontScale = static_cast<double>( scale ) / 100.0;
// update font, reuse old one
QFont newFont( font() );
newFont.setPointSizeF( defaultPointSize() * fontScale );
setFont( newFont );
}
void
OSDWidget::setHideWhenFullscreenWindowIsActive( bool hide )
{
m_hideWhenFullscreenWindowIsActive = hide;
}
/////////////////////////////////////////////////////////////////////////////////////////
// Class OSDPreviewWidget
/////////////////////////////////////////////////////////////////////////////////////////
OSDPreviewWidget::OSDPreviewWidget( QWidget *parent )
: OSDWidget( parent )
, m_dragging( false )
{
setObjectName( "osdpreview" );
setDuration( 0 );
setImage( Amarok::icon() );
setTranslucent( AmarokConfig::osdUseTranslucency() );
setText( i18n( "On-Screen-Display preview\nDrag to reposition" ) );
}
void
OSDPreviewWidget::mousePressEvent( QMouseEvent *event )
{
m_dragYOffset = event->pos();
if( event->button() == Qt::LeftButton && !m_dragging )
{
grabMouse( Qt::SizeAllCursor );
m_dragging = true;
}
}
void
OSDPreviewWidget::setUseCustomColors(const bool use, const QColor& fg)
{
if( use )
setTextColor( fg );
else
unsetColors();
}
void
OSDPreviewWidget::mouseReleaseEvent( QMouseEvent * /*event*/ )
{
if( m_dragging )
{
m_dragging = false;
releaseMouse();
emit positionChanged();
}
}
void
OSDPreviewWidget::mouseMoveEvent( QMouseEvent *e )
{
if( m_dragging && this == mouseGrabber() )
{
// Here we implement a "snap-to-grid" like positioning system for the preview widget
const QRect screenRect = QApplication::desktop()->screenGeometry( screen() );
const uint hcenter = screenRect.width() / 2;
const uint eGlobalPosX = e->globalPos().x() - screenRect.left();
const uint snapZone = screenRect.width() / 24;
QPoint destination = e->globalPos() - m_dragYOffset - screenRect.topLeft();
int maxY = screenRect.height() - height() - MARGIN;
if( destination.y() < MARGIN )
destination.ry() = MARGIN;
if( destination.y() > maxY )
destination.ry() = maxY;
if( eGlobalPosX < ( hcenter - snapZone ) )
{
setAlignment(Left);
destination.rx() = MARGIN;
}
else if( eGlobalPosX > ( hcenter + snapZone ) )
{
setAlignment(Right);
destination.rx() = screenRect.width() - MARGIN - width();
}
else {
const uint eGlobalPosY = e->globalPos().y() - screenRect.top();
const uint vcenter = screenRect.height() / 2;
destination.rx() = hcenter - width() / 2;
if( eGlobalPosY >= ( vcenter - snapZone ) && eGlobalPosY <= ( vcenter + snapZone ) )
{
setAlignment(Center);
destination.ry() = vcenter - height() / 2;
}
else
setAlignment(Middle);
}
destination += screenRect.topLeft();
move( destination );
// compute current Position && Y-offset
QDesktopWidget *desktop = QApplication::desktop();
const int currentScreen = desktop->screenNumber( pos() );
// set new data
OSDWidget::setScreen( currentScreen );
setYOffset( y() );
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// Class OSD
/////////////////////////////////////////////////////////////////////////////////////////
Amarok::OSD* Amarok::OSD::s_instance = 0;
Amarok::OSD*
Amarok::OSD::instance()
{
return s_instance ? s_instance : new OSD();
}
void
Amarok::OSD::destroy()
{
if ( s_instance )
{
delete s_instance;
s_instance = 0;
}
}
Amarok::OSD::OSD()
: OSDWidget( 0 )
{
s_instance = this;
EngineController* const engine = The::engineController();
if( engine->isPlaying() )
trackPlaying( engine->currentTrack() );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(trackPlaying(Meta::TrackPtr)) );
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped()) );
- connect( engine, SIGNAL(paused()),
- this, SLOT(paused()) );
+ connect( engine, &EngineController::trackPlaying,
+ this, &Amarok::OSD::trackPlaying );
+ connect( engine, &EngineController::stopped,
+ this, &Amarok::OSD::stopped );
+ connect( engine, &EngineController::paused,
+ this, &Amarok::OSD::paused );
- connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
- this, SLOT(metadataChanged()) );
- connect( engine, SIGNAL(albumMetadataChanged(Meta::AlbumPtr)),
- this, SLOT(metadataChanged()) );
+ connect( engine, &EngineController::trackMetadataChanged,
+ this, &Amarok::OSD::metadataChanged );
+ connect( engine, &EngineController::albumMetadataChanged,
+ this, &Amarok::OSD::metadataChanged );
- connect( engine, SIGNAL(volumeChanged(int)),
- this, SLOT(volumeChanged(int)) );
+ connect( engine, &EngineController::volumeChanged,
+ this, &Amarok::OSD::volumeChanged );
- connect( engine, SIGNAL(muteStateChanged(bool)),
- this, SLOT(muteStateChanged(bool)) );
+ connect( engine, &EngineController::muteStateChanged,
+ this, &Amarok::OSD::muteStateChanged );
}
Amarok::OSD::~OSD()
{}
void
Amarok::OSD::show( Meta::TrackPtr track ) //slot
{
setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
setYOffset( AmarokConfig::osdYOffset() );
QString text;
if( !track || track->playableUrl().isEmpty() )
{
text = i18n( "No track playing" );
setRating( 0 ); // otherwise stars from last rating change are visible
}
else
{
setRating( track->statistics()->rating() );
text = track->prettyName();
if( track->artist() && !track->artist()->prettyName().isEmpty() )
text = track->artist()->prettyName() + " - " + text;
if( track->album() && !track->album()->prettyName().isEmpty() )
text += "\n (" + track->album()->prettyName() + ") ";
else
text += '\n';
if( track->length() > 0 )
text += Meta::msToPrettyTime( track->length() );
}
if( text.isEmpty() )
text = track->playableUrl().fileName();
if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that.
text = text.mid( 2 );
if( text.isEmpty() ) //still
text = i18n("No information available for this track");
QImage image;
if( track && track->album() )
image = The::svgHandler()->imageWithBorder( track->album(), 100, 5 ).toImage();
OSDWidget::show( text, image );
}
void
Amarok::OSD::applySettings()
{
DEBUG_BLOCK
setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
setDuration( AmarokConfig::osdDuration() );
setEnabled( AmarokConfig::osdEnabled() );
setYOffset( AmarokConfig::osdYOffset() );
setScreen( AmarokConfig::osdScreen() );
setFontScale( AmarokConfig::osdFontScaling() );
setHideWhenFullscreenWindowIsActive( AmarokConfig::osdHideOnFullscreen() );
if( AmarokConfig::osdUseCustomColors() )
setTextColor( AmarokConfig::osdTextColor() );
else
unsetColors();
setTranslucent( AmarokConfig::osdUseTranslucency() );
}
void
Amarok::OSD::forceToggleOSD()
{
if ( !isVisible() )
{
const bool b = isEnabled();
setEnabled( true );
show( The::engineController()->currentTrack() );
setEnabled( b );
}
else
hide();
}
void
Amarok::OSD::muteStateChanged( bool mute )
{
Q_UNUSED( mute )
volumeChanged( The::engineController()->volume() );
}
void
Amarok::OSD::trackPlaying( Meta::TrackPtr track )
{
m_currentTrack = track;
setPaused(false);
show( m_currentTrack );
}
void
Amarok::OSD::stopped()
{
setImage( QImage( KIconLoader::global()->iconPath( "amarok", -KIconLoader::SizeHuge ) ) );
setRating( 0 ); // otherwise stars from last rating change are visible
OSDWidget::show( i18n( "Stopped" ) );
setPaused(false);
}
void
Amarok::OSD::paused()
{
setImage( QImage( KIconLoader::global()->iconPath( "amarok", -KIconLoader::SizeHuge ) ) );
setRating( 0 ); // otherwise stars from last rating change are visible
OSDWidget::show( i18n( "Paused" ) );
setPaused(true);
}
void
Amarok::OSD::metadataChanged()
{
// this also covers all cases where a stream get's new metadata.
show( m_currentTrack );
}
/* Code copied from kshadowengine.cpp
*
* Copyright (C) 2003 Laur Ivan <laurivan@eircom.net>
*
* Many thanks to:
* - Bernardo Hung <deciare@gta.igs.net> for the enhanced shadow
* algorithm (currently used)
* - Tim Jansen <tim@tjansen.de> for the API updates and fixes.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
namespace ShadowEngine
{
// Not sure, doesn't work above 10
static const int MULTIPLICATION_FACTOR = 3;
// Multiplication factor for pixels directly above, under, or next to the text
static const double AXIS_FACTOR = 2.0;
// Multiplication factor for pixels diagonal to the text
static const double DIAGONAL_FACTOR = 0.1;
// Self explanatory
static const int MAX_OPACITY = 200;
double decay( QImage&, int, int );
QImage makeShadow( const QPixmap& textPixmap, const QColor &bgColor )
{
const int w = textPixmap.width();
const int h = textPixmap.height();
const int bgr = bgColor.red();
const int bgg = bgColor.green();
const int bgb = bgColor.blue();
int alphaShadow;
// This is the source pixmap
QImage img = textPixmap.toImage();
QImage result( w, h, QImage::Format_ARGB32 );
result.fill( 0 ); // fill with black
static const int M = OSDWidget::SHADOW_SIZE;
for( int i = M; i < w - M; i++) {
for( int j = M; j < h - M; j++ )
{
alphaShadow = (int) decay( img, i, j );
result.setPixel( i,j, qRgba( bgr, bgg , bgb, qMin( MAX_OPACITY, alphaShadow ) ) );
}
}
return result;
}
double decay( QImage& source, int i, int j )
{
//if ((i < 1) || (j < 1) || (i > source.width() - 2) || (j > source.height() - 2))
// return 0;
double alphaShadow;
alphaShadow =(qGray(source.pixel(i-1,j-1)) * DIAGONAL_FACTOR +
qGray(source.pixel(i-1,j )) * AXIS_FACTOR +
qGray(source.pixel(i-1,j+1)) * DIAGONAL_FACTOR +
qGray(source.pixel(i ,j-1)) * AXIS_FACTOR +
0 +
qGray(source.pixel(i ,j+1)) * AXIS_FACTOR +
qGray(source.pixel(i+1,j-1)) * DIAGONAL_FACTOR +
qGray(source.pixel(i+1,j )) * AXIS_FACTOR +
qGray(source.pixel(i+1,j+1)) * DIAGONAL_FACTOR) / MULTIPLICATION_FACTOR;
return alphaShadow;
}
}
diff --git a/src/widgets/PlayPauseButton.cpp b/src/widgets/PlayPauseButton.cpp
index bbbebc4fdc..cff62b48cc 100644
--- a/src/widgets/PlayPauseButton.cpp
+++ b/src/widgets/PlayPauseButton.cpp
@@ -1,86 +1,86 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PlayPauseButton.h"
#include "SvgHandler.h"
#include <KLocale>
#include <QMouseEvent>
#include <QPainter>
PlayPauseButton::PlayPauseButton( QWidget *parent ) : IconButton( parent )
, m_isPlaying( false )
{
- connect (this, SIGNAL(clicked()), this, SLOT(toggle()) );
+ connect (this, &PlayPauseButton::clicked, this, &PlayPauseButton::toggle );
setToolTip( i18n( "Play" ) );
}
void PlayPauseButton::enterEvent( QEvent * )
{
setIcon( m_isPlaying ? m_icon.pause[1] : m_icon.play[1], 3 );
}
void PlayPauseButton::leaveEvent( QEvent * )
{
setIcon( m_isPlaying ? m_icon.pause[0] : m_icon.play[0], 6 );
}
void PlayPauseButton::mousePressEvent( QMouseEvent *me )
{
setIcon( m_isPlaying ? m_icon.pause[0] : m_icon.play[0] );
IconButton::mousePressEvent( me );
}
void PlayPauseButton::toggle()
{
emit toggled( !m_isPlaying );
}
void PlayPauseButton::reloadContent( const QSize &sz )
{
const int width = sz.width();
const int height = sz.height();
//NOTICE this is a bit cumbersome, as Qt renders faster to images than to pixmaps
// However we need the Image and generate the pixmap ourself - maybe extend the SvgHandler API
m_icon.play[0] = The::svgHandler()->renderSvg( "PLAYpause", width, height, "PLAYpause", true ).toImage();
m_icon.play[1] = The::svgHandler()->renderSvg( "PLAYpause_active", width, height, "PLAYpause_active", true ).toImage();
m_icon.pause[0] = The::svgHandler()->renderSvg( "playPAUSE", width, height, "playPAUSE", true ).toImage();
m_icon.pause[1] = The::svgHandler()->renderSvg( "playPAUSE_active", width, height, "playPAUSE_active", true ).toImage();
if( layoutDirection() == Qt::RightToLeft )
{
for ( int i = 0; i < 2; ++i )
{
m_icon.play[i] = m_icon.play[i].mirrored( true, false );
m_icon.pause[i] = m_icon.pause[i].mirrored( true, false );
}
}
setIcon( m_isPlaying ? m_icon.pause[underMouse()] : m_icon.play[underMouse()] );
}
void PlayPauseButton::setPlaying( bool playing )
{
if ( m_isPlaying == playing )
return;
setToolTip( playing ? i18n( "Pause" ) : i18n( "Play" ) );
m_isPlaying = playing;
setIcon( m_isPlaying ? m_icon.pause[underMouse()] : m_icon.play[underMouse()], 4 );
}
diff --git a/src/widgets/PrettyTreeView.cpp b/src/widgets/PrettyTreeView.cpp
index 6ab3f0d717..f0e5654e8a 100644
--- a/src/widgets/PrettyTreeView.cpp
+++ b/src/widgets/PrettyTreeView.cpp
@@ -1,261 +1,261 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "PrettyTreeView.h"
#include "PaletteHandler.h"
#include "SvgHandler.h"
#include "widgets/PrettyTreeRoles.h"
#include "widgets/PrettyTreeDelegate.h"
#include <KGlobalSettings>
#include <QAction>
#include <QMouseEvent>
#include <QPainter>
#include <QToolTip>
#include <QApplication>
Q_DECLARE_METATYPE( QAction* )
Q_DECLARE_METATYPE( QList<QAction*> )
using namespace Amarok;
PrettyTreeView::PrettyTreeView( QWidget *parent )
: QTreeView( parent )
, m_decoratorActionPressed( 0 )
{
setAlternatingRowColors( true );
setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
The::paletteHandler()->updateItemView( this );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(newPalette(QPalette)) );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &PrettyTreeView::newPalette );
#ifdef Q_WS_MAC
// for some bizarre reason w/ some styles on mac per-pixel scrolling is slower than
// per-item
setVerticalScrollMode( QAbstractItemView::ScrollPerItem );
setHorizontalScrollMode( QAbstractItemView::ScrollPerItem );
#else
// Scrolling per item is really not smooth and looks terrible
setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
setHorizontalScrollMode( QAbstractItemView::ScrollPerPixel );
#endif
setAnimated( KGlobalSettings::graphicEffectsLevel() != KGlobalSettings::NoEffects );
}
PrettyTreeView::~PrettyTreeView()
{
}
void
PrettyTreeView::edit( const QModelIndex &index )
{
QTreeView::edit( index );
}
bool
PrettyTreeView::edit( const QModelIndex &index, QAbstractItemView::EditTrigger trigger, QEvent *event )
{
QModelIndex parent = index.parent();
while( parent.isValid() )
{
expand( parent );
parent = parent.parent();
}
return QAbstractItemView::edit( index, trigger, event );
}
void
PrettyTreeView::drawRow( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QTreeView::drawRow( painter, option, index );
const int width = option.rect.width();
const int height = option.rect.height();
if( height > 0 )
{
QPixmap background = The::svgHandler()->renderSvgWithDividers(
"service_list_item", width, height, "service_list_item" );
painter->save();
painter->drawPixmap( option.rect.topLeft().x(), option.rect.topLeft().y(), background );
painter->restore();
}
}
void
PrettyTreeView::mouseMoveEvent( QMouseEvent *event )
{
// swallow the mouse move event in case the press was started on decorator action icon
if( m_decoratorActionPressed )
event->accept();
else
QTreeView::mouseMoveEvent( event );
// Make sure we repaint the item for the collection action buttons
const QModelIndex index = indexAt( event->pos() );
const int actionsCount = index.data( PrettyTreeRoles::DecoratorRoleCount ).toInt();
if( actionsCount )
update( index );
}
void
PrettyTreeView::mousePressEvent( QMouseEvent *event )
{
const QModelIndex index = indexAt( event->pos() );
// reset state variables on every mouse button press
m_expandCollapsePressedAt.reset();
m_decoratorActionPressed = 0;
// if root is decorated, it doesn't show any actions
QAction *action = rootIsDecorated() ? 0 : decoratorActionAt( index, event->pos() );
if( action &&
event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier &&
state() == QTreeView::NoState )
{
m_decoratorActionPressed = action;
update( index ); // trigger repaint to change icon effect
event->accept();
return;
}
bool prevExpandState = isExpanded( index );
// This will toggle the expansion of the current item when clicking
// on the fold marker but not on the item itself. Required here to
// enable dragging.
QTreeView::mousePressEvent( event );
// if we press left mouse button on valid item which did not cause the expansion,
// set m_expandCollapsePressedAt so that mouseReleaseEvent can perform the
// expansion/collapsing
if( index.isValid() &&
prevExpandState == isExpanded( index ) &&
event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier &&
state() == QTreeView::NoState )
{
m_expandCollapsePressedAt.reset( new QPoint( event->pos() ) );
}
}
void
PrettyTreeView::mouseReleaseEvent( QMouseEvent *event )
{
const QModelIndex index = indexAt( event->pos() );
// we want to reset m_expandCollapsePressedAt in either case, but still need its value
QScopedPointer<QPoint> expandCollapsePressedAt( m_expandCollapsePressedAt.take() );
// ditto for m_decoratorActionPressed
QAction *decoratorActionPressed = m_decoratorActionPressed;
m_decoratorActionPressed = 0;
// if root is decorated, it doesn't show any actions
QAction *action = rootIsDecorated() ? 0 : decoratorActionAt( index, event->pos() );
if( action &&
action == decoratorActionPressed &&
event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier )
{
action->trigger();
update( index ); // trigger repaint to change icon effect
event->accept();
return;
}
if( index.isValid() &&
event->button() == Qt::LeftButton &&
event->modifiers() == Qt::NoModifier &&
state() == QTreeView::NoState &&
expandCollapsePressedAt &&
( *expandCollapsePressedAt - event->pos() ).manhattanLength() < QApplication::startDragDistance() &&
KGlobalSettings::singleClick() &&
model()->hasChildren( index ) )
{
setExpanded( index, !isExpanded( index ) );
event->accept();
return;
}
QTreeView::mouseReleaseEvent( event );
}
bool
PrettyTreeView::viewportEvent( QEvent *event )
{
if( event->type() == QEvent::ToolTip )
{
QHelpEvent *helpEvent = static_cast<QHelpEvent *>( event );
const QModelIndex index = indexAt( helpEvent->pos() );
// if root is decorated, it doesn't show any actions
QAction *action = rootIsDecorated() ? 0 : decoratorActionAt( index, helpEvent->pos() );
if( action )
{
QToolTip::showText( helpEvent->globalPos(), action->toolTip() );
event->accept();
return true;
}
}
// swallow the mouse hover event in case the press was started on decorator action icon
// friend mouse move event is handled in mouseMoveEvent and triggers repaints
if( event->type() == QEvent::HoverMove && m_decoratorActionPressed )
{
event->accept();
return true;
}
return QAbstractItemView::viewportEvent( event );
}
QAction *
PrettyTreeView::decoratorActionAt( const QModelIndex &index, const QPoint &pos )
{
const int actionsCount = index.data( PrettyTreeRoles::DecoratorRoleCount ).toInt();
if( actionsCount <= 0 )
return 0;
PrettyTreeDelegate* ptd = qobject_cast<PrettyTreeDelegate*>( itemDelegate( index ) );
if( !ptd )
return 0;
QList<QAction *> actions = index.data( PrettyTreeRoles::DecoratorRole ).value<QList<QAction *> >();
QRect rect = visualRect( index );
for( int i = 0; i < actions.count(); i++ )
if( ptd->decoratorRect( rect, i ).contains( pos ) )
return actions.at( i );
return 0;
}
QAction *
PrettyTreeView::pressedDecoratorAction() const
{
return m_decoratorActionPressed;
}
void
PrettyTreeView::newPalette( const QPalette & palette )
{
Q_UNUSED( palette )
The::paletteHandler()->updateItemView( this );
reset(); // redraw all potential delegates
}
diff --git a/src/widgets/PrettyTreeView.h b/src/widgets/PrettyTreeView.h
index 17ffa84936..904a64dcf2 100644
--- a/src/widgets/PrettyTreeView.h
+++ b/src/widgets/PrettyTreeView.h
@@ -1,114 +1,114 @@
/****************************************************************************************
* Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_PRETTYTREEVIEW_H
#define AMAROK_PRETTYTREEVIEW_H
#include "amarok_export.h"
#include <QTreeView>
namespace Amarok
{
/**
* A utility QTreeView subcass that handles:
* - drawing nice (svg themed) rows
* - palette changes
* - nicer expanding/collapsing interaction even when single click is used
* - decorator actions for root level items when isRootDecorated() is false
*
* If you use decorator actions, don't forget to set mouseTracking to true as
* PrettyTreeView doesn't do it automatically as it would be too costly for models
* that don't use the actions.
*
* @author: Nikolaj Hald Nielsen <nhn@kde.org>
*/
class AMAROK_EXPORT PrettyTreeView : public QTreeView
{
Q_OBJECT
public:
PrettyTreeView( QWidget *parent = 0 );
virtual ~PrettyTreeView();
public Q_SLOTS:
/* There is a need to overload even this edit() variant, otherwise it hides
* QAbstactItemView's implementation. Note that it is NOT safe to do anything
* special in this method, as it is not virtual.
* bool edit( const QModelIndex &index, EditTrigger trigger, QEvent *event )
* IS virtual. */
void edit( const QModelIndex &index );
/**
* Return pointer to decorator action which was most recently mouse-pressed
* or null it mouse buttom was released since then. Used by PrettyTreeDelegate.
*/
QAction *pressedDecoratorAction() const;
protected:
bool edit( const QModelIndex &index, EditTrigger trigger, QEvent *event );
void drawRow( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
/**
* Reimplemented to trigger item redraw in case mouse is over an item which
* has decorator actions.
*/
void mouseMoveEvent( QMouseEvent *event );
/**
* Reimplemented to handle expanding with single-click mouse setting event
* when it is clicked outside the arrow and for consistency with
* mouseReleaseEvent() in case of decorator actions.
*/
void mousePressEvent( QMouseEvent *event );
/**
* Reimplemented to handle expanding with single-click mouse setting event
* when it is clicked outside the arrow and to handle clicking on decorator
* actions */
void mouseReleaseEvent( QMouseEvent *event );
/**
* Reimplemented to show proper tooltips for decorator actions.
*/
bool viewportEvent( QEvent *event );
/**
* Get dectorator action (little action icon as seen for example in collection
* items in collection browser) of index @p idx under mouse position @p pos.
*/
QAction *decoratorActionAt( const QModelIndex &idx, const QPoint &pos );
- private Q_SLOTS:
+ protected Q_SLOTS:
virtual void newPalette( const QPalette &palette );
private:
/**
* Position (relative to this widget) where the mouse button was pressed to
* trigger expand/collapse, or null pointer where expand/collapse shouldn't
* be handled in mouseReleaseEvent()
*/
QScopedPointer<QPoint> m_expandCollapsePressedAt;
/**
* Pointer to decorator action which was pressed in mousePressEvent() or null
* pointer if no action was pressed in the most recent mouse press
*/
QAction *m_decoratorActionPressed;
};
}
#endif
diff --git a/src/widgets/ProgressWidget.cpp b/src/widgets/ProgressWidget.cpp
index f9abba6be8..951959c3cf 100644
--- a/src/widgets/ProgressWidget.cpp
+++ b/src/widgets/ProgressWidget.cpp
@@ -1,282 +1,282 @@
/****************************************************************************************
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "ProgressWidget.h"
#include "amarokconfig.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "SliderWidget.h"
#include "TimeLabel.h"
#include "amarokurls/AmarokUrl.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "core/meta/Meta.h"
#include "core/meta/support/MetaUtility.h"
#include "core-impl/capabilities/timecode/TimecodeLoadCapability.h"
#include <KLocale>
#include <QHBoxLayout>
#include <QMouseEvent>
ProgressWidget::ProgressWidget( QWidget *parent )
: QWidget( parent )
{
QHBoxLayout *box = new QHBoxLayout( this );
setLayout( box );
box->setMargin( 0 );
box->setSpacing( 4 );
m_slider = new Amarok::TimeSlider( this );
m_slider->setToolTip( i18n( "Track Progress" ) );
m_slider->setMaximumSize( 600000, 20 );
m_timeLabelLeft = new TimeLabel( this );
m_timeLabelRight = new TimeLabel( this );
m_timeLabelRight->setAlignment( Qt::AlignRight );
updateTimeLabelTooltips();
m_timeLabelLeft->setShowTime( false );
m_timeLabelLeft->setAlignment( Qt::AlignRight );
m_timeLabelRight->setShowTime( false );
m_timeLabelRight->setAlignment( Qt::AlignLeft );
m_timeLabelLeft->show();
m_timeLabelRight->show();
box->addSpacing( 3 );
box->addWidget( m_timeLabelLeft );
box->addWidget( m_slider );
box->addWidget( m_timeLabelRight );
EngineController *engine = The::engineController();
if( engine->isPaused() )
paused();
else if( engine->isPlaying() )
trackPlaying();
else
stopped();
- connect( engine, SIGNAL(stopped(qint64,qint64)),
- this, SLOT(stopped()) );
- connect( engine, SIGNAL(paused()),
- this, SLOT(paused()) );
- connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)),
- this, SLOT(trackPlaying()) );
- connect( engine, SIGNAL(trackLengthChanged(qint64)),
- this, SLOT(trackLengthChanged(qint64)) );
- connect( engine, SIGNAL(trackPositionChanged(qint64,bool)),
- this, SLOT(trackPositionChanged(qint64)) );
-
- connect( m_slider, SIGNAL(sliderReleased(int)),
- engine, SLOT(seekTo(int)) );
-
- connect( m_slider, SIGNAL(valueChanged(int)),
- SLOT(drawTimeDisplay(int)) );
+ connect( engine, &EngineController::stopped,
+ this, &ProgressWidget::stopped );
+ connect( engine, &EngineController::paused,
+ this, &ProgressWidget::paused );
+ connect( engine, &EngineController::trackPlaying,
+ this, &ProgressWidget::trackPlaying );
+ connect( engine, &EngineController::trackLengthChanged,
+ this, &ProgressWidget::trackLengthChanged );
+ connect( engine, &EngineController::trackPositionChanged,
+ this, &ProgressWidget::trackPositionChanged );
+
+ connect( m_slider, &Amarok::TimeSlider::sliderReleased,
+ engine, &EngineController::seekTo );
+
+ connect( m_slider, &Amarok::TimeSlider::valueChanged,
+ this, &ProgressWidget::drawTimeDisplay );
setBackgroundRole( QPalette::BrightText );
- connect ( The::amarokUrlHandler(), SIGNAL(timecodesUpdated(const QString*)),
- this, SLOT(redrawBookmarks(const QString*)) );
- connect ( The::amarokUrlHandler(), SIGNAL(timecodeAdded(QString,int)),
- this, SLOT(addBookmark(QString,int)) );
+ connect ( The::amarokUrlHandler(), &AmarokUrlHandler::timecodesUpdated,
+ this, &ProgressWidget::redrawBookmarks );
+ connect ( The::amarokUrlHandler(), &AmarokUrlHandler::timecodeAdded,
+ this, &ProgressWidget::addBookmarkNoPopup );
}
void
-ProgressWidget::addBookmark( const QString &name, int milliSeconds )
+ProgressWidget::addBookmarkNoPopup( const QString &name, int milliSeconds )
{
addBookmark( name, milliSeconds, false );
}
void
ProgressWidget::addBookmark( const QString &name, int milliSeconds, bool showPopup )
{
DEBUG_BLOCK
if ( m_slider )
m_slider->drawTriangle( name, milliSeconds, showPopup );
}
void
ProgressWidget::updateTimeLabelTooltips()
{
TimeLabel *elapsedLabel = AmarokConfig::leftTimeDisplayRemaining() ? m_timeLabelRight : m_timeLabelLeft;
TimeLabel *remainingLabel = AmarokConfig::leftTimeDisplayRemaining() ? m_timeLabelLeft : m_timeLabelRight;
elapsedLabel->setToolTip( i18n( "The amount of time elapsed in current track" ) );
remainingLabel->setToolTip( i18n( "The amount of time remaining in current track" ) );
}
void
ProgressWidget::drawTimeDisplay( int ms ) //SLOT
{
if ( !isVisible() )
return;
const qint64 trackLength = The::engineController()->trackLength();
//sometimes the engine gives negative position and track length values for streams
//which causes the time sliders to show 'interesting' values like -322:0-35:0-59
int seconds = qMax(0, ms / 1000);
int remainingSeconds = qMax(0, int((trackLength - ms) / 1000));
QString sSeconds = Meta::secToPrettyTime( seconds );
QString sRemainingSeconds = '-' + Meta::secToPrettyTime( remainingSeconds );
if( AmarokConfig::leftTimeDisplayRemaining() )
{
m_timeLabelLeft->setText( sRemainingSeconds );
m_timeLabelLeft->setEnabled( remainingSeconds > 0 );
m_timeLabelRight->setText( sSeconds );
m_timeLabelRight->setEnabled( seconds > 0 );
}
else
{
m_timeLabelRight->setText( sRemainingSeconds );
m_timeLabelRight->setEnabled( remainingSeconds > 0 );
m_timeLabelLeft->setText( sSeconds );
m_timeLabelLeft->setEnabled( seconds > 0 );
}
}
void
ProgressWidget::stopped()
{
m_slider->setEnabled( false );
m_slider->setMinimum( 0 ); //needed because setMaximum() calls with bogus values can change minValue
m_slider->setMaximum( 0 );
m_timeLabelLeft->setEnabled( false );
m_timeLabelLeft->setEnabled( false );
m_timeLabelLeft->setShowTime( false );
m_timeLabelRight->setShowTime( false );
m_currentUrlId.clear();
m_slider->clearTriangles();
}
void
ProgressWidget::paused()
{
// I am wondering, is there a way that the track can get paused
// directly?
m_timeLabelLeft->setEnabled( true );
m_timeLabelRight->setEnabled( true );
}
void
ProgressWidget::trackPlaying()
{
m_timeLabelLeft->setEnabled( true );
m_timeLabelLeft->setEnabled( true );
m_timeLabelLeft->setShowTime( true );
m_timeLabelRight->setShowTime( true );
//in some cases (for streams mostly), we do not get an event for track length changes once
//loading is done, causing maximum() to return 0 at when playback starts. In this case we need
//to make sure that maximum is set correctly or the slider will not move.
trackLengthChanged( The::engineController()->trackLength() );
}
void
ProgressWidget::trackLengthChanged( qint64 milliseconds )
{
m_slider->setMinimum( 0 );
m_slider->setMaximum( milliseconds );
const int timeLength = Meta::msToPrettyTime( milliseconds ).length() + 1; // account for - in remaining time
QFontMetrics tFm( m_timeLabelRight->font() );
const int labelSize = tFm.width(QChar('0')) * timeLength;
//set the sizes of the labesl to the max needed by the length of the track
//this way the progressbar will not change size during playback of a track
m_timeLabelRight->setFixedWidth( labelSize );
m_timeLabelLeft->setFixedWidth( labelSize );
//get the urlid of the current track as the engine might stop and start several times
//when skipping lst.fm tracks, so we need to know if we are still on the same track...
if ( The::engineController()->currentTrack() )
m_currentUrlId = The::engineController()->currentTrack()->uidUrl();
redrawBookmarks();
}
void
ProgressWidget::trackPositionChanged( qint64 position )
{
m_slider->setSliderValue( position );
// update the enabled state. Phonon determines isSeekable somtimes too late.
m_slider->setEnabled( (m_slider->maximum() > 0) && The::engineController()->isSeekable() );
if ( !m_slider->isEnabled() )
drawTimeDisplay( position );
}
void
ProgressWidget::redrawBookmarks( const QString *BookmarkName )
{
DEBUG_BLOCK
m_slider->clearTriangles();
if ( The::engineController()->currentTrack() )
{
Meta::TrackPtr track = The::engineController()->currentTrack();
if ( track->has<Capabilities::TimecodeLoadCapability>() )
{
Capabilities::TimecodeLoadCapability *tcl = track->create<Capabilities::TimecodeLoadCapability>();
BookmarkList list = tcl->loadTimecodes();
debug() << "found " << list.count() << " timecodes on this track";
foreach( AmarokUrlPtr url, list )
{
if ( url->command() == "play" )
{
if ( url->args().keys().contains( "pos" ) )
{
int pos = url->args().value( "pos" ).toDouble() * 1000;
debug() << "showing timecode: " << url->name() << " at " << pos ;
addBookmark( url->name(), pos, ( BookmarkName && BookmarkName == url->name() ));
}
}
}
delete tcl;
}
}
}
void ProgressWidget::mousePressEvent(QMouseEvent* e)
{
QWidget* widgetUnderCursor = childAt(e->pos());
if( widgetUnderCursor == m_timeLabelLeft ||
widgetUnderCursor == m_timeLabelRight )
{
// user clicked on one of the time labels, switch display
AmarokConfig::setLeftTimeDisplayRemaining( !AmarokConfig::leftTimeDisplayRemaining() );
drawTimeDisplay( The::engineController()->trackPositionMs() );
updateTimeLabelTooltips();
}
QWidget::mousePressEvent(e);
}
QSize ProgressWidget::sizeHint() const
{
//int height = fontMetrics().boundingRect( "123456789:-" ).height();
return QSize( width(), 12 );
}
diff --git a/src/widgets/ProgressWidget.h b/src/widgets/ProgressWidget.h
index 929412a2a9..75c9e3dfa7 100644
--- a/src/widgets/ProgressWidget.h
+++ b/src/widgets/ProgressWidget.h
@@ -1,70 +1,70 @@
/****************************************************************************************
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef AMAROK_PROGRESSWIDGET_H
#define AMAROK_PROGRESSWIDGET_H
#include "core/meta/forward_declarations.h"
#include <Phonon/Global>
#include <QHash>
#include <QPainter>
#include <QPolygon>
#include <QWidget>
class TimeLabel;
namespace Amarok { class TimeSlider; }
class ProgressWidget : public QWidget
{
Q_OBJECT
public:
ProgressWidget( QWidget* );
virtual QSize sizeHint() const;
void addBookmark( const QString &name, int milliSeconds , bool instantDisplayPopUp );
Amarok::TimeSlider* slider() const { return m_slider; }
public Q_SLOTS:
void drawTimeDisplay( int position );
protected Q_SLOTS:
void stopped();
void paused();
void trackPlaying();
void trackLengthChanged( qint64 milliseconds );
void trackPositionChanged( qint64 position );
protected:
virtual void mousePressEvent( QMouseEvent * );
private Q_SLOTS:
- void addBookmark( const QString &name, int milliSeconds );
+ void addBookmarkNoPopup( const QString &name, int milliSeconds );
void redrawBookmarks(const QString *BookmarkName = 0);
private:
void updateTimeLabelTooltips();
TimeLabel *m_timeLabelLeft;
TimeLabel *m_timeLabelRight;
Amarok::TimeSlider *m_slider;
QString m_currentUrlId;
};
#endif
diff --git a/src/widgets/SearchWidget.cpp b/src/widgets/SearchWidget.cpp
index 5cfe292449..5694117a60 100644
--- a/src/widgets/SearchWidget.cpp
+++ b/src/widgets/SearchWidget.cpp
@@ -1,286 +1,289 @@
/****************************************************************************************
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2011 Sven Krohlas <sven@asbest-online.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SearchWidget.h"
#include "core/support/Debug.h"
#include "dialogs/EditFilterDialog.h"
#include <KLocale>
#include <KGlobalSettings>
#include <QAction>
#include <QToolBar>
#include <QVBoxLayout>
#include <QIcon>
#include <KLineEdit>
#include <KHBox>
#include <KPushButton>
#include <kstandarddirs.h>
SearchWidget::SearchWidget( QWidget *parent, bool advanced )
: QWidget( parent )
, m_sw( 0 )
, m_filterAction( 0 )
, m_timeout( 500 )
, m_runningSearches( 0 )
{
setContentsMargins( 0, 0, 0, 0 );
KHBox *searchBox = new KHBox( this );
searchBox->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
m_sw = new Amarok::ComboBox( searchBox );
m_sw->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
m_sw->setFrame( true );
m_sw->setCompletionMode( KCompletion::CompletionPopup );
m_sw->completionObject()->setIgnoreCase( true );
m_sw->setToolTip( i18n( "Enter space-separated terms to search." ) );
m_sw->addItem( KStandardGuiItem::find().icon(), QString() );
- connect( m_sw, SIGNAL(activated(int)), SLOT(onComboItemActivated(int)) );
- connect( m_sw, SIGNAL(editTextChanged(QString)), SLOT(resetFilterTimeout()) );
- connect( m_sw, SIGNAL(returnPressed()), SLOT(filterNow()) ); // filterNow() calls addCompletion()
- connect( m_sw, SIGNAL(returnPressed()), SIGNAL(returnPressed()) );
- connect( m_sw, SIGNAL(downPressed()), SLOT(advanceFocus()) );
+ connect( m_sw, QOverload<int>::of(&QComboBox::activated),
+ this, &SearchWidget::onComboItemActivated );
+ connect( m_sw, &Amarok::ComboBox::editTextChanged, this, &SearchWidget::resetFilterTimeout );
+ connect( m_sw, QOverload<>::of(&KComboBox::returnPressed),
+ this, &SearchWidget::filterNow ); // filterNow() calls addCompletion()
+ connect( m_sw, QOverload<>::of(&KComboBox::returnPressed),
+ this, &SearchWidget::returnPressed );
+ connect( m_sw, &Amarok::ComboBox::downPressed, this, &SearchWidget::advanceFocus );
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget( searchBox );
layout->setContentsMargins( 0, 0, 0, 0 );
setLayout( layout );
setClickMessage( i18n( "Enter search terms here" ) );
m_toolBar = new QToolBar( searchBox );
m_toolBar->setFixedHeight( m_sw->sizeHint().height() );
if( advanced )
{
m_filterAction = new QAction( QIcon::fromTheme( "document-properties" ), i18n( "Edit filter" ), this );
m_filterAction->setObjectName( "filter" );
m_toolBar->addAction( m_filterAction );
- connect( m_filterAction, SIGNAL(triggered()), this, SLOT(slotShowFilterEditor()) );
+ connect( m_filterAction, &QAction::triggered, this, &SearchWidget::slotShowFilterEditor );
}
m_filterTimer.setSingleShot( true );
- connect( &m_filterTimer, SIGNAL(timeout()), SLOT(filterNow()) );
+ connect( &m_filterTimer, &QTimer::timeout, this, &SearchWidget::filterNow );
m_animationTimer.setInterval( 500 );
- connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(nextAnimationTick()) );
+ connect( &m_animationTimer, &QTimer::timeout, this, &SearchWidget::nextAnimationTick );
}
void
SearchWidget::resetFilterTimeout()
{
m_filterTimer.start( m_timeout );
}
void
SearchWidget::filterNow()
{
m_filterTimer.stop();
addCompletion( m_sw->currentText() );
emit filterChanged( m_sw->currentText() );
}
void
SearchWidget::advanceFocus()
{
focusNextChild();
}
void
SearchWidget::addCompletion( const QString &text )
{
int index = m_sw->findText( text );
if( index == -1 )
{
m_sw->addItem( KStandardGuiItem::find().icon(), text );
m_sw->completionObject()->addItem( text );
}
index = m_sw->findText( text );
m_sw->setCurrentIndex( index );
}
void
SearchWidget::onComboItemActivated( int index )
{
// if data of UserRole exists, use that as the actual filter string
const QString userFilter = m_sw->itemData( index ).toString();
if( userFilter.isEmpty() )
m_sw->setEditText( m_sw->itemText(index) );
else
m_sw->setEditText( userFilter );
}
void
SearchWidget::slotShowFilterEditor()
{
EditFilterDialog *fd = new EditFilterDialog( this, m_sw->currentText() );
fd->setAttribute( Qt::WA_DeleteOnClose );
m_filterAction->setEnabled( false );
- connect( fd, SIGNAL(filterChanged(QString)), m_sw, SLOT(setEditText(QString)) );
- connect( fd, SIGNAL(finished(int)), this, SLOT(slotFilterEditorFinished(int)) );
+ connect( fd, &EditFilterDialog::filterChanged, m_sw, &Amarok::ComboBox::setEditText );
+ connect( fd, &QDialog::finished, this, &SearchWidget::slotFilterEditorFinished );
fd->show();
}
void
SearchWidget::slotFilterEditorFinished( int result )
{
m_filterAction->setEnabled( true );
if( result && !m_sw->currentText().isEmpty() ) // result == QDialog::Accepted
addCompletion( m_sw->currentText() );
}
QToolBar *
SearchWidget::toolBar()
{
return m_toolBar;
}
void
SearchWidget::showAdvancedButton( bool show )
{
if( show )
{
if( m_filterAction != 0 )
{
m_filterAction = new QAction( QIcon::fromTheme( "document-properties" ), i18n( "Edit filter" ), this );
m_filterAction->setObjectName( "filter" );
m_toolBar->addAction( m_filterAction );
- connect( m_filterAction, SIGNAL(triggered()), this, SLOT(slotShowFilterEditor()) );
+ connect( m_filterAction, &QAction::triggered, this, &SearchWidget::slotShowFilterEditor );
}
}
else
{
delete m_filterAction;
m_filterAction = 0;
}
}
void
SearchWidget::setClickMessage( const QString &message )
{
KLineEdit *edit = qobject_cast<KLineEdit*>( m_sw->lineEdit() );
edit->setClickMessage( message );
}
void
SearchWidget::setTimeout( quint16 newTimeout )
{
m_timeout = newTimeout;
}
// public slots:
void
SearchWidget::setSearchString( const QString &searchString )
{
if( searchString != currentText() ) {
m_sw->setEditText( searchString );
filterNow();
}
}
void
SearchWidget::searchStarted()
{
m_runningSearches++;
// start the animation
if( !m_animationTimer.isActive() )
{
m_sw->setItemIcon( m_sw->currentIndex(), QIcon( KStandardDirs::locate( "data", "amarok/images/loading1.png" ) ) );
m_currentFrame = 0;
m_animationTimer.start();
}
// If another search is running it might still have a part of the animation set as its icon.
// As the currentIndex() has changed we don't know which one. We now have to iterate through
// all of them and set the icon correctly. It's not as bad as it sounds: the number is quite
// limited.
for( int i = 0; i < m_sw->count(); i++ )
{
if( i != m_sw->currentIndex() ) // not the current one, which should be animated!
m_sw->setItemIcon( i, KStandardGuiItem::find().icon() );
}
}
void
SearchWidget::searchEnded()
{
if( m_runningSearches > 0 ) // just to be sure...
m_runningSearches--;
// stop the animation
if( m_runningSearches == 0 )
{
m_animationTimer.stop();
saveLineEditStatus();
m_sw->setItemIcon( m_sw->currentIndex(), KStandardGuiItem::find().icon() );
restoreLineEditStatus();
}
}
// private slots:
void
SearchWidget::nextAnimationTick()
{
saveLineEditStatus();
// switch frames
if( m_currentFrame == 0 )
m_sw->setItemIcon( m_sw->currentIndex(), QIcon( KStandardDirs::locate( "data", "amarok/images/loading2.png" ) ) );
else
m_sw->setItemIcon( m_sw->currentIndex(), QIcon( KStandardDirs::locate( "data", "amarok/images/loading1.png" ) ) );
restoreLineEditStatus();
m_currentFrame = !m_currentFrame;
}
// private:
void
SearchWidget::restoreLineEditStatus()
{
// restore text changes made by the user
m_sw->setEditText( m_text );
if( m_hasSelectedText )
m_sw->lineEdit()->setSelection( m_selectionStart, m_selectionLength ); // also sets cursor
else
m_sw->lineEdit()->setCursorPosition( m_cursorPosition );
}
void
SearchWidget::saveLineEditStatus()
{
// save text changes made by the user
m_text = m_sw->lineEdit()->text();
m_cursorPosition = m_sw->cursorPosition();
m_hasSelectedText = m_sw->lineEdit()->hasSelectedText();
m_selectionStart = m_sw->lineEdit()->selectionStart();
m_selectionLength = m_sw->lineEdit()->selectedText().length();
}
diff --git a/src/widgets/SearchWidget.h b/src/widgets/SearchWidget.h
index c8baed6ecf..476e1d949f 100644
--- a/src/widgets/SearchWidget.h
+++ b/src/widgets/SearchWidget.h
@@ -1,135 +1,136 @@
/****************************************************************************************
* Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com> *
* Copyright (c) 2011 Sven Krohlas <sven@asbest-online.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef SEARCHWIDGET_H
#define SEARCHWIDGET_H
#include "amarok_export.h"
#include "ComboBox.h"
#include <QWidget>
#include <QTimer>
class QToolBar;
class KPushButton;
// A Custom Widget that can be used globally to implement
// searching a treeview.
class AMAROK_EXPORT SearchWidget : public QWidget
{
Q_OBJECT
public:
/** Creates a search widget.
@param advanced If true generates a button that opens a edit filter dialog.
*/
explicit SearchWidget( QWidget *parent, bool advanced = true );
QString currentText() const { return m_sw->currentText(); }
Amarok::ComboBox *comboBox() { return m_sw; }
/**
* Sets the timout length after which the filterChanged() signal will be fired automatically.
* @param newTimeout timeout in milliseconds.
*/
void setTimeout( quint16 newTimeout );
QToolBar* toolBar();
void showAdvancedButton( bool show );
/**
* Sets the string that will be visible when the ComboBox's edit text is empty.
* @param message the string that will be visible when the ComboBox's edit text is empty.
*/
void setClickMessage( const QString &message );
public Q_SLOTS:
void setSearchString( const QString &searchString = QString() );
+ void emptySearchString() { setSearchString( QString() ); }
/**
* Tells the widget that a search operation has started. As a consequence the
* "search" icon changes to a progress animation.
*
* Note: You can call this slot several times if you ahve several search operations
* simultaneously. The widget has an internal counter to track them.
*/
void searchStarted();
/**
* Tells the widget that a search operation has ended. As a consequence the
* progress animation will be changed back to a search icon iff no other search
* operation is in progress.
*/
void searchEnded();
Q_SIGNALS:
/**
* Emitted when the filter value was changed.
* Note: This signal might be delayed while the user is typing
*/
void filterChanged( const QString &filter );
/**
* Emitted when the user hits enter after after typing in the filter. It is
* guaranteed that filterChanged() with the current text was emitted previously.
*/
void returnPressed();
private Q_SLOTS:
void resetFilterTimeout();
void filterNow();
void advanceFocus();
void addCompletion( const QString &text );
void nextAnimationTick();
void onComboItemActivated( int index );
void slotShowFilterEditor();
void slotFilterEditorFinished( int result );
private:
Amarok::ComboBox *m_sw;
QAction *m_filterAction;
QToolBar *m_toolBar;
QTimer m_animationTimer;
QTimer m_filterTimer;
quint16 m_timeout;
bool m_currentFrame;
unsigned int m_runningSearches;
// required to save/restore line edit status
QString m_text;
int m_cursorPosition;
bool m_hasSelectedText;
int m_selectionStart;
int m_selectionLength;
/**
* Restore the status of the internal line edit (text, selection, cursor position).
* Crete a snapshot with saveLineEditStatus() before using this method.
* Required to keep user changes during animations.
*/
void restoreLineEditStatus();
/**
* Save the status of the internal line edit (text, selection, cursor position) to
* restore it later with restoreLineEditStatus().
* Required to keep user changes during animations.
*/
void saveLineEditStatus();
};
#endif
diff --git a/src/widgets/SliderWidget.cpp b/src/widgets/SliderWidget.cpp
index 90f09cd617..ffd6d49fb5 100644
--- a/src/widgets/SliderWidget.cpp
+++ b/src/widgets/SliderWidget.cpp
@@ -1,418 +1,418 @@
/****************************************************************************************
* Copyright (c) 2003-2009 Mark Kretschmann <kretschmann@kde.org> *
* Copyright (c) 2005 Gabor Lehel <illissius@gmail.com> *
* Copyright (c) 2008 Dan Meltzer <parallelgrapefruit@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "SliderWidget.h"
#include <config.h>
#include "core/support/Amarok.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "amarokconfig.h"
#include "App.h"
#include "BookmarkTriangle.h"
#include "core/support/Debug.h"
#include "EngineController.h"
#include "core/meta/support/MetaUtility.h"
#include "SvgHandler.h"
#include "ProgressWidget.h"
#include <QIcon>
#include <KLocale>
#include <KStandardDirs>
#include <KGlobalSettings>
#include <QAction>
#include <QContextMenuEvent>
#include <QFontMetrics>
#include <QMenu>
#include <QStyle>
#include <QStyleOption>
#include <QPainter>
Amarok::Slider::Slider( Qt::Orientation orientation, uint max, QWidget *parent )
: QSlider( orientation, parent )
, m_sliding( false )
, m_outside( false )
, m_prevValue( 0 )
, m_needsResize( true )
{
setMouseTracking( true );
setRange( 0, max );
setAttribute( Qt::WA_NoMousePropagation, true );
setAttribute( Qt::WA_Hover, true );
if ( orientation == Qt::Vertical )
{
setInvertedAppearance( true );
setInvertedControls( true );
}
}
QRect
Amarok::Slider::sliderHandleRect( const QRect &slider, qreal percent ) const
{
QRect rect;
const bool inverse = ( orientation() == Qt::Horizontal ) ?
( invertedAppearance() != (layoutDirection() == Qt::RightToLeft) ) :
( !invertedAppearance() );
if( m_usingCustomStyle)
rect = The::svgHandler()->sliderKnobRect( slider, percent, inverse );
else
{
if ( inverse )
percent = 1.0 - percent;
const int handleSize = style()->pixelMetric( QStyle::PM_SliderControlThickness );
rect = QRect( 0, 0, handleSize, handleSize );
rect.moveTo( slider.x() + qRound( ( slider.width() - handleSize ) * percent ), slider.y() + 1 );
}
return rect;
}
void
Amarok::Slider::wheelEvent( QWheelEvent *e )
{
DEBUG_BLOCK
if( orientation() == Qt::Vertical )
{
// Will be handled by the parent widget
e->ignore();
return;
}
// Position Slider (horizontal)
// only used for progress slider now!
int step = e->delta() * 24;
int nval = value() + step;
nval = qMax(nval, minimum());
nval = qMin(nval, maximum());
QSlider::setValue( nval );
emit sliderReleased( value() );
}
void
Amarok::Slider::mouseMoveEvent( QMouseEvent *e )
{
if ( m_sliding )
{
//feels better, but using set value of 20 is bad of course
QRect rect( -20, -20, width()+40, height()+40 );
if ( orientation() == Qt::Horizontal && !rect.contains( e->pos() ) )
{
if ( !m_outside )
{
QSlider::setValue( m_prevValue );
//if mouse released outside of slider, emit sliderMoved to previous value
emit sliderMoved( m_prevValue );
}
m_outside = true;
}
else
{
m_outside = false;
slideEvent( e );
emit sliderMoved( value() );
}
}
else
QSlider::mouseMoveEvent( e );
}
void
Amarok::Slider::slideEvent( QMouseEvent *e )
{
QRect knob;
if ( maximum() > minimum() )
knob = sliderHandleRect( rect(), ((qreal)value()) / ( maximum() - minimum() ) );
int position;
int span;
if( orientation() == Qt::Horizontal )
{
position = e->pos().x() - knob.width() / 2;
span = width() - knob.width();
}
else
{
position = e->pos().y() - knob.height() / 2;
span = height() - knob.height();
}
const bool inverse = ( orientation() == Qt::Horizontal ) ?
( invertedAppearance() != (layoutDirection() == Qt::RightToLeft) ) :
( !invertedAppearance() );
const int val = QStyle::sliderValueFromPosition( minimum(), maximum(), position, span, inverse );
QSlider::setValue( val );
}
void
Amarok::Slider::mousePressEvent( QMouseEvent *e )
{
m_sliding = true;
m_prevValue = value();
QRect knob;
if ( maximum() > minimum() )
knob = sliderHandleRect( rect(), ((qreal)value()) / ( maximum() - minimum() ) );
if ( !knob.contains( e->pos() ) )
mouseMoveEvent( e );
}
void
Amarok::Slider::mouseReleaseEvent( QMouseEvent* )
{
if( !m_outside && value() != m_prevValue )
emit sliderReleased( value() );
m_sliding = false;
m_outside = false;
}
void
Amarok::Slider::setValue( int newValue )
{
//don't adjust the slider while the user is dragging it!
if ( !m_sliding || m_outside )
QSlider::setValue( newValue );
else
m_prevValue = newValue;
}
void Amarok::Slider::paintCustomSlider( QPainter *p, bool paintMoodbar )
{
qreal percent = 0.0;
if ( maximum() > minimum() )
percent = ((qreal)value()) / ( maximum() - minimum() );
QStyleOptionSlider opt;
initStyleOption( &opt );
if ( m_sliding ||
( underMouse() && sliderHandleRect( rect(), percent ).contains( mapFromGlobal(QCursor::pos()) ) ) )
{
opt.activeSubControls |= QStyle::SC_SliderHandle;
}
The::svgHandler()->paintCustomSlider( p, &opt, percent, paintMoodbar );
}
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS VolumeSlider
//////////////////////////////////////////////////////////////////////////////////////////
Amarok::VolumeSlider::VolumeSlider( uint max, QWidget *parent, bool customStyle )
: Amarok::Slider( customStyle ? Qt::Horizontal : Qt::Vertical, max, parent )
{
m_usingCustomStyle = customStyle;
setFocusPolicy( Qt::NoFocus );
setInvertedAppearance( false );
setInvertedControls( false );
}
void
Amarok::VolumeSlider::mousePressEvent( QMouseEvent *e )
{
if( e->button() != Qt::RightButton )
{
Amarok::Slider::mousePressEvent( e );
slideEvent( e );
}
}
void
Amarok::VolumeSlider::contextMenuEvent( QContextMenuEvent *e )
{
QMenu menu;
menu.setTitle( i18n( "Volume" ) );
menu.addAction( i18n( "100%" ) )->setData( 100 );
menu.addAction( i18n( "80%" ) )->setData( 80 );
menu.addAction( i18n( "60%" ) )->setData( 60 );
menu.addAction( i18n( "40%" ) )->setData( 40 );
menu.addAction( i18n( "20%" ) )->setData( 20 );
menu.addAction( i18n( "0%" ) )->setData( 0 );
/*
// TODO: Phonon
menu.addSeparator();
- menu.addAction( QIcon::fromTheme( "view-media-equalizer-amarok" ), i18n( "&Equalizer" ), qApp, SLOT(slotConfigEqualizer()) )->setData( -1 );
+ menu.addAction( QIcon::fromTheme( "view-media-equalizer-amarok" ), i18n( "&Equalizer" ), qApp, &QCoreApplication::slotConfigEqualizer()) )->setData( -1 );
*/
QAction* a = menu.exec( mapToGlobal( e->pos() ) );
if( a )
{
const int n = a->data().toInt();
if( n >= 0 )
{
QSlider::setValue( n );
emit sliderReleased( n );
}
}
}
void
Amarok::VolumeSlider::wheelEvent( QWheelEvent *e )
{
const uint step = e->delta() / Amarok::VOLUME_SENSITIVITY;
QSlider::setValue( QSlider::value() + step );
emit sliderReleased( value() );
}
void
Amarok::VolumeSlider::paintEvent( QPaintEvent *event )
{
if( m_usingCustomStyle )
{
QPainter p( this );
paintCustomSlider( &p );
p.end();
return;
}
QSlider::paintEvent( event );
}
//////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// TIMESLIDER ////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
Amarok::TimeSlider::TimeSlider( QWidget *parent )
: Amarok::Slider( Qt::Horizontal, 0, parent )
, m_triangles()
, m_knobX( 0.0 )
{
m_usingCustomStyle = true;
setFocusPolicy( Qt::NoFocus );
}
void
Amarok::TimeSlider::setSliderValue( int value )
{
Amarok::Slider::setValue( value );
}
void
Amarok::TimeSlider::paintEvent( QPaintEvent *pe )
{
QPainter p( this );
p.setClipRegion( pe->region() );
paintCustomSlider( &p, AmarokConfig::showMoodbarInSlider() );
p.end();
}
void Amarok::TimeSlider::resizeEvent(QResizeEvent * event)
{
Amarok::Slider::resizeEvent( event );
The::amarokUrlHandler()->updateTimecodes();
}
void Amarok::TimeSlider::sliderChange( SliderChange change )
{
if ( change == SliderValueChange || change == SliderRangeChange )
{
int oldKnobX = m_knobX;
qreal percent = 0.0;
if ( maximum() > minimum() )
percent = ((qreal)value()) / ( maximum() - minimum() );
QRect knob = sliderHandleRect( rect(), percent );
m_knobX = knob.x();
if (oldKnobX < m_knobX)
update( oldKnobX, knob.y(), knob.right() + 1 - oldKnobX, knob.height() );
else if (oldKnobX > m_knobX)
update( m_knobX, knob.y(), oldKnobX + knob.width(), knob.height() );
}
else
Amarok::Slider::sliderChange( change ); // calls update()
}
void Amarok::TimeSlider::drawTriangle( const QString& name, int milliSeconds, bool showPopup )
{
DEBUG_BLOCK
int sliderHeight = height() - ( s_sliderInsertY * 2 );
int sliderLeftWidth = sliderHeight / 3;
// This mess converts the # of seconds into the pixel width value where the triangle should be drawn
int x_pos = ( ( ( double ) milliSeconds - ( double ) minimum() ) / ( maximum() - minimum() ) ) * ( width() - ( sliderLeftWidth + sliderLeftWidth + s_sliderInsertX * 2 ) );
debug() << "drawing triangle at " << x_pos;
BookmarkTriangle * tri = new BookmarkTriangle( this, milliSeconds, name, width(), showPopup );
- connect( tri, SIGNAL(clicked(int)), SLOT(slotTriangleClicked(int)) );
- connect( tri, SIGNAL(focused(int)), SLOT(slotTriangleFocused(int)) );
+ connect( tri, &BookmarkTriangle::clicked, this, &TimeSlider::slotTriangleClicked );
+ connect( tri, &BookmarkTriangle::focused, this, &TimeSlider::slotTriangleFocused );
m_triangles << tri;
tri->setGeometry( x_pos + 6 /* to center the point */, 1 /*y*/, 11, 11 ); // 6 = hard coded border width
tri->show();
}
void Amarok::TimeSlider::slotTriangleClicked( int seconds )
{
emit sliderReleased( seconds );
}
void Amarok::TimeSlider::slotTriangleFocused( int seconds )
{
QList<BookmarkTriangle *>::iterator i;
for( i = m_triangles.begin(); i != m_triangles.end(); ++i ){
if( (*i)->getTimeValue() != seconds )
(*i)->hidePopup();
}
}
void Amarok::TimeSlider::clearTriangles()
{
QList<BookmarkTriangle *>::iterator i;
for( i = m_triangles.begin(); i != m_triangles.end(); ++i ){
(*i)->deleteLater();
}
m_triangles.clear();
}
void Amarok::TimeSlider::mousePressEvent( QMouseEvent *event )
{
if( !The::engineController()->isSeekable() )
return; // Eat the event,, it's not possible to seek
Amarok::Slider::mousePressEvent( event );
}
bool Amarok::TimeSlider::event( QEvent * event )
{
if( event->type() == QEvent::ToolTip )
{
// Make a QHelpEvent out of this
QHelpEvent * helpEvent = dynamic_cast<QHelpEvent *>( event );
if( helpEvent )
{
//figure out "percentage" of the track length that the mouse is hovering over the slider
qreal percentage = (qreal) helpEvent->x() / (qreal) width();
long trackLength = The::engineController()->trackLength();
int trackPosition = trackLength * percentage;
// Update tooltip to show the track position under the cursor
setToolTip( i18nc( "Tooltip shown when the mouse is over the progress slider, representing the position in the currently playing track that Amarok will seek to if you click the mouse. Keep it concise.", "Jump to: %1", Meta::msToPrettyTime( trackPosition ) ) );
}
}
return QWidget::event( event );
}
diff --git a/src/widgets/TokenDropTarget.cpp b/src/widgets/TokenDropTarget.cpp
index fb6b5c8191..36403c8b07 100644
--- a/src/widgets/TokenDropTarget.cpp
+++ b/src/widgets/TokenDropTarget.cpp
@@ -1,392 +1,392 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Lbking <thomas.luebking@web.de> *
* Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TokenDropTarget.h"
#include "Token.h"
#include "TokenPool.h"
#include "core/support/Debug.h"
#include <KLocale>
#include <QDropEvent>
#include <QPainter>
#include <QVBoxLayout>
#include <QMimeData>
TokenDropTarget::TokenDropTarget( QWidget *parent )
: QWidget( parent )
, m_rowLimit( 0 )
, m_rows( 0 )
, m_horizontalStretch( false ) // DANGER: m_horizontalStretch is used as int in the following code, assuming that true == 1
, m_verticalStretch( true )
, m_tokenFactory( new TokenFactory() )
{
setAcceptDrops( true );
QBoxLayout* mainLayout = new QVBoxLayout( this );
mainLayout->setSpacing( 0 );
mainLayout->addStretch( 1 ); // the vertical stretch
mainLayout->setContentsMargins( 0, 0, 0, 0 );
}
TokenDropTarget::~TokenDropTarget()
{
delete m_tokenFactory;
}
QSize
TokenDropTarget::sizeHint() const
{
QSize result = QWidget::sizeHint();
// we need at least space for the "empty" text.
int h = fontMetrics().height();
result = result.expandedTo( QSize( 36 * h, 2 * h ) );
return result;
}
QSize
TokenDropTarget::minimumSizeHint() const
{
QSize result = QWidget::minimumSizeHint();
// we need at least space for the "empty" text.
int h = fontMetrics().height();
result = result.expandedTo( QSize( 36 * h, 2 * h ) );
return result;
}
QHBoxLayout *
TokenDropTarget::appendRow()
{
QHBoxLayout *box = new QHBoxLayout;
box->setSpacing( 0 );
if( m_horizontalStretch )
box->addStretch();
static_cast<QVBoxLayout*>(layout())->insertLayout( rows(), box );
m_rows++;
return box;
}
void
TokenDropTarget::clear()
{
QList< Token *> allTokens = tokensAtRow();
foreach( Token* token, allTokens )
delete token;
}
int
TokenDropTarget::count() const
{
int c = 0;
for( int row = rows() - 1; row >= 0; --row )
if( QBoxLayout *box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() ) )
c += box->count() - m_horizontalStretch;
return c;
}
void
TokenDropTarget::setRowLimit( uint r )
{
// if we have more than one row we have a stretch at the end.
QBoxLayout *mainLayout = qobject_cast<QBoxLayout*>( layout() );
if( ( r == 1 ) && (m_rowLimit != 1 ) )
mainLayout->takeAt( mainLayout->count() - 1 );
else if( ( r != 1 ) && (m_rowLimit == 1 ) )
mainLayout->addStretch( 1 ); // the vertical stretch
m_rowLimit = r;
}
void
TokenDropTarget::deleteEmptyRows()
{
DEBUG_BLOCK;
for( int row = rows() - 1; row >= 0; --row )
{
QBoxLayout *box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
if( box && box->count() < ( 1 + m_horizontalStretch ) ) // sic! last is spacer
{
delete layout()->takeAt( row );
m_rows--;
}
}
update(); // this removes empty layouts somehow for deleted tokens. don't remove
}
QList< Token *>
TokenDropTarget::tokensAtRow( int row )
{
DEBUG_BLOCK;
int lower = 0;
int upper = (int)rows();
if( row > -1 && row < (int)rows() )
{
lower = row;
upper = row + 1;
}
QList< Token *> list;
Token *token;
for( row = lower; row < upper; ++row )
if ( QHBoxLayout *rowBox = qobject_cast<QHBoxLayout*>( layout()->itemAt( row )->layout() ) )
{
for( int col = 0; col < rowBox->count() - m_horizontalStretch; ++col )
if ( ( token = qobject_cast<Token*>( rowBox->itemAt( col )->widget() ) ) )
list << token;
}
debug() << "Row:"<<row<<"items:"<<list.count();
return list;
}
void
TokenDropTarget::insertToken( Token *token, int row, int col )
{
// - copy the token if it belongs to a token pool (fix BR 296136)
if( qobject_cast<TokenPool*>(token->parent() ) ) {
debug() << "Copying token" << token->name();
token = m_tokenFactory->createToken( token->name(),
token->iconName(),
token->value() );
}
token->setParent( this );
// - validate row
if ( row < 0 && rowLimit() && rows() >= rowLimit() )
row = rowLimit() - 1; // want to append, but we can't so use the last row instead
QBoxLayout *box;
if( row < 0 || row >= (int)rows() )
box = appendRow();
else
box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
// - validate col
if( col < 0 || col > box->count() - ( 1 + m_horizontalStretch ) )
col = box->count() - m_horizontalStretch;
// - copy the token if it belongs to a token pool (fix BR 296136)
if( qobject_cast<TokenPool*>(token->parent() ) ) {
debug() << "Copying token" << token->name();
token = m_tokenFactory->createToken( token->name(),
token->iconName(),
token->value() );
}
box->insertWidget( col, token );
token->show();
- connect( token, SIGNAL(changed()), this, SIGNAL(changed()) );
- connect( token, SIGNAL(gotFocus(Token*)), this, SIGNAL(tokenSelected(Token*)) );
- connect( token, SIGNAL(destroyed(QObject*)), this, SIGNAL(changed()) );
- connect( token, SIGNAL(destroyed(QObject*)), this, SLOT(deleteEmptyRows()) );
+ connect( token, &Token::changed, this, &TokenDropTarget::changed );
+ connect( token, &Token::gotFocus, this, &TokenDropTarget::tokenSelected );
+ connect( token, &Token::destroyed, this, &TokenDropTarget::changed );
+ connect( token, &Token::destroyed, this, &TokenDropTarget::deleteEmptyRows );
emit changed();
}
Token*
TokenDropTarget::tokenAt( const QPoint &pos ) const
{
for( uint row = 0; row < rows(); ++row )
if( QBoxLayout *rowBox = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() ) )
for( int col = 0; col < rowBox->count(); ++col )
if( QWidget *kid = rowBox->itemAt( col )->widget() )
{
if( kid->geometry().contains( pos ) )
return qobject_cast<Token*>(kid);
}
return 0;
}
void
TokenDropTarget::drop( Token *token, const QPoint &pos )
{
DEBUG_BLOCK;
if ( !token )
return;
// find the token at the position.
QWidget *child = childAt( pos );
Token *targetToken = qobject_cast<Token*>(child); // tokenAt( pos );
if( !targetToken && child && child->parent() ) // sometimes we get the label of the token.
targetToken = qobject_cast<Token*>( child->parent() );
// unlayout in case of move
if( QBoxLayout *box = rowBox( token ) )
{
box->removeWidget( token );
deleteEmptyRows(); // a row could now be empty due to a move
}
if( targetToken )
{ // we hit a sibling, -> prepend
QPoint idx;
rowBox( targetToken, &idx );
if( rowLimit() != 1 && rowLimit() < m_rows && idx.y() == (int)m_rows - 1 &&
pos.y() > targetToken->geometry().y() + ( targetToken->height() * 2 / 3 ) )
insertToken( token, idx.y() + 1, idx.x());
else if( pos.x() > targetToken->geometry().x() + targetToken->width() / 2 )
insertToken( token, idx.y(), idx.x() + 1);
else
insertToken( token, idx.y(), idx.x() );
}
else
{
- insertToken( token );
+ appendToken( token );
}
token->setFocus( Qt::OtherFocusReason ); // select the new token right away
}
void
TokenDropTarget::dragEnterEvent( QDragEnterEvent *event )
{
if( event->mimeData()->hasFormat( Token::mimeType() ) )
event->acceptProposedAction();
}
void
TokenDropTarget::dropEvent( QDropEvent *event )
{
if( event->mimeData()->hasFormat( Token::mimeType() ) )
{
event->acceptProposedAction();
Token *token = qobject_cast<Token*>( event->source() );
if ( !token ) // decode the stream created in TokenPool::dropEvent
token = m_tokenFactory->createTokenFromMime( event->mimeData(), this );
// - copy the token if it belongs to a token pool (fix BR 296136)
if( qobject_cast<TokenPool*>(token->parent() ) ) {
token = m_tokenFactory->createToken( token->name(),
token->iconName(),
token->value() );
}
if( token )
drop( token, event->pos() );
}
}
void
TokenDropTarget::paintEvent(QPaintEvent *pe)
{
QWidget::paintEvent(pe);
if (count())
return;
QPainter p(this);
QColor c = palette().color(foregroundRole());
c.setAlpha(c.alpha()*64/255);
p.setPen(c);
p.drawText(rect(), Qt::AlignCenter | Qt::TextWordWrap, i18n("Drag in and out items from above."));
p.end();
}
int
TokenDropTarget::row( Token *token ) const
{
for( uint row = 0; row <= rows(); ++row )
{
QBoxLayout *box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
if ( box && ( box->indexOf( token ) ) > -1 )
return row;
}
return -1;
}
QBoxLayout *
TokenDropTarget::rowBox( QWidget *w, QPoint *idx ) const
{
QBoxLayout *box = 0;
int col;
for( uint row = 0; row < rows(); ++row )
{
box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
if ( box && ( col = box->indexOf( w ) ) > -1 )
{
if ( idx )
{
idx->setX( col );
idx->setY( row );
}
return box;
}
}
return NULL;
}
QBoxLayout *
TokenDropTarget::rowBox( const QPoint &pt ) const
{
QBoxLayout *box = 0;
for( uint row = 0; row < rows(); ++row )
{
box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
if ( !box )
continue;
for ( int col = 0; col < box->count(); ++col )
{
if ( QWidget *w = box->itemAt( col )->widget() )
{
const QRect &geo = w->geometry();
if ( geo.y() <= pt.y() && geo.bottom() >= pt.y() )
return box;
break; // yes - all widgets are supposed of equal height. we checked on, we checked all
}
}
}
return NULL;
}
void
TokenDropTarget::setCustomTokenFactory( TokenFactory * factory )
{
delete m_tokenFactory;
m_tokenFactory = factory;
}
void
TokenDropTarget::setVerticalStretch( bool value )
{
if( value == m_verticalStretch )
return;
m_verticalStretch = value;
if( m_verticalStretch )
qobject_cast<QBoxLayout*>( layout() )->addStretch( 1 );
else
delete layout()->takeAt( layout()->count() - 1 );
}
diff --git a/src/widgets/TokenDropTarget.h b/src/widgets/TokenDropTarget.h
index a79da051f4..bef81b667f 100644
--- a/src/widgets/TokenDropTarget.h
+++ b/src/widgets/TokenDropTarget.h
@@ -1,135 +1,136 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Lbking <thomas.luebking@web.de> *
* Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef TOKENDROPTARGET_H
#define TOKENDROPTARGET_H
#include <QWidget>
class QBoxLayout;
class QHBoxLayout;
class QDropEvent;
class Token;
class TokenDragger;
class TokenFactory;
/** A widget that accepts dragging Tokens into it.
Used in several dialogs within Amarok e.g. the FilenameLayoutWidget and the
LayoutEditWidget.
The DropTarget can have one or more rows, limited by the rowLimit.
*/
class TokenDropTarget : public QWidget
{
Q_OBJECT
public:
explicit TokenDropTarget( QWidget *parent = 0 );
virtual ~TokenDropTarget();
QSize sizeHint() const;
QSize minimumSizeHint() const;
/** Removes all tokens from the drop target. */
void clear();
/** Returns the total number of all tokens contained in this drop traget. */
int count() const;
/** Returns the row and column position of the \p token. */
QPoint index( Token* token ) const;
/** Returns the row of the given \p Token or -1 if not found. */
int row ( Token* ) const;
/** Returns the number of rows that this layout has. */
uint rows() const { return m_rows; };
/** Returns the maximum allowed number of rows.
A number of 0 means that the row count is not limited at all.
*/
uint rowLimit() const { return m_rowLimit; }
void setRowLimit( uint r );
/** Set a custom factory that creates tokens.
The default factory is the one that creates normal tokens.
LayoutEditWidget set's this for the factory that creates StyledTokens.
The factory will be deleted by the TokenDropTarget.
*/
void setCustomTokenFactory( TokenFactory * factory );
void setVerticalStretch( bool value );
/** Returns all the tokens from the specified row.
If row == -1 returns all tokens. */
QList< Token *> tokensAtRow( int row = -1 );
public Q_SLOTS:
/** Insert the token at the given row and col position.
The token will be reparented for the TokenDropTarget.
*/
- void insertToken( Token*, int row = -1, int col = -1 ); // -1 -> append to last row
+ void insertToken( Token*, int row, int col );
+ void appendToken( Token *token ) { insertToken( token, -1, -1 ); } // -1 -> append to last row
void deleteEmptyRows();
Q_SIGNALS:
void changed();
/** Emitted if a new token got the focus. */
void tokenSelected( Token* );
protected:
void dragEnterEvent( QDragEnterEvent *event );
void dropEvent( QDropEvent *event );
/** Draws a "drop here" text if empty */
void paintEvent(QPaintEvent *);
/** Return the enclosing box layout and the row and column position of the widget \p w. */
QBoxLayout *rowBox( QWidget *w, QPoint *idx = 0 ) const;
/** Return the box layout at the position \p pt. */
QBoxLayout *rowBox( const QPoint &pt ) const;
private:
QHBoxLayout *appendRow();
/** Returns the token at the given global position */
Token* tokenAt( const QPoint &pos ) const;
void drop( Token*, const QPoint &pos = QPoint(0,0) );
private:
/** Maximum number of allowed rows.
If 0 the number is unlimited. */
uint m_rowLimit;
/** contains the number of real rows
(using the layout is not very practical in that since it seems that the layout
adds at least one empty entry by itself if it's empty) */
uint m_rows;
/** True if stretch are inserted at the ends of every row. */
bool m_horizontalStretch;
/** True if a stretch is inserted as a last row.
For now we always have a vertical stretch if the m_rowLimit > 1 */
bool m_verticalStretch;
TokenFactory *m_tokenFactory;
};
#endif
diff --git a/src/widgets/TokenWithLayout.cpp b/src/widgets/TokenWithLayout.cpp
index fb8cd2b964..bebec27f33 100644
--- a/src/widgets/TokenWithLayout.cpp
+++ b/src/widgets/TokenWithLayout.cpp
@@ -1,292 +1,292 @@
/****************************************************************************************
* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org> *
* Copyright (c) 2009 Roman Jarosz <kedgedev@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TokenWithLayout.h"
#include "core/support/Debug.h"
#include "TokenDropTarget.h"
#include "playlist/layouts/LayoutEditDialog.h"
#include <KColorScheme>
#include <QIcon>
#include <KLocale>
#include <QContextMenuEvent>
#include <QLayout>
#include <QPainter>
#include <QPushButton>
#include <QTimerEvent>
Wrench::Wrench( QWidget *parent ) : QLabel( parent )
{
setCursor( Qt::ArrowCursor );
setPixmap( QIcon::fromTheme( "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();
emit clicked();
}
void Wrench::mouseReleaseEvent( QMouseEvent * )
{
setMargin( 1 );
update();
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 );
}
QWeakPointer<LayoutEditDialog> 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, SIGNAL(clicked()), this, SLOT(showConfig()) );
+ 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.data()->setToken( this );
if( !m_dialog.data()->isVisible() )
{
m_dialog.data()->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.data()->QDialog::width() ) / 2 );
m_dialog.data()->move( pt );
}
m_dialog.data()->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 );
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 );
emit changed();
}
void TokenWithLayout::setPrefix( const QString& string )
{
if ( m_prefix == string )
return;
if ( string == i18n( "[prefix]" ) )
m_prefix.clear();
else
m_prefix = string;
emit changed();
}
void TokenWithLayout::setSuffix( const QString& string )
{
if ( m_suffix == string )
return;
if ( string == i18n( "[suffix]" ) )
m_suffix.clear();
else
m_suffix = string;
emit changed();
}
void TokenWithLayout::setWidth( int size )
{
m_width = qMax( qMin( 1.0, size/100.0 ), 0.0 ) ;
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 );
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 );
emit changed();
}
diff --git a/src/widgets/TrackActionButton.cpp b/src/widgets/TrackActionButton.cpp
index 1b81f9bd5a..8ce55c46c6 100644
--- a/src/widgets/TrackActionButton.cpp
+++ b/src/widgets/TrackActionButton.cpp
@@ -1,145 +1,145 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TrackActionButton.h"
#include <QAction>
#include <QEvent>
#include <QTimer>
TrackActionButton::TrackActionButton( QWidget *parent, const QAction *act ) : IconButton( parent )
{
if ( act )
setAction( act );
if ( parent )
parent->installEventFilter( this );
// this is during the labelslide - so we wait a short time with image processing ;-)
- QTimer::singleShot( 1200, this, SLOT(init()) );
+ QTimer::singleShot( 1200, this, &TrackActionButton::init );
}
bool TrackActionButton::eventFilter( QObject *o, QEvent *e )
{
if ( o == parentWidget() )
{
if ( e->type() == QEvent::Enter )
setIcon( m_icon.image[1], 3 );
else if ( e->type() == QEvent::Leave )
setIcon( m_icon.image[0], 6 );
}
return false;
}
void TrackActionButton::enterEvent( QEvent *e )
{
setIcon( m_icon.image[2], 3 );
IconButton::enterEvent( e );
}
void TrackActionButton::init()
{
reloadContent( size() );
}
void TrackActionButton::leaveEvent( QEvent *e )
{
setIcon( m_icon.image[1], 6 );
IconButton::leaveEvent( e );
}
void TrackActionButton::reloadContent( const QSize &sz )
{
if ( sz.isNull() )
return;
int r,g,b;
palette().color(foregroundRole()).getRgb(&r,&g,&b);
m_icon.image[2] = m_icon.icon.pixmap( sz ).toImage();
QImage img = m_icon.image[2].convertToFormat(QImage::Format_ARGB32);
int n = img.width() * img.height();
const uchar *bits = img.bits();
QRgb *pixel = (QRgb*)(const_cast<uchar*>(bits));
// this creates a (slightly) translucent monochromactic version of the
// image using the foreground color
// the gray value is turned into the opacity
#define ALPHA qAlpha(pixel[i])
#define GRAY qGray(pixel[i])
if ( qMax( qMax(r,g), b ) > 128 ) // value > 50%, bright foreground
for (int i = 0; i < n; ++i)
pixel[i] = qRgba( r,g,b, ( ALPHA * ( (160*GRAY) / 255 ) ) / 255 );
else // inverse
for (int i = 0; i < n; ++i)
pixel[i] = qRgba( r,g,b, ( ALPHA * ( (160*(255-GRAY)) / 255 ) ) / 255 );
// premultiplied is much faster on painting / alphablending
m_icon.image[1] = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
// and a very translucent variant
for (int i = 0; i < n; ++i)
pixel[i] = qRgba(r,g,b, ALPHA/3);
#undef ALPHA
#undef GRAY
m_icon.image[0] = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
int i = 0;
if ( underMouse() )
i = 2;
else if ( !parentWidget() || parentWidget()->underMouse() )
i = 1;
setIcon( m_icon.image[i] );
}
void TrackActionButton::setAction( const QAction *act )
{
- disconnect( SIGNAL(clicked()) );
+ disconnect( this, &TrackActionButton::clicked, 0, 0 );
m_action = act;
if ( act )
{
m_icon.icon = act->icon();
setToolTip( act->toolTip() );
- connect ( this, SIGNAL(clicked()), act, SLOT(trigger()) );
- connect ( act, SIGNAL(changed()), SLOT(updateAction()) );
+ connect ( this, &TrackActionButton::clicked, act, &QAction::trigger );
+ connect ( act, &QAction::changed, this, &TrackActionButton::updateAction );
}
else
{
m_icon.icon = QIcon();
setToolTip( QString() );
}
}
QSize TrackActionButton::sizeHint() const
{
return QSize( 16, 16 );
}
void TrackActionButton::updateAction()
{
if ( QAction *act = qobject_cast<QAction*>(sender()) )
{
if ( act == m_action )
{
m_icon.icon = act->icon();
setToolTip( act->toolTip() );
}
else // old action, stop listening
- disconnect ( act, SIGNAL(changed()), this, SLOT(updateAction()) );
+ disconnect ( act, &QAction::changed, this, &TrackActionButton::updateAction );
}
}
diff --git a/src/widgets/TrackSelectWidget.cpp b/src/widgets/TrackSelectWidget.cpp
index dbcda11da9..c4ba7ff09e 100644
--- a/src/widgets/TrackSelectWidget.cpp
+++ b/src/widgets/TrackSelectWidget.cpp
@@ -1,99 +1,99 @@
/****************************************************************************************
* Copyright (c) 2008-2010 Soren Harward <stharward@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#define DEBUG_PREFIX "TrackSelectWidget"
#include "TrackSelectWidget.h"
#include "amarokconfig.h"
#include "browsers/CollectionTreeItem.h"
#include "browsers/CollectionTreeItemModel.h"
#include "browsers/CollectionTreeView.h"
#include "core/meta/Meta.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include "widgets/PrettyTreeDelegate.h"
#include <KLocale>
#include <KSqueezedTextLabel>
#include <KVBox>
#include <QLabel>
TrackSelectWidget::TrackSelectWidget( QWidget* parent )
: KVBox( parent )
{
DEBUG_BLOCK
m_label = new KSqueezedTextLabel( this );
m_label->hide(); // TODO: decide whether the label should be shown or not
m_label->setTextElideMode( Qt::ElideRight );
setData( Meta::DataPtr() );
m_view = new CollectionTreeView( this );
m_view->setRootIsDecorated( false );
m_view->setFrameShape( QFrame::NoFrame );
m_view->setItemDelegate( new PrettyTreeDelegate( m_view ) );
QList<int> levelNumbers = Amarok::config( "Collection Browser" ).readEntry( "TreeCategory", QList<int>() );
QList<CategoryId::CatMenuId> levels;
foreach( int levelNumber, levelNumbers )
levels << CategoryId::CatMenuId( levelNumber );
if ( levels.isEmpty() )
levels << CategoryId::Artist << CategoryId::Album;
m_model = new CollectionTreeItemModel( levels );
m_model->setParent( this );
m_view->setModel( m_model );
- connect( m_view, SIGNAL(itemSelected(CollectionTreeItem*)),
- this, SLOT(recvNewSelection(CollectionTreeItem*)) );
+ connect( m_view, &CollectionTreeView::itemSelected,
+ this, &TrackSelectWidget::recvNewSelection );
}
TrackSelectWidget::~TrackSelectWidget() {}
void TrackSelectWidget::setData( const Meta::DataPtr& data )
{
debug() << "setting label to" << dataToLabel( data );
m_label->setText( i18n("Checkpoint: <b>%1</b>", dataToLabel( data ) ) );
}
void
TrackSelectWidget::recvNewSelection( CollectionTreeItem* item )
{
if ( item && item->isDataItem() ) {
Meta::DataPtr data = item->data();
if ( data != Meta::DataPtr() ) {
setData( data );
debug() << "new selection" << data->prettyName();
emit selectionChanged( data );
}
}
}
const QString TrackSelectWidget::dataToLabel( const Meta::DataPtr& data ) const
{
if ( data != Meta::DataPtr() ) {
if ( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( data ) ) {
return QString( i18n("Track: %1", track->prettyName() ) );
} else if ( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( data ) ) {
return QString( i18n("Album: %1", album->prettyName() ) );
} else if ( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( data ) ) {
return QString( i18n("Artist: %1", artist->prettyName() ) );
}
// TODO: can things other than tracks, artists, and albums end up here?
}
return QString( i18n("empty") );
}
diff --git a/src/widgets/VolumeDial.cpp b/src/widgets/VolumeDial.cpp
index 09df5297e1..b3045a8f39 100644
--- a/src/widgets/VolumeDial.cpp
+++ b/src/widgets/VolumeDial.cpp
@@ -1,343 +1,343 @@
/****************************************************************************************
* Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "VolumeDial.h"
#include "PaletteHandler.h"
#include "SvgHandler.h"
#include <QConicalGradient>
#include <QCoreApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QToolBar>
#include <QToolTip>
#include <KColorUtils>
#include <KLocale>
#include <cmath>
VolumeDial::VolumeDial( QWidget *parent ) : QDial( parent )
, m_isClick( false )
, m_isDown( false )
, m_muted( false )
{
m_anim.step = 0;
m_anim.timer = 0;
setMouseTracking( true );
- connect ( this, SIGNAL(valueChanged(int)), SLOT(valueChangedSlot(int)) );
- connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(paletteChanged(QPalette)) );
+ connect( this, &VolumeDial::valueChanged, this, &VolumeDial::valueChangedSlot );
+ connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &VolumeDial::paletteChanged );
}
void VolumeDial::addWheelProxies( QList<QWidget*> proxies )
{
foreach ( QWidget *proxy, proxies )
{
if ( !m_wheelProxies.contains( proxy ) )
{
proxy->installEventFilter( this );
- connect ( proxy, SIGNAL(destroyed(QObject*)), this, SLOT(removeWheelProxy(QObject*)) );
+ connect ( proxy, &QWidget::destroyed, this, &VolumeDial::removeWheelProxy );
m_wheelProxies << proxy;
}
}
}
void VolumeDial::paletteChanged( const QPalette &palette )
{
const QColor &fg = palette.color( foregroundRole() );
const QColor &hg = palette.color( QPalette::Highlight );
const qreal contrast = KColorUtils::contrastRatio( hg, palette.color( backgroundRole() ) );
m_highlightColor = KColorUtils::mix( hg, fg, 1.0 - contrast/3.0 );
renderIcons();
}
void VolumeDial::enterEvent( QEvent * )
{
startFade();
}
// NOTICE: we intercept wheelEvents for ourself to prevent the tooltip hiding on them,
// see ::wheelEvent()
// this is _NOT_ redundant to the code in MainToolbar.cpp
bool VolumeDial::eventFilter( QObject *o, QEvent *e )
{
if ( e->type() == QEvent::Wheel && !static_cast<QWheelEvent*>(e)->modifiers() )
{
if ( o == this || m_wheelProxies.contains( static_cast<QWidget*>( o ) ) )
{
QWheelEvent *wev = static_cast<QWheelEvent*>(e);
if ( o != this )
{
QPoint pos( 0, 0 ); // the event needs to be on us or nothing will happen
QWheelEvent nwev( pos, mapToGlobal( pos ), wev->delta(), wev->buttons(), wev->modifiers() );
wheelEvent( &nwev );
}
else
wheelEvent( wev );
return true;
}
else // we're not needed globally anymore
qApp->removeEventFilter( this );
}
return false;
}
void VolumeDial::leaveEvent( QEvent * )
{
startFade();
}
static bool onRing( const QRect &r, const QPoint &p )
{
const QPoint c = r.center();
const int dx = p.x() - c.x();
const int dy = p.y() - c.y();
return sqrt(dx*dx + dy*dy) > r.width()/4;
}
void VolumeDial::mouseMoveEvent( QMouseEvent *me )
{
if ( me->buttons() == Qt::NoButton )
setCursor( onRing( rect(), me->pos() ) ? Qt::PointingHandCursor : Qt::ArrowCursor );
else if ( m_isClick )
me->accept();
else
QDial::mouseMoveEvent( me );
}
void VolumeDial::mousePressEvent( QMouseEvent *me )
{
if ( me->button() != Qt::LeftButton )
{
QDial::mousePressEvent( me );
return;
}
m_isClick = !onRing( rect(), me->pos() );
if ( m_isClick )
update(); // hide the ring
else
{
setCursor( Qt::PointingHandCursor ); // hint dragging
QDial::mousePressEvent( me ); // this will directly jump to the proper position
}
// for value changes caused by mouseevent we'll only let our adjusted value changes be emitted
// see ::sliderChange()
m_formerValue = value();
blockSignals( true );
}
void VolumeDial::mouseReleaseEvent( QMouseEvent *me )
{
if ( me->button() != Qt::LeftButton )
return;
blockSignals( false ); // free signals
setCursor( Qt::ArrowCursor );
setSliderDown( false );
if ( m_isClick )
{
m_isClick = !onRing( rect(), me->pos() );
if ( m_isClick )
emit muteToggled( !m_muted );
}
m_isClick = false;
}
void VolumeDial::paintEvent( QPaintEvent * )
{
QPainter p( this );
int icon = m_muted ? 0 : 3;
if ( icon && value() < 66 )
icon = value() < 33 ? 1 : 2;
p.drawPixmap( 0,0, m_icon[ icon ] );
if ( !m_isClick )
{
p.setPen( QPen( m_sliderGradient, 3, Qt::SolidLine, Qt::RoundCap ) );
p.setRenderHint( QPainter::Antialiasing );
p.drawArc( rect().adjusted(4,4,-4,-4), -110*16, - value()*320*16 / (maximum() - minimum()) );
}
p.end();
}
void VolumeDial::removeWheelProxy( QObject *w )
{
m_wheelProxies.removeOne( static_cast<QWidget*>(w) );
}
void VolumeDial::resizeEvent( QResizeEvent *re )
{
if( width() != height() )
resize( height(), height() );
else
QDial::resizeEvent( re );
if( re->size() != re->oldSize() )
{
renderIcons();
m_sliderGradient = QPixmap( size() );
updateSliderGradient();
update();
}
}
void VolumeDial::renderIcons()
{
m_icon[0] = The::svgHandler()->renderSvg( "Muted", width(), height(), "Muted", true );
m_icon[1] = The::svgHandler()->renderSvg( "Volume_low", width(), height(), "Volume_low", true );
m_icon[2] = The::svgHandler()->renderSvg( "Volume_mid", width(), height(), "Volume_mid", true );
m_icon[3] = The::svgHandler()->renderSvg( "Volume", width(), height(), "Volume", true );
if( layoutDirection() == Qt::RightToLeft )
{
for ( int i = 0; i < 4; ++i )
m_icon[i] = QPixmap::fromImage( m_icon[i].toImage().mirrored( true, false ) );
}
}
void VolumeDial::startFade()
{
if ( m_anim.timer )
killTimer( m_anim.timer );
m_anim.timer = startTimer( 40 );
}
void VolumeDial::stopFade()
{
killTimer( m_anim.timer );
m_anim.timer = 0;
if ( m_anim.step < 0 )
m_anim.step = 0;
else if ( m_anim.step > 6 )
m_anim.step = 6;
}
void VolumeDial::timerEvent( QTimerEvent *te )
{
if ( te->timerId() != m_anim.timer )
return;
if ( underMouse() ) // fade in
{
m_anim.step += 2;
if ( m_anim.step > 5 )
stopFade();
}
else // fade out
{
--m_anim.step;
if ( m_anim.step < 1 )
stopFade();
}
updateSliderGradient();
repaint();
}
void VolumeDial::updateSliderGradient()
{
m_sliderGradient.fill( Qt::transparent );
QColor c = m_highlightColor;
if ( !m_anim.step )
{
c.setAlpha( 99 );
m_sliderGradient.fill( c );
return;
}
QConicalGradient cg( m_sliderGradient.rect().center(), -90 );
c.setAlpha( 99 + m_anim.step*156/6 );
cg.setColorAt( 0, c );
c.setAlpha( 99 + m_anim.step*42/6 );
cg.setColorAt( 1, c );
QPainter p( &m_sliderGradient );
p.fillRect( m_sliderGradient.rect(), cg );
p.end();
}
void VolumeDial::wheelEvent( QWheelEvent *wev )
{
QDial::wheelEvent( wev );
wev->accept();
const QPoint tooltipPosition = mapToGlobal( rect().translated( 7, -22 ).bottomLeft() );
QToolTip::showText( tooltipPosition, toolTip() );
// NOTICE: this is a bit tricky.
// the ToolTip "QTipLabel" just installed a global eventfilter that intercepts various
// events and hides itself on them. Therefore every even wheelevent will close the tip
// ("works - works not - works - works not - ...")
// so we post-install our own global eventfilter to handle wheel events meant for us bypassing
// the ToolTip eventfilter
// first remove to prevent multiple installations but ensure we're on top of the ToolTip filter
qApp->removeEventFilter( this );
// it's ultimately removed in the timer triggered ::hideToolTip() slot
qApp->installEventFilter( this );
}
void VolumeDial::setMuted( bool mute )
{
m_muted = mute;
setToolTip( m_muted ? i18n( "Muted" ) : i18n( "Volume: %1%", value() ) );
update();
}
QSize VolumeDial::sizeHint() const
{
if ( QToolBar *toolBar = qobject_cast<QToolBar*>( parentWidget() ) )
return toolBar->iconSize();
return QDial::sizeHint();
}
void VolumeDial::sliderChange( SliderChange change )
{
if ( change == SliderValueChange && isSliderDown() && signalsBlocked() )
{
int d = value() - m_formerValue;
if ( d && d < 33 && d > -33 ) // don't allow real "jumps" > 1/3
{
if ( d > 5 ) // ease movement
d = 5;
else if ( d < -5 )
d = -5;
m_formerValue += d;
blockSignals( false );
emit sliderMoved( m_formerValue );
emit valueChanged( m_formerValue );
blockSignals( true );
}
if ( d )
setValue( m_formerValue );
}
QDial::sliderChange(change);
}
void VolumeDial::valueChangedSlot( int v )
{
m_isClick = false;
setToolTip( m_muted ? i18n( "Muted" ) : i18n( "Volume: %1%", v ) );
update();
}
diff --git a/src/widgets/kdatecombo.cpp b/src/widgets/kdatecombo.cpp
index 349b803bf4..3a8e380240 100644
--- a/src/widgets/kdatecombo.cpp
+++ b/src/widgets/kdatecombo.cpp
@@ -1,147 +1,153 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "kdatecombo.h"
#include <QTimer>
//Added by qt3to4:
#include <QKeyEvent>
#include <QEvent>
#include <QVBoxLayout>
#include <KGlobal>
#include <KLocale>
#include <KDatePicker>
#include <KPopupFrame>
#include <KDebug>
#include <KConfigGroup>
KDateCombo::KDateCombo(QWidget *parent) : QComboBox(parent)
{
setEditable( false );
QDate date = QDate::currentDate();
initObject(date);
}
KDateCombo::KDateCombo(const QDate & date, QWidget *parent) : QComboBox(parent)
{
setEditable( false );
initObject(date);
}
void KDateCombo::initObject(const QDate & date)
{
setValidator(0);
popupFrame = new KPopupFrame(this);
popupFrame->installEventFilter(this);
datePicker = new KDatePicker(date, popupFrame);
datePicker->setMinimumSize(datePicker->sizeHint());
datePicker->installEventFilter(this);
QVBoxLayout *mainLayout = new QVBoxLayout;
popupFrame->setLayout(mainLayout);
mainLayout->addWidget(datePicker);
setDate(date);
- connect(datePicker, SIGNAL(dateSelected(QDate)), this, SLOT(dateEnteredEvent(QDate)));
- connect(datePicker, SIGNAL(dateEntered(QDate)), this, SLOT(dateEnteredEvent(QDate)));
+ connect(datePicker, &KDatePicker::dateSelected, this, &KDateCombo::dateEnteredEvent);
+ connect(datePicker, &KDatePicker::dateEntered, this, &KDateCombo::dateEnteredEvent);
}
KDateCombo::~KDateCombo()
{
delete datePicker;
delete popupFrame;
}
QString KDateCombo::date2String(const QDate & date)
{
return(KGlobal::locale()->formatDate(date, KLocale::ShortDate));
}
QDate & KDateCombo::string2Date(const QString & str, QDate *qd)
{
return *qd = KGlobal::locale()->readDate(str);
}
QDate & KDateCombo::getDate(QDate *currentDate)
{
return string2Date(currentText(), currentDate);
}
bool KDateCombo::setDate(const QDate & newDate)
{
if (newDate.isValid())
{
if (count())
clear();
addItem(date2String(newDate));
return true;
}
return false;
}
void KDateCombo::dateEnteredEvent(const QDate &newDate)
{
QDate tempDate = newDate;
if (!tempDate.isValid())
tempDate = datePicker->date();
popupFrame->hide();
setDate(tempDate);
}
+void KDateCombo::nullDateEnteredEvent()
+{
+ dateEnteredEvent(QDate());
+}
+
+
void KDateCombo::mousePressEvent (QMouseEvent * e)
{
if (e->button() & Qt::LeftButton)
{
if (rect().contains( e->pos()))
{
QDate tempDate;
getDate(& tempDate);
datePicker->setDate(tempDate);
popupFrame->popup(mapToGlobal(QPoint(0, height())));
}
}
}
bool KDateCombo::eventFilter (QObject*, QEvent* e)
{
if ( e->type() == QEvent::MouseButtonPress )
{
QMouseEvent *me = (QMouseEvent *)e;
QPoint p = mapFromGlobal( me->globalPos() );
if (rect().contains( p ) )
{
- QTimer::singleShot(10, this, SLOT(dateEnteredEvent()));
+ QTimer::singleShot(10, this, &KDateCombo::nullDateEnteredEvent);
return true;
}
}
else if ( e->type() == QEvent::KeyRelease )
{
QKeyEvent *k = (QKeyEvent *)e;
if (k->key()==Qt::Key_Escape) {
popupFrame->hide();
return true;
}
else {
return false;
}
}
return false;
}
diff --git a/src/widgets/kdatecombo.h b/src/widgets/kdatecombo.h
index c4f66ce319..a8463dc051 100644
--- a/src/widgets/kdatecombo.h
+++ b/src/widgets/kdatecombo.h
@@ -1,59 +1,60 @@
/****************************************************************************************
* Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#ifndef KDATECOMBO_H
#define KDATECOMBO_H
#include <QWidget>
#include <QComboBox>
#include <QDate>
/**
*@author Beppe Grimaldi
*/
class KDatePicker;
class KPopupFrame;
class KDateCombo : public QComboBox {
Q_OBJECT
public:
KDateCombo(QWidget *parent=0);
explicit KDateCombo(const QDate & date, QWidget *parent=0);
~KDateCombo();
QDate & getDate(QDate *currentDate);
bool setDate(const QDate & newDate);
private:
KPopupFrame * popupFrame;
KDatePicker * datePicker;
void initObject(const QDate & date);
QString date2String(const QDate &);
QDate & string2Date(const QString &, QDate * );
protected:
bool eventFilter (QObject*, QEvent*);
virtual void mousePressEvent (QMouseEvent * e);
protected Q_SLOTS:
- void dateEnteredEvent(const QDate &d=QDate());
+ void dateEnteredEvent(const QDate &d);
+ void nullDateEnteredEvent();
};
#endif
diff --git a/tests/core-impl/collections/aggregate/TestAggregateMeta.cpp b/tests/core-impl/collections/aggregate/TestAggregateMeta.cpp
index e1a3bc8efa..04fca2cf2e 100644
--- a/tests/core-impl/collections/aggregate/TestAggregateMeta.cpp
+++ b/tests/core-impl/collections/aggregate/TestAggregateMeta.cpp
@@ -1,516 +1,516 @@
/****************************************************************************************
* Copyright (c) 2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestAggregateMeta.h"
#include "core/capabilities/OrganiseCapability.h"
#include "core/meta/Meta.h"
#include "core/meta/TrackEditor.h"
#include "core/support/Debug.h"
#include "core-impl/collections/aggregate/AggregateCollection.h"
#include "core-impl/collections/aggregate/AggregateMeta.h"
#include "mocks/MetaMock.h"
#include "mocks/MockTrack.h"
#include <QMap>
#include <QSignalSpy>
#include <KCmdLineArgs>
#include <KGlobal>
#include <qtest_kde.h>
#include <gmock/gmock.h>
QTEST_KDEMAIN_CORE( TestAggregateMeta )
TestAggregateMeta::TestAggregateMeta()
{
KCmdLineArgs::init( KGlobal::activeComponent().aboutData() );
::testing::InitGoogleMock( &KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv() );
}
class MyTrackMock : public MetaMock
{
public:
MyTrackMock() : MetaMock( QVariantMap() ) {}
bool hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
bool result = results.value( type );
results.remove( type );
return result;
}
Capabilities::Capability* createCapabilityInterface(Capabilities::Capability::Type type)
{
Capabilities::Capability* cap = capabilities.value( type );
capabilities.remove( type );
return cap;
}
Meta::TrackEditorPtr editor()
{
return trackEditors.isEmpty() ? Meta::TrackEditorPtr() : trackEditors.takeFirst();
}
mutable QMap<Capabilities::Capability::Type, bool> results;
mutable QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
QList<Meta::TrackEditorPtr> trackEditors;
};
class MyAlbumMock : public MockAlbum
{
public:
MyAlbumMock() : MockAlbum( "testAlbum" ) {}
bool hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
bool result = results.value( type );
results.remove( type );
return result;
}
Capabilities::Capability* createCapabilityInterface(Capabilities::Capability::Type type)
{
Capabilities::Capability* cap = capabilities.value( type );
capabilities.remove( type );
return cap;
}
mutable QMap<Capabilities::Capability::Type, bool> results;
mutable QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
};
class MyArtistMock : public MockArtist
{
public:
MyArtistMock() : MockArtist( "testArtist" ) {}
bool hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
bool result = results.value( type );
results.remove( type );
return result;
}
Capabilities::Capability* createCapabilityInterface(Capabilities::Capability::Type type)
{
Capabilities::Capability* cap = capabilities.value( type );
capabilities.remove( type );
return cap;
}
mutable QMap<Capabilities::Capability::Type, bool> results;
mutable QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
};
class MyGenreMock : public MockGenre
{
public:
MyGenreMock() : MockGenre( "testGenre" ) {}
bool hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
bool result = results.value( type );
results.remove( type );
return result;
}
Capabilities::Capability* createCapabilityInterface(Capabilities::Capability::Type type)
{
Capabilities::Capability* cap = capabilities.value( type );
capabilities.remove( type );
return cap;
}
mutable QMap<Capabilities::Capability::Type, bool> results;
mutable QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
};
class MyComposerMock : public MockComposer
{
public:
MyComposerMock() : MockComposer( "testComposer" ) {}
bool hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
bool result = results.value( type );
results.remove( type );
return result;
}
Capabilities::Capability* createCapabilityInterface(Capabilities::Capability::Type type)
{
Capabilities::Capability* cap = capabilities.value( type );
capabilities.remove( type );
return cap;
}
mutable QMap<Capabilities::Capability::Type, bool> results;
mutable QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
};
class MyYearMock : public MockYear
{
public:
MyYearMock() : MockYear( "testYear" ) {}
bool hasCapabilityInterface( Capabilities::Capability::Type type ) const
{
bool result = results.value( type );
results.remove( type );
return result;
}
Capabilities::Capability* createCapabilityInterface(Capabilities::Capability::Type type)
{
Capabilities::Capability* cap = capabilities.value( type );
capabilities.remove( type );
return cap;
}
mutable QMap<Capabilities::Capability::Type, bool> results;
mutable QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
};
class MyOrganiseCapability : public Capabilities::OrganiseCapability
{
public:
void deleteTrack() {}
};
void
TestAggregateMeta::testHasCapabilityOnSingleTrack()
{
MyTrackMock *mock = new MyTrackMock();
QMap<Capabilities::Capability::Type, bool> results;
results.insert( Capabilities::Capability::Buyable, false );
results.insert( Capabilities::Capability::BookmarkThis, true );
mock->results = results;
Meta::TrackPtr ptr( mock );
Meta::AggregateTrack cut( 0, ptr );
QVERIFY( cut.hasCapabilityInterface( Capabilities::Capability::BookmarkThis ) );
QVERIFY( !cut.hasCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( mock->results.count(), 0 );
}
void
TestAggregateMeta::testCreateCapabilityOnSingleTrack()
{
MyTrackMock *mock = new MyTrackMock();
QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
capabilities.insert( Capabilities::Capability::Buyable, 0 );
Capabilities::Capability *cap = new MyOrganiseCapability();
capabilities.insert( Capabilities::Capability::Organisable, cap );
mock->capabilities = capabilities;
Meta::TrackPtr ptr( mock );
Meta::AggregateTrack cut( 0, ptr );
QVERIFY( ! cut.createCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( cut.createCapabilityInterface( Capabilities::Capability::Organisable ), cap );
QCOMPARE( mock->capabilities.count(), 0 );
delete cap;
}
void
TestAggregateMeta::testHasCapabilityOnSingleAlbum()
{
MyAlbumMock *mock = new MyAlbumMock();
QMap<Capabilities::Capability::Type, bool> results;
results.insert( Capabilities::Capability::Buyable, false );
results.insert( Capabilities::Capability::BookmarkThis, true );
mock->results = results;
Meta::AlbumPtr ptr( mock );
Meta::AggregateAlbum album( 0, ptr );
QVERIFY( album.hasCapabilityInterface( Capabilities::Capability::BookmarkThis ) );
QVERIFY( !album.hasCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( mock->results.count(), 0 );
}
void
TestAggregateMeta::testCreateCapabilityOnSingleAlbum()
{
MyAlbumMock *mock = new MyAlbumMock();
QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
capabilities.insert( Capabilities::Capability::Buyable, 0 );
Capabilities::Capability *cap = new MyOrganiseCapability();
capabilities.insert( Capabilities::Capability::Organisable, cap );
mock->capabilities = capabilities;
Meta::AlbumPtr ptr( mock );
Meta::AggregateAlbum album( 0, ptr );
QVERIFY( ! album.createCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( album.createCapabilityInterface( Capabilities::Capability::Organisable ), cap );
QCOMPARE( mock->capabilities.count(), 0 );
delete cap;
}
void
TestAggregateMeta::testHasCapabilityOnSingleArtist()
{
MyArtistMock *mock = new MyArtistMock();
QMap<Capabilities::Capability::Type, bool> results;
results.insert( Capabilities::Capability::Buyable, false );
results.insert( Capabilities::Capability::BookmarkThis, true );
mock->results = results;
Meta::ArtistPtr ptr( mock );
Meta::AggregateArtist artist( 0, ptr );
QVERIFY( artist.hasCapabilityInterface( Capabilities::Capability::BookmarkThis ) );
QVERIFY( !artist.hasCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( mock->results.count(), 0 );
}
void
TestAggregateMeta::testCreateCapabilityOnSingleArtist()
{
MyArtistMock *mock = new MyArtistMock();
QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
capabilities.insert( Capabilities::Capability::Buyable, 0 );
Capabilities::Capability *cap = new MyOrganiseCapability();
capabilities.insert( Capabilities::Capability::Organisable, cap );
mock->capabilities = capabilities;
Meta::ArtistPtr ptr( mock );
Meta::AggregateArtist artist( 0, ptr );
QVERIFY( ! artist.createCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( artist.createCapabilityInterface( Capabilities::Capability::Organisable ), cap );
QCOMPARE( mock->capabilities.count(), 0 );
delete cap;
}
void
TestAggregateMeta::testHasCapabilityOnSingleComposer()
{
MyComposerMock *mock = new MyComposerMock();
QMap<Capabilities::Capability::Type, bool> results;
results.insert( Capabilities::Capability::Buyable, false );
results.insert( Capabilities::Capability::BookmarkThis, true );
mock->results = results;
Meta::ComposerPtr ptr( mock );
Meta::AggregateComposer cut( 0, ptr );
QVERIFY( cut.hasCapabilityInterface( Capabilities::Capability::BookmarkThis ) );
QVERIFY( !cut.hasCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( mock->results.count(), 0 );
}
void
TestAggregateMeta::testCreateCapabilityOnSingleComposer()
{
MyComposerMock *mock = new MyComposerMock();
QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
capabilities.insert( Capabilities::Capability::Buyable, 0 );
Capabilities::Capability *cap = new MyOrganiseCapability();
capabilities.insert( Capabilities::Capability::Organisable, cap );
mock->capabilities = capabilities;
Meta::ComposerPtr ptr( mock );
Meta::AggregateComposer cut( 0, ptr );
QVERIFY( ! cut.createCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( cut.createCapabilityInterface( Capabilities::Capability::Organisable ), cap );
QCOMPARE( mock->capabilities.count(), 0 );
delete cap;
}
void
TestAggregateMeta::testHasCapabilityOnSingleGenre()
{
MyGenreMock *mock = new MyGenreMock();
QMap<Capabilities::Capability::Type, bool> results;
results.insert( Capabilities::Capability::Buyable, false );
results.insert( Capabilities::Capability::BookmarkThis, true );
mock->results = results;
Meta::GenrePtr ptr( mock );
Meta::AggregateGenre cut( 0, ptr );
QVERIFY( cut.hasCapabilityInterface( Capabilities::Capability::BookmarkThis ) );
QVERIFY( !cut.hasCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( mock->results.count(), 0 );
}
void
TestAggregateMeta::testCreateCapabilityOnSingleGenre()
{
MyGenreMock *mock = new MyGenreMock();
QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
capabilities.insert( Capabilities::Capability::Buyable, 0 );
Capabilities::Capability *cap = new MyOrganiseCapability();
capabilities.insert( Capabilities::Capability::Organisable, cap );
mock->capabilities = capabilities;
Meta::GenrePtr ptr( mock );
Meta::AggregateGenre cut( 0, ptr );
QVERIFY( ! cut.createCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( cut.createCapabilityInterface( Capabilities::Capability::Organisable ), cap );
QCOMPARE( mock->capabilities.count(), 0 );
delete cap;
}
void
TestAggregateMeta::testHasCapabilityOnSingleYear()
{
MyYearMock *mock = new MyYearMock();
QMap<Capabilities::Capability::Type, bool> results;
results.insert( Capabilities::Capability::Buyable, false );
results.insert( Capabilities::Capability::BookmarkThis, true );
mock->results = results;
Meta::YearPtr ptr( mock );
Meta::AggreagateYear cut( 0, ptr );
QVERIFY( cut.hasCapabilityInterface( Capabilities::Capability::BookmarkThis ) );
QVERIFY( !cut.hasCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( mock->results.count(), 0 );
}
void
TestAggregateMeta::testCreateCapabilityOnSingleYear()
{
MyYearMock *mock = new MyYearMock();
QMap<Capabilities::Capability::Type, Capabilities::Capability*> capabilities;
capabilities.insert( Capabilities::Capability::Buyable, 0 );
Capabilities::Capability *cap = new MyOrganiseCapability();
capabilities.insert( Capabilities::Capability::Organisable, cap );
mock->capabilities = capabilities;
Meta::YearPtr ptr( mock );
Meta::AggreagateYear cut( 0, ptr );
QVERIFY( ! cut.createCapabilityInterface( Capabilities::Capability::Buyable ) );
QCOMPARE( cut.createCapabilityInterface( Capabilities::Capability::Organisable ), cap );
QCOMPARE( mock->capabilities.count(), 0 );
delete cap;
}
class MyTrackEditor : public Meta::TrackEditor
{
public:
MyTrackEditor() : Meta::TrackEditor()
, beginCallCount(0)
, endCallcount(0) {}
virtual void setAlbum( const QString &newAlbum ) { Q_UNUSED( newAlbum ) }
virtual void setAlbumArtist( const QString &newAlbumArtist ) { Q_UNUSED( newAlbumArtist ) }
virtual void setArtist( const QString &newArtist ) { Q_UNUSED( newArtist ) }
virtual void setComposer( const QString &newComposer ) { Q_UNUSED( newComposer ) };
virtual void setGenre( const QString &newGenre ) { Q_UNUSED( newGenre ) };
virtual void setYear( int newYear ) { Q_UNUSED( newYear ) };
virtual void setTitle( const QString &newTitle ) { Q_UNUSED( newTitle ) };
virtual void setComment( const QString &newComment ) { Q_UNUSED( newComment ) };
virtual void setTrackNumber( int newTrackNumber ) { Q_UNUSED( newTrackNumber ) };
virtual void setDiscNumber( int newDiscNumber ) { Q_UNUSED( newDiscNumber ) };
virtual void setBpm( const qreal newBpm ) { Q_UNUSED( newBpm ) };
virtual void beginUpdate() { beginCallCount++; };
virtual void endUpdate() { endCallcount++; };
int beginCallCount;
int endCallcount;
};
void
TestAggregateMeta::testEditableCapabilityOnMultipleTracks()
{
MyTrackMock *mock1 = new MyTrackMock();
MyTrackMock *mock2 = new MyTrackMock();
KSharedPtr<MyTrackEditor> cap1 ( new MyTrackEditor() );
KSharedPtr<MyTrackEditor> cap2 ( new MyTrackEditor() );
mock1->trackEditors << Meta::TrackEditorPtr( cap1.data() );
mock2->trackEditors << Meta::TrackEditorPtr( cap2.data() );
Meta::TrackPtr ptr1( mock1 );
Meta::TrackPtr ptr2( mock2 );
Collections::AggregateCollection *collection = new Collections::AggregateCollection();
- QSignalSpy spy( collection, SIGNAL(updated()));
+ QSignalSpy spy( collection, &Collections::AggregateCollection::updated);
QVERIFY( spy.isValid() );
Meta::AggregateTrack cut( collection, ptr1 );
cut.add( ptr2 );
Meta::TrackEditorPtr editCap = cut.editor();
QVERIFY( editCap );
QCOMPARE( cap1->beginCallCount, 0 );
QCOMPARE( cap2->beginCallCount, 0 );
editCap->beginUpdate();
QCOMPARE( cap1->beginCallCount, 1 );
QCOMPARE( cap2->beginCallCount, 1 );
QCOMPARE( cap1->endCallcount, 0 );
QCOMPARE( cap2->endCallcount, 0 );
editCap->endUpdate();
QCOMPARE( cap1->endCallcount, 1 );
QCOMPARE( cap2->endCallcount, 1 );
//the signal is delayed a bit, but that is ok
QTest::qWait( 50 );
//required so that the colleection browser refreshes itself
QCOMPARE( spy.count(), 1 );
}
using ::testing::Return;
using ::testing::AnyNumber;
void
TestAggregateMeta::testPrettyUrl()
{
Meta::MockTrack *mock = new ::testing::NiceMock<Meta::MockTrack>();
EXPECT_CALL( *mock, prettyUrl() ).Times( AnyNumber() ).WillRepeatedly( Return( "foo" ) );
Meta::TrackPtr trackPtr( mock );
Meta::AggregateTrack track( 0, trackPtr );
QCOMPARE( track.prettyUrl(), QString( "foo" ) );
}
diff --git a/tests/core-impl/collections/db/sql/TestSqlCollection.cpp b/tests/core-impl/collections/db/sql/TestSqlCollection.cpp
index 69c957b42e..4a1896aedb 100644
--- a/tests/core-impl/collections/db/sql/TestSqlCollection.cpp
+++ b/tests/core-impl/collections/db/sql/TestSqlCollection.cpp
@@ -1,93 +1,93 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestSqlCollection.h"
#include <core/collections/Collection.h>
#include <core-impl/collections/db/sql/SqlCollection.h>
#include <core-impl/collections/db/sql/DatabaseUpdater.h>
#include <core-impl/storage/sql/mysqlestorage/MySqlEmbeddedStorage.h>
#include "SqlMountPointManagerMock.h"
#include <QSignalSpy>
#include <qtest_kde.h>
QTEST_KDEMAIN_CORE( TestSqlCollection )
TestSqlCollection::TestSqlCollection()
{
}
void
TestSqlCollection::initTestCase()
{
m_tmpDir = new KTempDir();
m_storage = new MySqlEmbeddedStorage();
QVERIFY( m_storage->init( m_tmpDir->name() ) );
m_collection = new Collections::SqlCollection( m_storage );
m_mpmMock = new SqlMountPointManagerMock( this, m_storage );
m_collection->setMountPointManager( m_mpmMock );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath) VALUES (1, 1, './IDoNotExist.mp3');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath) VALUES (2, 2, './IDoNotExistAsWell.mp3');" );
m_storage->query( "INSERT INTO tracks(id, url,title) VALUES ( 1,1,'test1');" );
}
void
TestSqlCollection::cleanupTestCase()
{
delete m_collection;
//m_mpMock is deleted by SqlCollection
//m_storage is deleted by SqlCollection
delete m_tmpDir;
}
void
TestSqlCollection::testDeviceAddedWithTracks()
{
- QSignalSpy spy( m_collection, SIGNAL(updated()));
+ QSignalSpy spy( m_collection, &Collections::SqlCollection::updated);
m_mpmMock->emitDeviceAdded( 1 );
QCOMPARE( spy.count(), 1 );
}
void
TestSqlCollection::testDeviceAddedWithoutTracks()
{
- QSignalSpy spy( m_collection, SIGNAL(updated()));
+ QSignalSpy spy( m_collection, &Collections::SqlCollection::updated);
m_mpmMock->emitDeviceAdded( 2 );
QCOMPARE( spy.count(), 0 );
}
void
TestSqlCollection::testDeviceRemovedWithTracks()
{
- QSignalSpy spy( m_collection, SIGNAL(updated()));
+ QSignalSpy spy( m_collection, &Collections::SqlCollection::updated);
m_mpmMock->emitDeviceRemoved( 1 );
QCOMPARE( spy.count(), 1 );
}
void
TestSqlCollection::testDeviceRemovedWithoutTracks()
{
- QSignalSpy spy( m_collection, SIGNAL(updated()));
+ QSignalSpy spy( m_collection, &Collections::SqlCollection::updated);
m_mpmMock->emitDeviceRemoved( 0 );
QCOMPARE( spy.count(), 0 );
}
diff --git a/tests/core-impl/collections/db/sql/TestSqlQueryMaker.cpp b/tests/core-impl/collections/db/sql/TestSqlQueryMaker.cpp
index cb7af8836d..6ad1409aec 100644
--- a/tests/core-impl/collections/db/sql/TestSqlQueryMaker.cpp
+++ b/tests/core-impl/collections/db/sql/TestSqlQueryMaker.cpp
@@ -1,1026 +1,1026 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestSqlQueryMaker.h"
#include "core/support/Debug.h"
#include "DatabaseUpdater.h"
#include "SqlCollection.h"
#include "SqlQueryMaker.h"
#include "SqlRegistry.h"
#include "core-impl/storage/sql/mysqlestorage/MySqlEmbeddedStorage.h"
#include "SqlMountPointManagerMock.h"
#include <QSignalSpy>
#include <qtest_kde.h>
using namespace Collections;
QTEST_KDEMAIN_CORE( TestSqlQueryMaker )
//required for QTest, this is not done in Querymaker.h
Q_DECLARE_METATYPE( Collections::QueryMaker::QueryType )
Q_DECLARE_METATYPE( Collections::QueryMaker::NumberComparison )
Q_DECLARE_METATYPE( Collections::QueryMaker::ReturnFunction )
Q_DECLARE_METATYPE( Collections::QueryMaker::AlbumQueryMode )
Q_DECLARE_METATYPE( Collections::QueryMaker::LabelQueryMode )
TestSqlQueryMaker::TestSqlQueryMaker()
{
qRegisterMetaType<Meta::TrackPtr>();
qRegisterMetaType<Meta::TrackList>();
qRegisterMetaType<Meta::AlbumPtr>();
qRegisterMetaType<Meta::AlbumList>();
qRegisterMetaType<Meta::ArtistPtr>();
qRegisterMetaType<Meta::ArtistList>();
qRegisterMetaType<Meta::GenrePtr>();
qRegisterMetaType<Meta::GenreList>();
qRegisterMetaType<Meta::ComposerPtr>();
qRegisterMetaType<Meta::ComposerList>();
qRegisterMetaType<Meta::YearPtr>();
qRegisterMetaType<Meta::YearList>();
qRegisterMetaType<Meta::LabelPtr>();
qRegisterMetaType<Meta::LabelList>();
qRegisterMetaType<Collections::QueryMaker::QueryType>();
qRegisterMetaType<Collections::QueryMaker::NumberComparison>();
qRegisterMetaType<Collections::QueryMaker::ReturnFunction>();
qRegisterMetaType<Collections::QueryMaker::AlbumQueryMode>();
qRegisterMetaType<Collections::QueryMaker::LabelQueryMode>();
}
void
TestSqlQueryMaker::initTestCase()
{
m_tmpDir = new KTempDir();
m_storage = new MySqlEmbeddedStorage();
QVERIFY( m_storage->init( m_tmpDir->name() ) );
m_collection = new Collections::SqlCollection( m_storage );
QMap<int,QString> mountPoints;
mountPoints.insert( 1, "/foo" );
mountPoints.insert( 2, "/bar" );
m_mpm = new SqlMountPointManagerMock( this, m_storage );
m_mpm->m_mountPoints = mountPoints;
m_collection->setMountPointManager( m_mpm );
//setup test data
m_storage->query( "INSERT INTO artists(id, name) VALUES (1, 'artist1');" );
m_storage->query( "INSERT INTO artists(id, name) VALUES (2, 'artist2');" );
m_storage->query( "INSERT INTO artists(id, name) VALUES (3, 'artist3');" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(1,'album1',1);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(2,'album2',1);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(3,'album3',2);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(4,'album4',NULL);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(5,'album4',3);" );
m_storage->query( "INSERT INTO composers(id, name) VALUES (1, 'composer1');" );
m_storage->query( "INSERT INTO composers(id, name) VALUES (2, 'composer2');" );
m_storage->query( "INSERT INTO composers(id, name) VALUES (3, 'composer3');" );
m_storage->query( "INSERT INTO genres(id, name) VALUES (1, 'genre1');" );
m_storage->query( "INSERT INTO genres(id, name) VALUES (2, 'genre2');" );
m_storage->query( "INSERT INTO genres(id, name) VALUES (3, 'genre3');" );
m_storage->query( "INSERT INTO years(id, name) VALUES (1, '1');" );
m_storage->query( "INSERT INTO years(id, name) VALUES (2, '2');" );
m_storage->query( "INSERT INTO years(id, name) VALUES (3, '3');" );
m_storage->query( "INSERT INTO directories(id, deviceid, dir) VALUES (1, -1, './');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (1, -1, './IDoNotExist.mp3', 1, '1');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (2, -1, './IDoNotExistAsWell.mp3', 1, '2');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (3, -1, './MeNeither.mp3', 1, '3');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (4, 2, './NothingHere.mp3', 1, '4');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (5, 1, './GuessWhat.mp3', 1, '5');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (6, 2, './LookItsA.flac', 1, '6');" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(1,1,'track1','comment1',1,1,1,1,1);" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(2,2,'track2','comment2',1,2,1,1,1);" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(3,3,'track3','comment3',3,4,1,1,1);" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(4,4,'track4','comment4',2,3,3,3,3);" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(5,5,'track5','',3,5,2,2,2);" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(6,6,'track6','',1,4,2,2,2);" );
m_storage->query( "INSERT INTO statistics(url,createdate,accessdate,score,rating,playcount) "
"VALUES(1,1000,10000, 50.0,2,100);" );
m_storage->query( "INSERT INTO statistics(url,createdate,accessdate,score,rating,playcount) "
"VALUES(2,2000,30000, 70.0,9,50);" );
m_storage->query( "INSERT INTO statistics(url,createdate,accessdate,score,rating,playcount) "
"VALUES(3,4000,20000, 60.0,4,10);" );
m_storage->query( "INSERT INTO labels(id,label) VALUES (1,'labelA'), (2,'labelB'),(3,'test');" );
m_storage->query( "INSERT INTO urls_labels(url,label) VALUES (1,1),(1,2),(2,2),(3,3),(4,3),(4,2);" );
}
void
TestSqlQueryMaker::cleanupTestCase()
{
delete m_collection;
//m_storage is deleted by SqlCollection
delete m_tmpDir;
}
void
TestSqlQueryMaker::cleanup()
{
m_collection->setMountPointManager( m_mpm );
}
void
TestSqlQueryMaker::testQueryAlbums()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setAlbumQueryMode( Collections::QueryMaker::AllAlbums );
qm.setQueryType( Collections::QueryMaker::Album );
qm.run();
QCOMPARE( qm.albums().count(), 5 );
}
void
TestSqlQueryMaker::testQueryArtists()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Artist );
qm.run();
QCOMPARE( qm.artists().count(), 3 );
}
void
TestSqlQueryMaker::testQueryComposers()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Composer );
qm.run();
QCOMPARE( qm.composers().count(), 3 );
}
void
TestSqlQueryMaker::testQueryGenres()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Genre );
qm.run();
QCOMPARE( qm.genres().count(), 3 );
}
void
TestSqlQueryMaker::testQueryYears()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Year );
qm.run();
QCOMPARE( qm.years().count(), 3 );
}
void
TestSqlQueryMaker::testQueryTracks()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.run();
QCOMPARE( qm.tracks().count(), 6 );
}
void
TestSqlQueryMaker::testAlbumQueryMode()
{
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations );
qm.setQueryType( Collections::QueryMaker::Album );
qm.run();
QCOMPARE( qm.albums().count(), 1 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums );
qm.setQueryType( Collections::QueryMaker::Album );
qm.run();
QCOMPARE( qm.albums().count(), 4 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations );
qm.run();
QCOMPARE( qm.tracks().count(), 2 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums );
qm.run();
QCOMPARE( qm.tracks().count(), 4 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Artist );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations );
qm.run();
QCOMPARE( qm.artists().count() , 2 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Artist );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums );
qm.run();
QCOMPARE( qm.artists().count(), 3 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations );
qm.setQueryType( Collections::QueryMaker::Genre );
qm.run();
QCOMPARE( qm.genres().count(), 2 );
}
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums );
qm.setQueryType( Collections::QueryMaker::Genre );
qm.run();
QCOMPARE( qm.genres().count(), 3 );
}
}
void
TestSqlQueryMaker::testDeleteQueryMakerWithRunningQuery()
{
int iteration = 0;
bool queryNotDoneYet = true;
//wait one second per query in total, that should be enough for it to complete
do
{
Collections::SqlQueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
- QSignalSpy spy( qm, SIGNAL(queryDone()) );
+ QSignalSpy spy( qm, &Collections::QueryMaker::queryDone );
qm->setQueryType( Collections::QueryMaker::Track );
qm->addFilter( Meta::valTitle, QString::number( iteration), false, false );
qm->run();
//wait 10 msec more per iteration, might have to be tweaked
if( iteration > 0 )
{
QTest::qWait( 10 * iteration );
}
delete qm;
queryNotDoneYet = ( spy.count() == 0 );
if( iteration > 50 )
{
break;
}
iteration++;
} while ( queryNotDoneYet );
qDebug() << "Iterations: " << iteration;
}
void
TestSqlQueryMaker::testAsyncAlbumQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Album );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::AlbumList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy1.wait( 1000 );
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<Meta::AlbumList>() );
QCOMPARE( args1.value(0).value<Meta::AlbumList>().count(), 5 );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Album );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(Meta::AlbumList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valAlbum, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy2.wait( 1000 );
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<Meta::AlbumList>() );
QCOMPARE( args2.value(0).value<Meta::AlbumList>().count(), 0 );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testAsyncArtistQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Artist );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::ArtistList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy1.wait( 1000 );
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<Meta::ArtistList>() );
QCOMPARE( args1.value(0).value<Meta::ArtistList>().count(), 3 );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Artist );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(Meta::ArtistList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valArtist, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy2.wait( 1000 );
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<Meta::ArtistList>() );
QCOMPARE( args2.value(0).value<Meta::ArtistList>().count(), 0 );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testAsyncComposerQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Composer );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::ComposerList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy1.wait( 1000 );
+
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<Meta::ComposerList>() );
QCOMPARE( args1.value(0).value<Meta::ComposerList>().count(), 3 );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Composer );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(Meta::ComposerList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valComposer, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy2.wait( 1000 );
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<Meta::ComposerList>() );
QCOMPARE( args2.value(0).value<Meta::ComposerList>().count(), 0 );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testAsyncTrackQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Track );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::TrackList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy1.wait( 1000 );
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<Meta::TrackList>() );
QCOMPARE( args1.value(0).value<Meta::TrackList>().count(), 6 );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Track );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(Meta::TrackList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valTitle, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy2.wait( 1000 );
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<Meta::TrackList>() );
QCOMPARE( args2.value(0).value<Meta::TrackList>().count(), 0 );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testAsyncGenreQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Genre );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::GenreList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy1.wait( 1000 );
+
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<Meta::GenreList>() );
QCOMPARE( args1.value(0).value<Meta::GenreList>().count(), 3 );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Genre );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(Meta::GenreList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valGenre, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy2.wait( 1000 );
+
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<Meta::GenreList>() );
QCOMPARE( args2.value(0).value<Meta::GenreList>().count(), 0 );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testAsyncYearQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Year );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::YearList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy1.wait( 1000 );
+
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<Meta::YearList>() );
QCOMPARE( args1.value(0).value<Meta::YearList>().count(), 3 );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Year );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(Meta::YearList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valYear, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy2.wait( 1000 );
+
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<Meta::YearList>() );
QCOMPARE( args2.value(0).value<Meta::YearList>().count(), 0 );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testAsyncCustomQuery()
{
Collections::QueryMaker *qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Custom );
qm->addReturnFunction( Collections::QueryMaker::Count, Meta::valTitle );
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(QStringList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy1.wait( 1000 );
+
QCOMPARE( resultSpy1.count(), 1 );
QList<QVariant> args1 = resultSpy1.takeFirst();
QVERIFY( args1.value(0).canConvert<QStringList>() );
QCOMPARE( args1.value(0).value<QStringList>().count(), 1 );
QCOMPARE( args1.value(0).value<QStringList>().first(), QString( "6" ) );
QCOMPARE( doneSpy1.count(), 1);
delete qm;
qm = new Collections::SqlQueryMaker( m_collection );
qm->setQueryType( Collections::QueryMaker::Custom );
qm->addReturnFunction( Collections::QueryMaker::Count, Meta::valTitle );
- QSignalSpy doneSpy2( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy2( qm, SIGNAL(newResultReady(QStringList)));
+ QSignalSpy doneSpy2( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy2( qm, &Collections::QueryMaker::newResultReady );
qm->addFilter( Meta::valTitle, "foo" ); //should result in no match
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
-
+ doneSpy2.wait( 1000 );
+
QCOMPARE( resultSpy2.count(), 1 );
QList<QVariant> args2 = resultSpy2.takeFirst();
QVERIFY( args2.value(0).canConvert<QStringList>() );
QCOMPARE( args2.value(0).value<QStringList>().count(), 1 );
QCOMPARE( args2.value(0).value<QStringList>().first(), QString( "0" ) );
QCOMPARE( doneSpy2.count(), 1);
}
void
TestSqlQueryMaker::testFilter_data()
{
QTest::addColumn<Collections::QueryMaker::QueryType>( "type" );
QTest::addColumn<qint64>( "value" );
QTest::addColumn<QString>( "filter" );
QTest::addColumn<bool>( "matchBeginning" );
QTest::addColumn<bool>( "matchEnd" );
QTest::addColumn<int>( "count" );
QTest::newRow( "track match all titles" ) << Collections::QueryMaker::Track << Meta::valTitle << "track" << false << false << 6;
QTest::newRow( "track match all title beginnings" ) << Collections::QueryMaker::Track << Meta::valTitle << "track" << true << false << 6;
QTest::newRow( "track match one title beginning" ) << Collections::QueryMaker::Track << Meta::valTitle << "track1" << true << false << 1;
QTest::newRow( "track match one title end" ) << Collections::QueryMaker::Track << Meta::valTitle << "rack2" << false << true << 1;
QTest::newRow( "track match title on both ends" ) << Collections::QueryMaker::Track << Meta::valTitle << "track3" << true << true << 1;
QTest::newRow( "track match artist" ) << Collections::QueryMaker::Track << Meta::valArtist << "artist1" << false << false << 3;
QTest::newRow( "artist match artist" ) << Collections::QueryMaker::Artist << Meta::valArtist << "artist1" << true << true << 1;
QTest::newRow( "album match artist" ) << Collections::QueryMaker::Album << Meta::valArtist << "artist3" << false << false << 2;
QTest::newRow( "track match genre" ) << Collections::QueryMaker::Track << Meta::valGenre << "genre1" << false << false << 3;
QTest::newRow( "genre match genre" ) << Collections::QueryMaker::Genre << Meta::valGenre << "genre1" << false << false << 1;
QTest::newRow( "track match composer" ) << Collections::QueryMaker::Track << Meta::valComposer << "composer2" << false << false << 2;
QTest::newRow( "composer match composer" ) << Collections::QueryMaker::Composer << Meta::valComposer << "composer2" << false << false << 1;
QTest::newRow( "track match year" ) << Collections::QueryMaker::Track << Meta::valYear << "2" << true << true << 2;
QTest::newRow( "year match year" ) << Collections::QueryMaker::Year << Meta::valYear << "1" << false << false << 1;
QTest::newRow( "album match album" ) << Collections::QueryMaker::Album << Meta::valAlbum << "album1" << false << false << 1;
QTest::newRow( "track match album" ) << Collections::QueryMaker::Track << Meta::valAlbum << "album1" << false << false << 1;
QTest::newRow( "track match albumartit" ) << Collections::QueryMaker::Track << Meta::valAlbumArtist << "artist1" << false << false << 2;
QTest::newRow( "album match albumartist" ) << Collections::QueryMaker::Album << Meta::valAlbumArtist << "artist2" << false << false << 1;
QTest::newRow( "album match all albumartists" ) << Collections::QueryMaker::Album << Meta::valAlbumArtist << "artist" << true << false << 4;
QTest::newRow( "genre match albumartist" ) << Collections::QueryMaker::Genre << Meta::valAlbumArtist << "artist1" << false << false << 1;
QTest::newRow( "year match albumartist" ) << Collections::QueryMaker::Year << Meta::valAlbumArtist << "artist1" << false << false << 1;
QTest::newRow( "composer match albumartist" ) << Collections::QueryMaker::Composer << Meta::valAlbumArtist << "artist1" << false << false << 1;
QTest::newRow( "genre match title" ) << Collections::QueryMaker::Genre << Meta::valTitle << "track1" << false << false << 1;
QTest::newRow( "composer match title" ) << Collections::QueryMaker::Composer << Meta::valTitle << "track1" << false << false << 1;
QTest::newRow( "year match title" ) << Collections::QueryMaker::Year << Meta::valTitle << "track1" << false << false << 1;
QTest::newRow( "album match title" ) << Collections::QueryMaker::Album << Meta::valTitle << "track1" << false << false << 1;
QTest::newRow( "artist match title" ) << Collections::QueryMaker::Artist << Meta::valTitle << "track1" << false << false << 1;
QTest::newRow( "track match comment" ) << Collections::QueryMaker::Track << Meta::valComment << "comment" << true << false << 4;
QTest::newRow( "track match url" ) << Collections::QueryMaker::Track << Meta::valUrl << "Exist" << false << false << 2;
QTest::newRow( "album match comment" ) << Collections::QueryMaker::Track << Meta::valComment << "comment1" << true << true << 1;
}
void
TestSqlQueryMaker::checkResultCount( Collections::SqlQueryMaker* qm,
Collections::QueryMaker::QueryType type, int count ) {
switch( type ) {
case QueryMaker::Track: QCOMPARE( qm->tracks().count(), count ); break;
case QueryMaker::Artist: QCOMPARE( qm->artists().count(), count ); break;
case QueryMaker::Album: QCOMPARE( qm->albums().count(), count ); break;
case QueryMaker::Genre: QCOMPARE( qm->genres().count(), count ); break;
case QueryMaker::Composer: QCOMPARE( qm->composers().count(), count ); break;
case QueryMaker::Year: QCOMPARE( qm->years().count(), count ); break;
case QueryMaker::Label: QCOMPARE( qm->labels().count(), count ); break;
default:
; // do nothing
}
}
void
TestSqlQueryMaker::testFilter()
{
QFETCH( Collections::QueryMaker::QueryType, type );
QFETCH( qint64, value );
QFETCH( QString, filter );
QFETCH( bool, matchBeginning );
QFETCH( bool, matchEnd );
QFETCH( int, count );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( type );
qm.addFilter( value, filter, matchBeginning, matchEnd );
qm.run();
checkResultCount( &qm, type, count );
}
void
TestSqlQueryMaker::testDynamicCollection()
{
//this will not crash as we reset the correct mock in cleanup()
SqlMountPointManagerMock mpm( this, m_storage );
QMap<int, QString> mountPoints;
mpm.m_mountPoints = mountPoints;
m_collection->setMountPointManager( &mpm );
Collections::SqlQueryMaker trackQm( m_collection );
trackQm.setQueryType( Collections::QueryMaker::Track );
trackQm.setBlocking( true );
trackQm.run();
QCOMPARE( trackQm.tracks().count(), 3 );
mpm.m_mountPoints.insert( 1, "/foo" );
Collections::SqlQueryMaker trackQm2( m_collection );
trackQm2.setQueryType( Collections::QueryMaker::Track );
trackQm2.setBlocking( true );
trackQm2.run();
QCOMPARE( trackQm2.tracks().count(), 4 );
Collections::SqlQueryMaker artistQm( m_collection );
artistQm.setQueryType( Collections::QueryMaker::Artist );
artistQm.setBlocking( true );
artistQm.run();
QCOMPARE( artistQm.artists().count(), 2 );
Collections::SqlQueryMaker albumQm( m_collection );
albumQm.setQueryType( Collections::QueryMaker::Album );
albumQm.setBlocking( true );
albumQm.run();
QCOMPARE( albumQm.albums().count(), 4 );
Collections::SqlQueryMaker genreQm( m_collection );
genreQm.setQueryType( Collections::QueryMaker::Genre );
genreQm.setBlocking( true );
genreQm.run();
QCOMPARE( genreQm.genres().count(), 2 );
Collections::SqlQueryMaker composerQm( m_collection );
composerQm.setQueryType( Collections::QueryMaker::Composer );
composerQm.setBlocking( true );
composerQm.run();
QCOMPARE( composerQm.composers().count(), 2 );
Collections::SqlQueryMaker yearQm( m_collection );
yearQm.setQueryType( Collections::QueryMaker::Year );
yearQm.setBlocking( true );
yearQm.run();
QCOMPARE( yearQm.years().count(), 2 );
}
void
TestSqlQueryMaker::testSpecialCharacters_data()
{
QTest::addColumn<QString>( "filter" );
QTest::addColumn<bool>( "like" );
QTest::newRow( "slash in filter w/o like" ) << "AC/DC" << false;
QTest::newRow( "slash in filter w/ like" ) << "AC/DC" << true;
QTest::newRow( "backslash in filter w/o like" ) << "AC\\DC" << false;
QTest::newRow( "backslash in filter w like" ) << "AC\\DC" << true;
QTest::newRow( "quote in filter w/o like" ) << "Foo'Bar" << false;
QTest::newRow( "quote in filter w like" ) << "Foo'Bar" << true;
QTest::newRow( "% in filter w/o like" ) << "Foo%Bar" << false;
QTest::newRow( "% in filter w/ like" ) << "Foo%Bar" << true;
QTest::newRow( "filter ending with % w/o like" ) << "Foo%" << false;
QTest::newRow( "filter ending with % w like" ) << "Foo%" << true;
QTest::newRow( "filter beginning with % w/o like" ) << "%Foo" << false;
QTest::newRow( "filter beginning with % w/o like" ) << "%Foo" << true;
QTest::newRow( "\" in filter w/o like" ) << "Foo\"Bar" << false;
QTest::newRow( "\" in filter w like" ) << "Foo\"Bar" << true;
QTest::newRow( "_ in filter w/o like" ) << "track_" << false;
QTest::newRow( "_ in filter w/ like" ) << "track_" << true;
QTest::newRow( "filter with two consecutive backslashes w/o like" ) << "Foo\\\\Bar" << false;
QTest::newRow( "filter with two consecutive backslashes w like" ) << "Foo\\\\Bar" << true;
QTest::newRow( "filter with backslash% w/o like" ) << "FooBar\\%" << false;
QTest::newRow( "filter with backslash% w like" ) << "FooBar\\%" << true;
}
void
TestSqlQueryMaker::testSpecialCharacters()
{
QFETCH( QString, filter );
QFETCH( bool, like );
QString insertTrack = QString( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(999,999,'%1','',1,1,1,1,1);").arg( m_storage->escape( filter ) );
//there is a unique index on TRACKS.URL
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES(999, -1, './foobar.mp3', 1, '999');");
m_storage->query( insertTrack );
QCOMPARE( m_storage->query( "select count(*) from urls where id = 999" ).first(), QString("1") );
QCOMPARE( m_storage->query( "select count(*) from tracks where id = 999" ).first(), QString("1") );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.addFilter( Meta::valTitle, filter, !like, !like );
qm.run();
m_storage->query( "DELETE FROM urls WHERE id = 999;" );
m_storage->query( "DELETE FROM tracks WHERE id = 999;" );
QCOMPARE( qm.tracks().count(), 1 );
}
void
TestSqlQueryMaker::testNumberFilter_data()
{
QTest::addColumn<Collections::QueryMaker::QueryType>( "type" );
QTest::addColumn<qint64>( "value" );
QTest::addColumn<int>( "filter" );
QTest::addColumn<Collections::QueryMaker::NumberComparison>( "comparison" );
QTest::addColumn<bool>( "exclude" );
QTest::addColumn<int>( "count" );
QTest::newRow( "include rating greater 4" ) << Collections::QueryMaker::Track << Meta::valRating << 4 << Collections::QueryMaker::GreaterThan << false << 1;
QTest::newRow( "exclude rating smaller 4" ) << Collections::QueryMaker::Album << Meta::valRating << 4 << Collections::QueryMaker::LessThan << true << 4;
QTest::newRow( "exclude tracks first played later than 2000" ) << Collections::QueryMaker::Track << Meta::valFirstPlayed << 2000 << Collections::QueryMaker::GreaterThan << true << 5;
//having never been played does not mean played before 20000
QTest::newRow( "include last played before 20000" ) << Collections::QueryMaker::Track << Meta::valLastPlayed << 20000 << Collections::QueryMaker::LessThan << false << 1;
QTest::newRow( "playcount equals 100" ) << Collections::QueryMaker::Album << Meta::valPlaycount << 100 << Collections::QueryMaker::Equals << false << 1;
//should include unplayed songs
QTest::newRow( "playcount != 50" ) << Collections::QueryMaker::Track << Meta::valPlaycount << 50 << Collections::QueryMaker::Equals << true << 5;
QTest::newRow( "score greater 60" ) << Collections::QueryMaker::Genre << Meta::valScore << 60 << Collections::QueryMaker::GreaterThan << false << 1;
}
void
TestSqlQueryMaker::testNumberFilter()
{
QFETCH( Collections::QueryMaker::QueryType, type );
QFETCH( qint64, value );
QFETCH( int, filter );
QFETCH( bool, exclude );
QFETCH( Collections::QueryMaker::NumberComparison, comparison );
QFETCH( int, count );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( type );
if( exclude )
qm.excludeNumberFilter( value, filter, comparison );
else
qm.addNumberFilter( value, filter, comparison );
qm.run();
checkResultCount( &qm, type, count );
}
void
TestSqlQueryMaker::testReturnFunctions_data()
{
QTest::addColumn<Collections::QueryMaker::ReturnFunction>( "function" );
QTest::addColumn<qint64>( "value" );
QTest::addColumn<QString>( "result" );
QTest::newRow( "count tracks" ) << Collections::QueryMaker::Count << Meta::valTitle << QString( "6" );
QTest::newRow( "sum of playcount" ) << Collections::QueryMaker::Sum << Meta::valPlaycount << QString( "160" );
QTest::newRow( "min score" ) << Collections::QueryMaker::Min << Meta::valScore << QString( "50" );
QTest::newRow( "max rating" ) << Collections::QueryMaker::Max << Meta::valRating << QString( "9" );
}
void
TestSqlQueryMaker::testReturnFunctions()
{
QFETCH( Collections::QueryMaker::ReturnFunction, function );
QFETCH( qint64, value );
QFETCH( QString, result );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Custom );
qm.addReturnFunction( function, value );
qm.run();
QCOMPARE( qm.customData().first(), result );
}
void
TestSqlQueryMaker::testLabelMatch()
{
Meta::LabelPtr label = m_collection->registry()->getLabel( "labelB" );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( QueryMaker::Track );
qm.addMatch( label );
qm.run();
QCOMPARE( qm.tracks().count(), 3 );
}
void
TestSqlQueryMaker::testMultipleLabelMatches()
{
Meta::LabelPtr labelB = m_collection->registry()->getLabel( "labelB" );
Meta::LabelPtr labelA = m_collection->registry()->getLabel( "labelA" );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( QueryMaker::Track );
qm.addMatch( labelB );
qm.addMatch( labelA );
qm.run();
QCOMPARE( qm.tracks().count(), 1 );
}
void
TestSqlQueryMaker::testQueryTypesWithLabelMatching_data()
{
QTest::addColumn<Collections::QueryMaker::QueryType>( "type" );
QTest::addColumn<int>( "result" );
QTest::newRow( "query tracks" ) << Collections::QueryMaker::Track << 1;
QTest::newRow( "query albums" ) << Collections::QueryMaker::Album << 1;
QTest::newRow( "query artists" ) << Collections::QueryMaker::Artist << 1;
QTest::newRow( "query genre" ) << Collections::QueryMaker::Genre << 1;
QTest::newRow( "query composers" ) << Collections::QueryMaker::Composer << 1;
QTest::newRow( "query years" ) << Collections::QueryMaker::Year << 1;
QTest::newRow( "query labels" ) << Collections::QueryMaker::Label << 2;
}
void
TestSqlQueryMaker::testQueryTypesWithLabelMatching()
{
QFETCH( Collections::QueryMaker::QueryType, type );
QFETCH( int, result );
Meta::LabelPtr labelB = m_collection->registry()->getLabel( "labelB" );
Meta::LabelPtr labelA = m_collection->registry()->getLabel( "labelA" );
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( type );
qm.addMatch( labelB );
qm.addMatch( labelA );
qm.run();
checkResultCount( &qm, type, result );
}
void
TestSqlQueryMaker::testFilterOnLabelsAndCombination()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.beginAnd();
qm.addFilter( Meta::valLabel, "labelB", true, true );
qm.addFilter( Meta::valLabel, "labelA", false, false );
qm.endAndOr();
qm.run();
QCOMPARE( qm.tracks().count(), 1 );
}
void
TestSqlQueryMaker::testFilterOnLabelsOrCombination()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.beginOr();
qm.addFilter( Meta::valLabel, "labelB", true, true );
qm.addFilter( Meta::valLabel, "labelA", false, false );
qm.endAndOr();
qm.run();
QCOMPARE( qm.tracks().count(), 3 );
}
void
TestSqlQueryMaker::testFilterOnLabelsNegationAndCombination()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.beginAnd();
qm.excludeFilter( Meta::valLabel, "labelB", true, true );
qm.excludeFilter( Meta::valLabel, "labelA", false, false );
qm.endAndOr();
qm.run();
QCOMPARE( qm.tracks().count(), 3 );
}
void
TestSqlQueryMaker::testFilterOnLabelsNegationOrCombination()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.beginOr();
qm.excludeFilter( Meta::valLabel, "labelB", true, true );
qm.excludeFilter( Meta::valLabel, "labelA", false, false );
qm.endAndOr();
qm.run();
QCOMPARE( qm.tracks().count(), 5 );
}
void
TestSqlQueryMaker::testComplexLabelsFilter()
{
Collections::SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( Collections::QueryMaker::Track );
qm.beginOr();
qm.addFilter( Meta::valLabel, "test", true, true );
qm.beginAnd();
qm.addFilter( Meta::valLabel, "labelB", false, false );
qm.excludeFilter( Meta::valLabel, "labelA", false, true );
qm.endAndOr();
qm.endAndOr();
qm.run();
QCOMPARE( qm.tracks().count(), 3 );
}
void
TestSqlQueryMaker::testLabelQueryMode_data()
{
QTest::addColumn<Collections::QueryMaker::QueryType>( "type" );
QTest::addColumn<Collections::QueryMaker::LabelQueryMode>( "labelMode" );
QTest::addColumn<Collections::QueryMaker::AlbumQueryMode>( "albumMode" );
QTest::addColumn<int>( "result" );
QTest::newRow( "labels with querymode WithoutLabels" ) << QueryMaker::Label << QueryMaker::OnlyWithoutLabels << QueryMaker::AllAlbums << 0;
QTest::newRow( "tracks with labels" ) << QueryMaker::Track << QueryMaker::OnlyWithLabels << QueryMaker::AllAlbums << 4;
QTest::newRow( "Compilations with labels" ) << QueryMaker::Album << QueryMaker::OnlyWithLabels << QueryMaker::OnlyCompilations << 1;
QTest::newRow( "Compilations without labels" ) << QueryMaker::Album << QueryMaker::OnlyWithoutLabels << QueryMaker::OnlyCompilations << 1;
}
void
TestSqlQueryMaker::testLabelQueryMode()
{
QFETCH( QueryMaker::QueryType, type );
QFETCH( QueryMaker::LabelQueryMode, labelMode );
QFETCH( QueryMaker::AlbumQueryMode, albumMode );
QFETCH( int, result );
SqlQueryMaker qm( m_collection );
qm.setBlocking( true );
qm.setQueryType( type );
qm.setAlbumQueryMode( albumMode );
qm.setLabelQueryMode( labelMode );
qm.run();
checkResultCount( &qm, type, result );
}
diff --git a/tests/core-impl/collections/db/sql/TestSqlScanManager.cpp b/tests/core-impl/collections/db/sql/TestSqlScanManager.cpp
index ff95262517..7457fecf69 100644
--- a/tests/core-impl/collections/db/sql/TestSqlScanManager.cpp
+++ b/tests/core-impl/collections/db/sql/TestSqlScanManager.cpp
@@ -1,1623 +1,1623 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestSqlScanManager.h"
#include "amarokconfig.h"
#include "MetaTagLib.h"
#include "scanner/GenericScanManager.h"
#include "core-impl/collections/db/sql/SqlCollection.h"
#include "core-impl/collections/db/sql/SqlQueryMaker.h"
#include "core-impl/collections/db/sql/SqlRegistry.h"
#include "core-impl/storage/sql/mysqlestorage/MySqlEmbeddedStorage.h"
#include "config-amarok-test.h"
#include "SqlMountPointManagerMock.h"
#include <qtest_kde.h>
#include <QTest>
#include <QScopedPointer>
#include <ThreadWeaver/Queue>
QTEST_KDEMAIN_CORE( TestSqlScanManager )
TestSqlScanManager::TestSqlScanManager()
: QObject()
{
QString help = i18n("Amarok"); // prevent a bug when the scanner is the first thread creating a translator
}
void
TestSqlScanManager::initTestCase()
{
// setenv( "LC_ALL", "", 1 ); // this breakes the test
// Amarok does not force LC_ALL=C but obviously the test does it which
// will prevent scanning of files with umlauts.
//Tell GenericScanManager that we want to use the recently built scanner, not an installed version.
const QString overridePath = QString( AMAROK_OVERRIDE_UTILITIES_PATH );
qApp->setProperty( "overrideUtilitiesPath", overridePath );
// that is the original mp3 file that we use to generate the "real" tracks
m_sourcePath = QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + "/data/audio/Platz 01.mp3" );
QVERIFY( QFile::exists( m_sourcePath ) );
m_tmpDatabaseDir = new KTempDir();
QVERIFY( m_tmpDatabaseDir->exists() );
m_storage = new MySqlEmbeddedStorage();
QVERIFY( m_storage->init( m_tmpDatabaseDir->name() ) );
m_collection = new Collections::SqlCollection( m_storage );
- connect( m_collection, SIGNAL(updated()), this, SLOT(slotCollectionUpdated()) );
+ connect( m_collection, &Collections::SqlCollection::updated, this, &TestSqlScanManager::slotCollectionUpdated );
// TODO: change the mock mount point manager so that it doesn't pull
// in all the devices. Not much of a mock like this.
SqlMountPointManagerMock *mock = new SqlMountPointManagerMock( this, m_storage );
m_collection->setMountPointManager( mock );
m_scanManager = m_collection->scanManager();
AmarokConfig::setScanRecursively( true );
AmarokConfig::setMonitorChanges( false );
// switch on writing back so that we can create the test files with all the information
AmarokConfig::setWriteBack( true );
AmarokConfig::setWriteBackStatistics( true );
AmarokConfig::setWriteBackCover( true );
// I just need the table and not the whole playlist manager
/*
m_storage->query( QString( "CREATE TABLE playlist_tracks ("
" id " + m_storage->idType() +
", playlist_id INTEGER "
", track_num INTEGER "
", url " + m_storage->exactTextColumnType() +
", title " + m_storage->textColumnType() +
", album " + m_storage->textColumnType() +
", artist " + m_storage->textColumnType() +
", length INTEGER "
", uniqueid " + m_storage->textColumnType(128) + ") ENGINE = MyISAM;" ) );
*/
}
void
TestSqlScanManager::cleanupTestCase()
{
// aborts a ThreadWeaver job that would otherwise cause next statement to stall
delete m_collection;
// we cannot simply call WeaverInterface::finish(), it stops event loop
if( !ThreadWeaver::Queue::instance()->isIdle() )
QVERIFY2( QTest::kWaitForSignal( ThreadWeaver::Queue::instance(),
SIGNAL(finished()), 5000 ), "threads did not finish in timeout" );
//m_storage is deleted by SqlCollection
delete m_tmpDatabaseDir;
}
void
TestSqlScanManager::init()
{
m_tmpCollectionDir = new KTempDir();
QVERIFY( m_tmpCollectionDir->exists() );
QStringList collectionFolders;
collectionFolders << m_tmpCollectionDir->name();
m_collection->mountPointManager()->setCollectionFolders( collectionFolders );
}
void
TestSqlScanManager::cleanup()
{
m_scanManager->abort();
m_storage->query( "BEGIN" );
m_storage->query( "TRUNCATE TABLE tracks;" );
m_storage->query( "TRUNCATE TABLE albums;" );
m_storage->query( "TRUNCATE TABLE artists;" );
m_storage->query( "TRUNCATE TABLE composers;" );
m_storage->query( "TRUNCATE TABLE genres;" );
m_storage->query( "TRUNCATE TABLE years;" );
m_storage->query( "TRUNCATE TABLE urls;" );
m_storage->query( "TRUNCATE TABLE statistics;" );
m_storage->query( "TRUNCATE TABLE directories;" );
m_storage->query( "COMMIT" );
m_collection->registry()->emptyCache();
delete m_tmpCollectionDir;
}
void
TestSqlScanManager::testScanSingle()
{
m_collectionUpdatedCount = 0;
createSingleTrack();
fullScanAndWait();
QVERIFY( m_collectionUpdatedCount > 0 );
// -- check the commit
Meta::TrackPtr track = m_collection->registry()->getTrack( 1 );
QVERIFY( track );
QCOMPARE( track->name(), QString("Theme From Armageddon") );
QVERIFY( track->artist() );
QCOMPARE( track->artist()->name(), QString("Soundtrack & Theme Orchestra") );
QVERIFY( track->album() );
QCOMPARE( track->album()->name(), QString("Big Screen Adventures") );
QVERIFY( track->album()->albumArtist() );
QCOMPARE( track->album()->albumArtist()->name(), QString("Theme Orchestra") );
QVERIFY( !track->album()->isCompilation() ); // One single track is not compilation
QCOMPARE( track->composer()->name(), QString("Unknown Composer") );
QCOMPARE( track->comment(), QString("Amazon.com Song ID: 210541237") );
QCOMPARE( track->year()->year(), 2009 );
QCOMPARE( track->type(), QString("mp3") );
QCOMPARE( track->trackNumber(), 28 );
QCOMPARE( track->bitrate(), 256 );
QCOMPARE( track->length(), qint64(12000) );
QCOMPARE( track->sampleRate(), 44100 );
QCOMPARE( track->filesize(), 389679 );
QDateTime aDate = QDateTime::currentDateTime();
QVERIFY( track->createDate().secsTo( aDate ) < 5 ); // I just imported the file
QVERIFY( track->createDate().secsTo( aDate ) >= 0 );
QVERIFY( track->modifyDate().secsTo( aDate ) < 5 ); // I just wrote the file
QVERIFY( track->modifyDate().secsTo( aDate ) >= 0 );
Meta::StatisticsPtr statistics = track->statistics();
qFuzzyCompare( statistics->score(), 0.875 );
QCOMPARE( statistics->playCount(), 5 );
QVERIFY( !statistics->firstPlayed().isValid() );
QVERIFY( !statistics->lastPlayed().isValid() );
QVERIFY( track->createDate().isValid() );
// -- check that a further scan doesn't change anything
m_collectionUpdatedCount = 0;
fullScanAndWait();
QCOMPARE( m_collectionUpdatedCount, 0 );
}
void
TestSqlScanManager::testScanDirectory()
{
createAlbum();
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->name(), QString("Thriller") );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
QVERIFY( !album->hasImage() );
}
void
TestSqlScanManager::testDuplicateUid()
{
Meta::FieldHash values;
// create two tracks with same uid
values.clear();
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96b") );
values.insert( Meta::valUrl, QVariant("track1.mp3") );
values.insert( Meta::valTitle, QVariant("Track 1") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96b") );
values.insert( Meta::valUrl, QVariant("track2.mp3") );
values.insert( Meta::valTitle, QVariant("Track 2") );
createTrack( values );
fullScanAndWait();
// -- check the commit (the database needs to have been updated correctly)
m_collection->registry()->emptyCache();
// -- both tracks should be present
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( 1 );
QVERIFY( album );
QVERIFY( album->tracks().count() >= 1 );
}
void
TestSqlScanManager::testLongUid()
{
Meta::FieldHash values;
// create two tracks with different very long
values.clear();
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96bbbbbbbbbbbbbbc6c29f50279ab9523a0f44928bc1e96b1") );
values.insert( Meta::valUrl, QVariant("track1.mp3") );
values.insert( Meta::valTitle, QVariant("Track 1") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96c6c29f50279ab9523a0f44928bc1e96bbbbbbbbbbbbbbc6c29f50279ab9523a0f44928bc1e96b2") );
values.insert( Meta::valUrl, QVariant("track2.mp3") );
values.insert( Meta::valTitle, QVariant("Track 2") );
createTrack( values );
fullScanAndWait();
// -- check the commit (the database needs to have been updated correctly)
m_collection->registry()->emptyCache();
// both tracks should be present
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( 1 );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 2 );
}
void
TestSqlScanManager::testCompilation()
{
createAlbum();
createCompilation();
createCompilationLookAlikeAlbum();
Meta::FieldHash values;
// create one compilation track
values.clear();
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96b") );
values.insert( Meta::valUrl, QVariant("Amazon MP3/The Sum Of All Fears (O.S.T.)/The Sum of All Fears/01 - If We Could Remember (O.S.T. LP Version).mp3") );
values.insert( Meta::valTitle, QVariant("If We Could Remember (O.S.T. LP Version)") );
values.insert( Meta::valArtist, QVariant("The Sum Of All Fears (O.S.T.)/Yolanda Adams") );
values.insert( Meta::valAlbum, QVariant("The Sum of All Fears") );
values.insert( Meta::valCompilation, QVariant(true) );
createTrack( values );
// create one various artists track
values.clear();
values.insert( Meta::valUniqueId, QVariant("6ae759476c34256ff1d06f0b5c964d75") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/06 - The Dream Of The Dolphin.mp3") );
values.insert( Meta::valTitle, QVariant("The Dream Of The Dolphin") );
values.insert( Meta::valArtist, QVariant("Various Artists") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valCompilation, QVariant(false) );
createTrack( values );
// create two tracks in the same directory with different albums
values.clear();
values.insert( Meta::valUniqueId, QVariant("7957bc25521c1dc91351d497321c27a6") );
values.insert( Meta::valUrl, QVariant("01 - Solid.mp3") );
values.insert( Meta::valTitle, QVariant("Solid") );
values.insert( Meta::valArtist, QVariant("Ashford & Simpson") );
values.insert( Meta::valAlbum, QVariant("Solid") );
createTrack( values );
// create one none compilation track
values.clear();
values.insert( Meta::valUniqueId, QVariant("b88c3405cfee64c50768b75eb6e3feea") );
values.insert( Meta::valUrl, QVariant("In-Mood feat. Juliette - The Last Unicorn (Elemental Radio Mix).mp3") );
values.insert( Meta::valTitle, QVariant("The Last Unicorn (Elemental Radio Mix)") );
values.insert( Meta::valArtist, QVariant("In-Mood") );
values.insert( Meta::valAlbum, QVariant("The Last Unicorn") );
createTrack( values );
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QCOMPARE( album->name(), QString("Top Gun") );
QCOMPARE( album->tracks().count(), 10 );
QVERIFY( album->isCompilation() );
album = m_collection->registry()->getAlbum( "The Sum of All Fears", QString() );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( album->isCompilation() );
album = m_collection->registry()->getAlbum( "The Cross Of Changes", QString() );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( album->isCompilation() ); // the album is by various artists
album = m_collection->registry()->getAlbum( "Solid", "Ashford & Simpson" );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( !album->isCompilation() );
album = m_collection->registry()->getAlbum( "The Last Unicorn", "In-Mood" );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( !album->isCompilation() );
// this album is a little tricky because it has some nasty special characters in it.
Meta::TrackPtr track = m_collection->registry()->getTrackFromUid( m_collection->uidUrlProtocol() + "://" + "0969ea6128444e128cfcac95207bd525" );
QVERIFY( track );
album = track->album();
QCOMPARE( album->tracks().count(), 13 );
QVERIFY( !album->isCompilation() );
}
void
TestSqlScanManager::testBlock()
{
/** TODO: do we need blocking at all?
createSingleTrack();
Meta::TrackPtr track;
m_scanManager->blockScan(); // block the incremental scanning
m_scanManager->requestFullScan();
QTest::qWait( 100 );
track = m_collection->registry()->getTrack( 1 );
QVERIFY( !track );
QVERIFY( !m_scanManager->isRunning() );
m_scanManager->unblockScan(); // block the incremental scanning
// now the actual behaviour is not defined.
// it might or might not continue with the old scan
waitScannerFinished(); // in case it does continue after all
*/
}
void
TestSqlScanManager::testAddDirectory()
{
createAlbum();
fullScanAndWait();
createCompilation();
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 10 );
QVERIFY( album->isCompilation() );
}
void
TestSqlScanManager::testRemoveDir()
{
Meta::AlbumPtr album;
createAlbum();
createCompilation();
fullScanAndWait();
// -- check the commit
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 10 );
QVERIFY( album->isCompilation() );
// -- remove one album
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
foreach( Meta::TrackPtr t, album->tracks() )
QVERIFY( QFile::remove( t->playableUrl().path() ) );
QVERIFY( QDir( m_tmpCollectionDir->name() ).rmdir( QFileInfo( album->tracks().first()->playableUrl().path() ).path() ) );
fullScanAndWait();
// this one is still here
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
// this one is gone
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 0 );
// -- remove the second album
// this time it's a directory inside a directory
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
foreach( Meta::TrackPtr t, album->tracks() )
QVERIFY( QFile::remove( t->playableUrl().path() ) );
QVERIFY( QDir( m_tmpCollectionDir->name() ).rmdir( QFileInfo( album->tracks().first()->playableUrl().path() ).path() ) );
incrementalScanAndWait();
// this time both are gone
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 0 );
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 0 );
}
void
TestSqlScanManager::testUidChangeMoveDirectoryIncrementalScan()
{
createAlbum();
fullScanAndWait();
Meta::AlbumPtr album;
Meta::TrackList tracks;
// -- check the commit
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
tracks = album->tracks();
QCOMPARE( tracks.count(), 9 );
QCOMPARE( tracks.first()->uidUrl(), QString("amarok-sqltrackuid://1dc7022c52a3e4c51b46577da9b3c8ff") );
QVERIFY( !album->isCompilation() );
// change all the track uids in a silly way
QHash<int, QString> uidChanges; // uid hashed with track number
foreach( const Meta::TrackPtr &track, tracks )
{
Meta::FieldHash uidChange;
QString uid = track->uidUrl().remove( QString("amarok-sqltrackuid://") );
QStringRef left = uid.leftRef( 10 );
QStringRef right = uid.rightRef( uid.size() - left.size() );
QString newUid = QString("%1%2").arg( right.toString(), left.toString() );
uidChange.insert( Meta::valUniqueId, newUid );
uidChanges.insert( track->trackNumber(), newUid );
QUrl url = track->playableUrl();
QVERIFY( url.isLocalFile() );
Meta::Tag::writeTags( url.path(), uidChange, true );
}
// move album directory
const QUrl oldUrl = tracks.first()->playableUrl();
const QString base = m_tmpCollectionDir->name() + "Pop";
QVERIFY( QFile::rename( base, base + "Albums" ) );
// do an incremental scan
incrementalScanAndWait();
// recheck album
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
tracks = album->tracks();
QCOMPARE( tracks.count(), 9 );
// check changed uids
foreach( const Meta::TrackPtr &track, tracks )
{
QString uid = track->uidUrl().remove( QString("amarok-sqltrackuid://") );
QCOMPARE( uid, uidChanges.value( track->trackNumber() ) );
}
}
void
TestSqlScanManager::testRemoveTrack()
{
Meta::AlbumPtr album;
Meta::TrackPtr track;
QDateTime aDate = QDateTime::currentDateTime();
createAlbum();
fullScanAndWait();
// -- check the commit
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
track = album->tracks().first(); // the tracks are sorted, so this is always the same track
QCOMPARE( track->trackNumber(), 1 );
QVERIFY( !track->statistics()->firstPlayed().isValid() );
static_cast<Meta::SqlTrack*>(track.data())->setFirstPlayed( aDate );
// -- remove one track
QVERIFY( QFile::remove( track->playableUrl().path() ) );
fullScanAndWait();
// -- check that the track is really gone
QCOMPARE( album->tracks().count(), 8 );
}
void
TestSqlScanManager::testMove()
{
createAlbum();
createCompilation();
// we use the created and first played attributes for identifying the moved tracks.
// currently those are not written back to the track
Meta::AlbumPtr album;
Meta::TrackPtr track;
QDateTime aDate = QDateTime::currentDateTime();
fullScanAndWait();
// -- check the commit
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
track = album->tracks().first();
QCOMPARE( track->trackNumber(), 1 );
QDateTime createDate = track->createDate();
QDateTime modifyDate = track->modifyDate();
// --- move one track
static_cast<Meta::SqlTrack*>(track.data())->setFirstPlayed( aDate );
const QString targetPath = m_tmpCollectionDir->name() + "moved.mp3";
QVERIFY( QFile::rename( track->playableUrl().path(), targetPath ) );
fullScanAndWait();
// -- check that the track is moved
QVERIFY( createDate == track->createDate() ); // create date should not have changed
QVERIFY( modifyDate == track->modifyDate() ); // we just changed the track. it should have changed
QCOMPARE( track->statistics()->firstPlayed(), aDate );
QCOMPARE( track->playableUrl().path(), targetPath );
// --- move a directory
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 10 );
track = album->tracks().first();
QUrl oldUrl = track->playableUrl();
QVERIFY( QFile::rename( m_tmpCollectionDir->name() + "Top Gun",
m_tmpCollectionDir->name() + "Top Gun - Soundtrack" ) );
// do an incremental scan
incrementalScanAndWait();
// check that the track is now moved (but still the old object)
QCOMPARE( album->tracks().count(), 10 ); // no doublicate tracks
QVERIFY( oldUrl != track->playableUrl() );
}
void
TestSqlScanManager::testFeat()
{
Meta::FieldHash values;
// create one compilation track
values.clear();
values.insert( Meta::valUniqueId, QVariant("b88c3405cfee64c50768b75eb6e3feea") );
values.insert( Meta::valUrl, QVariant("In-Mood feat. Juliette - The Last Unicorn (Elemental Radio Mix).mp3") );
values.insert( Meta::valTitle, QVariant("The Last Unicorn (Elemental Radio Mix)") );
values.insert( Meta::valArtist, QVariant("In-Mood feat. Juliette") );
values.insert( Meta::valAlbum, QVariant("The Last Unicorn") );
createTrack( values );
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( "The Last Unicorn", "In-Mood" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 1 );
}
void
TestSqlScanManager::testAlbumImage()
{
createSingleTrack();
createAlbum();
createCompilation();
// put an image into the album directory
QString imageSourcePath = QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + "/data/playlists/no-playlist.png" );
QVERIFY( QFile::exists( imageSourcePath ) );
QString targetPath;
targetPath = m_tmpCollectionDir->name() + "Pop/Thriller/cover.png";
QVERIFY( QFile::copy( m_sourcePath, targetPath ) );
// put an image into the compilation directory
targetPath = m_tmpCollectionDir->name() + "Top Gun/front.png";
QVERIFY( QFile::copy( m_sourcePath, targetPath ) );
// set an embedded image
targetPath = m_tmpCollectionDir->name() + "Various Artists/Big Screen Adventures/28 - Theme From Armageddon.mp3";
Meta::Tag::setEmbeddedCover( targetPath, QImage( 200, 200, QImage::Format_RGB32 ) );
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QVERIFY( album->hasImage() );
album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QVERIFY( album->hasImage() );
album = m_collection->registry()->getAlbum( "Big Screen Adventures", "Theme Orchestra" );
QVERIFY( album );
QVERIFY( album->hasImage() );
}
void
TestSqlScanManager::testMerges()
{
// songs from same album but different directory
// check that images are merged
// check that old image is not overwritten
Meta::FieldHash values;
values.clear();
values.insert( Meta::valUniqueId, QVariant("123456d040d5dd9b5b45c1494d84cc82") );
values.insert( Meta::valUrl, QVariant("Various Artists/Big Screen Adventures/28 - Theme From Armageddon.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Unnamed track") );
values.insert( Meta::valArtist, QVariant("Unknown artist") );
createTrack( values );
// -- check the commit
fullScanAndWait();
Meta::TrackPtr track = m_collection->registry()->getTrack( 1 );
QVERIFY( track );
QCOMPARE( track->name(), QString("Unnamed track") );
// -- now overwrite the track with changed information and a new uid
// - remove one track
QVERIFY( QFile::remove( track->playableUrl().path() ) );
values.clear();
values.insert( Meta::valUniqueId, QVariant("794b1bd040d5dd9b5b45c1494d84cc82") );
values.insert( Meta::valUrl, QVariant("Various Artists/Big Screen Adventures/28 - Theme From Armageddon.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Theme From Armageddon") );
values.insert( Meta::valArtist, QVariant("Soundtrack & Theme Orchestra") );
values.insert( Meta::valAlbum, QVariant("Big Screen Adventures") );
values.insert( Meta::valComposer, QVariant("Unknown Composer") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 210541237") );
values.insert( Meta::valGenre, QVariant("Broadway & Vocalists") );
values.insert( Meta::valYear, QVariant(2009) );
values.insert( Meta::valTrackNr, QVariant(28) );
values.insert( Meta::valScore, QVariant(0.875) );
values.insert( Meta::valPlaycount, QVariant(5) );
createTrack( values );
fullScanAndWait();
// -- check the commit
QCOMPARE( track->name(), QString("Theme From Armageddon") );
QVERIFY( track->artist() );
QCOMPARE( track->artist()->name(), QString("Soundtrack & Theme Orchestra") );
QVERIFY( track->album() );
QCOMPARE( track->album()->name(), QString("Big Screen Adventures") );
QCOMPARE( track->composer()->name(), QString("Unknown Composer") );
QCOMPARE( track->comment(), QString("Amazon.com Song ID: 210541237") );
QCOMPARE( track->year()->year(), 2009 );
QCOMPARE( track->type(), QString("mp3") );
QCOMPARE( track->trackNumber(), 28 );
QCOMPARE( track->bitrate(), 256 );
QCOMPARE( track->length(), qint64(12000) );
QCOMPARE( track->sampleRate(), 44100 );
QCOMPARE( track->filesize(), 389679 );
Meta::StatisticsPtr statistics = track->statistics();
qFuzzyCompare( statistics->score(), 0.875 );
QCOMPARE( statistics->playCount(), 5 );
QVERIFY( !statistics->firstPlayed().isValid() );
QVERIFY( !statistics->lastPlayed().isValid() );
QVERIFY( track->createDate().isValid() );
// -- now do an incremental scan
createAlbum(); // add a new album
incrementalScanAndWait();
// -- check the commit
Meta::AlbumPtr album;
// the old track is still there
album = m_collection->registry()->getAlbum( "Big Screen Adventures", "Soundtrack & Theme Orchestra" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 1 );
// the new album is now here
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
}
void
TestSqlScanManager::testLargeInsert()
{
if( qgetenv("AMAROK_RUN_LONG_TESTS").isNull() )
QSKIP( "takes too long to be run by default;\nDefine AMAROK_RUN_LONG_TESTS "
"environment variable to run all tests.", SkipAll );
// the old large insert test was misleading as the problems with
// the insertion started upwards of 20000 tracks.
//
// For now here are the "ok" numbers on a sensible fast computer:
// Scanning 10000 files <3 min
// Committing 10000 files <30 sec
// Scanning 50000 files <13 min
// Committing 50000 files <1 min
QDateTime aDate = QDateTime::currentDateTime();
// -- create the input data
QByteArray byteArray;
QBuffer *buffer = new QBuffer(&byteArray);
buffer->open(QIODevice::ReadWrite);
QXmlStreamWriter writer( buffer );
writer.writeStartElement( "scanner" );
int trackCount = 0;
// some simulated normal albums
for( int dirId = 0; dirId < 2000; dirId++ )
{
writer.writeStartElement( "directory" );
writer.writeTextElement( "path", QString::number(dirId) );
writer.writeTextElement( "rpath", '/' + QString::number(dirId) );
writer.writeTextElement( "mtime", QString::number(aDate.toTime_t()) );
for( int trackId = 0; trackId < 20; trackId++ )
{
writer.writeStartElement( "track" );
writer.writeTextElement( "uniqueid", "uid" + QString::number(trackCount) );
writer.writeTextElement( "path", "/path" + QString::number(trackCount) );
writer.writeTextElement( "rpath", "path" + QString::number(trackCount) );
trackCount++;
writer.writeTextElement( "title", "track" + QString::number(trackCount) );
writer.writeTextElement( "artist", "artist" + QString::number(dirId) );
writer.writeTextElement( "album", QString::number(dirId) );
writer.writeEndElement();
}
writer.writeEndElement();
}
// a simulated genre folders
for( int dirId = 0; dirId < 7; dirId++ )
{
writer.writeStartElement( "directory" );
writer.writeTextElement( "path", "genre" + QString::number(dirId) );
writer.writeTextElement( "rpath", "/genre" + QString::number(dirId) );
writer.writeTextElement( "mtime", QString::number(aDate.toTime_t()) );
for( int albumId = 0; albumId < 1000; albumId++ )
{
writer.writeStartElement( "track" );
writer.writeTextElement( "uniqueid", "uid" + QString::number(trackCount) );
writer.writeTextElement( "path", "/path" + QString::number(trackCount) );
writer.writeTextElement( "rpath", "path" + QString::number(trackCount) );
trackCount++;
writer.writeTextElement( "title", "track" + QString::number(trackCount) );
writer.writeTextElement( "artist",
"artist" + QString::number(dirId) +
"xx" + QString::number(albumId) );
writer.writeTextElement( "album",
"genre album" + QString::number(dirId) +
"xx" + QString::number(albumId) );
writer.writeEndElement();
}
writer.writeEndElement();
}
// A simulated amarok 1.4 collection folder
for( int dirId = 0; dirId < 3000; dirId++ )
{
writer.writeStartElement( "directory" );
writer.writeTextElement( "path", "collection" + QString::number(dirId) );
writer.writeTextElement( "rpath", "/collection" + QString::number(dirId) );
writer.writeTextElement( "mtime", QString::number(aDate.toTime_t()) );
writer.writeStartElement( "track" );
writer.writeTextElement( "uniqueid", "uid" + QString::number(trackCount) );
writer.writeTextElement( "path", "/path" + QString::number(trackCount) );
writer.writeTextElement( "rpath", "path" + QString::number(trackCount) );
trackCount++;
writer.writeTextElement( "title", "track" + QString::number(trackCount) );
writer.writeTextElement( "artist", "album artist" + QString::number(dirId % 200) );
writer.writeTextElement( "album", "album" + QString::number(dirId % 300) );
writer.writeEndElement();
writer.writeEndElement();
}
writer.writeEndElement();
aDate = QDateTime::currentDateTime();
// -- feed the scanner in batch mode
buffer->seek( 0 );
importAndWait( buffer );
qDebug() << "performance test secs:"<< aDate.secsTo( QDateTime::currentDateTime() );
QVERIFY( aDate.secsTo( QDateTime::currentDateTime() ) < 120 );
// -- get all tracks
Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() );
qm->setQueryType( Collections::QueryMaker::Track );
qm->setBlocking( true );
qm->run();
Meta::TrackList tracks = qm->tracks();
delete qm;
for( int i = 0; i < trackCount; i++ )
{
Meta::TrackPtr track = m_collection->registry()->getTrackFromUid( m_collection->uidUrlProtocol() + "://uid" + QString::number(i) );
QVERIFY( track );
}
qDebug() << "performance test secs:"<< aDate.secsTo( QDateTime::currentDateTime() ) << "tracks:" << trackCount;
QCOMPARE( tracks.count(), trackCount );
// -- scan the input a second time. that should be a lot faster (but currently isn't)
aDate = QDateTime::currentDateTime();
// -- feed the scanner in batch mode
buffer = new QBuffer(&byteArray); // the old scanner deleted the old buffer.
buffer->open(QIODevice::ReadWrite);
importAndWait( buffer );
qDebug() << "performance test secs:"<< aDate.secsTo( QDateTime::currentDateTime() );
QVERIFY( aDate.secsTo( QDateTime::currentDateTime() ) < 80 );
}
void
TestSqlScanManager::testIdentifyCompilationInMultipleDirectories()
{
// Compilations where each is track is from a different artist
// are often stored as one track per directory, e.g.
// /artistA/compilation/track1
// /artistB/compilation/track2
//
// this is how Amarok 1 (after using Organize Collection) and iTunes are storing
// these albums on disc
// the bad thing is that Amarok 1 (as far as I know) didn't set the id3 tags
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("5ef9fede5b3f98deb088b33428b0398e") );
values.insert( Meta::valUrl, QVariant("Kenny Loggins/Top Gun/Top Gun - 01 - Kenny Loggins - Danger Zone.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Danger Zone") );
values.insert( Meta::valArtist, QVariant("Kenny Loggins") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
values.insert( Meta::valTrackNr, QVariant("1") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("3e3970f52b0eda3f2a8c1b3a8c8d39ea") );
values.insert( Meta::valUrl, QVariant("Cheap Trick/Top Gun/Top Gun - 02 - Cheap Trick - Mighty Wings.mp3") );
values.insert( Meta::valTitle, QVariant("Mighty Wings") );
values.insert( Meta::valArtist, QVariant("Cheap Trick") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("6ea0bbd97ad8068df58ad75a81f271f7") );
values.insert( Meta::valUrl, QVariant("Kenny Loggins/Top Gun/Top Gun - 03 - Kenny Loggins - Playing With The Boys.mp3") );
values.insert( Meta::valTitle, QVariant("Playing With The Boys") );
values.insert( Meta::valArtist, QVariant("Kenny Loggins") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("f3ac2e15288361d779a0ae813a2018ba") );
values.insert( Meta::valUrl, QVariant("Teena Marie/Top Gun/Top Gun - 04 - Teena Marie - Lead Me On.mp3") );
values.insert( Meta::valTitle, QVariant("Lead Me On") );
values.insert( Meta::valArtist, QVariant("Teena Marie") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album = m_collection->registry()->getAlbum( "Top Gun", QString() );
QVERIFY( album );
QCOMPARE( album->name(), QString("Top Gun") );
QCOMPARE( album->tracks().count(), 4 );
QVERIFY( album->isCompilation() );
}
void
TestSqlScanManager::testAlbumArtistMerges()
{
// three tracks with the same artist but different album artist.
// (one is unset)
// Those should end up in different albums.
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("1ef9fede5b3f98deb088b33428b0398e") );
values.insert( Meta::valUrl, QVariant("test1/song1.mp3") );
values.insert( Meta::valTitle, QVariant("title1") );
values.insert( Meta::valArtist, QVariant("artist") );
values.insert( Meta::valAlbumArtist, QVariant("albumArtist1") );
values.insert( Meta::valAlbum, QVariant("test1") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("2ef9fede5b3f98deb088b33428b0398b") );
values.insert( Meta::valUrl, QVariant("test1/song2.mp3") );
values.insert( Meta::valTitle, QVariant("title2") );
values.insert( Meta::valArtist, QVariant("artist") );
values.insert( Meta::valAlbumArtist, QVariant("albumArtist2") );
values.insert( Meta::valAlbum, QVariant("test1") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("3ef9fede5b3f98deb088b33428b0398c") );
values.insert( Meta::valUrl, QVariant("test1/song3.mp3") );
values.insert( Meta::valTitle, QVariant("title3") );
values.insert( Meta::valArtist, QVariant("artist") );
values.insert( Meta::valAlbum, QVariant("test1") );
createTrack( values );
fullScanAndWait();
// -- check the commit
Meta::AlbumPtr album;
album = m_collection->registry()->getAlbum( "test1", QString() );
QVERIFY( album );
QCOMPARE( album->name(), QString("test1") );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( album->isCompilation() );
album = m_collection->registry()->getAlbum( "test1", QString("albumArtist1") );
QVERIFY( album );
QCOMPARE( album->name(), QString("test1") );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( !album->isCompilation() );
album = m_collection->registry()->getAlbum( "test1", QString("albumArtist2") );
QVERIFY( album );
QCOMPARE( album->name(), QString("test1") );
QCOMPARE( album->tracks().count(), 1 );
QVERIFY( !album->isCompilation() );
}
void
TestSqlScanManager::testCrossRenaming()
{
createAlbum();
// we use the created and first played attributes for identifying the moved tracks.
// currently those are not written back to the track
Meta::AlbumPtr album;
Meta::TrackPtr track;
fullScanAndWait();
// -- check the commit
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
QVERIFY( !album->isCompilation() );
// --- cross-rename two track
track = album->tracks().at( 0 );
static_cast<Meta::SqlTrack*>(track.data())->setRating( 1 );
QString path1 = track->playableUrl().path();
track = album->tracks().at( 1 );
static_cast<Meta::SqlTrack*>(track.data())->setRating( 2 );
QString path2 = track->playableUrl().path();
QString targetPath = m_tmpCollectionDir->name() + "moved.mp3";
QVERIFY( QFile::rename( path2, targetPath ) );
QVERIFY( QFile::rename( path1, path2 ) );
QVERIFY( QFile::rename( targetPath, path1 ) );
fullScanAndWait();
// -- check that the tracks are moved correctly
album = m_collection->registry()->getAlbum( "Thriller", "Michael Jackson" );
QVERIFY( album );
QCOMPARE( album->tracks().count(), 9 );
track = album->tracks().at( 0 );
QCOMPARE( track->statistics()->rating(), 1 );
QCOMPARE( track->playableUrl().path(), path2 );
track = album->tracks().at( 1 );
QCOMPARE( track->statistics()->rating(), 2 );
QCOMPARE( track->playableUrl().path(), path1 );
}
void
TestSqlScanManager::slotCollectionUpdated()
{
m_collectionUpdatedCount++;
}
void
TestSqlScanManager::fullScanAndWait()
{
QScopedPointer<Capabilities::CollectionScanCapability> csc( m_collection->create<Capabilities::CollectionScanCapability>());
if( csc )
{
csc->startFullScan();
waitScannerFinished();
}
}
void
TestSqlScanManager::incrementalScanAndWait()
{
// incremental scans use the modification time of the file system.
// this time is only in seconds, so to be sure that the incremental scan
// works we need to wait at least one second.
QTest::qWait( 1000 );
QScopedPointer<Capabilities::CollectionScanCapability> csc( m_collection->create<Capabilities::CollectionScanCapability>());
if( csc )
csc->startIncrementalScan();
waitScannerFinished();
}
void
TestSqlScanManager::importAndWait( QIODevice* input )
{
QScopedPointer<Capabilities::CollectionImportCapability> csc( m_collection->create<Capabilities::CollectionImportCapability>());
if( csc )
csc->import( input, 0 );
waitScannerFinished();
}
void
TestSqlScanManager::waitScannerFinished()
{
QVERIFY( m_scanManager->isRunning() );
- QSignalSpy succeedSpy( m_scanManager, SIGNAL(succeeded()) );
- QSignalSpy failSpy( m_scanManager, SIGNAL(failed(QString)) );
+ QSignalSpy succeedSpy( m_scanManager, &GenericScanManager::succeeded );
+ QSignalSpy failSpy( m_scanManager, &GenericScanManager::failed );
// connect the result signal *after* the spies to ensure they are updated first
- connect( m_scanManager, SIGNAL(succeeded()), this, SIGNAL(scanManagerResult()) );
- connect( m_scanManager, SIGNAL(failed(QString)), this, SIGNAL(scanManagerResult()));
+ connect( m_scanManager, &GenericScanManager::succeeded, this, &TestSqlScanManager::scanManagerResult );
+ connect( m_scanManager, &GenericScanManager::failed, this, &TestSqlScanManager::scanManagerResult);
const bool ok = QTest::kWaitForSignal( this, SIGNAL(scanManagerResult()), 60*1000 );
- disconnect( m_scanManager, SIGNAL(succeeded()), this, SIGNAL(scanManagerResult()) );
- disconnect( m_scanManager, SIGNAL(failed(QString)), this, SIGNAL(scanManagerResult()) );
+ disconnect( m_scanManager, &GenericScanManager::succeeded, this, &TestSqlScanManager::scanManagerResult );
+ disconnect( m_scanManager, &GenericScanManager::failed, this, &TestSqlScanManager::scanManagerResult );
QVERIFY2( ok, "Scan Manager timed out without a result" );
if( failSpy.count() > 0 )
{
QStringList errors;
foreach( const QList<QVariant> &arguments, static_cast<QList<QList<QVariant> > >( failSpy ) )
errors << arguments.value( 0 ).toString();
// this will fire each time:
qWarning() << "ScanManager failed with an error:" << errors.join( ", " );
}
QCOMPARE( qMakePair( succeedSpy.count(), failSpy.count() ), qMakePair( 1, 0 ) );
QVERIFY( !m_scanManager->isRunning() );
}
void
TestSqlScanManager::createTrack( const Meta::FieldHash &values )
{
// -- copy the file from our original
QVERIFY( values.contains( Meta::valUrl ) );
const QString targetPath = m_tmpCollectionDir->name() + values.value( Meta::valUrl ).toString();
QVERIFY( QDir( m_tmpCollectionDir->name() ).mkpath( QFileInfo( values.value( Meta::valUrl ).toString() ).path() ) );
QVERIFY( QFile::copy( m_sourcePath, targetPath ) );
// -- set all the values that we need
Meta::Tag::writeTags( targetPath, values, true );
}
void
TestSqlScanManager::createSingleTrack()
{
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("794b1bd040d5dd9b5b45c1494d84cc82") );
values.insert( Meta::valUrl, QVariant("Various Artists/Big Screen Adventures/28 - Theme From Armageddon.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Theme From Armageddon") );
values.insert( Meta::valArtist, QVariant("Soundtrack & Theme Orchestra") );
values.insert( Meta::valAlbumArtist, QVariant("Theme Orchestra") );
values.insert( Meta::valAlbum, QVariant("Big Screen Adventures") );
values.insert( Meta::valComposer, QVariant("Unknown Composer") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 210541237") );
values.insert( Meta::valGenre, QVariant("Broadway & Vocalists") );
values.insert( Meta::valYear, QVariant(2009) );
values.insert( Meta::valTrackNr, QVariant(28) );
// values.insert( Meta::valBitrate, QVariant(216) ); // the bitrate can not be set. it's computed
// values.insert( Meta::valLength, QVariant(184000) ); // also can't be set
// values.insert( Meta::valSamplerate, QVariant(44100) ); // again
// values.insert( Meta::valFilesize, QVariant(5094892) ); // again
values.insert( Meta::valScore, QVariant(0.875) );
values.insert( Meta::valPlaycount, QVariant(5) );
// TODO: set an embedded cover
createTrack( values );
}
void
TestSqlScanManager::createAlbum()
{
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("1dc7022c52a3e4c51b46577da9b3c8ff") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 01 - Michael Jackson - Track01.mp3") );
values.insert( Meta::valTitle, QVariant("Wanna Be Startin' Somethin'") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(1) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("1dc708934a3e4c51b46577da9b3ab11") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 02 - Michael Jackson - Track02.mp3") );
values.insert( Meta::valTitle, QVariant("Baby Be Mine") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(2) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("15a6b1bf79747fdc8e9c6b6f06203017") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 03 - Michael Jackson - Track03.mp3") );
values.insert( Meta::valTitle, QVariant("The Girl Is Mine") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(3) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("4aba4c8b1d1893c03c112cc3c01221e9") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 04 - Michael Jackson - Track04.mp3") );
values.insert( Meta::valTitle, QVariant("Thriller") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(4) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("cb44d2a3d8053829b04672723bf0bd6e") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 05 - Michael Jackson - Track05.mp3") );
values.insert( Meta::valTitle, QVariant("Beat It") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(5) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("eba1858eeeb3c6d97fe3385200114d86") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 06 - Michael Jackson - Track06.mp3") );
values.insert( Meta::valTitle, QVariant("Billy Jean") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(6) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("4623850290998486b0f7b39a2719904e") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 07 - Michael Jackson - Track07.mp3") );
values.insert( Meta::valTitle, QVariant("Human Nature") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(7) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("6d9a7de13af1e16bb13a6208e44b046d") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 08 - Michael Jackson - Track08.mp3") );
values.insert( Meta::valTitle, QVariant("P.Y.T. (Pretty Young Thing)") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(8) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("91cf9a7c0d255399f9f6babfacae432b") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 09 - Michael Jackson - Track09.mp3") );
values.insert( Meta::valTitle, QVariant("The Lady In My Life") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(9) );
createTrack( values );
}
void
TestSqlScanManager::createCompilation()
{
// a compilation without the compilation flags values.insert( Meta::valCompilation, QVariant(true) );
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("5ef9fede5b3f98deb088b33428b0398e") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 01 - Kenny Loggins - Danger Zone.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Danger Zone") );
values.insert( Meta::valArtist, QVariant("Kenny Loggins") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("3e3970f52b0eda3f2a8c1b3a8c8d39ea") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 02 - Cheap Trick - Mighty Wings.mp3") );
values.insert( Meta::valTitle, QVariant("Mighty Wings") );
values.insert( Meta::valArtist, QVariant("Cheap Trick") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("6ea0bbd97ad8068df58ad75a81f271f7") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 03 - Kenny Loggins - Playing With The Boys.mp3") );
values.insert( Meta::valTitle, QVariant("Playing With The Boys") );
values.insert( Meta::valArtist, QVariant("Kenny Loggins") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("f3ac2e15288361d779a0ae813a2018ba") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 04 - Teena Marie - Lead Me On.mp3") );
values.insert( Meta::valTitle, QVariant("Lead Me On") );
values.insert( Meta::valArtist, QVariant("Teena Marie") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("ffe2bb3e6e2f698983c95e40937545ff") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 05 - Berlin - Take My Breath Away (Love Theme From _Top Gun_).mp3") );
values.insert( Meta::valTitle, QVariant("Take My Breath Away (Love Theme From &quot;Top Gun&quot;)") );
values.insert( Meta::valArtist, QVariant("Berlin") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("c871dba16f92483898bcd6a1ed1bc14f") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 06 - Miami Sound Machine - Hot Summer Nights.mp3") );
values.insert( Meta::valTitle, QVariant("Hot Summer Nights") );
values.insert( Meta::valArtist, QVariant("Miami Sound Machine") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("80d157c36ed334192ed8df4c01bf0d4e") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 07 - Loverboy - Heaven In Your Eyes.mp3") );
values.insert( Meta::valTitle, QVariant("Heaven In Your Eyes") );
values.insert( Meta::valArtist, QVariant("Loverboy") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("1fe5897cdea860348c3a5eb40d47c382") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 08 - Larry Greene - Through The Fire.mp3") );
values.insert( Meta::valTitle, QVariant("Through The Fire") );
values.insert( Meta::valArtist, QVariant("Larry Greene") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("e0eacff604bfe38b5c275b45aa4f5323") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 09 - Marietta - Destination Unknown.mp3") );
values.insert( Meta::valTitle, QVariant("Destination Unknown") );
values.insert( Meta::valArtist, QVariant("Marietta") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("9f1b00dab2df7537b6c5b2be9f08b220") );
values.insert( Meta::valUrl, QVariant("Top Gun/Top Gun - 10 - Harold Faltermeyer &amp; Steve Stevens - Top Gun Anthem.mp3") );
values.insert( Meta::valTitle, QVariant("Top Gun Anthem") );
values.insert( Meta::valArtist, QVariant("Harold Faltermeyer &amp; Steve Stevens") );
values.insert( Meta::valAlbum, QVariant("Top Gun") );
createTrack( values );
}
void
TestSqlScanManager::createCompilationLookAlikeAlbum()
{
Meta::FieldHash values;
// Some systems have problems with the umlauts in the file names.
// That is the case where the system encoding when compiling does not
// match the one of the file system.
// the following is the original filename
// values.insert( Meta::valUrl, QVariant( "Glen Hansard & Markéta Irglová/Once/01 Glen Hansard & Markéta Irglová - Falling Slowly.mp3" ) );
values.insert( Meta::valUniqueId, QVariant( "8375aa24e0e0434ca0c36e382b6f188c" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/01 Glen Hansard & Marketa Irglova - Falling Slowly.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Falling Slowly" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "1" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "ff3f82b1c2e1434d9d1a7b6aec67ac9c" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/02 Glen Hansard & Marketa Irglova - If You Want Me.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "If You Want Me" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "2" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "8fb2396f8d974f6196d2b2ef93ba2551" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/03 Glen Hansard - Broken Hearted Hoover Fixer Sucker Guy.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Broken Hearted Hoover Fixer Sucker Guy" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "3" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "3a211546b91c4bf7a4ec9d41325e5a01" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/04 Glen Hansard & Marketa Irglova - When Your Mind's Made Up.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "When Your Mind's Made Up" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "4" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "e7a1ed52777c437582a217cd29cc35f7" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/05 Glen Hansard - Lies.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Lies" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "5" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "e0c88a85884d40c899522cd733718d9e" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/06 Interference - Gold.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Gold" ) );
values.insert( Meta::valArtist, QVariant( "Interference" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "6" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "0969ea6128444e128cfcac95207bd525" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/07 Marketa Irglova - The Hill.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "The Hill" ) );
values.insert( Meta::valArtist, QVariant( "Markéta Irglová" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "7" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "c1d6eff3cb6c42eaa0d63e186ef1b749" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/08 Glen Hansard - Fallen From the Sky.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Fallen From the Sky" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "8" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "b6611dbccd0e49bca8db5dc598b7bf4f" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/09 Glen Hansard - Leave.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Leave" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "9" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "46873076087f48dda553fc5ebd3c0fb6" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/10 Glen Hansard - Trying to Pull Myself Away.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Trying to Pull Myself Away" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "10" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "ea29de7b131c4cf28df177a8cda990ee" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/11 Glen Hansard - All the Way Down.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "All the Way Down" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "11" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "66259801d8ba4d50a2dfdf0129bc8792" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/12 Glen Hansard & Marketa Irglova - Once.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Once" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "12" ) );
createTrack( values );
values.insert( Meta::valUniqueId, QVariant( "a654e8c5afb14de7b55b6548ac02f724" ) );
values.insert( Meta::valUrl, QVariant( "Glen Hansard & Marketa Irglova/Once/13 Glen Hansard - Say It to Me Now.mp3" ) );
values.insert( Meta::valFormat, QVariant( "1" ) );
values.insert( Meta::valTitle, QVariant( "Say It to Me Now" ) );
values.insert( Meta::valArtist, QVariant( "Glen Hansard" ) );
values.insert( Meta::valAlbum, QVariant( "Once" ) );
values.insert( Meta::valAlbumArtist, QVariant( "Glen Hansard & Markéta Irglová" ) );
values.insert( Meta::valTrackNr, QVariant( "13" ) );
createTrack( values );
}
void
TestSqlScanManager::createCompilationTrack()
{
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96b") );
values.insert( Meta::valUrl, QVariant("Amazon MP3/The Sum Of All Fears (O.S.T.)/The Sum of All Fears/01 - If We Could Remember (O.S.T. LP Version).mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("If We Could Remember (O.S.T. LP Version)") );
values.insert( Meta::valArtist, QVariant("The Sum Of All Fears (O.S.T.)/Yolanda Adams") );
values.insert( Meta::valAlbumArtist, QVariant("The Sum Of All Fears (O.S.T.)") );
values.insert( Meta::valAlbum, QVariant("The Sum of All Fears") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 203452096") );
values.insert( Meta::valGenre, QVariant("Soundtracks") );
values.insert( Meta::valYear, QVariant("2002") );
values.insert( Meta::valTrackNr, QVariant("1") );
values.insert( Meta::valComposer, QVariant("Jerry Goldsmith") );
values.insert( Meta::valScore, QVariant("0.875") );
values.insert( Meta::valPlaycount, QVariant("6") );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("2188afd457cd75a363905f411966b9a0") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/01 - Second Chapter.mp3") );
values.insert( Meta::valFormat, QVariant(1) );
values.insert( Meta::valTitle, QVariant("Second Chapter") );
values.insert( Meta::valArtist, QVariant("Enigma") );
values.insert( Meta::valAlbumArtist, QVariant("Enigma") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 201985325") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant(2004) );
values.insert( Meta::valTrackNr, QVariant(1) );
values.insert( Meta::valComposer, QVariant("Curly M.C.") );
values.insert( Meta::valScore, QVariant("0.54") );
values.insert( Meta::valPlaycount, QVariant("2") );
values.insert( Meta::valUniqueId, QVariant("637bee4fd456d2ff9eafe65c71ba192e") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/02 - The Eyes Of Truth.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("The Eyes Of Truth") );
values.insert( Meta::valArtist, QVariant("Enigma") );
values.insert( Meta::valAlbumArtist, QVariant("Enigma") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 201985326") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant("2004") );
values.insert( Meta::valTrackNr, QVariant("2") );
values.insert( Meta::valComposer, QVariant("Curly M.C.") );
values.insert( Meta::valScore, QVariant("0.928572") );
values.insert( Meta::valPlaycount, QVariant("1286469632") );
values.insert( Meta::valUniqueId, QVariant("b4206da4bc0335d76c2bbc5d4c1b164c") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/03 - Return To Innocence.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Return To Innocence") );
values.insert( Meta::valArtist, QVariant("Enigma") );
values.insert( Meta::valAlbumArtist, QVariant("Enigma") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 201985327") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant("2004") );
values.insert( Meta::valTrackNr, QVariant("3") );
values.insert( Meta::valComposer, QVariant("Curly M.C.") );
values.insert( Meta::valScore, QVariant("0.75") );
values.insert( Meta::valPlaycount, QVariant("1286469888") );
values.insert( Meta::valUniqueId, QVariant("eb0061602f52d67140fd465dc275fbf2") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/04 - I Love You...I'Ll Kill You.mp3") );
values.insert( Meta::valFormat, 1 );
values.insert( Meta::valTitle, QVariant("I Love You...I'Ll Kill You") );
values.insert( Meta::valArtist, QVariant("Enigma") );
values.insert( Meta::valAlbumArtist, QVariant("Enigma") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 201985328") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant(2004) );
values.insert( Meta::valTrackNr, QVariant(4) );
values.insert( Meta::valComposer, QVariant("Curly M.C.") );
values.insert( Meta::valScore, QVariant(0.5) );
values.insert( Meta::valPlaycount, QVariant(1286470656) );
values.insert( Meta::valUniqueId, QVariant("94dabc09509379646458f62bee7e41ed") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/05 - Silent Warrior.mp3") );
values.insert( Meta::valFormat, 1 );
values.insert( Meta::valTitle, QVariant("Silent Warrior") );
values.insert( Meta::valArtist, QVariant("Enigma") );
values.insert( Meta::valAlbumArtist, QVariant("Enigma") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 201985329") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant(2004) );
values.insert( Meta::valTrackNr, QVariant(5) );
values.insert( Meta::valComposer, QVariant("Curly M.C.") );
values.insert( Meta::valScore, QVariant(0.96875) );
values.insert( Meta::valPlaycount, QVariant(6) );
values.insert( Meta::valUniqueId, QVariant("6ae759476c34256ff1d06f0b5c964d75") );
values.insert( Meta::valUrl, QVariant("The Cross Of Changes/06 - The Dream Of The Dolphin.mp3") );
values.insert( Meta::valTitle, QVariant("The Dream Of The Dolphin") );
values.insert( Meta::valArtist, QVariant("Enigma") );
values.insert( Meta::valAlbumArtist, QVariant("Enigma") );
values.insert( Meta::valAlbum, QVariant("The Cross Of Changes") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 201985330") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant("2004") );
values.insert( Meta::valTrackNr, QVariant(6) );
values.insert( Meta::valComposer, QVariant("Curly M.C.") );
values.insert( Meta::valScore, QVariant(0.5) );
values.insert( Meta::valPlaycount, QVariant(2) );
values.insert( Meta::valUniqueId, QVariant("7957bc25521c1dc91351d497321c27a6") );
values.insert( Meta::valUrl, QVariant("Amazon MP3/Ashford & Simpson/Solid/01 - Solid.mp3") );
values.insert( Meta::valTitle, QVariant("Solid") );
values.insert( Meta::valArtist, QVariant("Ashford &amp; Simpson") );
values.insert( Meta::valAlbumArtist, QVariant("Ashford &amp; Simpson") );
values.insert( Meta::valAlbum, QVariant("Solid") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 202265871") );
values.insert( Meta::valGenre, QVariant("Pop") );
values.insert( Meta::valYear, QVariant(2007) );
values.insert( Meta::valTrackNr, QVariant(1) );
values.insert( Meta::valComposer, QVariant("Valerie Simpson") );
values.insert( Meta::valRating, QVariant(0.898438) );
values.insert( Meta::valScore, QVariant(0.875) );
values.insert( Meta::valPlaycount, QVariant(12) );
}
diff --git a/tests/core-impl/collections/db/sql/TestSqlTrack.cpp b/tests/core-impl/collections/db/sql/TestSqlTrack.cpp
index b4646411ed..d7bfd4ae1e 100644
--- a/tests/core-impl/collections/db/sql/TestSqlTrack.cpp
+++ b/tests/core-impl/collections/db/sql/TestSqlTrack.cpp
@@ -1,559 +1,559 @@
/****************************************************************************************
* Copyright (c) 2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestSqlTrack.h"
#include "DefaultSqlQueryMakerFactory.h"
#include "core/meta/Meta.h"
#include "core-impl/storage/sql/mysqlestorage/MySqlEmbeddedStorage.h"
#include "SqlCollection.h"
#include "SqlMeta.h"
#include "SqlRegistry.h"
#include "SqlMountPointManagerMock.h"
#include "MetaNotificationSpy.h"
#include <QDateTime>
#include <QSignalSpy>
#include <qtest_kde.h>
QTEST_KDEMAIN_CORE( TestSqlTrack )
TestSqlTrack::TestSqlTrack()
: QObject()
, m_collection( 0 )
, m_storage( 0 )
, m_tmpDir( 0 )
{
}
void
TestSqlTrack::initTestCase()
{
m_tmpDir = new KTempDir();
m_storage = new MySqlEmbeddedStorage();
QVERIFY( m_storage->init( m_tmpDir->name() ) );
m_collection = new Collections::SqlCollection( m_storage );
m_collection->setMountPointManager( new SqlMountPointManagerMock( this, m_storage ) );
// I just need the table and not the whole playlist manager
m_storage->query( QString( "CREATE TABLE playlist_tracks ("
" id " + m_storage->idType() +
", playlist_id INTEGER "
", track_num INTEGER "
", url " + m_storage->exactTextColumnType() +
", title " + m_storage->textColumnType() +
", album " + m_storage->textColumnType() +
", artist " + m_storage->textColumnType() +
", length INTEGER "
", uniqueid " + m_storage->textColumnType(128) + ") ENGINE = MyISAM;" ) );
}
void
TestSqlTrack::cleanupTestCase()
{
delete m_collection;
//m_storage is deleted by SqlCollection
delete m_tmpDir;
}
void
TestSqlTrack::init()
{
//setup base data
m_storage->query( "INSERT INTO artists(id, name) VALUES (1, 'artist1');" );
m_storage->query( "INSERT INTO artists(id, name) VALUES (2, 'artist2');" );
m_storage->query( "INSERT INTO artists(id, name) VALUES (3, 'artist3');" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(1,'album1',1);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(2,'album2',1);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(3,'album3',2);" );
m_storage->query( "INSERT INTO albums(id,name,artist) VALUES(4,'album-compilation',0);" );
m_storage->query( "INSERT INTO composers(id, name) VALUES (1, 'composer1');" );
m_storage->query( "INSERT INTO composers(id, name) VALUES (2, 'composer2');" );
m_storage->query( "INSERT INTO composers(id, name) VALUES (3, 'composer3');" );
m_storage->query( "INSERT INTO genres(id, name) VALUES (1, 'genre1');" );
m_storage->query( "INSERT INTO genres(id, name) VALUES (2, 'genre2');" );
m_storage->query( "INSERT INTO genres(id, name) VALUES (3, 'genre3');" );
m_storage->query( "INSERT INTO years(id, name) VALUES (1, '1');" );
m_storage->query( "INSERT INTO years(id, name) VALUES (2, '2');" );
m_storage->query( "INSERT INTO years(id, name) VALUES (3, '3');" );
m_storage->query( "INSERT INTO directories(id, deviceid, dir) VALUES (1, -1, './');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (1, -1, './IDoNotExist.mp3', 1, '1');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (2, -1, './IDoNotExistAsWell.mp3', 1, '2');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (3, -1, './MeNeither.mp3', 1, '3');" );
m_storage->query( "INSERT INTO urls(id, deviceid, rpath, directory, uniqueid) VALUES (4, -1, './NothingHere.mp3', 1, '4');" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(1,1,'track1','comment1',1,1,1,1,1);" );
m_storage->query( "INSERT INTO tracks(id,url,title,comment,artist,album,genre,year,composer) "
"VALUES(2,2,'track2','comment2',1,2,1,1,1);" );
m_collection->registry()->emptyCache();
}
void
TestSqlTrack::cleanup()
{
m_storage->query( "TRUNCATE TABLE years;" );
m_storage->query( "TRUNCATE TABLE genres;" );
m_storage->query( "TRUNCATE TABLE composers;" );
m_storage->query( "TRUNCATE TABLE albums;" );
m_storage->query( "TRUNCATE TABLE artists;" );
m_storage->query( "TRUNCATE TABLE tracks;" );
m_storage->query( "TRUNCATE TABLE urls;" );
m_storage->query( "TRUNCATE TABLE directories;" );
m_storage->query( "TRUNCATE TABLE statistics;" );
m_storage->query( "TRUNCATE TABLE labels;" );
m_storage->query( "TRUNCATE TABLE urls_labels;" );
}
void
TestSqlTrack::setAllValues( Meta::SqlTrack *track )
{
track->setTitle( "New Title" );
track->setAlbum( "New Album" );
track->setArtist( "New Artist" );
track->setComposer( "New Composer" );
track->setYear( 1999 );
track->setGenre( "New Genre" );
track->setUrl( -1, "./new_url", 2 );
track->setBpm( 32.0 );
track->setComment( "New Comment" );
track->setScore( 64.0 );
track->setRating( 5 );
track->setLength( 5000 );
track->setSampleRate( 4400 );
track->setBitrate( 128 );
track->setTrackNumber( 4 );
track->setDiscNumber( 1 );
track->setFirstPlayed( QDateTime::fromTime_t(100) );
track->setLastPlayed( QDateTime::fromTime_t(200) );
track->setPlayCount( 20 );
Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain,
Meta::ReplayGain_Track_Peak,
Meta::ReplayGain_Album_Gain,
Meta::ReplayGain_Album_Peak };
for( int i=0; i<4; i++ )
track->setReplayGain( modes[i], qreal(i) );
track->addLabel( "New Label" );
}
void
TestSqlTrack::getAllValues( Meta::SqlTrack *track )
{
QCOMPARE( track->name(), QString( "New Title" ) );
QCOMPARE( track->album()->name(), QString( "New Album" ) );
QCOMPARE( track->artist()->name(), QString( "New Artist" ) );
QCOMPARE( track->composer()->name(), QString( "New Composer" ) );
QCOMPARE( track->year()->name(), QString( "1999" ) );
QCOMPARE( track->genre()->name(), QString( "New Genre" ) );
QCOMPARE( track->playableUrl().path(), QString( "/new_url" ) );
QCOMPARE( track->bpm(), 32.0 );
QCOMPARE( track->comment(), QString( "New Comment" ) );
QCOMPARE( track->score(), 64.0 );
QCOMPARE( track->rating(), 5 );
QCOMPARE( track->length(), qint64(5000) );
QCOMPARE( track->sampleRate(), 4400 );
QCOMPARE( track->bitrate(), 128 );
QCOMPARE( track->trackNumber(), 4 );
QCOMPARE( track->discNumber(), 1 );
QCOMPARE( track->firstPlayed(), QDateTime::fromTime_t(100) );
QCOMPARE( track->lastPlayed(), QDateTime::fromTime_t(200) );
QCOMPARE( track->playCount(), 20 );
Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain,
Meta::ReplayGain_Track_Peak,
Meta::ReplayGain_Album_Gain,
Meta::ReplayGain_Album_Peak };
for( int i=0; i<4; i++ )
QCOMPARE( track->replayGain( modes[i] ), qreal(i) );
QVERIFY( track->labels().count() > 0 );
QVERIFY( track->labels().contains( m_collection->registry()->getLabel("New Label") ) );
}
/** Check that the registry always returns the same track pointer */
void
TestSqlTrack::testGetTrack()
{
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( 1 );
Meta::TrackPtr track2 = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::TrackPtr track3 = m_collection->registry()->getTrackFromUid( "1" );
QVERIFY( track1 );
QVERIFY( track1 == track2 );
QVERIFY( track1 == track3 );
}
// and also after empty cache
m_collection->registry()->emptyCache();
// changed order...
{
Meta::TrackPtr track2 = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::TrackPtr track3 = m_collection->registry()->getTrackFromUid( "1" );
Meta::TrackPtr track1 = m_collection->registry()->getTrack( 1 );
QVERIFY( track1 );
QVERIFY( track1 == track2 );
QVERIFY( track1 == track3 );
}
// do again creating a new track
cleanup();
m_collection->registry()->emptyCache();
// changed order...
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( -1, "./newTrack.mp3", 2, "amarok-sqltrackuid://newuid" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
sqlTrack1->setBpm( 100 ); // have to commit the new track
QVERIFY( track1 );
QCOMPARE( track1->playableUrl().path(), QString("/newTrack.mp3" ));
QCOMPARE( track1->uidUrl(), QString("amarok-sqltrackuid://newuid" ));
}
m_collection->registry()->emptyCache();
// changed order...
{
Meta::TrackPtr track1 = m_collection->registry()->getTrackFromUid("amarok-sqltrackuid://newuid");
QVERIFY( track1 );
QCOMPARE( track1->playableUrl().path(), QString("/newTrack.mp3" ));
QCOMPARE( track1->uidUrl(), QString("amarok-sqltrackuid://newuid" ));
QCOMPARE( track1->bpm(), 100.0 );
}
}
void
TestSqlTrack::testSetAllValuesSingleNotExisting()
{
{
// get a new track
Meta::TrackPtr track1 = m_collection->registry()->getTrack( -1, "./IamANewTrack.mp3", 1, "1e34fb213489" );
- QSignalSpy spy( m_collection, SIGNAL(updated()));
+ QSignalSpy spy( m_collection, &Collections::SqlCollection::updated);
MetaNotificationSpy metaSpy;
metaSpy.subscribeTo( track1 );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
setAllValues( sqlTrack1 );
getAllValues( sqlTrack1 );
// new track should have an up-to-date create time (not more than 3 seconds old)
QVERIFY( track1->createDate().secsTo(QDateTime::currentDateTime()) < 3 );
QVERIFY( metaSpy.notificationsFromTracks().count() > 1 ); // we should be notified about the changes
}
// and also after empty cache
m_collection->registry()->emptyCache();
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/new_url" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
QVERIFY( track1 );
getAllValues( sqlTrack1 );
}
}
/** Set all track values but before that create them in the registry. */
void
TestSqlTrack::testSetAllValuesSingleExisting()
{
{
Meta::GenrePtr genre = m_collection->registry()->getGenre( "New Genre" );
Meta::ComposerPtr composer = m_collection->registry()->getComposer( "New Composer" );
Meta::YearPtr year = m_collection->registry()->getYear( 1999 );
Meta::AlbumPtr album = m_collection->registry()->getAlbum( "New Album", "New Artist" );
m_collection->registry()->getLabel( "New Label" );
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
setAllValues( sqlTrack1 );
getAllValues( sqlTrack1 );
// check that the existing object are really updated with the new tracklist
QCOMPARE( genre->tracks().count(), 1 );
QCOMPARE( genre->tracks().first().data(), track1.data() );
QCOMPARE( composer->tracks().count(), 1 );
QCOMPARE( composer->tracks().first().data(), track1.data() );
QCOMPARE( year->tracks().count(), 1 );
QCOMPARE( year->tracks().first().data(), track1.data() );
// the logic, how renaming the track artist influences its album is still
// unfinished. For sure the track must be in an album with the defined
// name
QCOMPARE( sqlTrack1->album()->name(), QString("New Album") );
QCOMPARE( sqlTrack1->album()->tracks().count(), 1 );
QCOMPARE( sqlTrack1->album()->tracks().first().data(), track1.data() );
}
// and also after empty cache
m_collection->registry()->emptyCache();
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/new_url" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
QVERIFY( track1 );
getAllValues( sqlTrack1 );
Meta::GenrePtr genre = m_collection->registry()->getGenre( "New Genre" );
Meta::ComposerPtr composer = m_collection->registry()->getComposer( "New Composer" );
Meta::YearPtr year = m_collection->registry()->getYear( 1999 );
Meta::AlbumPtr album = m_collection->registry()->getAlbum( "New Album", "New Artist" );
// check that the existing object are really updated with the new tracklist
QCOMPARE( genre->tracks().count(), 1 );
QCOMPARE( genre->tracks().first().data(), track1.data() );
QCOMPARE( composer->tracks().count(), 1 );
QCOMPARE( composer->tracks().first().data(), track1.data() );
QCOMPARE( year->tracks().count(), 1 );
QCOMPARE( year->tracks().first().data(), track1.data() );
// the logic, how renaming the track artist influences its album is still
// unfinished. For sure the track must be in an album with the defined
// name
QCOMPARE( sqlTrack1->album()->name(), QString("New Album") );
QCOMPARE( sqlTrack1->album()->tracks().count(), 1 );
QCOMPARE( sqlTrack1->album()->tracks().first().data(), track1.data() );
}
}
void
TestSqlTrack::testSetAllValuesBatch()
{
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
- QSignalSpy spy( m_collection, SIGNAL(updated()));
+ QSignalSpy spy( m_collection, &Collections::SqlCollection::updated);
MetaNotificationSpy metaSpy;
metaSpy.subscribeTo( track1 );
sqlTrack1->beginUpdate();
setAllValues( sqlTrack1 );
QCOMPARE( metaSpy.notificationsFromTracks().count(), 1 ); // add label does one notify
sqlTrack1->endUpdate();
QCOMPARE( metaSpy.notificationsFromTracks().count(), 2 ); // only one notificate for all the changes
getAllValues( sqlTrack1 );
}
// and also after empty cache
m_collection->registry()->emptyCache();
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/new_url" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
QVERIFY( track1 );
getAllValues( sqlTrack1 );
}
}
void
TestSqlTrack::testUnsetValues()
{
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
setAllValues( sqlTrack1 );
// now unset the values again
sqlTrack1->setAlbum( "" );
sqlTrack1->setArtist( "" );
sqlTrack1->setComposer( "" );
sqlTrack1->setYear( 0 ); // it is not clear what an empty year exactly is
sqlTrack1->setGenre( "" );
// note: Amarok is still not clear if an empty artist means track->artist() == 0
QVERIFY( !track1->album() || track1->album()->name().isEmpty() );
QVERIFY( !track1->artist() || track1->artist()->name().isEmpty() );
QVERIFY( !track1->composer() || track1->composer()->name().isEmpty() );
QVERIFY( !track1->year() || track1->year()->year() == 0 );
QVERIFY( !track1->genre() || track1->genre()->name().isEmpty() );
}
// and also after empty cache
m_collection->registry()->emptyCache();
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/new_url" );
QVERIFY( track1 );
QVERIFY( !track1->album() || track1->album()->name().isEmpty() );
QVERIFY( !track1->artist() || track1->artist()->name().isEmpty() );
QVERIFY( !track1->composer() || track1->composer()->name().isEmpty() );
QVERIFY( !track1->year() || track1->year()->year() == 0 );
QVERIFY( !track1->genre() || track1->genre()->name().isEmpty() );
}
}
void
TestSqlTrack::testFinishedPlaying()
{
Meta::TrackPtr track1 = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
sqlTrack1->setLength( 5000 );
QCOMPARE( sqlTrack1->score(), 0.0 );
QCOMPARE( sqlTrack1->playCount(), 0 );
QVERIFY( !sqlTrack1->firstPlayed().isValid() );
QVERIFY( !sqlTrack1->lastPlayed().isValid() );
// now play the track not really
sqlTrack1->finishedPlaying( 0.1 );
// can't do a statement about the score here
QCOMPARE( sqlTrack1->playCount(), 0 );
QVERIFY( !sqlTrack1->firstPlayed().isValid() );
QVERIFY( !sqlTrack1->lastPlayed().isValid() );
// and now really play it
sqlTrack1->finishedPlaying( 1.0 );
QVERIFY( sqlTrack1->score() > 0.0 );
QCOMPARE( sqlTrack1->playCount(), 1 );
QVERIFY( sqlTrack1->firstPlayed().secsTo( QDateTime::currentDateTime() ) < 2 );
QVERIFY( sqlTrack1->lastPlayed().secsTo( QDateTime::currentDateTime() ) < 2 );
}
void
TestSqlTrack::testAlbumRemaingsNonCompilationAfterChangingAlbumName()
{
m_storage->query( "INSERT INTO tracks(id,url,title,artist,album,genre,year,composer) "
"VALUES (3,3,'track1',1,1,1,1,1 );" );
m_storage->query( "INSERT INTO tracks(id,url,title,artist,album,genre,year,composer) "
"VALUES (4,4,'track2',1,1,1,1,1 );" );
Meta::TrackPtr track1 = m_collection->registry()->getTrack( 3 );
Meta::TrackPtr track2 = m_collection->registry()->getTrack( 4 );
QCOMPARE( track1->album()->name(), QString( "album1" ) );
QVERIFY( track1->album()->hasAlbumArtist() );
QCOMPARE( track1->album().data(), track2->album().data() );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
sqlTrack1->setAlbum( "album2" );
Meta::SqlTrack *sqlTrack2 = static_cast<Meta::SqlTrack*>( track2.data() );
sqlTrack2->beginUpdate();
sqlTrack2->setAlbum( "album2" );
sqlTrack2->endUpdate();
QCOMPARE( track1->album()->name(), QString( "album2" ) );
QVERIFY( track1->album()->hasAlbumArtist() );
QVERIFY( track1->album() == track2->album() );
}
void
TestSqlTrack::testAlbumRemainsCompilationAfterChangingAlbumName()
{
m_storage->query( "INSERT INTO tracks(id,url,title,artist,album,genre,year,composer) "
"VALUES (3,3,'track1',1,4,1,1,1 );" );
m_storage->query( "INSERT INTO tracks(id,url,title,artist,album,genre,year,composer) "
"VALUES (4,4,'track2',1,4,1,1,1 );" );
Meta::TrackPtr track1 = m_collection->registry()->getTrack( 3 );
Meta::TrackPtr track2 = m_collection->registry()->getTrack( 4 );
QVERIFY( track1 );
QVERIFY( track1->album() );
QVERIFY( track2 );
QVERIFY( track2->album() );
QCOMPARE( track1->album()->name(), QString( "album-compilation" ) );
QVERIFY( track1->album()->isCompilation() );
QVERIFY( track1->album().data() == track2->album().data() );
Meta::SqlTrack *sqlTrack1 = static_cast<Meta::SqlTrack*>( track1.data() );
Meta::SqlTrack *sqlTrack2 = static_cast<Meta::SqlTrack*>( track2.data() );
sqlTrack1->setAlbum( "album2" );
sqlTrack2->beginUpdate();
sqlTrack2->setAlbum( "album2" );
sqlTrack2->endUpdate();
QCOMPARE( track1->album()->name(), QString( "album2" ) );
QVERIFY( track1->album()->isCompilation() );
QVERIFY( track1->album() == track2->album() );
}
void
TestSqlTrack::testRemoveLabelFromTrack()
{
Meta::TrackPtr track = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::LabelPtr label = m_collection->registry()->getLabel( "A" );
track->addLabel( label );
QCOMPARE( track->labels().count(), 1 );
track->removeLabel( label );
QCOMPARE( track->labels().count(), 0 );
QStringList urlsLabelsCount = m_storage->query( "SELECT COUNT(*) FROM urls_labels;" );
QCOMPARE( urlsLabelsCount.first().toInt(), 0 );
}
void
TestSqlTrack::testRemoveLabelFromTrackWhenNotInCache()
{
m_storage->query( "INSERT INTO labels(id,label) VALUES (1,'A');" );
m_storage->query( "INSERT INTO urls_labels(url,label) VALUES (1,1);" );
Meta::TrackPtr track = m_collection->registry()->getTrack( "/IDoNotExist.mp3" );
Meta::LabelPtr label = m_collection->registry()->getLabel( "A" );
track->removeLabel( label );
QCOMPARE( track->labels().count(), 0 );
QStringList urlsLabelsCount = m_storage->query( "SELECT COUNT(*) FROM urls_labels;" );
QCOMPARE( urlsLabelsCount.first().toInt(), 0 );
}
diff --git a/tests/core-impl/collections/support/TestMemoryQueryMaker.cpp b/tests/core-impl/collections/support/TestMemoryQueryMaker.cpp
index bfbfbdef20..ba120deb8a 100644
--- a/tests/core-impl/collections/support/TestMemoryQueryMaker.cpp
+++ b/tests/core-impl/collections/support/TestMemoryQueryMaker.cpp
@@ -1,299 +1,299 @@
/****************************************************************************************
* Copyright (c) 2010 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestMemoryQueryMaker.h"
#include "mocks/MetaMock.h"
#include "mocks/MockTrack.h"
#include "FileType.h"
#include <QVariantMap>
#include <QSharedPointer>
#include <QSignalSpy>
#include <KCmdLineArgs>
#include <KGlobal>
#include <qtest_kde.h>
#include <gmock/gmock.h>
using ::testing::AnyNumber;
using ::testing::Return;
QTEST_KDEMAIN_CORE( TestMemoryQueryMaker )
TestMemoryQueryMaker::TestMemoryQueryMaker()
{
KCmdLineArgs::init( KGlobal::activeComponent().aboutData() );
::testing::InitGoogleMock( &KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv() );
qRegisterMetaType<Meta::TrackList>();
qRegisterMetaType<Meta::AlbumList>();
qRegisterMetaType<Meta::ArtistList>();
}
void
TestMemoryQueryMaker::initTestCase()
{
// prepare a memory collection with some test data
m_mc = QSharedPointer<Collections::MemoryCollection>( new Collections::MemoryCollection() );
MetaMock *track;
QVariantMap map;
map.insert( Meta::Field::UNIQUEID, "1" );
map.insert( Meta::Field::TITLE, "Skater Boy" );
map.insert( Meta::Field::RATING, 3 );
// map.insert( Meta::Field::TYPE, int(Amarok::Mp3) );
map.insert( Meta::Field::TRACKNUMBER, 3 );
track = new MetaMock( map );
track->m_artist = new MockArtist("Avril Lavigne");
track->m_album = new MockAlbum("Let Go");
m_mc->addTrack( Meta::TrackPtr( track ) );
map.insert( Meta::Field::UNIQUEID, "2" );
map.insert( Meta::Field::TITLE, "Substitute" );
map.insert( Meta::Field::RATING, 4 );
// map.insert( Meta::Field::TYPE, int(Amarok::Ogg) );
map.insert( Meta::Field::TRACKNUMBER, 1 );
track = new MetaMock( map );
track->m_artist = new MockArtist("Clout" );
track->m_album = new MockAlbum("Substitute" );
m_mc->addTrack( Meta::TrackPtr( track ) );
map.insert( Meta::Field::UNIQUEID, "3" );
map.insert( Meta::Field::TITLE, "I Say A Little Prayer" );
map.insert( Meta::Field::RATING, 2 );
// map.insert( Meta::Field::TYPE, int(Amarok::Wma) );
map.insert( Meta::Field::TRACKNUMBER, 1 );
map.insert( Meta::Field::DISCNUMBER, 2 );
track = new MetaMock( map );
track->m_artist = new MockArtist("The Bosshoss" );
track->m_album = new MockAlbum("Rodeo Radio" );
m_mc->addTrack( Meta::TrackPtr( track ) );
}
void TestMemoryQueryMaker::cleanupTestCase()
{
}
void
TestMemoryQueryMaker::testDeleteQueryMakerWhileQueryIsRunning()
{
QSharedPointer<Collections::MemoryCollection> mc( new Collections::MemoryCollection() );
mc->addTrack( Meta::TrackPtr( new MetaMock( QVariantMap() )));
mc->addTrack( Meta::TrackPtr( new MetaMock( QVariantMap() )));
Meta::MockTrack *mock = new Meta::MockTrack();
EXPECT_CALL( *mock, uidUrl() ).Times( AnyNumber() ).WillRepeatedly( Return( "track3" ) );
Meta::TrackPtr trackPtr( mock );
mc->addTrack( trackPtr );
Collections::MemoryQueryMaker *qm = new Collections::MemoryQueryMaker( mc.toWeakRef(), "test" );
qm->setQueryType( Collections::QueryMaker::Track );
qm->run();
delete qm;
//we cannot wait for a signal here....
//QTest::qWait( 500 );
}
void
TestMemoryQueryMaker::testDeleteCollectionWhileQueryIsRunning()
{
QSharedPointer<Collections::MemoryCollection> mc( new Collections::MemoryCollection() );
mc->addTrack( Meta::TrackPtr( new MetaMock( QVariantMap() )));
mc->addTrack( Meta::TrackPtr( new MetaMock( QVariantMap() )));
Collections::MemoryQueryMaker *qm = new Collections::MemoryQueryMaker( mc, "test" );
qm->setQueryType( Collections::QueryMaker::Track );
- QSignalSpy spy( qm, SIGNAL(queryDone()));
+ QSignalSpy spy( qm, &Collections::QueryMaker::queryDone);
qm->run();
mc.clear();
QTest::qWait( 500 );
QCOMPARE( spy.count(), 1 );
delete qm;
}
class TestStringMemoryFilter : public StringMemoryFilter
{
public:
TestStringMemoryFilter() : StringMemoryFilter() {}
protected:
QString value( Meta::TrackPtr track ) const { Q_UNUSED(track); return "abcdef"; }
};
void
TestMemoryQueryMaker::testStringMemoryFilterSpeedFullMatch()
{
//Test 1: match complete string
TestStringMemoryFilter filter1;
filter1.setFilter( QString( "abcdef" ), true, true );
QBENCHMARK {
filter1.filterMatches( Meta::TrackPtr() );
}
}
void
TestMemoryQueryMaker::testStringMemoryFilterSpeedMatchBegin()
{
//Test 2: match beginning of string
TestStringMemoryFilter filter2;
filter2.setFilter( QString( "abcd" ), true, false );
QBENCHMARK {
filter2.filterMatches( Meta::TrackPtr() );
}
}
void
TestMemoryQueryMaker::testStringMemoryFilterSpeedMatchEnd()
{
//Test 3: match end of string
TestStringMemoryFilter filter3;
filter3.setFilter( QString( "cdef" ), false, true );
QBENCHMARK {
filter3.filterMatches( Meta::TrackPtr() );
}
}
void
TestMemoryQueryMaker::testStringMemoryFilterSpeedMatchAnywhere()
{
//Test 4: match anywhere in string
TestStringMemoryFilter filter4;
filter4.setFilter( QString( "bcde" ), false, false );
QBENCHMARK {
filter4.filterMatches( Meta::TrackPtr() );
}
}
Meta::TrackList
TestMemoryQueryMaker::executeQueryMaker( Collections::QueryMaker *qm )
{
- QSignalSpy doneSpy1( qm, SIGNAL(queryDone()));
- QSignalSpy resultSpy1( qm, SIGNAL(newResultReady(Meta::TrackList)));
+ QSignalSpy doneSpy1( qm, &Collections::QueryMaker::queryDone );
+ QSignalSpy resultSpy1( qm, &Collections::QueryMaker::newResultReady );
qm->setQueryType( Collections::QueryMaker::Track );
qm->run();
- QTest::kWaitForSignal( qm, SIGNAL(queryDone()), 1000 );
+ doneSpy1.wait( 1000 );
if( resultSpy1.count() != 1 ) return Meta::TrackList();
if( doneSpy1.count() != 1 ) return Meta::TrackList();
QList<QVariant> args1 = resultSpy1.takeFirst();
if( !args1.value(0).canConvert<Meta::TrackList>() ) return Meta::TrackList();
delete qm;
return args1.value(0).value<Meta::TrackList>();
}
void
TestMemoryQueryMaker::testFilterTitle()
{
Meta::TrackList tracks;
// -- just get all the tracks
Collections::MemoryQueryMaker *qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 3 );
// -- filter for title
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->addFilter( Meta::valTitle, "Skater", true, false );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 1 );
QCOMPARE( tracks.first()->name(), QString("Skater Boy" ) );
// -- filter for album
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->addFilter( Meta::valAlbum, "S", false, false );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 1 );
QCOMPARE( tracks.first()->name(), QString("Substitute" ) );
// -- filter for artist
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->addFilter( Meta::valArtist, "Lavigne", false, true );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 1 );
QCOMPARE( tracks.first()->name(), QString("Skater Boy" ) );
}
void
TestMemoryQueryMaker::testFilterRating()
{
Meta::TrackList tracks;
Collections::MemoryQueryMaker *qm = 0;
// -- filter for Rating
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->addNumberFilter( Meta::valRating, 3, Collections::QueryMaker::Equals );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 1 );
QCOMPARE( tracks.first()->name(), QString("Skater Boy" ) );
// -- filter for Rating
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->addNumberFilter( Meta::valRating, 4, Collections::QueryMaker::LessThan );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 2 );
}
void
TestMemoryQueryMaker::testFilterAnd()
{
Meta::TrackList tracks;
Collections::MemoryQueryMaker *qm = 0;
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->beginAnd();
qm->addNumberFilter( Meta::valTrackNr, 1, Collections::QueryMaker::Equals );
qm->addFilter( Meta::valAlbum, "o", false, false );
qm->endAndOr();
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 1 );
QCOMPARE( tracks.first()->album()->name(), QString("Rodeo Radio" ) );
}
void
TestMemoryQueryMaker::testFilterFormat()
{
Meta::TrackList tracks;
Collections::MemoryQueryMaker *qm = 0;
// -- filter for title
qm = new Collections::MemoryQueryMaker( m_mc.toWeakRef(), "test" );
qm->addNumberFilter( Meta::valFormat,
int(Amarok::Mp3),
Collections::QueryMaker::Equals );
tracks = executeQueryMaker( qm );
QCOMPARE( tracks.count(), 0 );
}
diff --git a/tests/core-impl/meta/multi/TestMetaMultiTrack.cpp b/tests/core-impl/meta/multi/TestMetaMultiTrack.cpp
index b86bfd148c..773ebe60b5 100644
--- a/tests/core-impl/meta/multi/TestMetaMultiTrack.cpp
+++ b/tests/core-impl/meta/multi/TestMetaMultiTrack.cpp
@@ -1,175 +1,175 @@
/***************************************************************************
* Copyright (c) 2009 Sven Krohlas <sven@asbest-online.de> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "TestMetaMultiTrack.h"
#include "core/support/Components.h"
#include "EngineController.h"
#include "config-amarok-test.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/meta/multi/MultiTrack.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include <QTest>
#include <QDir>
#include <QFileInfo>
#include <qtest_kde.h>
#include <ThreadWeaver/Queue>
QTEST_KDEMAIN( TestMetaMultiTrack, GUI )
TestMetaMultiTrack::TestMetaMultiTrack()
: m_testMultiTrack( 0 )
{
}
void
TestMetaMultiTrack::tracksLoaded( Playlists::PlaylistPtr playlist )
{
emit tracksLoadedSignal( playlist );
}
void TestMetaMultiTrack::initTestCase()
{
qRegisterMetaType<Meta::TrackPtr>( "Meta::TrackPtr" );
//apparently the engine controller is needed somewhere, or we will get a crash...
EngineController *controller = new EngineController();
Amarok::Components::setEngineController( controller );
/* Collection manager needs to be instantiated in the main thread, but
* MetaProxy::Tracks used by playlist may trigger its creation in a different thread.
* Pre-create it explicitly */
CollectionManager::instance();
const QString path = QString( AMAROK_TEST_DIR ) + "/data/playlists/test.pls";
const QFileInfo file( QDir::toNativeSeparators( path ) );
QVERIFY( file.exists() );
const QString filePath = file.absoluteFilePath();
m_playlist = Playlists::loadPlaylistFile( QUrl::fromLocalFile(filePath) ).data();
QVERIFY( m_playlist ); // no playlist -> no test. that's life ;)
subscribeTo( m_playlist );
m_playlist->triggerTrackLoad();
if( m_playlist->trackCount() < 0 )
QVERIFY( QTest::kWaitForSignal( this, SIGNAL(tracksLoadedSignal(Playlists::PlaylistPtr)), 5000 ) );
QCOMPARE( m_playlist->name(), QString("test.pls") );
QCOMPARE( m_playlist->trackCount(), 4 );
// now wait for all MetaProxy::Tracks to actually load their real tracks:
NotifyObserversWaiter wainter( m_playlist->tracks().toSet() );
QVERIFY( QTest::kWaitForSignal( &wainter, SIGNAL(done()), 5000 ) );
}
void
TestMetaMultiTrack::cleanupTestCase()
{
// Wait for other jobs, like MetaProxys fetching meta data, to finish
ThreadWeaver::Queue::instance()->finish();
}
void TestMetaMultiTrack::init()
{
m_testMultiTrack = new Meta::MultiTrack( m_playlist );
}
void TestMetaMultiTrack::cleanup()
{
delete m_testMultiTrack;
}
void TestMetaMultiTrack::testSources()
{
QStringList sources = m_testMultiTrack->sources();
QCOMPARE( sources.size(), 4 );
QCOMPARE( sources.at( 0 ), QString( "http://85.214.44.27:8000" ) );
QCOMPARE( sources.at( 1 ), QString( "http://217.20.121.40:8000" ) );
QCOMPARE( sources.at( 2 ), QString( "http://85.214.44.27:8100" ) );
QCOMPARE( sources.at( 3 ), QString( "http://85.214.44.27:8200" ) );
}
void TestMetaMultiTrack::testSetSourceCurrentNextUrl()
{
QCOMPARE( m_testMultiTrack->current(), 0 );
QCOMPARE( m_testMultiTrack->playableUrl(), QUrl("http://85.214.44.27:8000") );
QCOMPARE( m_testMultiTrack->nextUrl(), QUrl("http://217.20.121.40:8000") );
m_testMultiTrack->setSource( 1 );
QCOMPARE( m_testMultiTrack->current(), 1 );
QCOMPARE( m_testMultiTrack->playableUrl(), QUrl("http://217.20.121.40:8000") );
QCOMPARE( m_testMultiTrack->nextUrl(), QUrl("http://85.214.44.27:8100") );
m_testMultiTrack->setSource( 2 );
QCOMPARE( m_testMultiTrack->current(), 2 );
QCOMPARE( m_testMultiTrack->playableUrl(), QUrl("http://85.214.44.27:8100") );
QCOMPARE( m_testMultiTrack->nextUrl(), QUrl("http://85.214.44.27:8200") );
m_testMultiTrack->setSource( 3 );
QCOMPARE( m_testMultiTrack->current(), 3 );
QCOMPARE( m_testMultiTrack->playableUrl(), QUrl("http://85.214.44.27:8200") );
QCOMPARE( m_testMultiTrack->nextUrl(), QUrl() );
m_testMultiTrack->setSource( 4 );
QCOMPARE( m_testMultiTrack->current(), 3 );
m_testMultiTrack->setSource( -1 );
QCOMPARE( m_testMultiTrack->current(), 3 );
}
void TestMetaMultiTrack::testHasCapabilityInterface()
{
QVERIFY( m_testMultiTrack->hasCapabilityInterface( Capabilities::Capability::MultiSource ) );
}
NotifyObserversWaiter::NotifyObserversWaiter( const QSet<Meta::TrackPtr> &tracks, QObject *parent )
: QObject( parent )
, m_tracks( tracks )
{
// we need to filter already resovled tracks in the next event loop iteration because
// the user wouldn't be able to get the done() signal yet.
- QTimer::singleShot( 0, this, SLOT(slotFilterResovled()) );
+ QTimer::singleShot( 0, this, &NotifyObserversWaiter::slotFilterResovled );
}
void
NotifyObserversWaiter::slotFilterResovled()
{
QMutexLocker locker( &m_mutex );
QMutableSetIterator<Meta::TrackPtr> it( m_tracks );
while( it.hasNext() )
{
const Meta::TrackPtr &track = it.next();
const MetaProxy::Track *proxyTrack = dynamic_cast<const MetaProxy::Track *>( track.data() );
Q_ASSERT( proxyTrack );
if( proxyTrack->isResolved() )
it.remove();
else
subscribeTo( track );
}
if( m_tracks.isEmpty() )
emit done();
}
void
NotifyObserversWaiter::metadataChanged( Meta::TrackPtr track )
{
QMutexLocker locker( &m_mutex );
m_tracks.remove( track );
if( m_tracks.isEmpty() )
emit done();
}
diff --git a/tests/core-impl/support/TestTrackLoader.cpp b/tests/core-impl/support/TestTrackLoader.cpp
index f404cf92cf..b7b84799b7 100644
--- a/tests/core-impl/support/TestTrackLoader.cpp
+++ b/tests/core-impl/support/TestTrackLoader.cpp
@@ -1,170 +1,170 @@
/***************************************************************************
* Copyright (c) 2009 Sven Krohlas <sven@asbest-online.de> *
* Copyright (c) 2013 Matěj Laitl <matej@laitl.cz> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "TestTrackLoader.h"
#include "config-amarok-test.h"
#include "core/meta/Meta.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/meta/proxy/MetaProxy.h"
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
#include "core-impl/support/TrackLoader.h"
#include <ThreadWeaver/Queue>
#include <qtest_kde.h>
QTEST_KDEMAIN_CORE( TestTrackLoader )
void
TestTrackLoader::initTestCase()
{
qRegisterMetaType<Meta::TrackPtr>();
qRegisterMetaType<Meta::TrackList>();
CollectionManager::instance(); // create in the main thread
KGlobal::locale(); // ditto
}
void
TestTrackLoader::cleanupTestCase()
{
// Wait for other jobs, like MetaProxys fetching meta data, to finish
ThreadWeaver::Queue::instance()->finish();
}
void
TestTrackLoader::testFullMetadataInit()
{
typedef QPair<QString, int> StringIntPair;
QList<StringIntPair> pathsCounts;
pathsCounts << qMakePair( dataPath( "data/audio/album" ), 3 )
<< qMakePair( dataPath( "data/audio/album2" ), 2 )
<< qMakePair( dataPath( "data/playlists/test.asx" ), 1 )
<< qMakePair( dataPath( "data/playlists/test.m3u" ), 10 )
<< qMakePair( dataPath( "data/playlists/test.pls" ), 4 )
<< qMakePair( dataPath( "data/playlists/test.xspf" ), 23 );
// it is more probable to get unresolved MetaProxy::Track for small runs:
foreach( const StringIntPair &pair, pathsCounts )
{
TrackLoader *loader = new TrackLoader( TrackLoader::FullMetadataRequired );
- QSignalSpy spy( loader, SIGNAL(finished(Meta::TrackList)) );
+ QSignalSpy spy( loader, &TrackLoader::finished );
loader->init( QUrl( pair.first ) );
if( spy.isEmpty() )
QVERIFY2( QTest::kWaitForSignal( loader, SIGNAL(finished(Meta::TrackList)), 5000 ),
"loader did not finish within timeout" );
Meta::TrackList found = spy.first().first().value<Meta::TrackList>();
QCOMPARE( found.count(), pair.second );
foreach( const Meta::TrackPtr &track, found )
{
MetaProxy::TrackPtr proxyTrack = MetaProxy::TrackPtr::dynamicCast( track );
if( !proxyTrack )
{
qDebug() << track->prettyUrl() << "is not a MetaProxy::Track. Strange and we cannot test it";
continue;
}
QVERIFY2( proxyTrack->isResolved(), proxyTrack->prettyUrl().toLocal8Bit().data() );
}
}
}
void
TestTrackLoader::testInit()
{
TrackLoader *loader1 = new TrackLoader();
- QSignalSpy spy1( loader1, SIGNAL(finished(Meta::TrackList)) );
+ QSignalSpy spy1( loader1, &TrackLoader::finished );
loader1->init( QUrl( dataPath( "data/audio" ) ) ); // test the convenience overload
if( spy1.isEmpty() )
QVERIFY2( QTest::kWaitForSignal( loader1, SIGNAL(finished(Meta::TrackList)), 5000 ),
"loader1 did not finish within timeout" );
Meta::TrackList found = spy1.first().first().value<Meta::TrackList>();
QCOMPARE( found.count(), 15 );
QVERIFY2( found.at( 0 )->uidUrl().endsWith( "audio/album/Track01.ogg" ), found.at( 0 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 1 )->uidUrl().endsWith( "audio/album/Track02.ogg" ), found.at( 1 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 2 )->uidUrl().endsWith( "audio/album/Track03.ogg" ), found.at( 2 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 3 )->uidUrl().endsWith( "audio/album2/Track01.ogg" ), found.at( 3 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 4 )->uidUrl().endsWith( "audio/album2/Track02.ogg" ), found.at( 4 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 5 )->uidUrl().endsWith( "audio/Platz%2001.mp3" ), found.at( 5 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 10 )->uidUrl().endsWith( "audio/Platz%2006.mp3" ), found.at( 10 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 14 )->uidUrl().endsWith( "audio/Platz%2010.mp3" ), found.at( 14 )->uidUrl().toLocal8Bit().data() );
TrackLoader *loader2 = new TrackLoader();
- QSignalSpy spy2( loader2, SIGNAL(finished(Meta::TrackList)) );
+ QSignalSpy spy2( loader2, &TrackLoader::finished );
loader2->init( QList<QUrl>() << QUrl( dataPath( "data/audio/album2" ) ) );
if( spy2.isEmpty() )
QVERIFY2( QTest::kWaitForSignal( loader2, SIGNAL(finished(Meta::TrackList)), 5000 ),
"loader2 did not finish within timeout" );
found = spy2.first().first().value<Meta::TrackList>();
QCOMPARE( found.count(), 2 );
QVERIFY2( found.at( 0 )->uidUrl().endsWith( "audio/album2/Track01.ogg" ), found.at( 0 )->uidUrl().toLocal8Bit().data() );
QVERIFY2( found.at( 1 )->uidUrl().endsWith( "audio/album2/Track02.ogg" ), found.at( 1 )->uidUrl().toLocal8Bit().data() );
}
void
TestTrackLoader::testInitWithPlaylists()
{
TrackLoader *loader = new TrackLoader();
- QSignalSpy spy( loader, SIGNAL(finished(Meta::TrackList)) );
+ QSignalSpy spy( loader, &TrackLoader::finished );
QList<QUrl> urls;
urls << QUrl( dataPath( "data/playlists/test.asx" ) )
<< QUrl( dataPath( "data/audio/album" ) )
<< QUrl( dataPath( "data/playlists/test.xspf" ) );
loader->init( urls );
if( spy.isEmpty() )
QVERIFY2( QTest::kWaitForSignal( loader, SIGNAL(finished(Meta::TrackList)), 5000 ),
"loader did not finish within timeout" );
Meta::TrackList found = spy.first().first().value<Meta::TrackList>();
QCOMPARE( found.count(), 1 + 3 + 23 );
QCOMPARE( found.at( 0 )->uidUrl(), QString( "http://85.214.44.27:8000" ) ); // test.asx playlist
QVERIFY( found.at( 1 )->uidUrl().endsWith( "/audio/album/Track01.ogg" ) ); // "audio/album" folder
QVERIFY( found.at( 2 )->uidUrl().endsWith( "/audio/album/Track02.ogg" ) );
QVERIFY( found.at( 3 )->uidUrl().endsWith( "/audio/album/Track03.ogg" ) );
QCOMPARE( found.at( 4 )->uidUrl(), QString( "http://he3.magnatune.com/all/01-Sunset-Ammonite.ogg" ) ); // start of test.xspf playlist
QCOMPARE( found.at( 5 )->uidUrl(), QString( "http://he3.magnatune.com/all/02-Heaven-Ammonite.ogg" ) );
}
void
TestTrackLoader::testDirectlyPassingPlaylists()
{
using namespace Playlists;
TrackLoader *loader = new TrackLoader();
- QSignalSpy spy( loader, SIGNAL(finished(Meta::TrackList)) );
+ QSignalSpy spy( loader, &TrackLoader::finished );
PlaylistList playlists;
playlists << PlaylistPtr::staticCast( loadPlaylistFile( QUrl( dataPath( "data/playlists/test.asx" ) ) ) )
<< PlaylistPtr::staticCast( loadPlaylistFile( QUrl( dataPath( "data/playlists/test.xspf" ) ) ) );
loader->init( playlists );
if( spy.isEmpty() )
QVERIFY2( QTest::kWaitForSignal( loader, SIGNAL(finished(Meta::TrackList)), 5000 ),
"loader did not finish within timeout" );
Meta::TrackList found = spy.first().first().value<Meta::TrackList>();
QCOMPARE( found.count(), 1 + 23 );
QCOMPARE( found.at( 0 )->uidUrl(), QString( "http://85.214.44.27:8000" ) ); // test.asx playlist
QCOMPARE( found.at( 1 )->uidUrl(), QString( "http://he3.magnatune.com/all/01-Sunset-Ammonite.ogg" ) ); // start of test.xspf playlist
QCOMPARE( found.at( 2 )->uidUrl(), QString( "http://he3.magnatune.com/all/02-Heaven-Ammonite.ogg" ) );
}
QString
TestTrackLoader::dataPath( const QString &relPath )
{
return QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + '/' + relPath );
}
diff --git a/tests/core/collections/TestQueryMaker.cpp b/tests/core/collections/TestQueryMaker.cpp
index 37b24fc8c1..572ad14d69 100644
--- a/tests/core/collections/TestQueryMaker.cpp
+++ b/tests/core/collections/TestQueryMaker.cpp
@@ -1,79 +1,79 @@
/****************************************************************************************
* Copyright (c) 2012 Jasneet Singh Bhatti <jazneetbhatti@gmail.com> *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestQueryMaker.h"
#include "mocks/MockQueryMaker.h"
#include <qtest_kde.h>
#include <QSignalSpy>
using namespace Collections;
QTEST_KDEMAIN_CORE( TestQueryMaker )
void
TestQueryMaker::initTestCase()
{
m_mockQueryMaker = new MockQueryMaker();
QVERIFY( m_mockQueryMaker );
}
void
TestQueryMaker::cleanupTestCase()
{
delete m_mockQueryMaker;
}
void
TestQueryMaker::testSetAutoDelete_data()
{
QTest::addColumn<bool>( "autoDelete" );
QTest::newRow( "true value" ) << true;
QTest::newRow( "false value" ) << false;
}
void
TestQueryMaker::testSetAutoDelete()
{
QFETCH( bool, autoDelete );
- QSignalSpy spyQueryDone( m_mockQueryMaker, SIGNAL(queryDone()) );
- QSignalSpy spyDestroyed( m_mockQueryMaker, SIGNAL(destroyed()) );
+ QSignalSpy spyQueryDone( m_mockQueryMaker, &MockQueryMaker::queryDone );
+ QSignalSpy spyDestroyed( m_mockQueryMaker, &MockQueryMaker::destroyed );
m_mockQueryMaker->setAutoDelete( autoDelete );
QVERIFY( m_mockQueryMaker );
m_mockQueryMaker->emitQueryDone();
// Ensure that queryDone() was indeed emitted
QCOMPARE( spyQueryDone.count(), 1 );
if( autoDelete )
{
// Signal queryDone() is connected to slot deleteLater()
// and the destroyed() signal is emitted
QCOMPARE( spyDestroyed.count(), 1 );
}
else
{
// Signal queryDone() is disconnected from slot deleteLater()
// and no destroyed() signal is emitted
QCOMPARE( spyDestroyed.count(), 0 );
}
}
diff --git a/tests/core/collections/support/TestTrackForUrlWorker.cpp b/tests/core/collections/support/TestTrackForUrlWorker.cpp
index 64a17bfda0..f8a59e61b9 100644
--- a/tests/core/collections/support/TestTrackForUrlWorker.cpp
+++ b/tests/core/collections/support/TestTrackForUrlWorker.cpp
@@ -1,121 +1,118 @@
/****************************************************************************************
* Copyright (c) 2012 Jasneet Singh Bhatti <jazneetbhatti@gmail.com> *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestTrackForUrlWorker.h"
#include "config-amarok-test.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "mocks/MockTrackForUrlWorker.h"
#include <QUrl>
#include <ThreadWeaver/Job>
#include <ThreadWeaver/Queue>
#include <qtest_kde.h>
#include <KSharedPtr>
#include <QMetaType>
#include <QSignalSpy>
QTEST_KDEMAIN_CORE( TestTrackForUrlWorker )
void
TestTrackForUrlWorker::initTestCase()
{
// To make queued signals/slots work with custom payload
qRegisterMetaType<Meta::TrackPtr>( "Meta::TrackPtr" );
qRegisterMetaType<ThreadWeaver::Job*>( "ThreadWeaver::Job*" );
}
QString
TestTrackForUrlWorker::dataPath( const QString &relPath )
{
return QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + '/' + relPath );
}
void
TestTrackForUrlWorker::testCompleteJobQUrl_data()
{
testCompleteJobInternal_data();
}
void
TestTrackForUrlWorker::testCompleteJobQUrl()
{
QUrl url;
MockTrackForUrlWorker *trackForUrlWorker = new MockTrackForUrlWorker( url );
QVERIFY( trackForUrlWorker );
testCompleteJobInternal( trackForUrlWorker );
}
void TestTrackForUrlWorker::testCompleteJobQString_data()
{
testCompleteJobInternal_data();
}
void
TestTrackForUrlWorker::testCompleteJobQString()
{
QString url;
MockTrackForUrlWorker *trackForUrlWorker = new MockTrackForUrlWorker( url );
QVERIFY( trackForUrlWorker );
testCompleteJobInternal( trackForUrlWorker );
}
void
TestTrackForUrlWorker::testCompleteJobInternal_data()
{
QTest::addColumn<Meta::TrackPtr>( "track" );
QTest::newRow( "track 1" ) << CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(dataPath( "data/audio/album/Track01.ogg" )) );
QTest::newRow( "track 2" ) << CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(dataPath( "data/audio/album/Track02.ogg" )) );
QTest::newRow( "track 3" ) << CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(dataPath( "data/audio/album/Track03.ogg" )) );
}
void
TestTrackForUrlWorker::testCompleteJobInternal( MockTrackForUrlWorker *trackForUrlWorker )
{
// Connect finishedLookup with setEmittedTrack() that will store the emitted track
- connect( trackForUrlWorker, SIGNAL(finishedLookup(Meta::TrackPtr)),
- this, SLOT(setEmittedTrack(Meta::TrackPtr)) );
+ connect( trackForUrlWorker, &MockTrackForUrlWorker::finishedLookup,
+ this, &TestTrackForUrlWorker::setEmittedTrack );
- QSignalSpy spyFinishedLookup( trackForUrlWorker, SIGNAL(finishedLookup(Meta::TrackPtr)) );
+ QSignalSpy spyFinishedLookup( trackForUrlWorker, &MockTrackForUrlWorker::finishedLookup );
+ QSignalSpy spyDone( trackForUrlWorker, &MockTrackForUrlWorker::done );
// Enqueue the job for execution and verify that it emits done when finished, which triggers completeJob
- ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(trackForUrlWorker) );
- bool receivedDone = QTest::kWaitForSignal( trackForUrlWorker, SIGNAL(done(ThreadWeaver::JobPointer)), 1000 );
+ ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>( trackForUrlWorker ) );
+ bool receivedDone = spyDone.wait( 1000 );
QVERIFY( receivedDone );
// Verify that finishedLookup was emitted
QCOMPARE( spyFinishedLookup.count(), 1 );
// Verify that the track emitted with finishedLookup is indeed the track set by run()
QFETCH( Meta::TrackPtr, track );
QCOMPARE( m_emittedTrack, track );
-
- // Check for emission of the destroyed signal after deferred delete ( deleteLater )
- bool receivedDestroyed = QTest::kWaitForSignal( trackForUrlWorker, SIGNAL(destroyed()), 1000 );
- QVERIFY( receivedDestroyed );
}
void
TestTrackForUrlWorker::setEmittedTrack( Meta::TrackPtr track )
{
m_emittedTrack = track;
}
diff --git a/tests/core/meta/TestMetaTrack.cpp b/tests/core/meta/TestMetaTrack.cpp
index 57bd9e6be5..8bff88cdd8 100644
--- a/tests/core/meta/TestMetaTrack.cpp
+++ b/tests/core/meta/TestMetaTrack.cpp
@@ -1,265 +1,265 @@
/***************************************************************************
* Copyright (c) 2009 Sven Krohlas <sven@asbest-online.de> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "TestMetaTrack.h"
#include "amarokconfig.h"
#include "config-amarok-test.h"
#include "core/meta/Meta.h"
#include "core/meta/Statistics.h"
#include "core-impl/collections/support/CollectionManager.h"
#include <QTest>
#include <qtest_kde.h>
QTEST_KDEMAIN( TestMetaTrack, GUI )
TestMetaTrack::TestMetaTrack()
: m_trackPath( dataPath( "/data/audio/Platz 01.mp3" ) )
{}
TestMetaTrack::~TestMetaTrack()
{
}
QString
TestMetaTrack::dataPath( const QString &relPath )
{
return QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + '/' + relPath );
}
void TestMetaTrack::initTestCase()
{
QString oldPath = m_trackPath;
m_trackPath = m_tempDir.path() + "TestMetaTrack-testTrack.mp3";
QVERIFY( QFile::copy( oldPath, m_trackPath ) );
m_testTrack1 = CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(m_trackPath) );
// If the pointer is 0, it makes no sense to continue. We would crash with a qFatal().
QVERIFY2( m_testTrack1, "The pointer to the test track is 0." );
// we need to enable this, otherwise testSetAndGetScore, testSetAndGetRating fails
AmarokConfig::setWriteBackStatistics( true );
}
void TestMetaTrack::testPrettyName()
{
QCOMPARE( m_testTrack1->prettyName(), QString( "Platz 01" ) );
}
void TestMetaTrack::testPlayableUrl()
{
QCOMPARE( m_testTrack1->playableUrl().path(), m_trackPath );
}
void TestMetaTrack::testPrettyUrl()
{
QCOMPARE( m_testTrack1->prettyUrl(), m_trackPath );
}
void TestMetaTrack::testUidUrl()
{
QCOMPARE( m_testTrack1->uidUrl(), QUrl::fromLocalFile(m_trackPath ).url() );
}
void TestMetaTrack::testIsPlayable()
{
QCOMPARE( m_testTrack1->isPlayable(), true );
}
void TestMetaTrack::testAlbum()
{
QCOMPARE( m_testTrack1->album().data()->name() , QString( "" ) );
}
void TestMetaTrack::testArtist()
{
QCOMPARE( m_testTrack1->artist().data()->name(), QString( "Free Music Charts" ) );
}
void TestMetaTrack::testComposer()
{
QCOMPARE( m_testTrack1->composer().data()->name(), QString( "" ) );
}
void TestMetaTrack::testGenre()
{
QCOMPARE( m_testTrack1->genre().data()->name(), QString( "Vocal" ) );
}
void TestMetaTrack::testYear()
{
QCOMPARE( m_testTrack1->year().data()->name(), QString( "2010" ) );
}
void TestMetaTrack::testComment()
{
QCOMPARE( m_testTrack1->comment(), QString( "" ) );
}
void TestMetaTrack::testSetAndGetScore()
{
Meta::StatisticsPtr statistics = m_testTrack1->statistics();
QCOMPARE( statistics->score(), 0.0 );
/* now the code actually stores the score in track and then it reads it back.
* the precision it uses is pretty low and it was failing the qFuzzyCompare<double>
* Just make it use qFuzzyCompare<float>() */
statistics->setScore( 3 );
QCOMPARE( float( statistics->score() ), float( 3.0 ) );
statistics->setScore( 12.55 );
QCOMPARE( float( statistics->score() ), float( 12.55 ) );
statistics->setScore( 100 );
QCOMPARE( float( statistics->score() ), float( 100.0 ) );
statistics->setScore( 0 );
QCOMPARE( float( statistics->score() ), float( 0.0 ) );
}
void TestMetaTrack::testSetAndGetRating()
{
Meta::StatisticsPtr statistics = m_testTrack1->statistics();
QCOMPARE( statistics->rating(), 0 );
statistics->setRating( 3 );
QCOMPARE( statistics->rating(), 3 );
statistics->setRating( 10 );
QCOMPARE( statistics->rating(), 10 );
statistics->setRating( 0 );
QCOMPARE( statistics->rating(), 0 );
}
void TestMetaTrack::testLength()
{
QCOMPARE( m_testTrack1->length(), 12000LL );
}
void TestMetaTrack::testFilesize()
{
QCOMPARE( m_testTrack1->filesize(), 389454 );
}
void TestMetaTrack::testSampleRate()
{
QCOMPARE( m_testTrack1->sampleRate(), 44100 );
}
void TestMetaTrack::testBitrate()
{
- QCOMPARE( m_testTrack1->bitrate(), 256 );
+ QCOMPARE( m_testTrack1->bitrate(), 257 );
}
void TestMetaTrack::testTrackNumber()
{
QCOMPARE( m_testTrack1->trackNumber(), 0 );
}
void TestMetaTrack::testDiscNumber()
{
QCOMPARE( m_testTrack1->discNumber(), 0 );
}
void TestMetaTrack::testLastPlayed()
{
QCOMPARE( m_testTrack1->statistics()->lastPlayed().toTime_t(), 4294967295U ); // portability?
}
void TestMetaTrack::testFirstPlayed()
{
QCOMPARE( m_testTrack1->statistics()->firstPlayed().toTime_t(), 4294967295U ); // portability?
}
void TestMetaTrack::testPlayCount()
{
QCOMPARE( m_testTrack1->statistics()->playCount(), 0 );
}
void TestMetaTrack::testReplayGain()
{
QCOMPARE( int(m_testTrack1->replayGain( Meta::ReplayGain_Track_Gain ) * 1000), -6655 );
QCOMPARE( int(m_testTrack1->replayGain( Meta::ReplayGain_Album_Gain ) * 1000), -6655 );
QCOMPARE( int(m_testTrack1->replayGain( Meta::ReplayGain_Track_Peak ) * 10000), 41263 );
QCOMPARE( int(m_testTrack1->replayGain( Meta::ReplayGain_Album_Peak ) * 10000), 41263 );
}
void TestMetaTrack::testType()
{
QCOMPARE( m_testTrack1->type(), QString( "mp3" ) );
}
void TestMetaTrack::testInCollection()
{
QVERIFY( !m_testTrack1->inCollection() );
}
void TestMetaTrack::testCollection()
{
QVERIFY( !m_testTrack1->collection() );
}
void TestMetaTrack::testSetAndGetCachedLyrics()
{
/* TODO: setCachedLyrics is not yet implemented
QCOMPARE( m_testTrack1->cachedLyrics(), QString( "" ) );
m_testTrack1->setCachedLyrics( "test" );
QCOMPARE( m_testTrack1->cachedLyrics(), QString( "test" ) );
m_testTrack1->setCachedLyrics( "aäaüoöß" );
QCOMPARE( m_testTrack1->cachedLyrics(), QString( "aäaüoöß" ) );
m_testTrack1->setCachedLyrics( "" );
QCOMPARE( m_testTrack1->cachedLyrics(), QString( "" ) );
*/
}
void TestMetaTrack::testOperatorEquals()
{
QVERIFY( m_testTrack1 == m_testTrack1 );
QVERIFY( m_testTrack1 != m_testTrack2 );
}
void TestMetaTrack::testLessThan()
{
Meta::TrackPtr albumTrack1, albumTrack2, albumTrack3;
albumTrack1 = CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(dataPath( "data/audio/album/Track01.ogg" )) );
albumTrack2 = CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(dataPath( "data/audio/album/Track02.ogg" )) );
albumTrack3 = CollectionManager::instance()->trackForUrl( QUrl::fromLocalFile(dataPath( "data/audio/album/Track03.ogg" )) );
QVERIFY( albumTrack1 );
QVERIFY( albumTrack2 );
QVERIFY( albumTrack3 );
QVERIFY( !Meta::Track::lessThan( m_testTrack1, m_testTrack1 ) );
QVERIFY( Meta::Track::lessThan( albumTrack1, albumTrack2 ) );
QVERIFY( Meta::Track::lessThan( albumTrack2, albumTrack3 ) );
QVERIFY( Meta::Track::lessThan( albumTrack1, albumTrack3 ) );
QVERIFY( !Meta::Track::lessThan( albumTrack3, albumTrack2 ) );
QVERIFY( !Meta::Track::lessThan( albumTrack3, albumTrack1 ) );
QVERIFY( !Meta::Track::lessThan( albumTrack3, albumTrack3 ) );
}
diff --git a/tests/core/playlists/TestPlaylistObserver.cpp b/tests/core/playlists/TestPlaylistObserver.cpp
index 4dab1850a1..92b283ba34 100644
--- a/tests/core/playlists/TestPlaylistObserver.cpp
+++ b/tests/core/playlists/TestPlaylistObserver.cpp
@@ -1,143 +1,149 @@
/****************************************************************************************
* Copyright (c) 2012 Tatjana Gornak <t.gornak@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestPlaylistObserver.h"
#include "EngineController.h"
#include "config-amarok-test.h"
#include "core/support/Components.h"
#include "core-impl/collections/support/CollectionManager.h"
#include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h"
#include <QDir>
#include <QEventLoop>
#include <QFile>
#include <QTemporaryFile>
#include <QTest>
#include <QTimer>
#include <ThreadWeaver/Queue>
#include <qtest_kde.h>
QTEST_KDEMAIN_CORE( TestPlaylistObserver )
TestPlaylistObserver::TestPlaylistObserver()
: m_observer( 0 )
{
}
QString
TestPlaylistObserver::dataPath( const QString &relPath )
{
return QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + "/data/playlists/" + relPath );
}
void
TestPlaylistObserver::initTestCase()
{
KGlobal::locale();
EngineController *controller = new EngineController();
Amarok::Components::setEngineController( controller );
CollectionManager::instance();
qRegisterMetaType<Meta::TrackPtr>( "Meta::TrackPtr" );
}
void
TestPlaylistObserver::cleanupTestCase()
{
// Wait for other jobs, like MetaProxys fetching meta data, to finish
ThreadWeaver::Queue::instance()->finish();
delete Amarok::Components::setEngineController( 0 );
}
void
TestPlaylistObserver::init()
{
const QString testXspf = "test.xspf";
const QUrl url = QUrl::fromLocalFile(dataPath( testXspf ));
m_testPlaylist = new Playlists::XSPFPlaylist( url );
// test that behaviour before loading is correct
QCOMPARE( m_testPlaylist->name(), QString( "test.xspf" ) );
QCOMPARE( m_testPlaylist->trackCount(), -1 );
m_observer = new Observer();
m_observer->subscribeTo( m_testPlaylist );
}
void
TestPlaylistObserver::cleanup()
{
delete m_observer;
m_observer = 0;
m_testPlaylist = 0;
}
void
TestPlaylistObserver::testMetadataChanged( )
{
QSKIP( "Functionality this test tests has not yet been implemented", SkipAll );
- QSignalSpy spy( m_observer, SIGNAL(metadataChangedSignal()) );
+ QSignalSpy spy( m_observer, &Observer::metadataChangedSignal );
m_testPlaylist->triggerTrackLoad();
- QVERIFY( QTest::kWaitForSignal( m_observer, SIGNAL(tracksLoadedSignal()), 10000 ) );
+ QSignalSpy spyTracksLoaded(m_observer, &Observer::tracksLoadedSignal);
+ QVERIFY( spyTracksLoaded.wait( 10000 ) );
QVERIFY( spy.count() > 0 );
// changed methadata means that we should get new name
QCOMPARE( m_testPlaylist->name(), QString( "my playlist" ) );
}
void
TestPlaylistObserver::testTracksLoaded()
{
m_testPlaylist->triggerTrackLoad();
- QVERIFY( QTest::kWaitForSignal( m_observer, SIGNAL(tracksLoadedSignal()), 10000 ) );
-
+ QSignalSpy spyTracksLoaded(m_observer, &Observer::tracksLoadedSignal);
+ QVERIFY( spyTracksLoaded.wait( 10000 ) );
+
QCOMPARE( m_testPlaylist->trackCount(), 23 );
}
void
TestPlaylistObserver::testTrackAdded( )
{
- QSignalSpy spy( m_observer, SIGNAL(trackAddedSignal()) );
+ QSignalSpy spy( m_observer, &Observer::trackAddedSignal );
m_testPlaylist->triggerTrackLoad();
- QVERIFY( QTest::kWaitForSignal( m_observer, SIGNAL(tracksLoadedSignal()), 10000 ) );
-
+ QSignalSpy spyTracksLoaded(m_observer, &Observer::tracksLoadedSignal);
+ QVERIFY( spyTracksLoaded.wait( 10000 ) );
+
QCOMPARE( spy.count(), 23 );
}
void
TestPlaylistObserver::testTrackRemoved()
{
m_testPlaylist->triggerTrackLoad();
- QVERIFY( QTest::kWaitForSignal( m_observer, SIGNAL(tracksLoadedSignal()), 10000 ) );
+
+ QSignalSpy spyTracksLoaded( m_observer, &Observer::tracksLoadedSignal );
+ QVERIFY( spyTracksLoaded.wait( 10000 ) );
QString newName = "test playlist written to.xspf";
m_testPlaylist->setName( newName ); // don't overwrite original playlist
- QSignalSpy spy( m_observer, SIGNAL(trackRemovedSignal()) );
+ QSignalSpy spyTrackRemoved( m_observer, &Observer::trackRemovedSignal );
+ QCOMPARE( m_testPlaylist->trackCount(), 23 );
m_testPlaylist->removeTrack( -1 ); // no effect
m_testPlaylist->removeTrack( 0 ); // has effect
m_testPlaylist->removeTrack( 22 ); // no effect, too far
m_testPlaylist->removeTrack( 21 ); // has effect
QCOMPARE( m_testPlaylist->trackCount(), 21 );
- QCOMPARE( spy.count(), 2 );
+ QCOMPARE( spyTrackRemoved.count(), 2 );
qDebug() << dataPath( newName );
QVERIFY( QFile::remove( dataPath( newName ) ) );
}
diff --git a/tests/dynamic/TestDynamicModel.cpp b/tests/dynamic/TestDynamicModel.cpp
index 2812654a94..d78e5d97b2 100644
--- a/tests/dynamic/TestDynamicModel.cpp
+++ b/tests/dynamic/TestDynamicModel.cpp
@@ -1,355 +1,355 @@
/****************************************************************************************
* Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestDynamicModel.h"
#include "dynamic/Bias.h"
#include "dynamic/BiasedPlaylist.h"
#include "dynamic/DynamicModel.h"
#include "core/support/Amarok.h"
#include "core/support/Debug.h"
#include <QByteArray>
#include <QDataStream>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <qtest_kde.h>
Q_DECLARE_METATYPE(QModelIndex);
QTemporaryDir *s_tmpDir = 0; // Memory leak here now, but if it's deleted, we have a segfault
// We return a special saveLocation.
QString Amarok::saveLocation( const QString &directory )
{
return s_tmpDir->path() + directory;
}
QTEST_KDEMAIN( TestDynamicModel, GUI )
TestDynamicModel::TestDynamicModel()
{
qRegisterMetaType<QModelIndex>();
}
void
TestDynamicModel::init()
{
s_tmpDir = new QTemporaryDir();
}
void
TestDynamicModel::cleanup()
{
}
void
TestDynamicModel::testData()
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
// load from the empty directory
model->loadPlaylists();
// now we should have the four default playlists
QModelIndex playlistIndex = model->index( 0, 0 );
QCOMPARE( model->data( playlistIndex ).toString(), QString("Random") );
QCOMPARE( model->data( playlistIndex, Qt::EditRole ).toString(), QString("Random") );
QVERIFY( model->data( playlistIndex, Dynamic::DynamicModel::PlaylistRole ).isValid() );
QVERIFY( !model->data( playlistIndex, Dynamic::DynamicModel::BiasRole ).isValid() );
QModelIndex biasIndex = model->index( 0, 0, playlistIndex );
QVERIFY( !model->data( biasIndex ).toString().isEmpty() );
QVERIFY( !model->data( biasIndex, Dynamic::DynamicModel::PlaylistRole ).isValid() );
QVERIFY( model->data( biasIndex, Dynamic::DynamicModel::BiasRole ).isValid() );
}
void
TestDynamicModel::testPlaylistIndex()
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
// load from the empty directory
model->loadPlaylists();
// now we should have the four default playlists
QCOMPARE( model->rowCount(), 4 );
QCOMPARE( model->columnCount(), 1 );
// -- random playlist with one bias
QModelIndex playlistIndex = model->index( 0, 0 );
QModelIndex biasIndex = model->index( 0, 0, playlistIndex );
QCOMPARE( model->rowCount( playlistIndex ), 1 );
QCOMPARE( model->rowCount( biasIndex ), 0 );
QCOMPARE( playlistIndex.parent(), QModelIndex() );
QCOMPARE( biasIndex.parent(), playlistIndex );
// -- albumplay playlist with bias structure
playlistIndex = model->index( 2, 0 );
biasIndex = model->index( 0, 0, playlistIndex );
QModelIndex subBiasIndex = model->index( 1, 0, biasIndex );
QCOMPARE( model->rowCount( playlistIndex ), 1 );
QCOMPARE( model->rowCount( biasIndex ), 2 );
QCOMPARE( model->rowCount( subBiasIndex ), 0 );
QCOMPARE( playlistIndex.parent(), QModelIndex() );
QCOMPARE( biasIndex.parent(), playlistIndex );
QCOMPARE( subBiasIndex.parent(), biasIndex );
// and now the non-model index functions:
model->setActivePlaylist( 2 );
Dynamic::DynamicPlaylist* playlist = model->activePlaylist();
playlistIndex = model->index( model->activePlaylistIndex(), 0 );
QCOMPARE( model->index( playlist ), playlistIndex );
Dynamic::BiasPtr bias = qobject_cast<Dynamic::BiasedPlaylist*>(playlist)->bias();
biasIndex = model->index( 0, 0, playlistIndex );
QCOMPARE( model->index( bias ), biasIndex );
Dynamic::BiasPtr subBias = qobject_cast<Dynamic::AndBias*>(bias.data())->biases().at(0);
subBiasIndex = model->index( 0, 0, biasIndex );
QCOMPARE( model->index( subBias ), subBiasIndex );
}
void
TestDynamicModel::testSlots()
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
// load from the empty directory
model->loadPlaylists();
- QSignalSpy spy1( model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)) );
- QSignalSpy spy2( model, SIGNAL(rowsRemoved(QModelIndex,int,int)) );
- QSignalSpy spy3( model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)) );
- QSignalSpy spy4( model, SIGNAL(rowsInserted(QModelIndex,int,int)) );
+ QSignalSpy spy1( model, &Dynamic::DynamicModel::rowsAboutToBeRemoved );
+ QSignalSpy spy2( model, &Dynamic::DynamicModel::rowsRemoved );
+ QSignalSpy spy3( model, &Dynamic::DynamicModel::rowsAboutToBeInserted );
+ QSignalSpy spy4( model, &Dynamic::DynamicModel::rowsInserted );
// -- removeAt with playlist
QModelIndex playlistIndex = model->index( 1, 0 );
QString oldName = model->data( playlistIndex ).toString();
model->removeAt( playlistIndex );
QCOMPARE( spy1.count(), 1 );
QCOMPARE( spy3.count(), 0 );
QList<QVariant> args1 = spy1.takeFirst();
QVERIFY( args1.value(0).canConvert<QModelIndex>() );
QCOMPARE( args1.value(0).value<QModelIndex>(), QModelIndex() );
QCOMPARE( args1.value(1).toInt(), 1 );
QCOMPARE( args1.value(2).toInt(), 1 );
QCOMPARE( spy2.count(), 1 );
spy2.takeFirst();
// name should be different
playlistIndex = model->index( 1, 0 );
QVERIFY( model->data( playlistIndex ).toString() != oldName );
QCOMPARE( model->rowCount(), 3 );
// -- removeAt with bias
playlistIndex = model->index( 1, 0 );
QModelIndex biasIndex = model->index( 0, 0, playlistIndex );
QModelIndex subBiasIndex = model->index( 0, 0, biasIndex );
QCOMPARE( model->rowCount( biasIndex ), 2 );
model->removeAt( subBiasIndex );
QCOMPARE( spy1.count(), 1 );
QCOMPARE( spy3.count(), 0 );
args1 = spy1.takeFirst();
QCOMPARE( args1.count(), 3 );
QVERIFY( args1.value(0).canConvert<QModelIndex>() );
QCOMPARE( args1.value(0).value<QModelIndex>(), biasIndex );
QCOMPARE( args1.value(1).toInt(), 0 );
QCOMPARE( args1.value(2).toInt(), 0 );
QCOMPARE( spy2.count(), 1 );
spy2.takeFirst();
QCOMPARE( model->rowCount( biasIndex ), 1 );
QCOMPARE( model->rowCount(), 3 ); // only the bias was removed
// -- cloneAt with level 2 bias
playlistIndex = model->index( 1, 0 );
biasIndex = model->index( 0, 0, playlistIndex );
subBiasIndex = model->index( 0, 0, biasIndex );
QCOMPARE( model->rowCount( biasIndex ), 1 );
QModelIndex resultIndex = model->cloneAt(subBiasIndex);
QCOMPARE( resultIndex.row(), 1 );
QCOMPARE( resultIndex.parent(), biasIndex );
QCOMPARE( spy3.count(), 1 );
args1 = spy3.takeFirst();
QVERIFY( args1.value(0).canConvert<QModelIndex>() );
QCOMPARE( args1.value(0).value<QModelIndex>(), biasIndex );
QCOMPARE( args1.value(1).toInt(), 1 );
QCOMPARE( args1.value(2).toInt(), 1 );
QCOMPARE( spy4.count(), 1 );
spy4.takeFirst();
QCOMPARE( model->rowCount( biasIndex ), 2 );
QCOMPARE( model->rowCount(), 3 ); // only the bias was cloned
// -- newPlaylist
QCOMPARE( spy1.count(), 0 );
QCOMPARE( spy3.count(), 0 );
QCOMPARE( model->rowCount(), 3 );
resultIndex = model->newPlaylist();
QCOMPARE( model->rowCount(), 4 );
QCOMPARE( resultIndex.row(), 3 );
QCOMPARE( resultIndex.parent(), QModelIndex() );
QCOMPARE( spy1.count(), 0 );
QCOMPARE( spy3.count(), 1 );
args1 = spy3.takeFirst();
QVERIFY( args1.value(0).canConvert<QModelIndex>() );
QCOMPARE( args1.value(0).value<QModelIndex>(), QModelIndex() );
QCOMPARE( args1.value(1).toInt(), 3 );
QCOMPARE( args1.value(2).toInt(), 3 );
QCOMPARE( spy4.count(), 1 );
spy4.takeFirst();
// -- cloneAt with playlist
playlistIndex = model->index( 1, 0 );
QCOMPARE( model->rowCount(), 4 );
resultIndex = model->cloneAt(playlistIndex);
QCOMPARE( model->rowCount(), 5 );
QCOMPARE( resultIndex.row(), 4 );
QCOMPARE( resultIndex.parent(), QModelIndex() );
QCOMPARE( model->rowCount( resultIndex ), 1 );
QCOMPARE( spy3.count(), 1 );
args1 = spy3.takeFirst();
QVERIFY( args1.value(0).canConvert<QModelIndex>() );
QCOMPARE( args1.value(0).value<QModelIndex>(), QModelIndex() );
QCOMPARE( args1.value(1).toInt(), 4 );
QCOMPARE( args1.value(2).toInt(), 4 );
QCOMPARE( spy4.count(), 1 );
spy4.takeFirst();
}
QModelIndex
TestDynamicModel::serializeUnserialize( const QModelIndex& index )
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
QByteArray bytes;
QDataStream stream( &bytes, QIODevice::WriteOnly );
model->serializeIndex( &stream, index );
QDataStream stream2( &bytes, QIODevice::ReadOnly );
return model->unserializeIndex( &stream2 );
}
void
TestDynamicModel::testSerializeIndex()
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
// load from the empty directory
model->loadPlaylists();
QModelIndex playlistIndex = model->index( 2, 0 );
QModelIndex biasIndex = model->index( 0, 0, playlistIndex );
QModelIndex subBiasIndex = model->index( 0, 0, biasIndex );
QCOMPARE( QModelIndex(), serializeUnserialize( QModelIndex() ) );
QCOMPARE( biasIndex, serializeUnserialize( biasIndex ) );
QCOMPARE( subBiasIndex, serializeUnserialize( subBiasIndex ) );
}
void
TestDynamicModel::testDnD()
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
// load from the empty directory
model->loadPlaylists();
// -- copy a playlist
QModelIndex playlistIndex = model->index( 2, 0 );
QModelIndexList indexes;
indexes << playlistIndex;
int oldRowCount = model->rowCount();
QString oldName = model->data( playlistIndex ).toString();
QMimeData* data = model->mimeData( indexes );
QVERIFY( model->dropMimeData( data, Qt::CopyAction, 0, 0, QModelIndex() ) );
QCOMPARE( model->rowCount(), oldRowCount + 1 );
playlistIndex = model->index( 0, 0 );
QCOMPARE( oldName, model->data( playlistIndex ).toString() );
delete data;
// -- move a playlist (to the end)
playlistIndex = model->index( 0, 0 );
indexes.clear();
indexes << playlistIndex;
oldRowCount = model->rowCount();
oldName = model->data( playlistIndex ).toString();
data = model->mimeData( indexes );
QVERIFY( model->dropMimeData( data, Qt::MoveAction, oldRowCount, 0, QModelIndex() ) );
QCOMPARE( model->rowCount(), oldRowCount );
playlistIndex = model->index( oldRowCount - 1, 0 );
QCOMPARE( oldName, model->data( playlistIndex ).toString() );
delete data;
// -- copy a bias
// TODO
QModelIndex biasIndex = model->index( 0, 0, playlistIndex );
QModelIndex subBiasIndex = model->index( 0, 0, biasIndex );
}
void
TestDynamicModel::testRemoveActive()
{
Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance();
// load from the empty directory
model->loadPlaylists();
QCOMPARE( model->rowCount(), 4 );
// -- try to remove the active playlist
model->setActivePlaylist( 2 );
QCOMPARE( model->activePlaylistIndex(), 2 );
Dynamic::DynamicPlaylist* pl = model->activePlaylist();
model->removeAt( model->index( pl ) );
QCOMPARE( model->rowCount(), 3 );
QVERIFY( model->activePlaylist() != pl );
// -- now remove all playlists remaining three playlists
model->removeAt( model->index( model->activePlaylist() ) );
model->removeAt( model->index( model->activePlaylist() ) );
model->removeAt( model->index( model->activePlaylist() ) );
QCOMPARE( model->rowCount(), 0 );
QCOMPARE( model->activePlaylist(), static_cast<Dynamic::DynamicPlaylist*>(0) );
}
diff --git a/tests/importers/TestImporterProvider.cpp b/tests/importers/TestImporterProvider.cpp
index 2a97b6cd66..e9984eaf2a 100644
--- a/tests/importers/TestImporterProvider.cpp
+++ b/tests/importers/TestImporterProvider.cpp
@@ -1,134 +1,134 @@
/****************************************************************************************
* Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestImporterProvider.h"
#include "core/support/Amarok.h"
#include "core/support/Components.h"
#include <qtest_kde.h>
QTEST_KDEMAIN_CORE( TestImporterProvider )
using namespace ::testing;
void
TestImporterProvider::constructorShouldSetConfigAndManager()
{
QVariantMap cfg;
cfg["nanananana"] = QString( "Batman" );
MockProvider provider( cfg, m_mockManager );
QVERIFY( provider.config().contains( QString( "nanananana" ) ) );
QCOMPARE( provider.manager(), m_mockManager );
}
void
TestImporterProvider::constructorShouldSetUidIfNotSet()
{
QVERIFY( !MockProvider( QVariantMap(), 0 ).id().isEmpty() );
}
void
TestImporterProvider::idShouldReturnConfiguredId()
{
QVariantMap cfg;
cfg["uid"] = QString( "Joker" );
QCOMPARE( MockProvider( cfg, 0 ).config(), cfg );
}
void
TestImporterProvider::descriptionShouldDelegateToManager()
{
EXPECT_CALL( *m_mockManager, description() ).WillOnce( Return( QString( "Ivy" ) ) );
QCOMPARE( m_mockProvider->description(), QString( "Ivy" ) );
}
void
TestImporterProvider::iconShouldDelegateToManager()
{
EXPECT_CALL( *m_mockManager, icon() ).WillOnce( Return( QIcon::fromTheme( "amarok" ) ) );
QCOMPARE( m_mockProvider->icon().name(), QIcon::fromTheme( "amarok" ).name() );
}
void
TestImporterProvider::nameShouldReturnConfiguredName()
{
QVariantMap cfg;
cfg["uid"] = QString( "Bane" );
cfg["name"] = QString( "Ra's" );
MockProvider provider( cfg, m_mockManager );
QCOMPARE( provider.prettyName(), QString( "Ra's" ) );
}
void
TestImporterProvider::nameShouldNotCrashIfNameIsNotConfigured()
{
QVariantMap cfg;
cfg["uid"] = QString( "TwoFace" );
MockProvider provider( cfg, m_mockManager );
QCOMPARE( provider.prettyName(), QString() );
}
void
TestImporterProvider::isConfigurableShouldReturnTrue()
{
QVERIFY( m_mockProvider->isConfigurable() );
}
void
TestImporterProvider::configWidgetShouldDelegateToManager()
{
StatSyncing::ProviderConfigWidget *widget = 0;
EXPECT_CALL( *m_mockManager, configWidget( Eq(m_mockProvider->config()) ) )
.WillOnce( Return( widget ) );
QCOMPARE( m_mockProvider->configWidget(), widget );
}
void
TestImporterProvider::reconfigureShouldEmitSignal()
{
QVariantMap cfg = m_mockProvider->config();
cfg["customField"] = QString( "Selena" );
- QSignalSpy spy( m_mockProvider, SIGNAL(reconfigurationRequested(QVariantMap)) );
+ QSignalSpy spy( m_mockProvider, &MockProvider::reconfigurationRequested );
m_mockProvider->reconfigure( cfg );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.takeFirst().at( 0 ).toMap(), cfg );
}
void
TestImporterProvider::reconfigureShouldNotEmitSignalOnDifferentUid()
{
QVariantMap cfg;
cfg["uid"] = "Different";
- QSignalSpy spy( m_mockProvider, SIGNAL(reconfigurationRequested(QVariantMap)) );
+ QSignalSpy spy( m_mockProvider, &MockProvider::reconfigurationRequested );
m_mockProvider->reconfigure( cfg );
QCOMPARE( spy.count(), 0 );
}
void
TestImporterProvider::defaultPreferenceShouldReturnNoByDefault()
{
QCOMPARE( m_mockProvider->defaultPreference(), StatSyncing::Provider::NoByDefault );
}
diff --git a/tests/mocks/MockTrackForUrlWorker.cpp b/tests/mocks/MockTrackForUrlWorker.cpp
index 46f462f000..8ac69badef 100644
--- a/tests/mocks/MockTrackForUrlWorker.cpp
+++ b/tests/mocks/MockTrackForUrlWorker.cpp
@@ -1,37 +1,41 @@
/****************************************************************************************
* Copyright (c) 2012 Jasneet Singh Bhatti <jazneetbhatti@gmail.com> *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "MockTrackForUrlWorker.h"
#include "core/meta/Meta.h"
MockTrackForUrlWorker::MockTrackForUrlWorker( const QUrl &url )
: TrackForUrlWorker( url )
{
}
MockTrackForUrlWorker::MockTrackForUrlWorker( const QString &url )
: TrackForUrlWorker( url )
{
}
void
MockTrackForUrlWorker::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
{
- Q_UNUSED(self);
Q_UNUSED(thread);
+
+ emit started(self);
+
QFETCH( Meta::TrackPtr, track );
m_track = track;
+
+ emit done(self);
}
diff --git a/tests/qt-modeltest/modeltest.cpp b/tests/qt-modeltest/modeltest.cpp
index ad31e7e5c4..cac139b067 100644
--- a/tests/qt-modeltest/modeltest.cpp
+++ b/tests/qt-modeltest/modeltest.cpp
@@ -1,539 +1,523 @@
/****************************************************************************
**
** Copyright (C) 2007 Trolltech ASA. All rights reserved.
**
** This file is part of the Qt Concurrent project on Trolltech Labs.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sales@trolltech.com.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
#include <QtGui>
#include "modeltest.h"
Q_DECLARE_METATYPE(QModelIndex)
/*!
Connect to all of the models signals. Whenever anything happens recheck everything.
*/
ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false)
{
Q_ASSERT(model);
- connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(runAllTests()));
- connect(model, SIGNAL(layoutChanged()), this, SLOT(runAllTests()));
- connect(model, SIGNAL(modelReset()), this, SLOT(runAllTests()));
- connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
- connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
- this, SLOT(runAllTests()));
+ connect(model, &QAbstractItemModel::columnsAboutToBeInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::columnsAboutToBeRemoved, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::columnsInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::columnsRemoved, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::dataChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::headerDataChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::layoutChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::modelReset, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::runAllTests);
// Special checks for inserting/removing
- connect(model, SIGNAL(layoutAboutToBeChanged()),
- this, SLOT(layoutAboutToBeChanged()));
- connect(model, SIGNAL(layoutChanged()),
- this, SLOT(layoutChanged()));
-
- connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
- this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)));
- connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
- this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)));
- connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
- this, SLOT(rowsInserted(QModelIndex,int,int)));
- connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
- this, SLOT(rowsRemoved(QModelIndex,int,int)));
+ connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelTest::layoutAboutToBeChanged);
+ connect(model, &QAbstractItemModel::layoutChanged, this, &ModelTest::layoutChanged);
+
+ connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::rowsAboutToBeInserted);
+ connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::rowsAboutToBeRemoved);
+ connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::rowsInserted);
+ connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::rowsRemoved);
runAllTests();
}
void ModelTest::runAllTests()
{
if (fetchingMore)
return;
nonDestructiveBasicTest();
rowCount();
columnCount();
hasIndex();
index();
parent();
data();
}
/*!
nonDestructiveBasicTest tries to call a number of the basic functions (not all)
to make sure the model doesn't outright segfault, testing the functions that makes sense.
*/
void ModelTest::nonDestructiveBasicTest()
{
Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
model->canFetchMore(QModelIndex());
Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
Q_ASSERT(model->data(QModelIndex()) == QVariant());
fetchingMore = true;
model->fetchMore(QModelIndex());
fetchingMore = false;
Qt::ItemFlags flags = model->flags(QModelIndex());
Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
model->hasChildren(QModelIndex());
model->hasIndex(0, 0);
model->headerData(0, Qt::Horizontal);
model->index(0, 0);
Q_ASSERT(model->index(-1, -1) == QModelIndex());
model->itemData(QModelIndex());
QVariant cache;
model->match(QModelIndex(), -1, cache);
model->mimeTypes();
Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
Q_ASSERT(model->rowCount() >= 0);
QVariant variant;
model->setData(QModelIndex(), variant, -1);
model->setHeaderData(-1, Qt::Horizontal, QVariant());
model->setHeaderData(0, Qt::Horizontal, QVariant());
model->setHeaderData(999999, Qt::Horizontal, QVariant());
QMap<int, QVariant> roles;
model->sibling(0, 0, QModelIndex());
model->span(QModelIndex());
model->supportedDropActions();
}
/*!
Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
Models that are dynamically populated are not as fully tested here.
*/
void ModelTest::rowCount()
{
// check top row
QModelIndex topIndex = model->index(0, 0, QModelIndex());
int rows = model->rowCount(topIndex);
Q_ASSERT(rows >= 0);
if (rows > 0)
Q_ASSERT(model->hasChildren(topIndex) == true);
QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
if (secondLevelIndex.isValid()) { // not the top level
// check a row count where parent is valid
rows = model->rowCount(secondLevelIndex);
Q_ASSERT(rows >= 0);
if (rows > 0)
Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
}
// The models rowCount() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
*/
void ModelTest::columnCount()
{
// check top row
QModelIndex topIndex = model->index(0, 0, QModelIndex());
Q_ASSERT(model->columnCount(topIndex) >= 0);
// check a column count where parent is valid
QModelIndex childIndex = model->index(0, 0, topIndex);
if (childIndex.isValid())
Q_ASSERT(model->columnCount(childIndex) >= 0);
// columnCount() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::hasIndex()
*/
void ModelTest::hasIndex()
{
// Make sure that invalid values returns an invalid index
Q_ASSERT(model->hasIndex(-2, -2) == false);
Q_ASSERT(model->hasIndex(-2, 0) == false);
Q_ASSERT(model->hasIndex(0, -2) == false);
int rows = model->rowCount();
int columns = model->columnCount();
// check out of bounds
Q_ASSERT(model->hasIndex(rows, columns) == false);
Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
if (rows > 0)
Q_ASSERT(model->hasIndex(0, 0) == true);
// hasIndex() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::index()
*/
void ModelTest::index()
{
// Make sure that invalid values returns an invalid index
Q_ASSERT(model->index(-2, -2) == QModelIndex());
Q_ASSERT(model->index(-2, 0) == QModelIndex());
Q_ASSERT(model->index(0, -2) == QModelIndex());
int rows = model->rowCount();
int columns = model->columnCount();
if (rows == 0)
return;
// Catch off by one errors
Q_ASSERT(model->index(rows, columns) == QModelIndex());
Q_ASSERT(model->index(0, 0).isValid() == true);
// Make sure that the same index is *always* returned
QModelIndex a = model->index(0, 0);
QModelIndex b = model->index(0, 0);
Q_ASSERT(a == b);
// index() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::parent()
*/
void ModelTest::parent()
{
// Make sure the model wont crash and will return an invalid QModelIndex
// when asked for the parent of an invalid index.
Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
if (model->rowCount() == 0)
return;
// Column 0 | Column 1 |
// QModelIndex() | |
// \- topIndex | topIndex1 |
// \- childIndex | childIndex1 |
// Common error test #1, make sure that a top level index has a parent
// that is a invalid QModelIndex.
QModelIndex topIndex = model->index(0, 0, QModelIndex());
Q_ASSERT(model->parent(topIndex) == QModelIndex());
// Common error test #2, make sure that a second level index has a parent
// that is the first level index.
if (model->rowCount(topIndex) > 0) {
QModelIndex childIndex = model->index(0, 0, topIndex);
Q_ASSERT(model->parent(childIndex) == topIndex);
}
// Common error test #3, the second column should NOT have the same children
// as the first column in a row.
// Usually the second column shouldn't have children.
QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
if (model->rowCount(topIndex1) > 0) {
QModelIndex childIndex = model->index(0, 0, topIndex);
QModelIndex childIndex1 = model->index(0, 0, topIndex1);
Q_ASSERT(childIndex != childIndex1);
}
// Full test, walk n levels deep through the model making sure that all
// parent's children correctly specify their parent.
checkChildren(QModelIndex());
}
/*!
Called from the parent() test.
A model that returns an index of parent X should also return X when asking
for the parent of the index.
This recursive function does pretty extensive testing on the whole model in an
effort to catch edge cases.
This function assumes that rowCount(), columnCount() and index() already work.
If they have a bug it will point it out, but the above tests should have already
found the basic bugs because it is easier to figure out the problem in
those tests then this one.
*/
void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth)
{
// First just try walking back up the tree.
QModelIndex p = parent;
while (p.isValid())
p = p.parent();
// For models that are dynamically populated
if (model->canFetchMore(parent)) {
fetchingMore = true;
model->fetchMore(parent);
fetchingMore = false;
}
int rows = model->rowCount(parent);
int columns = model->columnCount(parent);
if (rows > 0)
Q_ASSERT(model->hasChildren(parent));
// Some further testing against rows(), columns(), and hasChildren()
Q_ASSERT(rows >= 0);
Q_ASSERT(columns >= 0);
if (rows > 0)
Q_ASSERT(model->hasChildren(parent) == true);
//qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
// << "columns:" << columns << "parent column:" << parent.column();
Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
for (int r = 0; r < rows; ++r) {
if (model->canFetchMore(parent)) {
fetchingMore = true;
model->fetchMore(parent);
fetchingMore = false;
}
Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
for (int c = 0; c < columns; ++c) {
Q_ASSERT(model->hasIndex(r, c, parent) == true);
QModelIndex index = model->index(r, c, parent);
// rowCount() and columnCount() said that it existed...
Q_ASSERT(index.isValid() == true);
// index() should always return the same index when called twice in a row
QModelIndex modifiedIndex = model->index(r, c, parent);
Q_ASSERT(index == modifiedIndex);
// Make sure we get the same index if we request it twice in a row
QModelIndex a = model->index(r, c, parent);
QModelIndex b = model->index(r, c, parent);
Q_ASSERT(a == b);
// Some basic checking on the index that is returned
Q_ASSERT(index.model() == model);
Q_ASSERT(index.row() == r);
Q_ASSERT(index.column() == c);
// While you can technically return a QVariant usually this is a sign
// of an bug in data() Disable if this really is ok in your model.
//Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
// If the next test fails here is some somewhat useful debug you play with.
/*
if (model->parent(index) != parent) {
qDebug() << r << c << currentDepth << model->data(index).toString()
<< model->data(parent).toString();
qDebug() << index << parent << model->parent(index);
// And a view that you can even use to show the model.
//QTreeView view;
//view.setModel(model);
//view.show();
}*/
// Check that we can get back our real parent.
QModelIndex p = model->parent(index);
//qDebug() << "child:" << index;
//qDebug() << p;
//qDebug() << parent;
Q_ASSERT(model->parent(index) == parent);
// recursively go down the children
if (model->hasChildren(index) && currentDepth < 10 ) {
//qDebug() << r << c << "has children" << model->rowCount(index);
checkChildren(index, ++currentDepth);
}/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
// make sure that after testing the children that the index doesn't change.
QModelIndex newerIndex = model->index(r, c, parent);
Q_ASSERT(index == newerIndex);
}
}
}
/*!
Tests model's implementation of QAbstractItemModel::data()
*/
void ModelTest::data()
{
// Invalid index should return an invalid qvariant
Q_ASSERT(!model->data(QModelIndex()).isValid());
if (model->rowCount() == 0)
return;
// A valid index should have a valid QVariant data
Q_ASSERT(model->index(0, 0).isValid());
// shouldn't be able to set data on an invalid index
Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false);
// General Purpose roles that should return a QString
QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
if (variant.isValid()) {
Q_ASSERT(qVariantCanConvert<QString>(variant));
}
variant = model->data(model->index(0, 0), Qt::StatusTipRole);
if (variant.isValid()) {
Q_ASSERT(qVariantCanConvert<QString>(variant));
}
variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
if (variant.isValid()) {
Q_ASSERT(qVariantCanConvert<QString>(variant));
}
// General Purpose roles that should return a QSize
variant = model->data(model->index(0, 0), Qt::SizeHintRole);
if (variant.isValid()) {
Q_ASSERT(qVariantCanConvert<QSize>(variant));
}
// General Purpose roles that should return a QFont
QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
if (fontVariant.isValid()) {
Q_ASSERT(qVariantCanConvert<QFont>(fontVariant));
}
// Check that the alignment is one we know about
QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
if (textAlignmentVariant.isValid()) {
int alignment = textAlignmentVariant.toInt();
Q_ASSERT(alignment == Qt::AlignLeft ||
alignment == Qt::AlignRight ||
alignment == Qt::AlignHCenter ||
alignment == Qt::AlignJustify ||
alignment == Qt::AlignTop ||
alignment == Qt::AlignBottom ||
alignment == Qt::AlignVCenter ||
alignment == Qt::AlignCenter ||
alignment == Qt::AlignAbsolute ||
alignment == Qt::AlignLeading ||
alignment == Qt::AlignTrailing);
}
// General Purpose roles that should return a QColor
QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
if (colorVariant.isValid()) {
Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
}
colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
if (colorVariant.isValid()) {
Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
}
// Check that the "check state" is one we know about.
QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
if (checkStateVariant.isValid()) {
int state = checkStateVariant.toInt();
Q_ASSERT(state == Qt::Unchecked ||
state == Qt::PartiallyChecked ||
state == Qt::Checked);
}
}
/*!
Store what is about to be inserted to make sure it actually happens
\sa rowsInserted()
*/
void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(end);
Changing c;
c.parent = parent;
c.oldSize = model->rowCount(parent);
c.last = model->data(model->index(start - 1, 0, parent));
c.next = model->data(model->index(start, 0, parent));
insert.push(c);
}
/*!
Confirm that what was said was going to happen actually did
\sa rowsAboutToBeInserted()
*/
void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end)
{
Changing c = insert.pop();
Q_ASSERT(c.parent == parent);
Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
/*
if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
qDebug() << start << end;
for (int i=0; i < model->rowCount(); ++i)
qDebug() << model->index(i, 0).data().toString();
qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
}
*/
Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
}
void ModelTest::layoutAboutToBeChanged()
{
for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
changing.append(QPersistentModelIndex(model->index(i, 0)));
}
void ModelTest::layoutChanged()
{
for (int i = 0; i < changing.count(); ++i) {
QPersistentModelIndex p = changing[i];
Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
}
changing.clear();
}
/*!
Store what is about to be inserted to make sure it actually happens
\sa rowsRemoved()
*/
void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
Changing c;
c.parent = parent;
c.oldSize = model->rowCount(parent);
c.last = model->data(model->index(start - 1, 0, parent));
c.next = model->data(model->index(end + 1, 0, parent));
remove.push(c);
}
/*!
Confirm that what was said was going to happen actually did
\sa rowsAboutToBeRemoved()
*/
void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end)
{
Changing c = remove.pop();
Q_ASSERT(c.parent == parent);
Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
}
diff --git a/tests/scanner/TestGenericScanManager.cpp b/tests/scanner/TestGenericScanManager.cpp
index 92c95a0b8c..31a5cebbc8 100644
--- a/tests/scanner/TestGenericScanManager.cpp
+++ b/tests/scanner/TestGenericScanManager.cpp
@@ -1,380 +1,380 @@
/****************************************************************************************
* Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> *
* Copyright (c) 2010-2013 Ralf Engels <ralf-engels@gmx.de> *
* *
* 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 <http://www.gnu.org/licenses/>. *
****************************************************************************************/
#include "TestGenericScanManager.h"
#include "amarokconfig.h"
#include "MetaTagLib.h"
#include "scanner/GenericScanManager.h"
#include "config-amarok-test.h"
#include <qtest_kde.h>
#include <QImage>
#include <QScopedPointer>
#include <QTest>
QTEST_KDEMAIN_CORE( TestGenericScanManager )
TestGenericScanManager::TestGenericScanManager()
: QObject()
{
// QString help = i18n("Amarok"); // prevent a bug when the scanner is the first thread creating a translator
}
void
TestGenericScanManager::initTestCase()
{
// setenv( "LC_ALL", "", 1 ); // this breakes the test
// Amarok does not force LC_ALL=C but obviously the test does it which
// will prevent scanning of files with umlauts.
// that is the original mp3 file that we use to generate the "real" tracks
m_sourcePath = QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + "/data/audio/Platz 01.mp3" );
QVERIFY( QFile::exists( m_sourcePath ) );
m_scanManager = new GenericScanManager( this );
- connect( m_scanManager, SIGNAL(started(GenericScanManager::ScanType)),
- SLOT(slotStarted(GenericScanManager::ScanType)) );
- connect( m_scanManager, SIGNAL(directoryCount(int)), SLOT(slotDirectoryCount(int)) );
- connect( m_scanManager, SIGNAL(directoryScanned(QSharedPointer<CollectionScanner::Directory>)),
- SLOT(slotDirectoryScanned(QSharedPointer<CollectionScanner::Directory>)) );
- connect( m_scanManager, SIGNAL(succeeded()), SLOT(slotSucceeded()) );
- connect( m_scanManager, SIGNAL(failed(QString)), SLOT(slotFailed(QString)) );
+ connect( m_scanManager, &GenericScanManager::started,
+ this, &TestGenericScanManager::slotStarted );
+ connect( m_scanManager, &GenericScanManager::directoryCount, this, &TestGenericScanManager::slotDirectoryCount );
+ connect( m_scanManager, &GenericScanManager::directoryScanned,
+ this, &TestGenericScanManager::slotDirectoryScanned );
+ connect( m_scanManager, &GenericScanManager::succeeded, this, &TestGenericScanManager::slotSucceeded );
+ connect( m_scanManager, &GenericScanManager::failed, this, &TestGenericScanManager::slotFailed );
AmarokConfig::setScanRecursively( true );
AmarokConfig::setMonitorChanges( false );
// switch on writing back so that we can create the test files with all the information
AmarokConfig::setWriteBack( true );
AmarokConfig::setWriteBackStatistics( true );
AmarokConfig::setWriteBackCover( true );
}
void
TestGenericScanManager::cleanupTestCase()
{ }
void
TestGenericScanManager::init()
{
m_tmpCollectionDir = new KTempDir();
QVERIFY( m_tmpCollectionDir->exists() );
QStringList collectionFolders;
collectionFolders << m_tmpCollectionDir->name();
m_started = false;
m_finished = false;
m_scannedDirsCount = 0;
m_scannedTracksCount = 0;
m_scannedCoversCount = 0;
}
void
TestGenericScanManager::cleanup()
{
m_scanManager->abort();
delete m_tmpCollectionDir;
}
void
TestGenericScanManager::testScanSingle()
{
createSingleTrack();
fullScanAndWait();
QCOMPARE( m_scannedDirsCount, 3 );
QCOMPARE( m_scannedTracksCount, 1 );
QCOMPARE( m_scannedCoversCount, 0 );
}
void
TestGenericScanManager::testScanDirectory()
{
createAlbum();
fullScanAndWait();
// -- check the commit
QCOMPARE( m_scannedDirsCount, 3 );
QCOMPARE( m_scannedTracksCount, 9 );
QCOMPARE( m_scannedCoversCount, 0 );
}
void
TestGenericScanManager::testRestartScanner()
{
#ifndef QT_NO_DEBUG
createAlbum();
// the scanner crashes at a special file:
Meta::FieldHash values;
values.clear();
values.insert( Meta::valUniqueId, QVariant("c6c29f50279ab9523a0f44928bc1e96b") );
values.insert( Meta::valUrl, QVariant("Thriller/crash_amarok_here.ogg") );
createTrack( values );
fullScanAndWait();
// -- check the commit
QCOMPARE( m_scannedDirsCount, 4 );
QCOMPARE( m_scannedTracksCount, 9 );
QCOMPARE( m_scannedCoversCount, 0 );
#else
QSKIP( "Collection scanner only crashes in debug build.", SkipAll );
#endif
}
void
TestGenericScanManager::testAlbumImage()
{
createSingleTrack();
createAlbum();
// put an image into the album directory
QString imageSourcePath = QDir::toNativeSeparators( QString( AMAROK_TEST_DIR ) + "/data/playlists/no-playlist.png" );
QVERIFY( QFile::exists( imageSourcePath ) );
QString targetPath;
targetPath = m_tmpCollectionDir->name() + "Pop/Thriller/cover.png";
QVERIFY( QFile::copy( m_sourcePath, targetPath ) );
// set an embedded image
targetPath = m_tmpCollectionDir->name() + "Various Artists/Big Screen Adventures/28 - Theme From Armageddon.mp3";
Meta::Tag::setEmbeddedCover( targetPath, QImage( 200, 200, QImage::Format_RGB32 ) );
fullScanAndWait();
// -- check the commit
QCOMPARE( m_scannedDirsCount, 5 );
QCOMPARE( m_scannedTracksCount, 10 );
QCOMPARE( m_scannedCoversCount, 1 );
}
void
TestGenericScanManager::slotStarted( GenericScanManager::ScanType type )
{
Q_UNUSED( type );
QVERIFY( !m_started );
QVERIFY( !m_finished );
m_started = true;
}
void
TestGenericScanManager::slotDirectoryCount( int count )
{
Q_UNUSED( count );
QVERIFY( m_started );
QVERIFY( !m_finished );
}
void
TestGenericScanManager::slotDirectoryScanned( QSharedPointer<CollectionScanner::Directory> dir )
{
QVERIFY( m_started );
QVERIFY( !m_finished );
m_scannedDirsCount += 1;
m_scannedTracksCount += dir->tracks().count();
m_scannedCoversCount += dir->covers().count();
}
void
TestGenericScanManager::slotSucceeded()
{
QVERIFY( m_started );
QVERIFY( !m_finished );
m_finished = true;
}
void
TestGenericScanManager::slotFailed( const QString& message )
{
Q_UNUSED( message );
QVERIFY( m_started );
QVERIFY( !m_finished );
m_finished = true;
}
void
TestGenericScanManager::fullScanAndWait()
{
QList<QUrl> urls;
urls << QUrl::fromLocalFile( m_tmpCollectionDir->name() );
m_scanManager->requestScan( urls );
waitScannerFinished();
QVERIFY( m_started );
QVERIFY( m_finished );
}
void
TestGenericScanManager::waitScannerFinished()
{
QVERIFY( m_scanManager->isRunning() );
QVERIFY2( QTest::kWaitForSignal( m_scanManager, SIGNAL(succeeded()), 60*1000 ),
"ScanManager didn't finish scan within timeout" );
QVERIFY( !m_scanManager->isRunning() );
}
void
TestGenericScanManager::createTrack( const Meta::FieldHash &values )
{
// -- copy the file from our original
QVERIFY( values.contains( Meta::valUrl ) );
const QString targetPath = m_tmpCollectionDir->name() + values.value( Meta::valUrl ).toString();
QVERIFY( QDir( m_tmpCollectionDir->name() ).mkpath( QFileInfo( values.value( Meta::valUrl ).toString() ).path() ) );
QVERIFY( QFile::copy( m_sourcePath, targetPath ) );
// -- set all the values that we need
Meta::Tag::writeTags( targetPath, values, true );
}
void
TestGenericScanManager::createSingleTrack()
{
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("794b1bd040d5dd9b5b45c1494d84cc82") );
values.insert( Meta::valUrl, QVariant("Various Artists/Big Screen Adventures/28 - Theme From Armageddon.mp3") );
values.insert( Meta::valFormat, QVariant("1") );
values.insert( Meta::valTitle, QVariant("Theme From Armageddon") );
values.insert( Meta::valArtist, QVariant("Soundtrack & Theme Orchestra") );
values.insert( Meta::valAlbumArtist, QVariant("Theme Orchestra") );
values.insert( Meta::valAlbum, QVariant("Big Screen Adventures") );
values.insert( Meta::valComposer, QVariant("Unknown Composer") );
values.insert( Meta::valComment, QVariant("Amazon.com Song ID: 210541237") );
values.insert( Meta::valGenre, QVariant("Broadway & Vocalists") );
values.insert( Meta::valYear, QVariant(2009) );
values.insert( Meta::valTrackNr, QVariant(28) );
// values.insert( Meta::valBitrate, QVariant(216) ); // the bitrate can not be set. it's computed
// values.insert( Meta::valLength, QVariant(184000) ); // also can't be set
// values.insert( Meta::valSamplerate, QVariant(44100) ); // again
// values.insert( Meta::valFilesize, QVariant(5094892) ); // again
values.insert( Meta::valScore, QVariant(0.875) );
values.insert( Meta::valPlaycount, QVariant(5) );
// TODO: set an embedded cover
createTrack( values );
}
void
TestGenericScanManager::createAlbum()
{
Meta::FieldHash values;
values.insert( Meta::valUniqueId, QVariant("1dc7022c52a3e4c51b46577da9b3c8ff") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 01 - Michael Jackson - Track01.mp3") );
values.insert( Meta::valTitle, QVariant("Wanna Be Startin' Somethin'") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(1) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("1dc708934a3e4c51b46577da9b3ab11") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 02 - Michael Jackson - Track02.mp3") );
values.insert( Meta::valTitle, QVariant("Baby Be Mine") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(2) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("15a6b1bf79747fdc8e9c6b6f06203017") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 03 - Michael Jackson - Track03.mp3") );
values.insert( Meta::valTitle, QVariant("The Girl Is Mine") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(3) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("4aba4c8b1d1893c03c112cc3c01221e9") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 04 - Michael Jackson - Track04.mp3") );
values.insert( Meta::valTitle, QVariant("Thriller") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(4) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("cb44d2a3d8053829b04672723bf0bd6e") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 05 - Michael Jackson - Track05.mp3") );
values.insert( Meta::valTitle, QVariant("Beat It") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(5) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("eba1858eeeb3c6d97fe3385200114d86") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 06 - Michael Jackson - Track06.mp3") );
values.insert( Meta::valTitle, QVariant("Billy Jean") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(6) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("4623850290998486b0f7b39a2719904e") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 07 - Michael Jackson - Track07.mp3") );
values.insert( Meta::valTitle, QVariant("Human Nature") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(7) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("6d9a7de13af1e16bb13a6208e44b046d") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 08 - Michael Jackson - Track08.mp3") );
values.insert( Meta::valTitle, QVariant("P.Y.T. (Pretty Young Thing)") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(8) );
createTrack( values );
values.clear();
values.insert( Meta::valUniqueId, QVariant("91cf9a7c0d255399f9f6babfacae432b") );
values.insert( Meta::valUrl, QVariant("Pop/Thriller/Thriller - 09 - Michael Jackson - Track09.mp3") );
values.insert( Meta::valTitle, QVariant("The Lady In My Life") );
values.insert( Meta::valArtist, QVariant("Michael Jackson") );
values.insert( Meta::valAlbum, QVariant("Thriller") );
values.insert( Meta::valYear, QVariant(1982) );
values.insert( Meta::valTrackNr, QVariant(9) );
createTrack( values );
}
diff --git a/utilities/collectionscanner/CollectionScanner.cpp b/utilities/collectionscanner/CollectionScanner.cpp
index 53d24ac7e9..c1f2bbe676 100644
--- a/utilities/collectionscanner/CollectionScanner.cpp
+++ b/utilities/collectionscanner/CollectionScanner.cpp
@@ -1,438 +1,438 @@
/***************************************************************************
* Copyright (C) 2003-2005 Max Howell <max.howell@methylblue.com> *
* (C) 2003-2010 Mark Kretschmann <kretschmann@kde.org> *
* (C) 2005-2007 Alexandre Oliveira <aleprj@gmail.com> *
* (C) 2008 Dan Meltzer <parallelgrapefruit@gmail.com> *
* (C) 2008-2009 Jeff Mitchell <mitchell@kde.org> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "CollectionScanner.h"
#include "Version.h" // for AMAROK_VERSION
#include "collectionscanner/BatchFile.h"
#include "collectionscanner/Directory.h"
#include "collectionscanner/Track.h"
#include <QTimer>
#include <QThread>
#include <QString>
#include <QStringList>
#include <QDir>
#include <QFile>
#include <QDateTime>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QSharedMemory>
#include <QByteArray>
#include <QTextStream>
#include <QDataStream>
#include <QBuffer>
#include <QDebug>
#ifdef Q_OS_LINUX
// for ioprio
#include <unistd.h>
#include <sys/syscall.h>
enum {
IOPRIO_CLASS_NONE,
IOPRIO_CLASS_RT,
IOPRIO_CLASS_BE,
IOPRIO_CLASS_IDLE
};
enum {
IOPRIO_WHO_PROCESS = 1,
IOPRIO_WHO_PGRP,
IOPRIO_WHO_USER
};
#define IOPRIO_CLASS_SHIFT 13
#endif
int
main( int argc, char *argv[] )
{
CollectionScanner::Scanner scanner( argc, argv );
return scanner.exec();
}
CollectionScanner::Scanner::Scanner( int &argc, char **argv )
: QCoreApplication( argc, argv )
, m_charset( false )
, m_newerTime(0)
, m_incremental( false )
, m_recursively( false )
, m_restart( false )
, m_idlePriority( false )
{
setObjectName( "amarokcollectionscanner" );
readArgs();
if( m_idlePriority )
{
bool ioPriorityWorked = false;
#if defined(Q_OS_LINUX) && defined(SYS_ioprio_set)
// try setting the idle priority class
ioPriorityWorked = ( syscall( SYS_ioprio_set, IOPRIO_WHO_PROCESS, 0,
IOPRIO_CLASS_IDLE << IOPRIO_CLASS_SHIFT ) >= 0 );
// try setting the lowest priority in the best-effort priority class (the default class)
if( !ioPriorityWorked )
ioPriorityWorked = ( syscall( SYS_ioprio_set, IOPRIO_WHO_PROCESS, 0,
7 | ( IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT ) ) >= 0 );
#endif
if( !ioPriorityWorked && QThread::currentThread() )
QThread::currentThread()->setPriority( QThread::IdlePriority );
}
}
CollectionScanner::Scanner::~Scanner()
{
}
void
CollectionScanner::Scanner::readBatchFile( const QString &path )
{
QFile batchFile( path );
if( !batchFile.exists() )
error( tr( "File \"%1\" not found." ).arg( path ) );
if( !batchFile.open( QIODevice::ReadOnly ) )
error( tr( "Could not open file \"%1\"." ).arg( path ) );
BatchFile batch( path );
foreach( const QString &str, batch.directories() )
{
m_folders.append( str );
}
foreach( const CollectionScanner::BatchFile::TimeDefinition &def, batch.timeDefinitions() )
{
m_mTimes.insert( def.first, def.second );
}
}
void
CollectionScanner::Scanner::readNewerTime( const QString &path )
{
QFileInfo file( path );
if( !file.exists() )
error( tr( "File \"%1\" not found." ).arg( path ) );
m_newerTime = qMax( m_newerTime, file.lastModified().toTime_t() );
}
void
CollectionScanner::Scanner::doJob() //SLOT
{
QFile xmlFile;
xmlFile.open( stdout, QIODevice::WriteOnly );
QXmlStreamWriter xmlWriter( &xmlFile );
xmlWriter.setAutoFormatting( true );
// get a list of folders to scan. We do it even if resuming because we don't want
// to save the (perhaps very big) list of directories into shared memory, bug 327812
QStringList entries;
{
QSet<QString> entriesSet;
foreach( QString dir, m_folders ) // krazy:exclude=foreach
{
if( dir.isEmpty() )
//apparently somewhere empty strings get into the mix
//which results in a full-system scan! Which we can't allow
continue;
// Make sure that all paths are absolute, not relative
if( QDir::isRelativePath( dir ) )
dir = QDir::cleanPath( QDir::currentPath() + '/' + dir );
if( !dir.endsWith( '/' ) )
dir += '/';
addDir( dir, &entriesSet ); // checks m_recursively
}
entries = entriesSet.toList();
qSort( entries ); // the sort is crucial because of restarts and lastDirectory handling
}
if( m_restart )
{
m_scanningState.readFull();
QString lastEntry = m_scanningState.lastDirectory();
int index = entries.indexOf( lastEntry );
if( index >= 0 )
// strip already processed entries, but *keep* the lastEntry
entries = entries.mid( index );
else
qWarning() << Q_FUNC_INFO << "restarting scan after a crash, but lastDirectory"
<< lastEntry << "not found in folders to scan (size" << entries.size()
<< "). Starting scanning from the beginning.";
}
else // first attempt
{
m_scanningState.writeFull(); // just trigger write to initialise memory
xmlWriter.writeStartDocument();
xmlWriter.writeStartElement("scanner");
xmlWriter.writeAttribute("count", QString::number( entries.count() ) );
if( m_incremental )
xmlWriter.writeAttribute("incremental", QString());
// write some information into the file and close previous tag
xmlWriter.writeComment("Created by amarokcollectionscanner " AMAROK_VERSION " on "+QDateTime::currentDateTime().toString());
xmlFile.flush();
}
// --- now do the scanning
foreach( const QString &path, entries )
{
CollectionScanner::Directory dir( path, &m_scanningState,
m_incremental && !isModified( path ) );
xmlWriter.writeStartElement( "directory" );
dir.toXml( &xmlWriter );
xmlWriter.writeEndElement();
xmlFile.flush();
}
// --- write the end element (must be done by hand as we might not have written the start element when restarting)
xmlFile.write("\n</scanner>\n");
quit();
}
void
CollectionScanner::Scanner::addDir( const QString& dir, QSet<QString>* entries )
{
// Linux specific, but this fits the 90% rule
if( dir.startsWith( "/dev" ) || dir.startsWith( "/sys" ) || dir.startsWith( "/proc" ) )
return;
if( entries->contains( dir ) )
return;
QDir d( dir );
if( !d.exists() )
{
QTextStream stream( stderr );
stream << "Directory \""<<dir<<"\" does not exist." << endl;
return;
}
entries->insert( dir );
if( !m_recursively )
return; // finished
d.setFilter( QDir::NoDotAndDotDot | QDir::Dirs );
QFileInfoList fileInfos = d.entryInfoList();
foreach( const QFileInfo &fi, fileInfos )
{
if( !fi.exists() )
continue;
const QFileInfo &f = fi.isSymLink() ? QFileInfo( fi.symLinkTarget() ) : fi;
if( !f.exists() )
continue;
if( f.isDir() )
{
addDir( QString( f.absoluteFilePath() + '/' ), entries );
}
}
}
bool
CollectionScanner::Scanner::isModified( const QString& dir )
{
QFileInfo info( dir );
if( !info.exists() )
return false;
uint lastModified = info.lastModified().toTime_t();
if( m_mTimes.contains( dir ) )
return m_mTimes.value( dir ) != lastModified;
else
return m_newerTime < lastModified;
}
void
CollectionScanner::Scanner::readArgs()
{
QStringList argslist = arguments();
if( argslist.size() < 2 )
displayHelp();
bool missingArg = false;
for( int argnum = 1; argnum < argslist.count(); argnum++ )
{
QString arg = argslist.at( argnum );
if( arg.startsWith( "--" ) )
{
QString myarg = QString( arg ).remove( 0, 2 );
if( myarg == "newer" )
{
if( argslist.count() > argnum + 1 )
readNewerTime( argslist.at( argnum + 1 ) );
else
missingArg = true;
argnum++;
}
else if( myarg == "batch" )
{
if( argslist.count() > argnum + 1 )
readBatchFile( argslist.at( argnum + 1 ) );
else
missingArg = true;
argnum++;
}
else if( myarg == "sharedmemory" )
{
if( argslist.count() > argnum + 1 )
m_scanningState.setKey( argslist.at( argnum + 1 ) );
else
missingArg = true;
argnum++;
}
else if( myarg == "version" )
displayVersion();
else if( myarg == "incremental" )
m_incremental = true;
else if( myarg == "recursive" )
m_recursively = true;
else if( myarg == "restart" )
m_restart = true;
else if( myarg == "idlepriority" )
m_idlePriority = true;
else if( myarg == "charset" )
m_charset = true;
else
displayHelp();
}
else if( arg.startsWith( '-' ) )
{
QString myarg = QString( arg ).remove( 0, 1 );
int pos = 0;
while( pos < myarg.length() )
{
if( myarg[pos] == 'r' )
m_recursively = true;
else if( myarg[pos] == 'v' )
displayVersion();
else if( myarg[pos] == 's' )
m_restart = true;
else if( myarg[pos] == 'c' )
m_charset = true;
else if( myarg[pos] == 'i' )
m_incremental = true;
else
displayHelp();
++pos;
}
}
else
{
if( !arg.isEmpty() )
m_folders.append( arg );
}
}
if( missingArg )
displayHelp( tr( "Missing argument for option %1" ).arg( argslist.last() ) );
CollectionScanner::Track::setUseCharsetDetector( m_charset );
// Start the actual scanning job
- QTimer::singleShot( 0, this, SLOT(doJob()) );
+ QTimer::singleShot( 0, this, &Scanner::doJob );
}
void
CollectionScanner::Scanner::error( const QString &str )
{
QTextStream stream( stderr );
stream << str << endl;
stream.flush();
// Nothing else to do, so we exit directly
::exit( 0 );
}
/** This function is called by Amarok to verify that Amarok an Scanner versions match */
void
CollectionScanner::Scanner::displayVersion()
{
QTextStream stream( stdout );
stream << AMAROK_VERSION << endl;
stream.flush();
// Nothing else to do, so we exit directly
::exit( 0 );
}
void
CollectionScanner::Scanner::displayHelp( const QString &error )
{
QTextStream stream( error.isEmpty() ? stdout : stderr );
stream << error
<< tr( "Amarok Collection Scanner\n"
"Scans directories and outputs a xml file with the results.\n"
"For more information see http://community.kde.org/Amarok/Development/BatchMode\n\n"
"Usage: amarokcollectionscanner [options] <Folder(s)>\n"
"User-modifiable Options:\n"
"<Folder(s)> : list of folders to scan\n"
"-h, --help : This help text\n"
"-v, --version : Print the version of this tool\n"
"-r, --recursive : Scan folders recursively\n"
"-i, --incremental : Incremental scan (modified folders only)\n"
"-s, --restart : After a crash, restart the scanner in its last position\n"
" --idlepriority : Run at idle priority\n"
" --sharedmemory <key> : A shared memory segment to be used for restarting a scan\n"
" --newer <path> : Only scan directories if modification time is newer than <path>\n"
" Only useful in incremental scan mode\n"
" --batch <path> : Add the directories from the batch xml file\n"
" batch file format should look like this:\n"
" <scanner>\n"
" <directory>\n"
" <path>/absolute/path/of/directory</path>\n"
" <mtime>1234</mtime> (this is optional)\n"
" </directory>\n"
" </scanner>\n"
" You can also use a previous scan result for that.\n"
)
<< endl;
stream.flush();
::exit(0);
}