Refactor the last.fm, magnatune, spotify and di.fm settings pages - moving the login state display into a separate widget.

This commit is contained in:
David Sansome 2011-08-27 22:01:28 +01:00
parent 143bbb4019
commit 13fc24f6c6
31 changed files with 605 additions and 383 deletions

View File

@ -1,2 +1,2 @@
# Increment this whenever the user needs to download a new blob # Increment this whenever the user needs to download a new blob
set(SPOTIFY_BLOB_VERSION 2) set(SPOTIFY_BLOB_VERSION 3)

View File

@ -320,8 +320,7 @@
<file>schema/schema-33.sql</file> <file>schema/schema-33.sql</file>
<file>spotify-core-logo-128x128.png</file> <file>spotify-core-logo-128x128.png</file>
<file>icons/22x22/dialog-warning.png</file> <file>icons/22x22/dialog-warning.png</file>
<file>icons/22x22/task-complete.png</file> <file>icons/22x22/dialog-ok-apply.png</file>
<file>icons/22x22/task-reject.png</file>
<file>schema/schema-34.sql</file> <file>schema/schema-34.sql</file>
<file>pythonlibs/uic/driver.py</file> <file>pythonlibs/uic/driver.py</file>
<file>pythonlibs/uic/exceptions.py</file> <file>pythonlibs/uic/exceptions.py</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -108,12 +108,25 @@ void SpotifyClient::Init(quint16 port) {
void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) { void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
const bool success = error == SP_ERROR_OK; const bool success = error == SP_ERROR_OK;
protobuf::LoginResponse_Error error_code = protobuf::LoginResponse_Error_Other;
if (!success) { if (!success) {
qLog(Warning) << "Failed to login" << sp_error_message(error); qLog(Warning) << "Failed to login" << sp_error_message(error);
} }
me->SendLoginCompleted(success, sp_error_message(error)); switch (error) {
case SP_ERROR_BAD_USERNAME_OR_PASSWORD:
error_code = protobuf::LoginResponse_Error_BadUsernameOrPassword;
break;
case SP_ERROR_USER_BANNED:
error_code = protobuf::LoginResponse_Error_UserBanned;
break;
case SP_ERROR_USER_NEEDS_PREMIUM :
error_code = protobuf::LoginResponse_Error_UserNeedsPremium;
break;
}
me->SendLoginCompleted(success, sp_error_message(error), error_code);
if (success) { if (success) {
sp_playlistcontainer_add_callbacks( sp_playlistcontainer_add_callbacks(
@ -208,7 +221,7 @@ void SpotifyClient::Login(const QString& username, const QString& password) {
sp_error error = sp_session_create(&spotify_config_, &session_); sp_error error = sp_session_create(&spotify_config_, &session_);
if (error != SP_ERROR_OK) { if (error != SP_ERROR_OK) {
qLog(Warning) << "Failed to create session" << sp_error_message(error); qLog(Warning) << "Failed to create session" << sp_error_message(error);
SendLoginCompleted(false, sp_error_message(error)); SendLoginCompleted(false, sp_error_message(error), protobuf::LoginResponse_Error_Other);
return; return;
} }
@ -217,13 +230,18 @@ void SpotifyClient::Login(const QString& username, const QString& password) {
sp_session_login(session_, username.toUtf8().constData(), password.toUtf8().constData()); sp_session_login(session_, username.toUtf8().constData(), password.toUtf8().constData());
} }
void SpotifyClient::SendLoginCompleted(bool success, const QString& error) { void SpotifyClient::SendLoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code) {
protobuf::SpotifyMessage message; protobuf::SpotifyMessage message;
protobuf::LoginResponse* response = message.mutable_login_response(); protobuf::LoginResponse* response = message.mutable_login_response();
response->set_success(success); response->set_success(success);
response->set_error(DataCommaSizeFromQString(error)); response->set_error(DataCommaSizeFromQString(error));
if (!success) {
response->set_error_code(error_code);
}
handler_->SendMessage(message); handler_->SendMessage(message);
} }

View File

@ -52,7 +52,8 @@ private slots:
void MediaSocketDisconnected(); void MediaSocketDisconnected();
private: private:
void SendLoginCompleted(bool success, const QString& error); void SendLoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code);
void SendPlaybackError(const QString& error); void SendPlaybackError(const QString& error);
// Spotify session callbacks. // Spotify session callbacks.

View File

@ -30,8 +30,16 @@ message LoginRequest {
} }
message LoginResponse { message LoginResponse {
enum Error {
BadUsernameOrPassword = 1;
UserBanned = 2;
UserNeedsPremium = 3;
Other = 4;
}
required bool success = 1; required bool success = 1;
required string error = 2; required string error = 2;
optional Error error_code = 3 [default = Other];
} }
message Playlists { message Playlists {

View File

@ -275,6 +275,7 @@ set(SOURCES
widgets/groupediconview.cpp widgets/groupediconview.cpp
widgets/lineedit.cpp widgets/lineedit.cpp
widgets/linetextedit.cpp widgets/linetextedit.cpp
widgets/loginstatewidget.cpp
widgets/multiloadingindicator.cpp widgets/multiloadingindicator.cpp
widgets/nowplayingwidget.cpp widgets/nowplayingwidget.cpp
widgets/osd.cpp widgets/osd.cpp
@ -482,6 +483,7 @@ set(HEADERS
widgets/groupediconview.h widgets/groupediconview.h
widgets/lineedit.h widgets/lineedit.h
widgets/linetextedit.h widgets/linetextedit.h
widgets/loginstatewidget.h
widgets/multiloadingindicator.h widgets/multiloadingindicator.h
widgets/nowplayingwidget.h widgets/nowplayingwidget.h
widgets/osd.h widgets/osd.h
@ -567,6 +569,7 @@ set(UI
widgets/equalizerslider.ui widgets/equalizerslider.ui
widgets/errordialog.ui widgets/errordialog.ui
widgets/fileview.ui widgets/fileview.ui
widgets/loginstatewidget.ui
widgets/osdpretty.ui widgets/osdpretty.ui
widgets/trackslider.ui widgets/trackslider.ui

View File

@ -28,6 +28,12 @@ DigitallyImportedSettingsPage::DigitallyImportedSettingsPage(SettingsDialog* dia
{ {
ui_->setupUi(this); ui_->setupUi(this);
setWindowIcon(QIcon(":/providers/digitallyimported-32.png")); setWindowIcon(QIcon(":/providers/digitallyimported-32.png"));
ui_->login_state->SetAccountTypeText(tr(
"You can listen for free without an account, but Premium members can "
"listen to higher quality streams without advertisements."));
ui_->login_state->SetAccountTypeVisible(true);
ui_->login_state->HideLoggedInState();
} }
DigitallyImportedSettingsPage::~DigitallyImportedSettingsPage() { DigitallyImportedSettingsPage::~DigitallyImportedSettingsPage() {

View File

@ -14,6 +14,9 @@
<string>Digitally Imported</string> <string>Digitally Imported</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_10"> <widget class="QGroupBox" name="groupBox_10">
<property name="sizePolicy"> <property name="sizePolicy">
@ -51,16 +54,6 @@
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_24">
<property name="text">
<string>You can &lt;b&gt;listen for free&lt;/b&gt; without an account, but Premium members can listen to &lt;b&gt;higher quality&lt;/b&gt; streams without advertisements.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="upgrade_link"> <widget class="QLabel" name="upgrade_link">
<property name="text"> <property name="text">
<string>&lt;a href=&quot;http://www.di.fm/premium/&quot;&gt;Upgrade to Premium now&lt;/a&gt;</string> <string>&lt;a href=&quot;http://www.di.fm/premium/&quot;&gt;Upgrade to Premium now&lt;/a&gt;</string>
@ -157,6 +150,14 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>LoginStateWidget</class>
<extends>QWidget</extends>
<header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@ -329,6 +329,8 @@ void LastFMService::UpdateSubscriberStatusFinished() {
Q_ASSERT(reply); Q_ASSERT(reply);
reply->deleteLater(); reply->deleteLater();
bool is_subscriber = false;
try { try {
const lastfm::XmlQuery lfm = lastfm::ws::parse(reply); const lastfm::XmlQuery lfm = lastfm::ws::parse(reply);
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@ -338,26 +340,20 @@ void LastFMService::UpdateSubscriberStatusFinished() {
connection_problems_ = false; connection_problems_ = false;
QString subscriber = lfm["user"]["subscriber"].text(); QString subscriber = lfm["user"]["subscriber"].text();
const bool is_subscriber = (subscriber.toInt() == 1); is_subscriber = (subscriber.toInt() == 1);
QSettings settings; QSettings settings;
settings.beginGroup(kSettingsGroup); settings.beginGroup(kSettingsGroup);
settings.setValue("Subscriber", is_subscriber); settings.setValue("Subscriber", is_subscriber);
qLog(Info) << lastfm::ws::Username << "Subscriber status:" << is_subscriber; qLog(Info) << lastfm::ws::Username << "Subscriber status:" << is_subscriber;
emit UpdatedSubscriberStatus(is_subscriber);
} catch (lastfm::ws::ParseError e) { } catch (lastfm::ws::ParseError e) {
qLog(Error) << "Last.fm parse error: " << e.enumValue(); qLog(Error) << "Last.fm parse error: " << e.enumValue();
if (e.enumValue() == lastfm::ws::MalformedResponse) { connection_problems_ = e.enumValue() == lastfm::ws::MalformedResponse;
// The connection to the server is unavailable
connection_problems_ = true;
emit UpdatedSubscriberStatus(false);
} else {
// Errors not related to connection
connection_problems_ = false;
}
} catch (std::runtime_error& e) { } catch (std::runtime_error& e) {
qLog(Error) << e.what(); qLog(Error) << e.what();
} }
emit UpdatedSubscriberStatus(is_subscriber);
} }
QUrl LastFMService::FixupUrl(const QUrl& url) { QUrl LastFMService::FixupUrl(const QUrl& url) {

View File

@ -27,32 +27,27 @@
#include <QMovie> #include <QMovie>
#include <QSettings> #include <QSettings>
// Use Qt specific icons, since freedesktop doesn't seem to have suitable icons.
const char* kSubscribedIcon = "task-complete";
const char* kNotSubscribedIcon = "dialog-warning";
const char* kWaitingIcon = ":spinner.gif";
LastFMSettingsPage::LastFMSettingsPage(SettingsDialog* dialog) LastFMSettingsPage::LastFMSettingsPage(SettingsDialog* dialog)
: SettingsPage(dialog), : SettingsPage(dialog),
service_(static_cast<LastFMService*>(InternetModel::ServiceByName("Last.fm"))), service_(static_cast<LastFMService*>(InternetModel::ServiceByName("Last.fm"))),
ui_(new Ui_LastFMSettingsPage), ui_(new Ui_LastFMSettingsPage),
loading_icon_(new QMovie(kWaitingIcon, QByteArray(), this)),
waiting_for_auth_(false) waiting_for_auth_(false)
{ {
ui_->setupUi(this); ui_->setupUi(this);
ui_->busy->hide();
// Icons // Icons
setWindowIcon(QIcon(":/last.fm/as.png")); setWindowIcon(QIcon(":/last.fm/as.png"));
ui_->sign_out->setIcon(IconLoader::Load("list-remove"));
ui_->warn_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(16));
ui_->warn_icon->setMinimumSize(16, 16);
connect(service_, SIGNAL(AuthenticationComplete(bool)), SLOT(AuthenticationComplete(bool))); connect(service_, SIGNAL(AuthenticationComplete(bool)), SLOT(AuthenticationComplete(bool)));
connect(service_, SIGNAL(UpdatedSubscriberStatus(bool)), SLOT(UpdatedSubscriberStatus(bool))); connect(service_, SIGNAL(UpdatedSubscriberStatus(bool)), SLOT(UpdatedSubscriberStatus(bool)));
connect(ui_->sign_out, SIGNAL(clicked()), SLOT(SignOut())); connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login())); connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
ui_->login_state->AddCredentialField(ui_->username);
ui_->login_state->AddCredentialField(ui_->password);
ui_->login_state->AddCredentialGroup(ui_->groupBox);
ui_->username->setMinimumWidth(QFontMetrics(QFont()).width("WWWWWWWWWWWW")); ui_->username->setMinimumWidth(QFontMetrics(QFont()).width("WWWWWWWWWWWW"));
resize(sizeHint()); resize(sizeHint());
} }
@ -62,9 +57,9 @@ LastFMSettingsPage::~LastFMSettingsPage() {
} }
void LastFMSettingsPage::Login() { void LastFMSettingsPage::Login() {
ui_->busy->show();
waiting_for_auth_ = true; waiting_for_auth_ = true;
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
service_->Authenticate(ui_->username->text(), ui_->password->text()); service_->Authenticate(ui_->username->text(), ui_->password->text());
} }
@ -72,7 +67,6 @@ void LastFMSettingsPage::AuthenticationComplete(bool success) {
if (!waiting_for_auth_) if (!waiting_for_auth_)
return; // Wasn't us that was waiting for auth return; // Wasn't us that was waiting for auth
ui_->busy->hide();
waiting_for_auth_ = false; waiting_for_auth_ = false;
if (success) { if (success) {
@ -93,8 +87,6 @@ void LastFMSettingsPage::Load() {
ui_->love_ban_->setChecked(service_->AreButtonsVisible()); ui_->love_ban_->setChecked(service_->AreButtonsVisible());
ui_->scrobble_button->setChecked(service_->IsScrobbleButtonVisible()); ui_->scrobble_button->setChecked(service_->IsScrobbleButtonVisible());
ui_->icon->setMovie(loading_icon_);
loading_icon_->start();
if (service_->IsAuthenticated()) { if (service_->IsAuthenticated()) {
service_->UpdateSubscriberStatus(); service_->UpdateSubscriberStatus();
} }
@ -103,21 +95,15 @@ void LastFMSettingsPage::Load() {
} }
void LastFMSettingsPage::UpdatedSubscriberStatus(bool is_subscriber) { void LastFMSettingsPage::UpdatedSubscriberStatus(bool is_subscriber) {
const char* icon_path = is_subscriber ? kSubscribedIcon : kNotSubscribedIcon; ui_->login_state->SetAccountTypeVisible(!is_subscriber);
ui_->icon->setPixmap(IconLoader::Load(icon_path).pixmap(16));
loading_icon_->stop();
if (is_subscriber) { if (!is_subscriber) {
ui_->subscriber_warning->hide();
ui_->warn_icon->hide();
} else {
ui_->warn_icon->show();
if (service_->HasConnectionProblems()) { if (service_->HasConnectionProblems()) {
ui_->subscriber_warning->setText( ui_->login_state->SetAccountTypeText(
tr("Clementine couldn't fetch your subscription status since there are problems " tr("Clementine couldn't fetch your subscription status since there are problems "
"with your connection. Played tracks will be cached and sent later to Last.fm.")); "with your connection. Played tracks will be cached and sent later to Last.fm."));
} else { } else {
ui_->subscriber_warning->setText( ui_->login_state->SetAccountTypeText(
tr("You will not be able to play Last.fm radio stations " tr("You will not be able to play Last.fm radio stations "
"as you are not a Last.fm subscriber.")); "as you are not a Last.fm subscriber."));
} }
@ -135,7 +121,7 @@ void LastFMSettingsPage::Save() {
service_->ReloadSettings(); service_->ReloadSettings();
} }
void LastFMSettingsPage::SignOut() { void LastFMSettingsPage::Logout() {
ui_->username->clear(); ui_->username->clear();
ui_->password->clear(); ui_->password->clear();
RefreshControls(false); RefreshControls(false);
@ -144,19 +130,15 @@ void LastFMSettingsPage::SignOut() {
} }
void LastFMSettingsPage::RefreshControls(bool authenticated) { void LastFMSettingsPage::RefreshControls(bool authenticated) {
ui_->groupBox->setVisible(!authenticated); ui_->login_state->SetLoggedIn(authenticated ? LoginStateWidget::LoggedIn
ui_->sign_out->setVisible(authenticated); : LoginStateWidget::LoggedOut,
if (authenticated) { lastfm::ws::Username);
ui_->status->setText(QString(tr("You're logged in as <b>%1</b>")).arg(lastfm::ws::Username)); ui_->login_state->SetAccountTypeVisible(!authenticated);
} else {
ui_->icon->setPixmap(IconLoader::Load("task-reject").pixmap(16));
ui_->status->setText(tr("Please fill in the blanks to login into Last.fm"));
ui_->subscriber_warning->setText( if (!authenticated) {
ui_->login_state->SetAccountTypeText(
tr("You can scrobble tracks for free, but only " tr("You can scrobble tracks for free, but only "
"<span style=\" font-weight:600;\">paid subscribers</span> " "<span style=\" font-weight:600;\">paid subscribers</span> "
"can stream Last.fm radio from Clementine.")); "can stream Last.fm radio from Clementine."));
ui_->subscriber_warning->show();
ui_->warn_icon->show();
} }
} }

View File

@ -36,7 +36,7 @@ public:
private slots: private slots:
void Login(); void Login();
void AuthenticationComplete(bool success); void AuthenticationComplete(bool success);
void SignOut(); void Logout();
void UpdatedSubscriberStatus(bool is_subscriber); void UpdatedSubscriberStatus(bool is_subscriber);
private: private:

View File

@ -15,64 +15,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QFrame" name="frame"> <widget class="LoginStateWidget" name="login_state" native="true"/>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="status">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sign_out">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Sign out</string>
</property>
</widget>
</item>
</layout>
</widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
@ -170,121 +113,24 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="warn_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="subscriber_warning">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>BusyIndicator</class> <class>LoginStateWidget</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>widgets/busyindicator.h</header> <header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>username</tabstop> <tabstop>username</tabstop>
<tabstop>password</tabstop> <tabstop>password</tabstop>
<tabstop>login</tabstop> <tabstop>login</tabstop>
<tabstop>sign_out</tabstop>
<tabstop>scrobble</tabstop> <tabstop>scrobble</tabstop>
<tabstop>love_ban_</tabstop> <tabstop>love_ban_</tabstop>
<tabstop>scrobble_button</tabstop> <tabstop>scrobble_button</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections> <connections/>
<connection>
<sender>username</sender>
<signal>returnPressed()</signal>
<receiver>login</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>332</x>
<y>104</y>
</hint>
<hint type="destinationlabel">
<x>706</x>
<y>103</y>
</hint>
</hints>
</connection>
<connection>
<sender>password</sender>
<signal>returnPressed()</signal>
<receiver>login</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>719</x>
<y>137</y>
</hint>
<hint type="destinationlabel">
<x>725</x>
<y>108</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@ -30,23 +30,28 @@
MagnatuneSettingsPage::MagnatuneSettingsPage(SettingsDialog* dialog) MagnatuneSettingsPage::MagnatuneSettingsPage(SettingsDialog* dialog)
: SettingsPage(dialog), : SettingsPage(dialog),
credentials_changed_(false),
network_(new NetworkAccessManager(this)), network_(new NetworkAccessManager(this)),
ui_(new Ui_MagnatuneSettingsPage) ui_(new Ui_MagnatuneSettingsPage),
logged_in_(false)
{ {
ui_->setupUi(this); ui_->setupUi(this);
ui_->busy->hide();
setWindowIcon(QIcon(":/providers/magnatune.png")); setWindowIcon(QIcon(":/providers/magnatune.png"));
connect(ui_->membership, SIGNAL(currentIndexChanged(int)), SLOT(MembershipChanged(int))); connect(ui_->membership, SIGNAL(currentIndexChanged(int)), SLOT(MembershipChanged(int)));
connect(network_, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), connect(network_, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
SLOT(AuthenticationRequired(QNetworkReply*, QAuthenticator*))); SLOT(AuthenticationRequired(QNetworkReply*, QAuthenticator*)));
connect(ui_->username, SIGNAL(textEdited(const QString&)),
SLOT(CredentialsChanged()));
connect(ui_->password, SIGNAL(textEdited(const QString&)),
SLOT(CredentialsChanged()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login())); connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
ui_->login_state->AddCredentialField(ui_->username);
ui_->login_state->AddCredentialField(ui_->password);
ui_->login_state->AddCredentialGroup(ui_->login_container);
ui_->login_state->SetAccountTypeText(tr(
"You can listen to Magnatune songs for free without an account. "
"Purchasing a membership removes the messages at the end of each track."));
} }
MagnatuneSettingsPage::~MagnatuneSettingsPage() { MagnatuneSettingsPage::~MagnatuneSettingsPage() {
@ -56,11 +61,14 @@ MagnatuneSettingsPage::~MagnatuneSettingsPage() {
const char* kMagnatuneDownloadValidateUrl = "http://download.magnatune.com/"; const char* kMagnatuneDownloadValidateUrl = "http://download.magnatune.com/";
const char* kMagnatuneStreamingValidateUrl = "http://streaming.magnatune.com/"; const char* kMagnatuneStreamingValidateUrl = "http://streaming.magnatune.com/";
void MagnatuneSettingsPage::Login() { void MagnatuneSettingsPage::UpdateLoginState() {
if (!credentials_changed_) ui_->login_state->SetLoggedIn(logged_in_ ? LoginStateWidget::LoggedIn
return; : LoginStateWidget::LoggedOut,
ui_->busy->show(); ui_->username->text());
ui_->login_state->SetAccountTypeVisible(!logged_in_);
}
void MagnatuneSettingsPage::Login() {
MagnatuneService::MembershipType type = MagnatuneService::MembershipType type =
MagnatuneService::MembershipType(ui_->membership->currentIndex()); MagnatuneService::MembershipType(ui_->membership->currentIndex());
@ -81,27 +89,33 @@ void MagnatuneSettingsPage::Login() {
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork); QNetworkRequest::AlwaysNetwork);
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
QNetworkReply* reply = network_->head(req); QNetworkReply* reply = network_->head(req);
connect(reply, SIGNAL(finished()), SLOT(LoginFinished())); connect(reply, SIGNAL(finished()), SLOT(LoginFinished()));
} }
void MagnatuneSettingsPage::Logout() {
logged_in_ = false;
UpdateLoginState();
}
void MagnatuneSettingsPage::LoginFinished() { void MagnatuneSettingsPage::LoginFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Q_ASSERT(reply); Q_ASSERT(reply);
reply->deleteLater(); reply->deleteLater();
ui_->busy->hide(); logged_in_ =
const bool success =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200;
credentials_changed_ = !success;
if (!success) { if (!logged_in_) {
QMessageBox::warning( QMessageBox::warning(
this, tr("Authentication failed"), tr("Your Magnatune credentials were incorrect")); this, tr("Authentication failed"), tr("Your Magnatune credentials were incorrect"));
} else { } else {
Save(); Save();
} }
ui_->login->setEnabled(!success);
UpdateLoginState();
} }
void MagnatuneSettingsPage::AuthenticationRequired( void MagnatuneSettingsPage::AuthenticationRequired(
@ -119,8 +133,11 @@ void MagnatuneSettingsPage::Load() {
ui_->username->setText(s.value("username").toString()); ui_->username->setText(s.value("username").toString());
ui_->password->setText(s.value("password").toString()); ui_->password->setText(s.value("password").toString());
ui_->format->setCurrentIndex(s.value("format", MagnatuneService::Format_Ogg).toInt()); ui_->format->setCurrentIndex(s.value("format", MagnatuneService::Format_Ogg).toInt());
logged_in_ = s.value("logged_in",
!ui_->username->text().isEmpty() &&
!ui_->password->text().isEmpty()).toBool();
credentials_changed_ = false; UpdateLoginState();
} }
void MagnatuneSettingsPage::Save() { void MagnatuneSettingsPage::Save() {
@ -131,6 +148,7 @@ void MagnatuneSettingsPage::Save() {
s.setValue("username", ui_->username->text()); s.setValue("username", ui_->username->text());
s.setValue("password", ui_->password->text()); s.setValue("password", ui_->password->text());
s.setValue("format", ui_->format->currentIndex()); s.setValue("format", ui_->format->currentIndex());
s.setValue("logged_in", logged_in_);
InternetModel::Service<MagnatuneService>()->ReloadSettings(); InternetModel::Service<MagnatuneService>()->ReloadSettings();
} }
@ -141,7 +159,3 @@ void MagnatuneSettingsPage::MembershipChanged(int value) {
ui_->login_container->setEnabled(enabled); ui_->login_container->setEnabled(enabled);
ui_->preferences_group->setEnabled(enabled); ui_->preferences_group->setEnabled(enabled);
} }
void MagnatuneSettingsPage::CredentialsChanged() {
credentials_changed_ = true;
}

View File

@ -37,15 +37,19 @@ public:
private slots: private slots:
void Login(); void Login();
void Logout();
void MembershipChanged(int value); void MembershipChanged(int value);
void LoginFinished(); void LoginFinished();
void AuthenticationRequired(QNetworkReply* reply, QAuthenticator* auth); void AuthenticationRequired(QNetworkReply* reply, QAuthenticator* auth);
void CredentialsChanged();
private: private:
bool credentials_changed_; void UpdateLoginState();
private:
NetworkAccessManager* network_; NetworkAccessManager* network_;
Ui_MagnatuneSettingsPage* ui_; Ui_MagnatuneSettingsPage* ui_;
bool logged_in_;
}; };
#endif // MAGNATUNESETTINGSPAGE_H #endif // MAGNATUNESETTINGSPAGE_H

View File

@ -14,12 +14,18 @@
<string>Magnatune</string> <string>Magnatune</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>Account details</string> <string>Account details</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_3"> <layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -47,16 +53,6 @@
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>You can listen to Magnatune songs for free without an account. Purchasing a membership removes the messages at the end of each track.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QWidget" name="login_container" native="true"> <widget class="QWidget" name="login_container" native="true">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
@ -166,42 +162,14 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>BusyIndicator</class> <class>LoginStateWidget</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>widgets/busyindicator.h</header> <header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>

View File

@ -98,7 +98,8 @@ void SpotifyServer::HandleMessage(const protobuf::SpotifyMessage& message) {
queued_messages_.clear(); queued_messages_.clear();
} }
emit LoginCompleted(response.success(), QStringFromStdString(response.error())); emit LoginCompleted(response.success(), QStringFromStdString(response.error()),
response.error_code());
} else if (message.has_playlists_updated()) { } else if (message.has_playlists_updated()) {
emit PlaylistsUpdated(message.playlists_updated()); emit PlaylistsUpdated(message.playlists_updated());
} else if (message.has_load_playlist_response()) { } else if (message.has_load_playlist_response()) {

View File

@ -50,7 +50,8 @@ public:
int server_port() const; int server_port() const;
signals: signals:
void LoginCompleted(bool success, const QString& error); void LoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code);
void PlaylistsUpdated(const protobuf::Playlists& playlists); void PlaylistsUpdated(const protobuf::Playlists& playlists);
void StarredLoaded(const protobuf::LoadPlaylistResponse& response); void StarredLoaded(const protobuf::LoadPlaylistResponse& response);

View File

@ -46,7 +46,8 @@ SpotifyService::SpotifyService(InternetModel* parent)
login_task_id_(0), login_task_id_(0),
pending_search_playlist_(NULL), pending_search_playlist_(NULL),
context_menu_(NULL), context_menu_(NULL),
search_delay_(new QTimer(this)) { search_delay_(new QTimer(this)),
login_state_(LoginState_OtherError) {
// Build the search path for the binary blob. // Build the search path for the binary blob.
// Look for one distributed alongside clementine first, then check in the // Look for one distributed alongside clementine first, then check in the
// user's home directory for any that have been downloaded. // user's home directory for any that have been downloaded.
@ -124,23 +125,43 @@ QModelIndex SpotifyService::GetCurrentIndex() {
} }
void SpotifyService::Login(const QString& username, const QString& password) { void SpotifyService::Login(const QString& username, const QString& password) {
delete server_; Logout();
delete blob_process_;
server_ = NULL;
blob_process_ = NULL;
EnsureServerCreated(username, password); EnsureServerCreated(username, password);
} }
void SpotifyService::LoginCompleted(bool success, const QString& error) { void SpotifyService::LoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code) {
if (login_task_id_) { if (login_task_id_) {
model()->task_manager()->SetTaskFinished(login_task_id_); model()->task_manager()->SetTaskFinished(login_task_id_);
login_task_id_ = 0; login_task_id_ = 0;
} }
login_state_ = LoginState_LoggedIn;
if (!success) { if (!success) {
QMessageBox::warning(NULL, tr("Spotify login error"), error, QMessageBox::Close); QMessageBox::warning(NULL, tr("Spotify login error"), error, QMessageBox::Close);
switch (error_code) {
case protobuf::LoginResponse_Error_BadUsernameOrPassword:
login_state_ = LoginState_BadCredentials;
break;
case protobuf::LoginResponse_Error_UserBanned:
login_state_ = LoginState_Banned;
break;
case protobuf::LoginResponse_Error_UserNeedsPremium:
login_state_ = LoginState_NoPremium;
break;
default:
login_state_ = LoginState_OtherError;
} }
}
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("login_state", login_state_);
emit LoginFinished(success); emit LoginFinished(success);
} }
@ -155,6 +176,13 @@ void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
} }
} }
void SpotifyService::ReloadSettings() {
QSettings s;
s.beginGroup(kSettingsGroup);
login_state_ = LoginState(s.value("login_state", LoginState_OtherError).toInt());
}
void SpotifyService::EnsureServerCreated(const QString& username, void SpotifyService::EnsureServerCreated(const QString& username,
const QString& password) { const QString& password) {
if (server_ && blob_process_) { if (server_ && blob_process_) {
@ -164,7 +192,8 @@ void SpotifyService::EnsureServerCreated(const QString& username,
delete server_; delete server_;
server_ = new SpotifyServer(this); server_ = new SpotifyServer(this);
connect(server_, SIGNAL(LoginCompleted(bool,QString)), SLOT(LoginCompleted(bool,QString))); connect(server_, SIGNAL(LoginCompleted(bool,QString,protobuf::LoginResponse_Error)),
SLOT(LoginCompleted(bool,QString,protobuf::LoginResponse_Error)));
connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)), connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)),
SLOT(PlaylistsUpdated(protobuf::Playlists))); SLOT(PlaylistsUpdated(protobuf::Playlists)));
connect(server_, SIGNAL(InboxLoaded(protobuf::LoadPlaylistResponse)), connect(server_, SIGNAL(InboxLoaded(protobuf::LoadPlaylistResponse)),
@ -593,3 +622,12 @@ void SpotifyService::SyncPlaylistProgress(
void SpotifyService::ShowConfig() { void SpotifyService::ShowConfig() {
emit OpenSettingsAtPage(SettingsDialog::Page_Spotify); emit OpenSettingsAtPage(SettingsDialog::Page_Spotify);
} }
void SpotifyService::Logout() {
delete server_;
delete blob_process_;
server_ = NULL;
blob_process_ = NULL;
login_state_ = LoginState_OtherError;
}

View File

@ -35,17 +35,29 @@ public:
Role_UserPlaylistIndex = InternetModel::RoleCount, Role_UserPlaylistIndex = InternetModel::RoleCount,
}; };
// Values are persisted - don't change.
enum LoginState {
LoginState_LoggedIn = 1,
LoginState_Banned = 2,
LoginState_BadCredentials = 3,
LoginState_NoPremium = 4,
LoginState_OtherError = 5
};
static const char* kServiceName; static const char* kServiceName;
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const char* kBlobDownloadUrl; static const char* kBlobDownloadUrl;
static const int kSearchDelayMsec; static const int kSearchDelayMsec;
void ReloadSettings();
QStandardItem* CreateRootItem(); QStandardItem* CreateRootItem();
void LazyPopulate(QStandardItem* parent); void LazyPopulate(QStandardItem* parent);
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
void ItemDoubleClicked(QStandardItem* item); void ItemDoubleClicked(QStandardItem* item);
PlaylistItem::Options playlistitem_options() const; PlaylistItem::Options playlistitem_options() const;
void Logout();
void Login(const QString& username, const QString& password); void Login(const QString& username, const QString& password);
void Search(const QString& text, Playlist* playlist, bool now = false); void Search(const QString& text, Playlist* playlist, bool now = false);
Q_INVOKABLE void LoadImage(const QUrl& url); Q_INVOKABLE void LoadImage(const QUrl& url);
@ -55,6 +67,9 @@ public:
bool IsBlobInstalled() const; bool IsBlobInstalled() const;
void InstallBlob(); void InstallBlob();
// Persisted in the settings and updated on each Login().
LoginState login_state() const { return login_state_; }
static void SongFromProtobuf(const protobuf::Track& track, Song* song); static void SongFromProtobuf(const protobuf::Track& track, Song* song);
signals: signals:
@ -77,7 +92,8 @@ private:
private slots: private slots:
void BlobProcessError(QProcess::ProcessError error); void BlobProcessError(QProcess::ProcessError error);
void LoginCompleted(bool success, const QString& error); void LoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code);
void PlaylistsUpdated(const protobuf::Playlists& response); void PlaylistsUpdated(const protobuf::Playlists& response);
void InboxLoaded(const protobuf::LoadPlaylistResponse& response); void InboxLoaded(const protobuf::LoadPlaylistResponse& response);
void StarredLoaded(const protobuf::LoadPlaylistResponse& response); void StarredLoaded(const protobuf::LoadPlaylistResponse& response);
@ -122,6 +138,8 @@ private:
int inbox_sync_id_; int inbox_sync_id_;
int starred_sync_id_; int starred_sync_id_;
QMap<int, int> playlist_sync_ids_; QMap<int, int> playlist_sync_ids_;
LoginState login_state_;
}; };
#endif #endif

View File

@ -37,8 +37,6 @@ SpotifySettingsPage::SpotifySettingsPage(SettingsDialog* dialog)
validated_(false) validated_(false)
{ {
ui_->setupUi(this); ui_->setupUi(this);
ui_->busy->hide();
ui_->warn_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(16));
setWindowIcon(QIcon(":/icons/svg/spotify.svg")); setWindowIcon(QIcon(":/icons/svg/spotify.svg"));
@ -48,10 +46,16 @@ SpotifySettingsPage::SpotifySettingsPage(SettingsDialog* dialog)
connect(ui_->download_blob, SIGNAL(clicked()), SLOT(DownloadBlob())); connect(ui_->download_blob, SIGNAL(clicked()), SLOT(DownloadBlob()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login())); connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(service_, SIGNAL(LoginFinished(bool)), SLOT(LoginFinished(bool))); connect(service_, SIGNAL(LoginFinished(bool)), SLOT(LoginFinished(bool)));
connect(service_, SIGNAL(BlobStateChanged()), SLOT(BlobStateChanged())); connect(service_, SIGNAL(BlobStateChanged()), SLOT(BlobStateChanged()));
ui_->login_state->AddCredentialField(ui_->username);
ui_->login_state->AddCredentialField(ui_->password);
ui_->login_state->AddCredentialGroup(ui_->account_group);
BlobStateChanged(); BlobStateChanged();
} }
@ -82,15 +86,12 @@ void SpotifySettingsPage::Login() {
} }
if (ui_->username->text() == original_username_ && if (ui_->username->text() == original_username_ &&
ui_->password->text() == original_password_) { ui_->password->text() == original_password_ &&
service_->login_state() == SpotifyService::LoginState_LoggedIn) {
return; return;
} }
if (!validated_) { ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
return;
}
ui_->busy->show();
service_->Login(ui_->username->text(), ui_->password->text()); service_->Login(ui_->username->text(), ui_->password->text());
} }
@ -104,6 +105,8 @@ void SpotifySettingsPage::Load() {
ui_->username->setText(original_username_); ui_->username->setText(original_username_);
ui_->password->setText(original_password_); ui_->password->setText(original_password_);
validated_ = false; validated_ = false;
UpdateLoginState();
} }
void SpotifySettingsPage::Save() { void SpotifySettingsPage::Save() {
@ -112,15 +115,41 @@ void SpotifySettingsPage::Save() {
s.setValue("username", ui_->username->text()); s.setValue("username", ui_->username->text());
s.setValue("password", ui_->password->text()); s.setValue("password", ui_->password->text());
InternetModel::Service<SpotifyService>()->ReloadSettings();
} }
void SpotifySettingsPage::LoginFinished(bool success) { void SpotifySettingsPage::LoginFinished(bool success) {
validated_ = success; validated_ = success;
ui_->busy->hide();
ui_->login->setEnabled(!success);
// Save the settings
Save(); Save();
UpdateLoginState();
}
void SpotifySettingsPage::UpdateLoginState() {
const bool logged_in =
service_->login_state() == SpotifyService::LoginState_LoggedIn;
ui_->login_state->SetLoggedIn(logged_in ? LoginStateWidget::LoggedIn
: LoginStateWidget::LoggedOut,
ui_->username->text());
ui_->login_state->SetAccountTypeVisible(!logged_in);
switch (service_->login_state()) {
case SpotifyService::LoginState_NoPremium:
ui_->login_state->SetAccountTypeText(tr("You do not have a Spotify Premium account."));
break;
case SpotifyService::LoginState_Banned:
case SpotifyService::LoginState_BadCredentials:
ui_->login_state->SetAccountTypeText(tr("Your username or password was incorrect."));
break;
default:
ui_->login_state->SetAccountTypeText(tr("A Spotify Premium account is required."));
break;
}
}
void SpotifySettingsPage::Logout() {
service_->Logout();
UpdateLoginState();
} }

View File

@ -41,6 +41,10 @@ public slots:
private slots: private slots:
void Login(); void Login();
void LoginFinished(bool success); void LoginFinished(bool success);
void Logout();
private:
void UpdateLoginState();
private: private:
NetworkAccessManager* network_; NetworkAccessManager* network_;

View File

@ -14,50 +14,15 @@
<string>Spotify</string> <string>Spotify</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item> <item>
<widget class="QGroupBox" name="account_group"> <widget class="QGroupBox" name="account_group">
<property name="title"> <property name="title">
<string>Account details</string> <string>Account details</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="warn_icon">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>A Spotify Premium account is required.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QWidget" name="login_container" native="true"> <widget class="QWidget" name="login_container" native="true">
<property name="enabled"> <property name="enabled">
@ -206,42 +171,14 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>BusyIndicator</class> <class>LoginStateWidget</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>widgets/busyindicator.h</header> <header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>

View File

@ -30,11 +30,14 @@ class SettingsPage : public QWidget {
public: public:
SettingsPage(SettingsDialog* dialog); SettingsPage(SettingsDialog* dialog);
// Return false to grey out the page's item in the list.
virtual bool IsEnabled() const { return true; } virtual bool IsEnabled() const { return true; }
// Load is called when the dialog is shown, Save when the user clicks OK.
virtual void Load() = 0; virtual void Load() = 0;
virtual void Save() = 0; virtual void Save() = 0;
// The dialog that this page belongs to.
SettingsDialog* dialog() const { return dialog_; } SettingsDialog* dialog() const { return dialog_; }
signals: signals:

View File

@ -39,6 +39,7 @@ void BusyIndicator::Init(const QString& text) {
icon->setMinimumSize(16, 16); icon->setMinimumSize(16, 16);
label_->setWordWrap(true); label_->setWordWrap(true);
label_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QHBoxLayout* layout = new QHBoxLayout(this); QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
@ -63,7 +64,10 @@ void BusyIndicator::hideEvent(QHideEvent*) {
void BusyIndicator::set_text(const QString& text) { void BusyIndicator::set_text(const QString& text) {
label_->setText(text); label_->setText(text);
if (text.isEmpty()) label_->setVisible(!text.isEmpty());
label_->hide(); }
QString BusyIndicator::text() const {
return label_->text();
} }

View File

@ -24,6 +24,7 @@ class QMovie;
class BusyIndicator : public QWidget { class BusyIndicator : public QWidget {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString text READ text WRITE set_text)
public: public:
explicit BusyIndicator(const QString& text, QWidget* parent = 0); explicit BusyIndicator(const QString& text, QWidget* parent = 0);

View File

@ -0,0 +1,104 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "loginstatewidget.h"
#include "ui_loginstatewidget.h"
#include "ui/iconloader.h"
#include <QKeyEvent>
LoginStateWidget::LoginStateWidget(QWidget* parent)
: QWidget(parent),
ui_(new Ui_LoginStateWidget)
{
ui_->setupUi(this);
ui_->signed_in->hide();
ui_->account_type->hide();
ui_->busy->hide();
ui_->sign_out->setIcon(IconLoader::Load("list-remove"));
QFont bold_font(font());
bold_font.setBold(true);
ui_->signed_out_label->setFont(bold_font);
connect(ui_->sign_out, SIGNAL(clicked()), SLOT(Logout()));
}
LoginStateWidget::~LoginStateWidget() {
delete ui_;
}
void LoginStateWidget::Logout() {
SetLoggedIn(LoggedOut);
emit LogoutClicked();
}
void LoginStateWidget::SetAccountTypeText(const QString& text) {
ui_->account_type_label->setText(text);
}
void LoginStateWidget::SetAccountTypeVisible(bool visible) {
ui_->account_type->setVisible(visible);
}
void LoginStateWidget::SetLoggedIn(State state, const QString& account_name) {
ui_->signed_in->setVisible(state == LoggedIn);
ui_->signed_out->setVisible(state != LoggedIn);
ui_->busy->setVisible(state == LoginInProgress);
if (account_name.isEmpty())
ui_->signed_in_label->setText("<b>" + tr("You are signed in.") + "</b>");
else
ui_->signed_in_label->setText(tr("You are signed in as %1.").arg("<b>" + account_name + "</b>"));
foreach (QWidget* widget, credential_groups_) {
widget->setVisible(state != LoggedIn);
widget->setEnabled(state != LoginInProgress);
}
}
void LoginStateWidget::HideLoggedInState() {
ui_->signed_in->hide();
ui_->signed_out->hide();
}
void LoginStateWidget::AddCredentialField(QWidget* widget) {
widget->installEventFilter(this);
credential_fields_ << widget;
}
void LoginStateWidget::AddCredentialGroup(QWidget* widget) {
credential_groups_ << widget;
}
bool LoginStateWidget::eventFilter(QObject* object, QEvent* event) {
if (!credential_fields_.contains(object))
return QWidget::eventFilter(object, event);
if (event->type() == QEvent::KeyPress) {
QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
if (key_event->key() == Qt::Key_Enter ||
key_event->key() == Qt::Key_Return) {
emit LoginClicked();
return true;
}
}
return QWidget::eventFilter(object, event);
}

View File

@ -0,0 +1,74 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LOGINSTATEWIDGET_H
#define LOGINSTATEWIDGET_H
#include <QWidget>
class Ui_LoginStateWidget;
class LoginStateWidget : public QWidget {
Q_OBJECT
public:
LoginStateWidget(QWidget* parent = 0);
~LoginStateWidget();
enum State {
LoggedIn,
LoginInProgress,
LoggedOut
};
// Installs an event handler on the field so that pressing enter will emit
// LoginClicked() instead of doing the default action (closing the dialog).
void AddCredentialField(QWidget* widget);
// This widget (usually a QGroupBox) will be hidden when SetLoggedIn(true)
// is called.
void AddCredentialGroup(QWidget* widget);
// QObject
bool eventFilter(QObject* object, QEvent* event);
public slots:
// Changes the "You are logged in/out" label, shows/hides any QGroupBoxes
// added with AddCredentialGroup.
void SetLoggedIn(State state, const QString& account_name = QString::null);
// Hides the "You are logged in/out" label completely.
void HideLoggedInState();
void SetAccountTypeText(const QString& text);
void SetAccountTypeVisible(bool visible);
signals:
void LogoutClicked();
void LoginClicked();
private slots:
void Logout();
private:
Ui_LoginStateWidget* ui_;
QList<QObject*> credential_fields_;
QList<QWidget*> credential_groups_;
};
#endif // LOGINSTATEWIDGET_H

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoginStateWidget</class>
<widget class="QWidget" name="LoginStateWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>526</width>
<height>187</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="signed_out" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="signed_out_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>You are not signed in.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="signed_in" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="pixmap">
<pixmap resource="../../data/data.qrc">:/icons/22x22/dialog-ok-apply.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="signed_in_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sign_out">
<property name="text">
<string>Sign out</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="account_type" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="pixmap">
<pixmap resource="../../data/data.qrc">:/icons/22x22/dialog-warning.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="account_type_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="busy" native="true">
<property name="text" stdset="0">
<string>Signing in...</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections/>
</ui>