/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtspotifymain.h" #include "logindialog.h" #include "qtplaylist.h" #include "storedplaylistmodel.h" #include "coverdatabase.h" #include "searchdialog.h" #include "searchmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "despotify_cpp.h" Q_DECLARE_METATYPE(track *) namespace { class SelectTrackEvent: public QEvent { public: SelectTrackEvent(track *t) : QEvent(QEvent::User), m_track(t) {} track *selectedTrack() const { return m_track; } private: track *m_track; }; } static void callback(despotify_session *session, int signal, void *data, void *callbackData) { if (callbackData == 0 || data == 0 || session == 0) return; switch (signal) { case DESPOTIFY_NEW_TRACK: { track *t = reinterpret_cast(data); QtSpotifyMain *qsm = reinterpret_cast(session->client_callback_data); QCoreApplication::postEvent(qsm, new SelectTrackEvent(t)); } }; } QtSpotifyMain::QtSpotifyMain(QWidget *parent) : QMainWindow(parent), m_session(0), m_machine(new QStateMachine), m_debugging(false), m_authenticationWatcher(0), m_retrievingPlayListWatcher(0) { m_session = despotify_init_client(callback, this, true, true); if (m_session != 0) { initUi(); initWatchers(); initMachine(); } else { qWarning("Couldn't initialize despotify"); } } QtSpotifyMain::~QtSpotifyMain() { QSettings settings("qtspotify", "qtspotify"); settings.setValue(QString::fromLatin1("debuggingOutput"), m_ui.actionDebuggingOutput->isChecked()); if (m_session != 0) { endSession(); } } void QtSpotifyMain::initUi() { m_ui.setupUi(this); m_logInDialog = new LogInDialog(this); QSettings settings("qtspotify", "qtspotify"); bool debuggingOutput = settings.value(QString::fromLatin1("debuggingOutput")).toBool(); setDebugging(debuggingOutput); m_ui.actionDebuggingOutput->setChecked(debuggingOutput); connect(m_ui.actionDebuggingOutput, SIGNAL(toggled(bool)), this, SLOT(setDebugging(bool))); QString userName = settings.value(QString::fromLatin1("userName")).toString(); QString password = settings.value(QString::fromLatin1("password")).toString(); if (userName.isEmpty() || password.isEmpty()) { m_logInDialog->setRememberSettings(false); } else { m_logInDialog->setRememberSettings(true); m_logInDialog->setUserName(userName); m_logInDialog->setPassword(password); } setStatusBar(new MyStatusBar(this)); m_ui.searchListView->setModel(new SearchModel(m_session, m_ui.searchListView)); m_coverDatabase = new CoverDatabase(m_session, this); connect(m_coverDatabase, SIGNAL(coverLoaded(QPixmap)), m_ui.coverArtLabel, SLOT(setPixmap(QPixmap))); connect(m_ui.actionSearch, SIGNAL(triggered()), this, SLOT(search())); connect(m_ui.searchMoreButton, SIGNAL(clicked()), this, SLOT(searchMore())); connect(m_ui.skipSongButton, SIGNAL(clicked()), this, SLOT(skip())); connect(m_ui.previousButton, SIGNAL(clicked()), this, SLOT(previous())); connect(m_ui.playList, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(playlistItemActivated())); connect(m_ui.playList, SIGNAL(activated(QModelIndex)), this, SLOT(playlistActivated(QModelIndex))); } void QtSpotifyMain::playlistActivated(const QModelIndex &index) { if (index.column() == 0) { // Artist } else if (index.column() == 2) { // Album } } void QtSpotifyMain::searchMore() { QModelIndex idx = m_ui.searchListView->currentIndex(); QVariant variant = idx.data(Qt::UserRole); QtPlaylist *pl = variant.value(); if (pl != 0) { pl->searchMore(); emit searched(); } } void QtSpotifyMain::skip() { QTreeWidgetItem *item = m_ui.playList->currentItem(); if (item == 0) return; int idx = m_ui.playList->indexOfTopLevelItem(item); if (idx < 0) return; if (++idx >= m_ui.playList->topLevelItemCount()) return; m_ui.playList->setCurrentItem(m_ui.playList->topLevelItem(idx)); emit playlistItemActivated(); } void QtSpotifyMain::previous() { QTreeWidgetItem *item = m_ui.playList->currentItem(); if (item == 0) return; int idx = m_ui.playList->indexOfTopLevelItem(item) - 1; if (idx < 0) return; m_ui.playList->setCurrentItem(m_ui.playList->topLevelItem(idx)); emit playlistItemActivated(); } void QtSpotifyMain::initWatchers() { m_authenticationWatcher = new QFutureWatcher(this); connect(m_authenticationWatcher, SIGNAL(finished()), this, SLOT(decideLoginResult())); m_retrievingPlayListWatcher = new QFutureWatcher(this); connect(m_retrievingPlayListWatcher, SIGNAL(finished()), this, SLOT(populateSearchBox())); } bool QtSpotifyMain::event(QEvent *e) { if (e->type() == QEvent::User) { SelectTrackEvent *ste = static_cast(e); selectTrack(ste->selectedTrack()); return true; } return QMainWindow::event(e); } void QtSpotifyMain::selectTrack(track *newTrack) { debug(tr("Selecting new track, trying to find it in the playlist")); for (int i=0; itopLevelItemCount(); ++i) { QVariant data = m_ui.playList->topLevelItem(i)->data(0, Qt::UserRole); track *t = data.value(); if (t == newTrack) { m_ui.playList->setCurrentItem(m_ui.playList->topLevelItem(i)); setNewTrack(t); return; } } // Playlist changed debug(tr("Track could not be found")); } void QtSpotifyMain::retrievePlayLists() { debug(tr("Retrieving playlists")); QFuture retrieving = QtConcurrent::run(despotify_get_stored_playlists, m_session); m_retrievingPlayListWatcher->setFuture(retrieving); } void QtSpotifyMain::selectSearchOrPlaylist(QListView *listView) { m_ui.playList->clear(); QModelIndex idx = listView->currentIndex(); if (idx.isValid()) { QVariant data = idx.data(Qt::UserRole); QtPlaylist *pl = data.value(); if (pl != 0) { debug(tr("Selecting playlist '%1'").arg(idx.data(Qt::DisplayRole).toString())); setPlaylist(pl->tracks()); return; } } else { debug(tr("Selected playlist index is not valid")); } } void QtSpotifyMain::selectPlayList() { selectSearchOrPlaylist(m_ui.storedPlaylists); } void QtSpotifyMain::selectSearch() { selectSearchOrPlaylist(m_ui.searchListView); } void QtSpotifyMain::populateSearchBox() { debug(tr("Populating search box")); playlist *rootPlaylist = m_retrievingPlayListWatcher->result(); if (rootPlaylist == 0) debug(tr("Cannot get stored playlists, error==%1").arg(QString::fromUtf8(m_session->last_error))); m_ui.storedPlaylists->setModel(new StoredPlaylistModel(m_session, rootPlaylist, m_ui.storedPlaylists)); } void QtSpotifyMain::search() { SearchDialog searchDialog; if (searchDialog.exec() == QDialog::Accepted) { QString searchTerm = searchDialog.searchTerm(); if (!searchTerm.isEmpty()) { QAbstractItemModel *model = m_ui.searchListView->model(); SearchModel *searchModel = qobject_cast(model); if (searchModel != 0) { searchModel->addSearch(searchTerm); m_ui.searchListView->setCurrentIndex(searchModel->index(searchModel->rowCount()-1)); emit searched(); } } } } void QtSpotifyMain::stop() { debug(tr("Stopping")); m_audio.pause(); despotify_stop(m_session); } void QtSpotifyMain::pause() { debug(tr("Pausing")); m_audio.pause(); } void QtSpotifyMain::resume() { debug(tr("Resuming")); m_audio.play(); } void QtSpotifyMain::play() { if (m_ui.playList->currentItem() == 0) { qWarning("Nothing to play"); return; } QVariant data = m_ui.playList->currentItem()->data(0, Qt::UserRole); track *t = data.value(); if (t != 0) { m_audio.pause(); debug(tr("Playing '%1'").arg(t->title)); if (!despotify_play(m_session, t, true)) { QMessageBox::information(this, tr("Error when playing track"), tr("Despotify error: %1").arg(QString::fromUtf8(m_session->last_error))); } else { m_audio.play(); setNewTrack(t); } } else { qWarning("No track connected to current item"); } } void QtSpotifyMain::initPlayingState(QState *playingState) { QState *notPaused = new QState(playingState); { notPaused->setObjectName("notPaused"); notPaused->assignProperty(m_ui.playButton, "text", tr("Pause")); playingState->setInitialState(notPaused); } QState *paused = new QState(playingState); { paused->assignProperty(m_ui.playButton, "text", tr("Play")); paused->setObjectName("paused"); connect(paused, SIGNAL(entered()), this, SLOT(pause())); connect(paused, SIGNAL(exited()), this, SLOT(resume())); } notPaused->addTransition(m_ui.playButton, SIGNAL(clicked()), paused); paused->addTransition(m_ui.playButton, SIGNAL(clicked()), notPaused); } void QtSpotifyMain::decideLoginResult() { if (m_authenticationWatcher->result()) { debug(tr("Login succeeded")); m_audio.setDespotifySession(m_session); emit loggedIn(); } else { QMessageBox::warning(this, tr("Failed to log in"), tr("Sorry, I failed to log in with your credentials.\n" "Despotify error: %1").arg(QString::fromUtf8(m_session->last_error))); emit loginFailed(); } } void QtSpotifyMain::initPlayBackHandlingState(QState *playBackHandlingState) { QState *stoppedState = new QState(playBackHandlingState); { stoppedState->assignProperty(m_ui.albumLabel, "text", QString()); stoppedState->assignProperty(m_ui.artistLabel, "text", QString()); stoppedState->assignProperty(m_ui.songLabel, "text", QString()); stoppedState->assignProperty(m_ui.coverArtLabel, "pixmap", QPixmap()); stoppedState->assignProperty(m_ui.coverArtLabel, "text", QString()); stoppedState->assignProperty(m_ui.playButton, "enabled", true); stoppedState->assignProperty(m_ui.skipSongButton, "enabled", false); stoppedState->assignProperty(m_ui.previousButton, "enabled", false); stoppedState->setObjectName("stoppedState"); connect(stoppedState, SIGNAL(entered()), this, SLOT(stop())); playBackHandlingState->setInitialState(stoppedState); } QState *playingState = new QState(playBackHandlingState); { playingState->setObjectName("playingState"); playingState->assignProperty(m_ui.topLevelTabs, "currentIndex", 2); connect(playingState, SIGNAL(entered()), this, SLOT(play())); playingState->assignProperty(m_ui.playButton, "enabled", true); playingState->assignProperty(m_ui.skipSongButton, "enabled", true); playingState->assignProperty(m_ui.previousButton, "enabled", false); initPlayingState(playingState); } stoppedState->addTransition(this, SIGNAL(playlistItemActivated()), playingState); playingState->addTransition(this, SIGNAL(playlistItemActivated()), playingState); } void QtSpotifyMain::initLoggedInState(QState *loggedIn) { QState *playListHandlingState = new QState(loggedIn); { playListHandlingState->setObjectName("playListHandlingState"); initPlayListHandlingState(playListHandlingState); } QState *playBackHandlingState = new QState(loggedIn); { playBackHandlingState->setObjectName("playBackHandlingState"); initPlayBackHandlingState(playBackHandlingState); } } void QtSpotifyMain::initIdleState(QState *idle) { QState *requestedRetrieve = new QState(idle); { requestedRetrieve->assignProperty(m_ui.storedPlaylists, "enabled", false); requestedRetrieve->assignProperty(m_ui.searchListView, "enabled", false); requestedRetrieve->assignProperty(m_ui.actionSearch, "enabled", false); requestedRetrieve->assignProperty(statusBar(), "statusMessage", tr("Retrieving playlists, please wait...")); connect(requestedRetrieve, SIGNAL(entered()), this, SLOT(retrievePlayLists())); requestedRetrieve->setObjectName("requestedRetrieve"); } QHistoryState *historyState = new QHistoryState(idle); { historyState->setDefaultState(requestedRetrieve); idle->setInitialState(historyState); } QState *retrieved = new QState(idle); { retrieved->assignProperty(m_ui.storedPlaylists, "enabled", true); retrieved->assignProperty(m_ui.searchListView, "enabled", true); retrieved->assignProperty(m_ui.actionSearch, "enabled", true); retrieved->assignProperty(statusBar(), "statusMessage", tr("Go ahead")); retrieved->setObjectName("retrieved"); } requestedRetrieve->addTransition(m_retrievingPlayListWatcher, SIGNAL(finished()), retrieved); } void QtSpotifyMain::initPlayListHandlingState(QState *playListHandlingState) { QState *idle = new QState(playListHandlingState); { idle->setObjectName("idle"); idle->assignProperty(m_ui.actionSearch, "enabled", true); idle->assignProperty(m_ui.playList, "enabled", true); initIdleState(idle); playListHandlingState->setInitialState(idle); } QState *playListSelected = new QState(playListHandlingState); { playListSelected->setObjectName("playListSelected"); playListSelected->assignProperty(statusBar(), "statusMessage", tr("Fetching playlist contents")); connect(playListSelected, SIGNAL(entered()), this, SLOT(selectPlayList())); } QState *searchSelected = new QState(playListHandlingState); { searchSelected->setObjectName("searchSelected"); searchSelected->assignProperty(statusBar(), "statusMessage", tr("Searching...")); connect(searchSelected, SIGNAL(entered()), this, SLOT(selectSearch())); } idle->addTransition(m_ui.storedPlaylists, SIGNAL(activated(QModelIndex)), playListSelected); idle->addTransition(m_ui.searchListView, SIGNAL(activated(QModelIndex)), searchSelected); idle->addTransition(this, SIGNAL(searched()), searchSelected); playListSelected->addTransition(idle); searchSelected->addTransition(idle); } void QtSpotifyMain::debug(const QString &text) { if (m_debugging) qDebug() << text; } void QtSpotifyMain::initLoggingInState(QState *loggingInState) { QState *logInDialogShown = new QState(loggingInState); { connect(logInDialogShown, SIGNAL(entered()), m_logInDialog, SLOT(exec())); logInDialogShown->assignProperty(statusBar(), "statusMessage", tr("Waiting for input")); logInDialogShown->setObjectName("logInDialogShown"); loggingInState->setInitialState(logInDialogShown); } QState *logInDialogAccepted = new QState(loggingInState); { logInDialogAccepted->assignProperty(statusBar(), "statusMessage", tr("Trying to log in...")); logInDialogAccepted->setObjectName("logInDialogAccepted"); connect(logInDialogAccepted, SIGNAL(entered()), this, SLOT(logIn())); } logInDialogShown->addTransition(m_logInDialog, SIGNAL(accepted()), logInDialogAccepted); } void QtSpotifyMain::initMachine() { QState *notLoggedInState = new QState(m_machine); { notLoggedInState->assignProperty(m_ui.actionLogIn, "enabled", true); notLoggedInState->assignProperty(m_ui.actionSearch, "enabled", false); notLoggedInState->assignProperty(m_ui.centralwidget, "enabled", false); notLoggedInState->assignProperty(statusBar(), "statusMessage", tr("Select 'log in' from menu to log in")); notLoggedInState->assignProperty(this, "debuggingMessage", tr("Entered 'notLoggedInState'")); notLoggedInState->setObjectName("notLoggedInState"); } QState *loggingInState = new QState(m_machine); { loggingInState->assignProperty(m_ui.actionLogIn, "enabled", false); loggingInState->assignProperty(m_ui.actionSearch, "enabled", false); loggingInState->assignProperty(m_ui.centralwidget, "enabled", false); loggingInState->setObjectName("loggingInState"); loggingInState->assignProperty(this, "debuggingMessage", tr("Entered 'loggingInState'")); initLoggingInState(loggingInState); m_machine->setInitialState(loggingInState); } QState *loggedInState = new QState(QState::ParallelStates, m_machine); { loggedInState->assignProperty(m_ui.actionLogIn, "enabled", false); loggedInState->assignProperty(m_ui.centralwidget, "enabled", true); loggedInState->setObjectName("loggedInState"); loggedInState->assignProperty(this, "debuggingMessage", tr("Entered 'loggedInState'")); initLoggedInState(loggedInState); } notLoggedInState->addTransition(m_ui.actionLogIn, SIGNAL(triggered()), loggingInState); loggingInState->addTransition(m_logInDialog, SIGNAL(rejected()), notLoggedInState); loggingInState->addTransition(this, SIGNAL(loginFailed()), notLoggedInState); loggingInState->addTransition(this, SIGNAL(loggedIn()), loggedInState); m_machine->start(); } void QtSpotifyMain::setPlaylist(QList tracks) { debug(tr("Setting new playlist")); m_ui.playList->clear(); foreach (track *t, tracks) { QTreeWidgetItem *w = new QTreeWidgetItem(); w->setData(0, Qt::DisplayRole, QString::fromUtf8(t->artist->name)); w->setData(1, Qt::DisplayRole, QString::fromUtf8(t->title)); w->setData(2, Qt::DisplayRole, QString::fromUtf8(t->album)); w->setData(0, Qt::UserRole, QVariant::fromValue(t)); QFont font = m_ui.playList->font(); font.setUnderline(true); w->setFont(0, font); w->setFont(2, font); m_ui.playList->addTopLevelItem(w); } } void QtSpotifyMain::endSession() { debug("Ending session"); qDeleteAll(m_searches); despotify_exit(m_session); } void QtSpotifyMain::logIn() { if (m_session == 0) { emit loginFailed(); return; } QString userName = m_logInDialog->userName(); QString password = m_logInDialog->password(); QSettings settings("qtspotify", "qtspotify"); if (m_logInDialog->rememberSettings()) { debug("Saving settings"); settings.setValue(QString::fromLatin1("userName"), userName); settings.setValue(QString::fromLatin1("password"), password); } else { debug("Clearing settings"); settings.remove(QString::fromLatin1("userName")); settings.remove(QString::fromLatin1("password")); } debug(tr("Logging in as user '%1'").arg(userName)); QFuture authentication = QtConcurrent::run(despotify_authenticate, m_session, userName.toLocal8Bit(), password.toLocal8Bit()); m_authenticationWatcher->setFuture(authentication); } void QtSpotifyMain::setNewTrack(track *t) { debug(tr("Setting new track '%1'").arg(QString::fromUtf8(t->title))); m_ui.artistLabel->setText(QString::fromUtf8(t->artist->name)); m_ui.albumLabel->setText(QLatin1Char('(') + QString::fromUtf8(t->album) + QLatin1Char(')')); m_ui.songLabel->setText(QString::fromUtf8(t->title)); m_ui.coverArtLabel->setPixmap(QPixmap()); m_ui.coverArtLabel->setText(QString()); m_coverDatabase->loadCover(reinterpret_cast(t->cover_id)); }