summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYoann Lopes <yoann.lopes@nokia.com>2011-10-20 18:56:56 +0200
committerYoann Lopes <yoann.lopes@nokia.com>2011-10-20 18:56:56 +0200
commit240d8f7fe7e9e2664c4d4d8fe184574beb7c746d (patch)
tree0fa95235cb95b30ecae47295420cf46ff7375066
Initial commit.
-rw-r--r--.gitignore106
-rw-r--r--MeeSpot.conf2
-rw-r--r--MeeSpot.desktop11
-rw-r--r--MeeSpot.pngbin0 -> 6723 bytes
-rw-r--r--MeeSpot.pro111
-rw-r--r--README25
-rw-r--r--deployment.pri127
-rw-r--r--libQtSpotify/QtSpotify20
-rw-r--r--libQtSpotify/libQtSpotify.pri53
-rw-r--r--libQtSpotify/qspotify_qmlplugin.h66
-rw-r--r--libQtSpotify/qspotifyalbum.cpp119
-rw-r--r--libQtSpotify/qspotifyalbum.h112
-rw-r--r--libQtSpotify/qspotifyalbumbrowse.cpp202
-rw-r--r--libQtSpotify/qspotifyalbumbrowse.h98
-rw-r--r--libQtSpotify/qspotifyartist.cpp89
-rw-r--r--libQtSpotify/qspotifyartist.h86
-rw-r--r--libQtSpotify/qspotifyartistbrowse.cpp253
-rw-r--r--libQtSpotify/qspotifyartistbrowse.h125
-rw-r--r--libQtSpotify/qspotifyimageprovider.cpp60
-rw-r--r--libQtSpotify/qspotifyimageprovider.h55
-rw-r--r--libQtSpotify/qspotifyobject.cpp71
-rw-r--r--libQtSpotify/qspotifyobject.h78
-rw-r--r--libQtSpotify/qspotifyplaylist.cpp526
-rw-r--r--libQtSpotify/qspotifyplaylist.h167
-rw-r--r--libQtSpotify/qspotifyplaylistcontainer.cpp212
-rw-r--r--libQtSpotify/qspotifyplaylistcontainer.h83
-rw-r--r--libQtSpotify/qspotifyplayqueue.cpp291
-rw-r--r--libQtSpotify/qspotifyplayqueue.h102
-rw-r--r--libQtSpotify/qspotifysearch.cpp189
-rw-r--r--libQtSpotify/qspotifysearch.h105
-rw-r--r--libQtSpotify/qspotifysession.cpp1106
-rw-r--r--libQtSpotify/qspotifysession.h285
-rw-r--r--libQtSpotify/qspotifytoplist.cpp205
-rw-r--r--libQtSpotify/qspotifytoplist.h96
-rw-r--r--libQtSpotify/qspotifytrack.cpp316
-rw-r--r--libQtSpotify/qspotifytrack.h177
-rw-r--r--libQtSpotify/qspotifytracklist.cpp218
-rw-r--r--libQtSpotify/qspotifytracklist.h105
-rw-r--r--libQtSpotify/qspotifyuser.cpp234
-rw-r--r--libQtSpotify/qspotifyuser.h110
-rw-r--r--libQtSpotify/spotify_key.h9
-rw-r--r--libspotify/.gitignore1
-rw-r--r--main.cpp68
-rw-r--r--qml/AboutDialog.qml181
-rw-r--r--qml/AdvancedTextField.qml78
-rw-r--r--qml/AlbumDelegate.qml168
-rw-r--r--qml/AlbumHeader.qml154
-rw-r--r--qml/AlbumMenu.qml146
-rw-r--r--qml/AlbumPage.qml121
-rw-r--r--qml/AlbumTrackDelegate.qml163
-rw-r--r--qml/ArtistDelegate.qml120
-rw-r--r--qml/ArtistHeader.qml131
-rw-r--r--qml/ArtistPage.qml263
-rw-r--r--qml/FullControls.qml468
-rw-r--r--qml/HeaderSearchField.qml148
-rw-r--r--qml/InboxTrackDelegate.qml221
-rw-r--r--qml/LoginPage.qml216
-rw-r--r--qml/MainPage.qml153
-rw-r--r--qml/MyFader.qml202
-rw-r--r--qml/MyMenu.qml361
-rw-r--r--qml/MyMenuItem.qml118
-rw-r--r--qml/MyMenuItemStyle.qml72
-rw-r--r--qml/MyMenuLayout.qml84
-rw-r--r--qml/MyPopup.qml124
-rw-r--r--qml/MySheet.qml234
-rw-r--r--qml/NotificationBanner.qml254
-rw-r--r--qml/Player.qml100
-rw-r--r--qml/PlaylistDelegate.qml270
-rw-r--r--qml/PlaylistMenu.qml120
-rw-r--r--qml/PlaylistNameSheet.qml91
-rw-r--r--qml/PlaylistPage.qml194
-rw-r--r--qml/QuickControls.qml189
-rw-r--r--qml/SearchPage.qml234
-rw-r--r--qml/Selector.qml119
-rw-r--r--qml/Separator.qml48
-rw-r--r--qml/SettingsPage.qml232
-rw-r--r--qml/SpotifyImage.qml51
-rw-r--r--qml/ToplistPage.qml307
-rw-r--r--qml/TrackDelegate.qml220
-rw-r--r--qml/TrackMenu.qml166
-rw-r--r--qml/TracklistPage.qml128
-rw-r--r--qml/UIConstants.js182
-rw-r--r--qml/Utilities.js62
-rw-r--r--qml/ViewHeader.qml94
-rw-r--r--qml/images/icon-m-collaborative-playlist.pngbin0 -> 596 bytes
-rw-r--r--qml/images/icon-m-common-green.pngbin0 -> 2111 bytes
-rw-r--r--qml/images/icon-m-toolbar-repeat-white-selected.pngbin0 -> 698 bytes
-rw-r--r--qml/images/icon-m-toolbar-shuffle-white-selected.pngbin0 -> 719 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background-pressed-vertical-bottom.pngbin0 -> 682 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background-pressed-vertical-center.pngbin0 -> 213 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background-pressed-vertical-top.pngbin0 -> 950 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background-vertical-bottom.pngbin0 -> 695 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background-vertical-center.pngbin0 -> 224 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background-vertical-top.pngbin0 -> 960 bytes
-rw-r--r--qml/images/meegotouch-list-inverted-background.pngbin0 -> 1393 bytes
-rw-r--r--qml/images/meegotouch-menu-background-inverted.pngbin0 -> 477 bytes
-rw-r--r--qml/images/meegotouch-progressindicator-bar-known-texture.pngbin0 -> 176 bytes
-rw-r--r--qml/images/meegotouch-sheet-header-inverted-background.pngbin0 -> 974 bytes
-rw-r--r--qml/images/meegotouch-slider-elapsed-background-horizontal.pngbin0 -> 1205 bytes
-rw-r--r--qml/images/meegotouch-slider-handle-background-horizontal.pngbin0 -> 1102 bytes
-rw-r--r--qml/images/meegotouch-slider-handle-background-pressed-horizontal.pngbin0 -> 1095 bytes
-rw-r--r--qml/images/meespot-logo.pngbin0 -> 16564 bytes
-rw-r--r--qml/images/player-quickcontrols-back-closed.pngbin0 -> 195 bytes
-rw-r--r--qml/images/player-quickcontrols-back-open.pngbin0 -> 234 bytes
-rw-r--r--qml/images/spotify-core-logo-128x128.pngbin0 -> 7082 bytes
-rw-r--r--qml/main.qml101
-rw-r--r--qtc_packaging/debian_harmattan/README6
-rw-r--r--qtc_packaging/debian_harmattan/changelog12
-rw-r--r--qtc_packaging/debian_harmattan/compat1
-rw-r--r--qtc_packaging/debian_harmattan/control15
-rw-r--r--qtc_packaging/debian_harmattan/copyright44
-rw-r--r--qtc_packaging/debian_harmattan/manifest.aegis0
-rwxr-xr-xqtc_packaging/debian_harmattan/postrm6
-rwxr-xr-xqtc_packaging/debian_harmattan/rules90
-rw-r--r--res.qrc67
-rwxr-xr-xsplash.pngbin0 -> 47057 bytes
116 files changed, 13803 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1136503
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,106 @@
+# This file is used to ignore files which are generated in the Qt build system
+# ----------------------------------------------------------------------------
+
+# MeeSpot specific stuff
+libspotify/*/*
+MeeSpot
+*.deb
+*.changes
+configure-stamp
+build-stamp
+debian/*
+
+# General
+callgrind.out.*
+pcviewer.cfg
+*~
+*.a
+*.la
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.swp
+*.rej
+*.so
+*.pbxuser
+*.mode1
+*.mode1v3
+*_pch.h.cpp
+*_resource.rc
+*.dylib
+.#*
+*.*#
+.qmake.cache
+.qmake.vars
+*.prl
+tags
+.DS_Store
+*.debug
+Makefile*
+*.prl
+*.app
+*.pro.user*
+*.qmlproject.user*
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+.directory
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+
+# MinGW generated files
+*.Debug
+*.Release
+
+.pch
+.rcc
+
+# Symbian build system generated files
+# ---------------------
+
+ABLD.BAT
+bld.inf
+*.mmp
+*.mk
+*.rss
+*.loc
+!s60main.rss
+*.pkg
+plugin_commonU.def
+*.qtplugin
+*.sis
+*.sisx
+
+# Generated by abldfast.bat from devtools.
+.abldsteps.*
+
+# Carbide project files
+# ---------------------
+.project
+.cproject
+.make.cache
+
+qtc-debugging-helper
+
+#svn file
+*/.svn/*
+
+# linux shared libraries
+*.so*
diff --git a/MeeSpot.conf b/MeeSpot.conf
new file mode 100644
index 0000000..ec0dfa1
--- /dev/null
+++ b/MeeSpot.conf
@@ -0,0 +1,2 @@
+[classify mediasrc]
+/opt/MeeSpot/bin/MeeSpot
diff --git a/MeeSpot.desktop b/MeeSpot.desktop
new file mode 100644
index 0000000..6e00b41
--- /dev/null
+++ b/MeeSpot.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Terminal=false
+Name=MeeSpot
+Exec=invoker --single-instance --splash /usr/share/MeeSpot/splash.png --type=d /opt/MeeSpot/bin/MeeSpot
+Icon=/usr/share/icons/hicolor/64x64/apps/MeeSpot.png
+X-Window-Icon=
+X-HildonDesk-ShowInToolbar=true
+X-Osso-Type=application/x-executable
diff --git a/MeeSpot.png b/MeeSpot.png
new file mode 100644
index 0000000..08341a7
--- /dev/null
+++ b/MeeSpot.png
Binary files differ
diff --git a/MeeSpot.pro b/MeeSpot.pro
new file mode 100644
index 0000000..74a365d
--- /dev/null
+++ b/MeeSpot.pro
@@ -0,0 +1,111 @@
+# Add more folders to ship with the application, here
+
+# Additional import path used to resolve QML modules in Creator's code model
+QML_IMPORT_PATH =
+
+QT+= declarative
+symbian:TARGET.UID3 = 0xE119349A
+
+# Smart Installer package's UID
+# This UID is from the protected range and therefore the package will
+# fail to install if self-signed. By default qmake uses the unprotected
+# range value if unprotected UID is defined for the application and
+# 0x2002CCCF value if protected UID is given to the application
+#symbian:DEPLOYMENT.installer_header = 0x2002CCCF
+
+# Allow network access on Symbian
+symbian:TARGET.CAPABILITY += NetworkServices
+
+# If your application uses the Qt Mobility libraries, uncomment the following
+# lines and add the respective components to the MOBILITY variable.
+# CONFIG += mobility
+# MOBILITY +=
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp \
+
+OTHER_FILES += \
+ qml/MainPage.qml \
+ qml/main.qml \
+ MeeSpot.desktop \
+ MeeSpot.png \
+ qtc_packaging/debian_harmattan/rules \
+ qtc_packaging/debian_harmattan/README \
+ qtc_packaging/debian_harmattan/copyright \
+ qtc_packaging/debian_harmattan/control \
+ qtc_packaging/debian_harmattan/compat \
+ qtc_packaging/debian_harmattan/changelog \
+ MeeSpot.conf \
+ qml/LoginPage.qml \
+ qml/UIConstants.js \
+ qml/PlaylistPage.qml \
+ qml/TracklistPage.qml \
+ qml/PlaylistDelegate.qml \
+ qml/TrackDelegate.qml \
+ qml/Player.qml \
+ qml/QuickControls.qml \
+ qml/FullControls.qml \
+ qml/NotificationBanner.qml \
+ qml/SettingsPage.qml \
+ qml/ViewHeader.qml \
+ qml/Selector.qml \
+ qml/SearchPage.qml \
+ qml/SpotifyImage.qml \
+ qml/AdvancedTextField.qml \
+ qml/Utilities.js \
+ qml/TrackMenu.qml \
+ qml/PlaylistMenu.qml \
+ qml/MyMenuLayout.qml \
+ qml/MyMenuItem.qml \
+ qml/MyMenu.qml \
+ qml/MyPopup.qml \
+ qml/MyFader.qml \
+ qml/PlaylistNameSheet.qml \
+ qml/AlbumPage.qml \
+ qml/AlbumHeader.qml \
+ qml/AlbumTrackDelegate.qml \
+ qml/AlbumMenu.qml \
+ qml/ArtistPage.qml \
+ qml/ArtistHeader.qml \
+ qml/AlbumDelegate.qml \
+ qml/ArtistDelegate.qml \
+ qml/AboutDialog.qml \
+ qml/MyMenuItemStyle.qml \
+ qml/MySheet.qml \
+ qml/InboxTrackDelegate.qml \
+ qml/HeaderSearchField.qml \
+ qml/Separator.qml \
+ qtc_packaging/debian_harmattan/postrm \
+ qml/ToplistPage.qml
+
+RESOURCES += \
+ res.qrc
+
+# Please do not modify the following two lines. Required for deployment.
+include(deployment.pri)
+qtcAddDeployment()
+
+INCLUDEPATH += /usr/include/applauncherd
+LIBS += -lmdeclarativecache
+
+include(libQtSpotify/libQtSpotify.pri)
+
+# enable booster
+CONFIG += qdeclarative-boostable
+QMAKE_CXXFLAGS += -fPIC -fvisibility=hidden -fvisibility-inlines-hidden
+QMAKE_LFLAGS += -pie -rdynamic -Wl,-rpath,/opt/MeeSpot/lib
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README b/README
new file mode 100644
index 0000000..37a412b
--- /dev/null
+++ b/README
@@ -0,0 +1,25 @@
+You need to download libspotify 9 for ARM to be able to compile and create the .deb package.
+Get it there: http://developer.spotify.com/en/libspotify/overview/
+Extract the content of the tarball inside 'libspotify'
+at the root of the project.
+
+The resulting hierarchy should be:
+libspotify/
+ include/
+ libspotify/
+ api.h
+ lib/
+ libspotify.so
+ libspotify.so.9
+
+You also need your own libpotify API key to be able to compile and run the program
+(see https://developer.spotify.com/en/libspotify/application-key/)
+Just copy the provided key into libQtSpotify/spotify_key.h with this format:
+
+ const uint8_t g_appkey[] = { 0x00, 0x00, ..., ... };
+ const size_t g_appkey_size = sizeof(g_appkey);
+
+
+To compile the project, use the Qt SDK (version 1.1 or higher) with the Harmattan component
+installed from the Qt SDK maintenance tool.
+--> Open the project in Qt Creator, compile and deploy on the device! \ No newline at end of file
diff --git a/deployment.pri b/deployment.pri
new file mode 100644
index 0000000..c965f26
--- /dev/null
+++ b/deployment.pri
@@ -0,0 +1,127 @@
+# checksum 0x9840 version 0x4000b
+# This file was generated by an application wizard of Qt Creator.
+# The code below handles deployment to Symbian and Maemo, aswell as copying
+# of the application data to shadow build directories on desktop.
+# It is recommended not to modify this file, since newer versions of Qt Creator
+# may offer an updated version of it.
+
+defineTest(qtcAddDeployment) {
+for(deploymentfolder, DEPLOYMENTFOLDERS) {
+ item = item$${deploymentfolder}
+ itemsources = $${item}.sources
+ $$itemsources = $$eval($${deploymentfolder}.source)
+ itempath = $${item}.path
+ $$itempath= $$eval($${deploymentfolder}.target)
+ export($$itemsources)
+ export($$itempath)
+ DEPLOYMENT += $$item
+}
+
+MAINPROFILEPWD = $$PWD
+
+symbian {
+ isEmpty(ICON):exists($${TARGET}.svg):ICON = $${TARGET}.svg
+ isEmpty(TARGET.EPOCHEAPSIZE):TARGET.EPOCHEAPSIZE = 0x20000 0x2000000
+} else:win32 {
+ copyCommand =
+ for(deploymentfolder, DEPLOYMENTFOLDERS) {
+ source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
+ source = $$replace(source, /, \\)
+ sourcePathSegments = $$split(source, \\)
+ target = $$OUT_PWD/$$eval($${deploymentfolder}.target)/$$last(sourcePathSegments)
+ target = $$replace(target, /, \\)
+ !isEqual(source,$$target) {
+ !isEmpty(copyCommand):copyCommand += &&
+ isEqual(QMAKE_DIR_SEP, \\) {
+ copyCommand += $(COPY_DIR) \"$$source\" \"$$target\"
+ } else {
+ source = $$replace(source, \\\\, /)
+ target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
+ target = $$replace(target, \\\\, /)
+ copyCommand += test -d \"$$target\" || mkdir -p \"$$target\" && cp -r \"$$source\" \"$$target\"
+ }
+ }
+ }
+ !isEmpty(copyCommand) {
+ copyCommand = @echo Copying application data... && $$copyCommand
+ copydeploymentfolders.commands = $$copyCommand
+ first.depends = $(first) copydeploymentfolders
+ export(first.depends)
+ export(copydeploymentfolders.commands)
+ QMAKE_EXTRA_TARGETS += first copydeploymentfolders
+ }
+} else:unix {
+ maemo5 {
+ desktopfile.path = /usr/share/applications/hildon
+ } else {
+ desktopfile.path = /usr/share/applications
+ copyCommand =
+ for(deploymentfolder, DEPLOYMENTFOLDERS) {
+ source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
+ source = $$replace(source, \\\\, /)
+ macx {
+ target = $$OUT_PWD/$${TARGET}.app/Contents/Resources/$$eval($${deploymentfolder}.target)
+ } else {
+ target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
+ }
+ target = $$replace(target, \\\\, /)
+ sourcePathSegments = $$split(source, /)
+ targetFullPath = $$target/$$last(sourcePathSegments)
+ !isEqual(source,$$targetFullPath) {
+ !isEmpty(copyCommand):copyCommand += &&
+ copyCommand += $(MKDIR) \"$$target\"
+ copyCommand += && $(COPY_DIR) \"$$source\" \"$$target\"
+ }
+ }
+ !isEmpty(copyCommand) {
+ copyCommand = @echo Copying application data... && $$copyCommand
+ copydeploymentfolders.commands = $$copyCommand
+ first.depends = $(first) copydeploymentfolders
+ export(first.depends)
+ export(copydeploymentfolders.commands)
+ QMAKE_EXTRA_TARGETS += first copydeploymentfolders
+ }
+ }
+ installPrefix = /opt/$${TARGET}
+ for(deploymentfolder, DEPLOYMENTFOLDERS) {
+ item = item$${deploymentfolder}
+ itemfiles = $${item}.files
+ $$itemfiles = $$eval($${deploymentfolder}.source)
+ itempath = $${item}.path
+ $$itempath = $${installPrefix}/$$eval($${deploymentfolder}.target)
+ export($$itemfiles)
+ export($$itempath)
+ INSTALLS += $$item
+ }
+ icon.files = $${TARGET}.png
+ icon.path = /usr/share/icons/hicolor/64x64/apps
+ desktopfile.files = $${TARGET}.desktop
+ target.path = $${installPrefix}/bin
+ export(icon.files)
+ export(icon.path)
+ export(desktopfile.files)
+ export(desktopfile.path)
+ export(target.path)
+ libspotify.files = $$PWD/libspotify/lib/libspotify.so.9
+ libspotify.path = /opt/MeeSpot/lib
+ export(libspotify.files)
+ export(libspotify.path)
+ controlgroup.files = $${TARGET}.conf
+ controlgroup.path = /usr/share/policy/etc/syspart.conf.d
+ export(controlgroup.files)
+ export(controlgroup.path)
+ splash.files = splash.png
+ splash.path = /usr/share/MeeSpot
+ export(splash.files)
+ export(splash.path)
+ INSTALLS += desktopfile icon target libspotify controlgroup splash
+}
+
+export (ICON)
+export (INSTALLS)
+export (DEPLOYMENT)
+export (TARGET.EPOCHEAPSIZE)
+export (TARGET.CAPABILITY)
+export (LIBS)
+export (QMAKE_EXTRA_TARGETS)
+}
diff --git a/libQtSpotify/QtSpotify b/libQtSpotify/QtSpotify
new file mode 100644
index 0000000..2ba80cb
--- /dev/null
+++ b/libQtSpotify/QtSpotify
@@ -0,0 +1,20 @@
+#ifndef QTSPOTIFY_MODULE_H
+#define QTSPOTIFY_MODULE_H
+
+#include "qspotifyalbum.h"
+#include "qspotifyalbumbrowse.h"
+#include "qspotifyartist.h"
+#include "qspotifyartistbrowse.h"
+#include "qspotifyimageprovider.h"
+#include "qspotifyobject.h"
+#include "qspotifyplaylist.h"
+#include "qspotifyplaylistcontainer.h"
+#include "qspotifyplayqueue.h"
+#include "qspotifysearch.h"
+#include "qspotifysession.h"
+#include "qspotifytoplist.h"
+#include "qspotifytrack.h"
+#include "qspotifytracklist.h"
+#include "qspotifyuser.h"
+
+#endif // QTSPOTIFY_MODULE_H
diff --git a/libQtSpotify/libQtSpotify.pri b/libQtSpotify/libQtSpotify.pri
new file mode 100644
index 0000000..7358fab
--- /dev/null
+++ b/libQtSpotify/libQtSpotify.pri
@@ -0,0 +1,53 @@
+
+QT += network
+CONFIG += mobility
+MOBILITY += multimedia
+
+INCLUDEPATH += $$PWD
+
+SOURCES += $$PWD/qspotifysession.cpp \
+ $$PWD/qspotifyuser.cpp \
+ $$PWD/qspotifyobject.cpp \
+ $$PWD/qspotifytrack.cpp \
+ $$PWD/qspotifyplaylist.cpp \
+ $$PWD/qspotifyplaylistcontainer.cpp \
+ $$PWD/qspotifytracklist.cpp \
+ $$PWD/qspotifyartist.cpp \
+ $$PWD/qspotifyalbum.cpp \
+ $$PWD/qspotifyimageprovider.cpp \
+ $$PWD/qspotifysearch.cpp \
+ $$PWD/qspotifyplayqueue.cpp \
+ $$PWD/qspotifyalbumbrowse.cpp \
+ $$PWD/qspotifyartistbrowse.cpp \
+ $$PWD/qspotifytoplist.cpp
+
+HEADERS += $$PWD/qspotifysession.h \
+ $$PWD/spotify_key.h \
+ $$PWD/qspotifyuser.h \
+ $$PWD/qspotifyobject.h \
+ $$PWD/qspotifytrack.h \
+ $$PWD/qspotifyplaylist.h \
+ $$PWD/qspotifyplaylistcontainer.h \
+ $$PWD/qspotifytracklist.h \
+ $$PWD/QtSpotify \
+ $$PWD/qspotify_qmlplugin.h \
+ $$PWD/qspotifyartist.h \
+ $$PWD/qspotifyalbum.h \
+ $$PWD/qspotifyimageprovider.h \
+ $$PWD/qspotifysearch.h \
+ $$PWD/qspotifyplayqueue.h \
+ $$PWD/qspotifyalbumbrowse.h \
+ $$PWD/qspotifyartistbrowse.h \
+ $$PWD/qspotifytoplist.h
+
+INCLUDEPATH += $$PWD/../libspotify/include
+LIBS += -L$$PWD/../libspotify/lib -lspotify
+
+INCLUDEPATH += /usr/include/resource/qt4
+PKGCONFIG += libresourceqt1
+
+
+
+
+
+
diff --git a/libQtSpotify/qspotify_qmlplugin.h b/libQtSpotify/qspotify_qmlplugin.h
new file mode 100644
index 0000000..73d264f
--- /dev/null
+++ b/libQtSpotify/qspotify_qmlplugin.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFY_QMLPLUGIN_H
+#define QSPOTIFY_QMLPLUGIN_H
+
+#include <QtSpotify>
+#include <QtDeclarative/qdeclarative.h>
+
+void registerQmlTypes()
+{
+ QLatin1String uri("QtSpotify");
+
+ qmlRegisterUncreatableType<QSpotifySession>(uri.latin1(), 1, 0, "SpotifySession", QLatin1String("Use the Context property instead."));
+ qmlRegisterUncreatableType<QSpotifyUser>(uri.latin1(), 1, 0, "SpotifyUser", QLatin1String("Retrieve it from the SpotifySession"));
+ qmlRegisterUncreatableType<QSpotifyPlaylist>(uri.latin1(), 1, 0, "SpotifyPlaylist", QLatin1String("Retrieve it from the SpotifySession"));
+ qmlRegisterUncreatableType<QSpotifyTrack>(uri.latin1(), 1, 0, "SpotifyTrack", QLatin1String("Retrieve it from the SpotifySession"));
+ qmlRegisterUncreatableType<QSpotifyAlbum>(uri.latin1(), 1, 0, "SpotifyAlbum", QLatin1String("Retrieve it from the SpotifySession"));
+ qmlRegisterUncreatableType<QSpotifyArtist>(uri.latin1(), 1, 0, "SpotifyArtist", QLatin1String("Retrieve it from the SpotifySession"));
+ qmlRegisterUncreatableType<QSpotifyPlayQueue>(uri.latin1(), 1, 0, "SpotifyPlayQueue", QLatin1String("Retrieve it from the SpotifySession"));
+
+ qmlRegisterType<QSpotifySearch>(uri.latin1(), 1, 0, "SpotifySearch");
+ qmlRegisterType<QSpotifyAlbumBrowse>(uri.latin1(), 1, 0, "SpotifyAlbumBrowse");
+ qmlRegisterType<QSpotifyArtistBrowse>(uri.latin1(), 1, 0, "SpotifyArtistBrowse");
+ qmlRegisterType<QSpotifyToplist>(uri.latin1(), 1, 0, "SpotifyToplist");
+}
+
+#endif // QSPOTIFY_QMLPLUGIN_H
diff --git a/libQtSpotify/qspotifyalbum.cpp b/libQtSpotify/qspotifyalbum.cpp
new file mode 100644
index 0000000..cb371af
--- /dev/null
+++ b/libQtSpotify/qspotifyalbum.cpp
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyalbum.h"
+#include "qspotifyartist.h"
+#include "qspotifysession.h"
+
+#include <libspotify/api.h>
+#include <QtCore/QDebug>
+
+QSpotifyAlbum::QSpotifyAlbum(sp_album *album)
+ : QSpotifyObject(true)
+ , m_isAvailable(false)
+ , m_year(0)
+ , m_type(Unknown)
+{
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(albumDataChanged()));
+ sp_album_add_ref(album);
+ m_sp_album = album;
+ metadataUpdated();
+}
+
+QSpotifyAlbum::~QSpotifyAlbum()
+{
+ sp_album_release(m_sp_album);
+}
+
+bool QSpotifyAlbum::isLoaded()
+{
+ return sp_album_is_loaded(m_sp_album);
+}
+
+bool QSpotifyAlbum::updateData()
+{
+ bool updated = false;
+
+ bool isAvailable = sp_album_is_available(m_sp_album);
+ sp_artist *a = sp_album_artist((m_sp_album));
+ QString artist;
+ if (a)
+ artist = QString::fromUtf8(sp_artist_name(a));
+ QString name = QString::fromUtf8(sp_album_name(m_sp_album));
+ int year = sp_album_year(m_sp_album);
+ Type type = Type(sp_album_type(m_sp_album));
+
+ // Get cover
+ const byte *album_cover_id = sp_album_cover(m_sp_album);
+ if (album_cover_id != 0 && m_coverId.isEmpty()) {
+ sp_link *link = sp_link_create_from_album_cover(m_sp_album);
+ if (link) {
+ char buffer[200];
+ int uriSize = sp_link_as_string(link, &buffer[0], 200);
+ m_coverId = QString::fromLatin1(&buffer[0], uriSize);
+ sp_link_release(link);
+ updated = true;
+ }
+ }
+
+ if (isAvailable != m_isAvailable) {
+ m_isAvailable = isAvailable;
+ updated = true;
+ }
+ if (artist != m_artist) {
+ m_artist = artist;
+ updated = true;
+ }
+ if (name != m_name) {
+ m_name = name;
+ updated = true;
+ }
+ if (year != m_year) {
+ m_year = year;
+ updated = true;
+ }
+ if (type != m_type) {
+ m_type = type;
+ updated = true;
+ }
+
+ return updated;
+}
diff --git a/libQtSpotify/qspotifyalbum.h b/libQtSpotify/qspotifyalbum.h
new file mode 100644
index 0000000..dd33ece
--- /dev/null
+++ b/libQtSpotify/qspotifyalbum.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYALBUM_H
+#define QSPOTIFYALBUM_H
+
+#include "qspotifyobject.h"
+
+struct sp_album;
+struct sp_artist;
+class QSpotifyArtist;
+
+class QSpotifyAlbum : public QSpotifyObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool isAvailable READ isAvailable NOTIFY albumDataChanged)
+ Q_PROPERTY(QString artist READ artist NOTIFY albumDataChanged)
+ Q_PROPERTY(QString name READ name NOTIFY albumDataChanged)
+ Q_PROPERTY(int year READ year NOTIFY albumDataChanged)
+ Q_PROPERTY(Type type READ type NOTIFY albumDataChanged)
+ Q_PROPERTY(QString sectionType READ sectionType NOTIFY albumDataChanged)
+ Q_PROPERTY(QString coverId READ coverId NOTIFY albumDataChanged)
+ Q_ENUMS(Type)
+public:
+ enum Type {
+ Album = 0,
+ Single = 1,
+ Compilation = 2,
+ Unknown = 3
+ };
+
+ ~QSpotifyAlbum();
+
+ bool isLoaded();
+
+ bool isAvailable() const { return m_isAvailable; }
+ QString artist() const { return m_artist; }
+ QString name() const { return m_name; }
+ int year() const { return m_year; }
+ Type type() const { return m_type; }
+ void setSectionType(const QString &t) { m_sectionType = t; }
+ QString sectionType() const { return m_sectionType; }
+ QString coverId() const { return m_coverId; }
+
+ sp_album *spalbum() const { return m_sp_album; }
+
+Q_SIGNALS:
+ void albumDataChanged();
+
+protected:
+ bool updateData();
+
+private:
+ QSpotifyAlbum(sp_album *album);
+
+ sp_album *m_sp_album;
+
+ bool m_isAvailable;
+ QString m_artist;
+ QString m_name;
+ int m_year;
+ Type m_type;
+ QString m_sectionType;
+ QString m_coverId;
+
+ friend class QSpotifySession;
+ friend class QSpotifyTrack;
+ friend class QSpotifyArtistBrowse;
+ friend class QSpotifySearch;
+ friend class QSpotifyToplist;
+};
+
+#endif // QSPOTIFYALBUM_H
diff --git a/libQtSpotify/qspotifyalbumbrowse.cpp b/libQtSpotify/qspotifyalbumbrowse.cpp
new file mode 100644
index 0000000..71f31a9
--- /dev/null
+++ b/libQtSpotify/qspotifyalbumbrowse.cpp
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyalbumbrowse.h"
+
+#include "qspotifytracklist.h"
+#include "qspotifyalbum.h"
+#include "qspotifysession.h"
+#include "qspotifyuser.h"
+#include "qspotifyplaylist.h"
+#include "qspotifytrack.h"
+#include "qspotifyplayqueue.h"
+
+#include <QtCore/QHash>
+#include <libspotify/api.h>
+
+static QHash<sp_albumbrowse*, QSpotifyAlbumBrowse*> g_albumBrowseObjects;
+static QMutex g_mutex;
+
+static void callback_albumbrowse_complete(sp_albumbrowse *result, void *)
+{
+ QMutexLocker lock(&g_mutex);
+ QSpotifyAlbumBrowse *s = g_albumBrowseObjects.value(result);
+ if (s)
+ QCoreApplication::postEvent(s, new QEvent(QEvent::User));
+}
+
+QSpotifyAlbumBrowse::QSpotifyAlbumBrowse(QObject *parent)
+ : QObject(parent)
+ , m_sp_albumbrowse(0)
+ , m_album(0)
+ , m_albumTracks(0)
+ , m_hasMultipleArtists(false)
+{
+}
+
+QSpotifyAlbumBrowse::~QSpotifyAlbumBrowse()
+{
+ clearData();
+}
+
+bool QSpotifyAlbumBrowse::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ processData();
+ e->accept();
+ return true;
+ }
+ return QObject::event(e);
+}
+
+void QSpotifyAlbumBrowse::setAlbum(QSpotifyAlbum *album)
+{
+ if (m_album == album)
+ return;
+ m_album = album;
+ emit albumChanged();
+ clearData();
+
+ if (!m_album)
+ return;
+
+ QMutexLocker lock(&g_mutex);
+ m_sp_albumbrowse = sp_albumbrowse_create(QSpotifySession::instance()->spsession(), m_album->spalbum(), callback_albumbrowse_complete, 0);
+ g_albumBrowseObjects.insert(m_sp_albumbrowse, this);
+}
+
+QList<QObject *> QSpotifyAlbumBrowse::tracks() const
+{
+ QList<QObject*> list;
+ if (m_albumTracks != 0) {
+ int c = m_albumTracks->m_tracks.count();
+ for (int i = 0; i < c; ++i)
+ list.append((QObject*)(m_albumTracks->m_tracks[i]));
+ }
+ return list;
+}
+
+void QSpotifyAlbumBrowse::clearData()
+{
+ if (m_sp_albumbrowse) {
+ g_albumBrowseObjects.remove(m_sp_albumbrowse);
+ sp_albumbrowse_release(m_sp_albumbrowse);
+ m_sp_albumbrowse = 0;
+ }
+ if (m_albumTracks) {
+ m_albumTracks->release();
+ m_albumTracks = 0;
+ }
+ m_hasMultipleArtists = false;
+}
+
+void QSpotifyAlbumBrowse::processData()
+{
+ if (m_sp_albumbrowse) {
+ if (sp_albumbrowse_error(m_sp_albumbrowse) != SP_ERROR_OK)
+ return;
+
+ m_albumTracks = new QSpotifyTrackList;
+ int c = sp_albumbrowse_num_tracks(m_sp_albumbrowse);
+ for (int i = 0; i < c; ++i) {
+ sp_track *track = sp_albumbrowse_track(m_sp_albumbrowse, i);
+ QSpotifyTrack *qtrack = new QSpotifyTrack(track, m_albumTracks);
+ m_albumTracks->m_tracks.append(qtrack);
+ connect(qtrack, SIGNAL(isStarredChanged()), this, SIGNAL(isStarredChanged()));
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksAdded(QVector<sp_track*>)), qtrack, SLOT(onStarredListTracksAdded(QVector<sp_track*>)));
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksRemoved(QVector<sp_track*>)), qtrack, SLOT(onStarredListTracksRemoved(QVector<sp_track*>)));
+ if (qtrack->artists() != m_album->artist())
+ m_hasMultipleArtists = true;
+ }
+
+ emit tracksChanged();
+ }
+}
+
+int QSpotifyAlbumBrowse::totalDuration() const
+{
+ if (m_albumTracks)
+ return m_albumTracks->totalDuration();
+ else
+ return 0;
+}
+
+void QSpotifyAlbumBrowse::play()
+{
+ if (!m_albumTracks || m_albumTracks->m_tracks.isEmpty())
+ return;
+
+ QSpotifyTrack *track = m_albumTracks->m_tracks.at(0);
+ QSpotifySession::instance()->playQueue()->playTrack(track);
+}
+
+void QSpotifyAlbumBrowse::enqueue()
+{
+ if (!m_albumTracks)
+ return;
+
+ QSpotifySession::instance()->playQueue()->enqueueTracks(m_albumTracks->m_tracks);
+}
+
+bool QSpotifyAlbumBrowse::isStarred() const
+{
+ if (!m_albumTracks)
+ return false;
+
+ int c = m_albumTracks->m_tracks.count();
+ for (int i = 0; i < c; ++i) {
+ if (!m_albumTracks->m_tracks.at(i)->isStarred())
+ return false;
+ }
+ return true;
+}
+
+void QSpotifyAlbumBrowse::setStarred(bool s)
+{
+ if (!m_albumTracks)
+ return;
+
+ int c = m_albumTracks->m_tracks.count();
+ const sp_track *tracks[c];
+ for (int i = 0; i < c; ++i)
+ tracks[i] = m_albumTracks->m_tracks.at(i)->sptrack();
+ sp_track_set_starred(QSpotifySession::instance()->spsession(), const_cast<sp_track* const*>(tracks), c, s);
+}
diff --git a/libQtSpotify/qspotifyalbumbrowse.h b/libQtSpotify/qspotifyalbumbrowse.h
new file mode 100644
index 0000000..4895639
--- /dev/null
+++ b/libQtSpotify/qspotifyalbumbrowse.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYALBUMBROWSE_H
+#define QSPOTIFYALBUMBROWSE_H
+
+#include <QtCore/QObject>
+
+struct sp_albumbrowse;
+class QSpotifyAlbum;
+class QSpotifyTrackList;
+
+class QSpotifyAlbumBrowse : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QSpotifyAlbum *album READ album WRITE setAlbum NOTIFY albumChanged)
+ Q_PROPERTY(QList<QObject *> tracks READ tracks NOTIFY tracksChanged)
+ Q_PROPERTY(int totalDuration READ totalDuration NOTIFY tracksChanged)
+ Q_PROPERTY(bool isStarred READ isStarred WRITE setStarred NOTIFY isStarredChanged)
+ Q_PROPERTY(bool hasMultipleArtists READ hasMultipleArtists NOTIFY albumChanged)
+public:
+ QSpotifyAlbumBrowse(QObject *parent = 0);
+ ~QSpotifyAlbumBrowse();
+
+ QSpotifyAlbum *album() const { return m_album; }
+ void setAlbum(QSpotifyAlbum *album);
+
+ QList<QObject *> tracks() const;
+ int totalDuration() const;
+ bool hasMultipleArtists() const { return m_hasMultipleArtists; }
+
+ bool event(QEvent *);
+
+ Q_INVOKABLE void play();
+ Q_INVOKABLE void enqueue();
+
+ bool isStarred() const;
+ void setStarred(bool s);
+
+Q_SIGNALS:
+ void albumChanged();
+ void tracksChanged();
+ void isStarredChanged();
+
+private:
+ void clearData();
+ void processData();
+
+ sp_albumbrowse *m_sp_albumbrowse;
+
+ QSpotifyAlbum *m_album;
+ QSpotifyTrackList *m_albumTracks;
+
+ bool m_hasMultipleArtists;
+
+ friend class QSpotifyPlaylist;
+ friend class QSpotifyUser;
+};
+
+#endif // QSPOTIFYALBUMBROWSE_H
diff --git a/libQtSpotify/qspotifyartist.cpp b/libQtSpotify/qspotifyartist.cpp
new file mode 100644
index 0000000..8ef6b32
--- /dev/null
+++ b/libQtSpotify/qspotifyartist.cpp
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyartist.h"
+
+#include <QtCore/QDebug>
+#include <libspotify/api.h>
+
+QSpotifyArtist::QSpotifyArtist(sp_artist *artist)
+ : QSpotifyObject(true)
+{
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(artistDataChanged()));
+
+ sp_artist_add_ref(artist);
+ m_sp_artist = artist;
+ metadataUpdated();
+}
+
+QSpotifyArtist::~QSpotifyArtist()
+{
+ sp_artist_release(m_sp_artist);
+}
+
+bool QSpotifyArtist::isLoaded()
+{
+ return sp_artist_is_loaded(m_sp_artist);
+}
+
+bool QSpotifyArtist::updateData()
+{
+ bool updated = false;
+
+ QString name = QString::fromUtf8(sp_artist_name(m_sp_artist));
+ if (m_name != name) {
+ m_name = name;
+ updated = true;
+ }
+
+ if (m_pictureId.isEmpty()) {
+ sp_link *link = sp_link_create_from_artist_portrait(m_sp_artist);
+ if (link) {
+ char buffer[200];
+ int uriSize = sp_link_as_string(link, &buffer[0], 200);
+ m_pictureId = QString::fromLatin1(&buffer[0], uriSize);
+ sp_link_release(link);
+ updated = true;
+ }
+ }
+
+ return updated;
+}
diff --git a/libQtSpotify/qspotifyartist.h b/libQtSpotify/qspotifyartist.h
new file mode 100644
index 0000000..70e58f2
--- /dev/null
+++ b/libQtSpotify/qspotifyartist.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYARTIST_H
+#define QSPOTIFYARTIST_H
+
+#include "qspotifyobject.h"
+
+struct sp_artist;
+
+class QSpotifyArtist : public QSpotifyObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ name NOTIFY artistDataChanged)
+ Q_PROPERTY(QString pictureId READ pictureId NOTIFY artistDataChanged)
+public:
+ ~QSpotifyArtist();
+
+ bool isLoaded();
+
+ QString name() const { return m_name; }
+ QString pictureId() const { return m_pictureId; }
+
+ sp_artist *spartist() const { return m_sp_artist; }
+
+Q_SIGNALS:
+ void artistDataChanged();
+
+protected:
+ bool updateData();
+
+private:
+ QSpotifyArtist(sp_artist *artist);
+
+ sp_artist *m_sp_artist;
+
+ QString m_name;
+ QString m_pictureId;
+
+ friend class QSpotifySession;
+ friend class QSpotifyTrack;
+ friend class QSpotifyAlbum;
+ friend class QSpotifySearch;
+ friend class QSpotifyToplist;
+ friend class QSpotifyArtistBrowse;
+};
+
+#endif // QSPOTIFYARTIST_H
diff --git a/libQtSpotify/qspotifyartistbrowse.cpp b/libQtSpotify/qspotifyartistbrowse.cpp
new file mode 100644
index 0000000..85303d5
--- /dev/null
+++ b/libQtSpotify/qspotifyartistbrowse.cpp
@@ -0,0 +1,253 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyartistbrowse.h"
+
+#include "qspotifyartist.h"
+#include "qspotifyalbum.h"
+#include "qspotifysession.h"
+#include "qspotifytracklist.h"
+#include "qspotifytrack.h"
+#include "qspotifyuser.h"
+#include "qspotifyplaylist.h"
+
+#include <QtCore/QHash>
+#include <QtConcurrentRun>
+#include <libspotify/api.h>
+
+static QHash<sp_artistbrowse*, QSpotifyArtistBrowse*> g_artistBrowseObjects;
+static QMutex g_mutex;
+
+static void callback_artistbrowse_complete(sp_artistbrowse *result, void *)
+{
+ QMutexLocker lock(&g_mutex);
+ QSpotifyArtistBrowse *o = g_artistBrowseObjects.value(result);
+ if (o)
+ QCoreApplication::postEvent(o, new QEvent(QEvent::User));
+}
+
+QSpotifyArtistBrowse::QSpotifyArtistBrowse(QObject *parent)
+ : QObject(parent)
+ , m_sp_artistbrowse(0)
+ , m_artist(0)
+ , m_topTracks(0)
+ , m_busy(false)
+ , m_topHitsReady(false)
+ , m_dataReady(false)
+{
+ m_hackSearch.setTracksLimit(0);
+ m_hackSearch.setAlbumsLimit(0);
+ m_topHitsSearch.setAlbumsLimit(0);
+ m_topHitsSearch.setArtistsLimit(0);
+ m_topHitsSearch.setTracksLimit(50);
+ connect(&m_topHitsSearch, SIGNAL(resultsChanged()), this, SLOT(processTopHits()));
+}
+
+QSpotifyArtistBrowse::~QSpotifyArtistBrowse()
+{
+ clearData();
+}
+
+void QSpotifyArtistBrowse::setArtist(QSpotifyArtist *artist)
+{
+ if (m_artist == artist)
+ return;
+ clearData();
+ m_artist = artist;
+ emit artistChanged();
+
+ if (!m_artist)
+ return;
+
+ m_topHitsReady = false;
+ m_dataReady = false;
+ m_busy = true;
+ emit busyChanged();
+
+ QMutexLocker lock(&g_mutex);
+ m_sp_artistbrowse = sp_artistbrowse_create(QSpotifySession::instance()->spsession(), m_artist->spartist(), callback_artistbrowse_complete, 0);
+ g_artistBrowseObjects.insert(m_sp_artistbrowse, this);
+
+ m_topHitsSearch.setQuery(QString(QLatin1String("artist:\"%1\"")).arg(m_artist->name()));
+ m_topHitsSearch.search();
+}
+
+QList<QObject *> QSpotifyArtistBrowse::topTracks() const
+{
+ QList<QObject*> list;
+ if (m_topTracks != 0) {
+ int c = m_topTracks->m_tracks.count();
+ for (int i = 0; i < c; ++i)
+ list.append((QObject*)(m_topTracks->m_tracks[i]));
+ }
+ return list;
+}
+
+bool QSpotifyArtistBrowse::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ g_mutex.lock();
+ g_artistBrowseObjects.remove(m_sp_artistbrowse);
+ g_mutex.unlock();
+ processData();
+ e->accept();
+ return true;
+ }
+ return QObject::event(e);
+}
+
+void QSpotifyArtistBrowse::clearData()
+{
+ if (m_sp_artistbrowse) {
+ QMutexLocker lock(&g_mutex);
+ g_artistBrowseObjects.remove(m_sp_artistbrowse);
+ sp_artistbrowse_release(m_sp_artistbrowse);
+ m_sp_artistbrowse = 0;
+ }
+ m_biography.clear();
+ if (m_topTracks)
+ m_topTracks->release();
+ m_topTracks = 0;
+ qDeleteAll(m_albums);
+ m_albums.clear();
+ qDeleteAll(m_singles);
+ m_singles.clear();
+ qDeleteAll(m_compilations);
+ m_compilations.clear();
+ qDeleteAll(m_appearsOn);
+ m_appearsOn.clear();
+ qDeleteAll(m_similarArtists);
+ m_similarArtists.clear();
+}
+
+void QSpotifyArtistBrowse::processData()
+{
+ if (m_sp_artistbrowse) {
+ m_dataReady = true;
+
+ if (sp_artistbrowse_error(m_sp_artistbrowse) != SP_ERROR_OK)
+ return;
+
+ m_biography = QString::fromUtf8(sp_artistbrowse_biography(m_sp_artistbrowse)).split(QLatin1Char('\n'), QString::SkipEmptyParts);
+
+ if (sp_artistbrowse_num_portraits(m_sp_artistbrowse) > 0) {
+ sp_link *link = sp_link_create_from_artistbrowse_portrait(m_sp_artistbrowse, 0);
+ char buffer[200];
+ int uriSize = sp_link_as_string(link, &buffer[0], 200);
+ m_pictureId = QString::fromLatin1(&buffer[0], uriSize);
+ sp_link_release(link);
+ }
+
+ int c = qMin(80, sp_artistbrowse_num_albums(m_sp_artistbrowse));
+ for (int i = 0; i < c; ++i) {
+ sp_album *album = sp_artistbrowse_album(m_sp_artistbrowse, i);
+ if (!sp_album_is_available(album))
+ continue;
+ QSpotifyAlbum *qalbum = new QSpotifyAlbum(album);
+ if ((qalbum->type() == QSpotifyAlbum::Album || qalbum->type() == QSpotifyAlbum::Unknown) && qalbum->artist() == m_artist->name()) {
+ qalbum->setSectionType("Albums");
+ m_albums.append((QObject *)qalbum);
+ } else if (qalbum->type() == QSpotifyAlbum::Single) {
+ qalbum->setSectionType("Singles");
+ m_singles.append((QObject *)qalbum);
+ } else if (qalbum->type() == QSpotifyAlbum::Compilation) {
+ qalbum->setSectionType("Compilations");
+ m_compilations.append((QObject *)qalbum);
+ } else {
+ qalbum->setSectionType("Appears on");
+ m_appearsOn.append((QObject *)qalbum);
+ }
+ }
+
+ QStringList similArt;
+ c = sp_artistbrowse_num_similar_artists(m_sp_artistbrowse);
+ for (int i = 0; i < c; ++i) {
+ QSpotifyArtist *artist = new QSpotifyArtist(sp_artistbrowse_similar_artist(m_sp_artistbrowse, i));
+ m_similarArtists.append((QObject *)artist);
+ similArt.append(QString(QLatin1String("\"%1\"")).arg(artist->name()));
+ }
+
+ if (c > 0) {
+ connect(&m_hackSearch, SIGNAL(resultsChanged()), this, SLOT(searchArtists()));
+ m_hackSearch.setArtistsLimit(similArt.count() * 2);
+ m_hackSearch.setQuery(similArt.join(QLatin1String(" OR ")));
+ m_hackSearch.search();
+ } else if (m_topHitsReady) {
+ m_busy = false;
+ emit busyChanged();
+ emit dataChanged();
+ }
+ }
+}
+
+void QSpotifyArtistBrowse::searchArtists()
+{
+ disconnect(&m_hackSearch, SIGNAL(resultsChanged()), this, SLOT(searchArtists()));
+ for (int i = 0; i < m_similarArtists.count(); ++i)
+ dynamic_cast<QSpotifyArtist *>(m_similarArtists[i])->metadataUpdated();
+
+ if (m_topHitsReady) {
+ m_busy = false;
+ emit busyChanged();
+ emit dataChanged();
+ }
+}
+
+void QSpotifyArtistBrowse::processTopHits()
+{
+ m_topHitsReady = true;
+ m_topTracks = new QSpotifyTrackList;
+ int c = m_topHitsSearch.trackResults()->m_tracks.count();
+ for (int i = 0; i < c && m_topTracks->m_tracks.count() < 10; ++i) {
+ QSpotifyTrack *t = m_topHitsSearch.trackResults()->m_tracks[i];
+ QStringList artists = t->artists().split(", ");
+ if (artists.contains(m_artist->name())) {
+ m_topTracks->m_tracks.append(t);
+ t->addRef();
+ }
+ }
+
+ if (m_dataReady) {
+ m_busy = false;
+ emit busyChanged();
+ emit dataChanged();
+ }
+}
diff --git a/libQtSpotify/qspotifyartistbrowse.h b/libQtSpotify/qspotifyartistbrowse.h
new file mode 100644
index 0000000..08b825c
--- /dev/null
+++ b/libQtSpotify/qspotifyartistbrowse.h
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYARTISTBROWSE_H
+#define QSPOTIFYARTISTBROWSE_H
+
+#include <QtCore/QObject>
+#include <QtCore/QStringList>
+#include "qspotifysearch.h"
+
+struct sp_artistbrowse;
+class QSpotifyAlbum;
+class QSpotifyArtist;
+class QSpotifyTrackList;
+
+class QSpotifyArtistBrowse : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QSpotifyArtist *artist READ artist WRITE setArtist NOTIFY artistChanged)
+ Q_PROPERTY(QList<QObject *> topTracks READ topTracks NOTIFY dataChanged)
+ Q_PROPERTY(QList<QObject *> albums READ albums NOTIFY dataChanged)
+ Q_PROPERTY(int albumCount READ albumCount NOTIFY dataChanged)
+ Q_PROPERTY(int singleCount READ singleCount NOTIFY dataChanged)
+ Q_PROPERTY(int compilationCount READ compilationCount NOTIFY dataChanged)
+ Q_PROPERTY(int appearsOnCount READ appearsOnCount NOTIFY dataChanged)
+ Q_PROPERTY(QString pictureId READ pictureId NOTIFY dataChanged)
+ Q_PROPERTY(QStringList biography READ biography NOTIFY dataChanged)
+ Q_PROPERTY(QList<QObject *> similarArtists READ similarArtists NOTIFY dataChanged)
+ Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
+public:
+ QSpotifyArtistBrowse(QObject *parent = 0);
+ ~QSpotifyArtistBrowse();
+
+ QSpotifyArtist *artist() const { return m_artist; }
+ void setArtist(QSpotifyArtist *artist);
+
+ QList<QObject *> topTracks() const;
+ QList<QObject *> albums() const { return m_albums + m_singles + m_compilations + m_appearsOn; }
+
+ int albumCount() const { return m_albums.count(); }
+ int singleCount() const { return m_singles.count(); }
+ int compilationCount() const { return m_compilations.count(); }
+ int appearsOnCount() const { return m_appearsOn.count(); }
+
+ QString pictureId() const { return m_pictureId; }
+
+ QStringList biography() const { return m_biography; }
+
+ QList<QObject *> similarArtists() const { return m_similarArtists; }
+
+ bool busy() const { return m_busy; }
+
+ bool event(QEvent *);
+
+Q_SIGNALS:
+ void artistChanged();
+ void dataChanged();
+ void busyChanged();
+
+private Q_SLOTS:
+ void searchArtists();
+ void processTopHits();
+
+private:
+ void clearData();
+ void processData();
+
+ sp_artistbrowse *m_sp_artistbrowse;
+
+ QSpotifyArtist *m_artist;
+ QSpotifyTrackList *m_topTracks;
+ QList<QObject *> m_albums;
+ QList<QObject *> m_singles;
+ QList<QObject *> m_compilations;
+ QList<QObject *> m_appearsOn;
+ QString m_pictureId;
+ QStringList m_biography;
+ QList<QObject *> m_similarArtists;
+ bool m_busy;
+ QSpotifySearch m_hackSearch;
+ QSpotifySearch m_topHitsSearch;
+
+ bool m_topHitsReady;
+ bool m_dataReady;
+};
+
+#endif // QSPOTIFYARTISTBROWSE_H
diff --git a/libQtSpotify/qspotifyimageprovider.cpp b/libQtSpotify/qspotifyimageprovider.cpp
new file mode 100644
index 0000000..b93f82a
--- /dev/null
+++ b/libQtSpotify/qspotifyimageprovider.cpp
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyimageprovider.h"
+
+#include "qspotifysession.h"
+#include <libspotify/api.h>
+#include <QtConcurrentRun>
+
+QSpotifyImageProvider::QSpotifyImageProvider()
+ : QDeclarativeImageProvider(QDeclarativeImageProvider::Image)
+{
+}
+
+QImage QSpotifyImageProvider::requestImage(const QString &id, QSize *size, const QSize &)
+{
+ QImage im = QSpotifySession::instance()->requestSpotifyImage(id);
+
+ if (size)
+ *size = im.size();
+ return im;
+}
diff --git a/libQtSpotify/qspotifyimageprovider.h b/libQtSpotify/qspotifyimageprovider.h
new file mode 100644
index 0000000..af2d5e2
--- /dev/null
+++ b/libQtSpotify/qspotifyimageprovider.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYIMAGEPROVIDER_H
+#define QSPOTIFYIMAGEPROVIDER_H
+
+#include <QtDeclarative/QDeclarativeImageProvider>
+
+class QSpotifyImageProvider : public QDeclarativeImageProvider
+{
+public:
+ QSpotifyImageProvider();
+
+ QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
+};
+
+#endif // QSPOTIFYIMAGEPROVIDER_H
diff --git a/libQtSpotify/qspotifyobject.cpp b/libQtSpotify/qspotifyobject.cpp
new file mode 100644
index 0000000..31caa10
--- /dev/null
+++ b/libQtSpotify/qspotifyobject.cpp
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyobject.h"
+#include "qspotifysession.h"
+
+QSpotifyObject::QSpotifyObject(bool autoConnectToSessionSignal)
+ : QObject(0)
+ , m_isLoaded(false)
+ , m_refCount(1)
+{
+ if (autoConnectToSessionSignal)
+ connect(QSpotifySession::instance(), SIGNAL(metadataUpdated()), this, SLOT(metadataUpdated()));
+}
+
+void QSpotifyObject::metadataUpdated()
+{
+ bool updated = updateData();
+ bool newIsLoaded = isLoaded();
+ if (m_isLoaded != newIsLoaded) {
+ m_isLoaded = newIsLoaded;
+ emit isLoadedChanged();
+ }
+ if (updated)
+ emit dataChanged();
+}
+
+void QSpotifyObject::release()
+{
+ --m_refCount;
+ if (m_refCount == 0)
+ deleteLater();
+}
diff --git a/libQtSpotify/qspotifyobject.h b/libQtSpotify/qspotifyobject.h
new file mode 100644
index 0000000..823345c
--- /dev/null
+++ b/libQtSpotify/qspotifyobject.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYOBJECT_H
+#define QSPOTIFYOBJECT_H
+
+#include <QObject>
+
+class QSpotifySession;
+
+class QSpotifyObject : public QObject
+{
+ Q_OBJECT
+public:
+ QSpotifyObject(bool autoConnectToSessionSignal);
+ virtual ~QSpotifyObject() { }
+
+ virtual bool isLoaded() = 0;
+
+ void addRef() { ++m_refCount; }
+ void release();
+
+public Q_SLOTS:
+ void metadataUpdated();
+
+Q_SIGNALS:
+ void isLoadedChanged();
+ void dataChanged();
+
+protected:
+ virtual bool updateData() = 0;
+
+private:
+ bool m_isLoaded;
+
+ int m_refCount;
+
+};
+
+#endif // QSPOTIFYOBJECT_H
diff --git a/libQtSpotify/qspotifyplaylist.cpp b/libQtSpotify/qspotifyplaylist.cpp
new file mode 100644
index 0000000..95c991e
--- /dev/null
+++ b/libQtSpotify/qspotifyplaylist.cpp
@@ -0,0 +1,526 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyplaylist.h"
+#include "qspotifytrack.h"
+#include "qspotifysession.h"
+#include "qspotifyplayqueue.h"
+#include "qspotifyuser.h"
+#include "qspotifyalbumbrowse.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QHash>
+#include <QtCore/QDebug>
+
+static QHash<sp_playlist*, QSpotifyPlaylist*> g_playlistObjects;
+
+class QSpotifyTracksAddedEvent : public QEvent
+{
+public:
+ QSpotifyTracksAddedEvent(QVector<sp_track*> tracks, int pos)
+ : QEvent(Type(User + 3))
+ , m_tracks(tracks)
+ , m_position(pos)
+ { }
+
+ QVector<sp_track*> tracks() const { return m_tracks; }
+ int position() const { return m_position; }
+
+private:
+ QVector<sp_track*> m_tracks;
+ int m_position;
+};
+
+class QSpotifyTracksRemovedEvent : public QEvent
+{
+public:
+ QSpotifyTracksRemovedEvent(QVector<int> positions)
+ : QEvent(Type(User + 4))
+ , m_positions(positions)
+ { }
+
+ QVector<int> positions() const { return m_positions; }
+
+private:
+ QVector<int> m_positions;
+};
+
+class QSpotifyTracksMovedEvent : public QEvent
+{
+public:
+ QSpotifyTracksMovedEvent(QVector<int> positions, int newpos)
+ : QEvent(Type(User + 5))
+ , m_positions(positions)
+ , m_newposition(newpos)
+ { }
+
+ QVector<int> positions() const { return m_positions; }
+ int newPosition() const { return m_newposition; }
+
+private:
+ QVector<int> m_positions;
+ int m_newposition;
+};
+
+class QSpotifyTrackSeenEvent : public QEvent
+{
+public:
+ QSpotifyTrackSeenEvent(int pos, bool seen)
+ : QEvent(Type(User + 6))
+ , m_position(pos)
+ , m_seen(seen)
+ { }
+
+ int position() const { return m_position; }
+ bool seen() const { return m_seen; }
+
+private:
+ int m_position;
+ bool m_seen;
+};
+
+static void callback_playlist_state_changed(sp_playlist *playlist, void *)
+{
+ QCoreApplication::postEvent(g_playlistObjects.value(playlist), new QEvent(QEvent::User));
+}
+
+static void callback_playlist_metadata_updated(sp_playlist *playlist, void *)
+{
+ QCoreApplication::postEvent(g_playlistObjects.value(playlist), new QEvent(QEvent::Type(QEvent::User + 1)));
+}
+
+static void callback_playlist_renamed(sp_playlist *playlist, void *)
+{
+ QCoreApplication::postEvent(g_playlistObjects.value(playlist), new QEvent(QEvent::Type(QEvent::User + 2)));
+}
+
+static void callback_tracks_added(sp_playlist *pl, sp_track *const *tracks, int num_tracks, int position, void *)
+{
+ QVector<sp_track*> vec;
+ for (int i = 0; i < num_tracks; ++i)
+ vec.append(tracks[i]);
+ QCoreApplication::postEvent(g_playlistObjects.value(pl), new QSpotifyTracksAddedEvent(vec, position));
+}
+
+static void callback_tracks_removed(sp_playlist *pl, const int *tracks, int num_tracks, void *)
+{
+ QVector<int> vec;
+ for (int i = 0; i < num_tracks; ++i)
+ vec.append(tracks[i]);
+ QCoreApplication::postEvent(g_playlistObjects.value(pl), new QSpotifyTracksRemovedEvent(vec));
+}
+
+static void callback_tracks_moved(sp_playlist *pl, const int *tracks, int num_tracks, int new_position, void *)
+{
+ QVector<int> vec;
+ for (int i = 0; i < num_tracks; ++i)
+ vec.append(tracks[i]);
+ QCoreApplication::postEvent(g_playlistObjects.value(pl), new QSpotifyTracksMovedEvent(vec, new_position));
+}
+
+static void callback_track_seen_changed(sp_playlist *pl, int position, bool seen, void *)
+{
+ QCoreApplication::postEvent(g_playlistObjects.value(pl), new QSpotifyTrackSeenEvent(position, seen));
+}
+
+
+QSpotifyPlaylist::QSpotifyPlaylist(Type type, sp_playlist *playlist, bool incrRefCount)
+ : QSpotifyObject(true)
+ , m_type(type)
+ , m_offlineStatus(No)
+ , m_collaborative(false)
+ , m_offlineDownloadProgress(0)
+ , m_availableOffline(false)
+{
+ m_trackList = new QSpotifyTrackList(type == Starred || type == Inbox);
+
+ if (incrRefCount)
+ sp_playlist_add_ref(playlist);
+ m_sp_playlist = playlist;
+ g_playlistObjects.insert(playlist, this);
+ m_callbacks = new sp_playlist_callbacks;
+ m_callbacks->playlist_state_changed = callback_playlist_state_changed;
+ m_callbacks->description_changed = 0;
+ m_callbacks->image_changed = 0;
+ m_callbacks->playlist_metadata_updated = callback_playlist_metadata_updated;
+ m_callbacks->playlist_renamed = callback_playlist_renamed;
+ m_callbacks->playlist_update_in_progress = 0;
+ m_callbacks->subscribers_changed = 0;
+ m_callbacks->tracks_added = callback_tracks_added;
+ m_callbacks->tracks_moved = callback_tracks_moved;
+ m_callbacks->tracks_removed = callback_tracks_removed;
+ m_callbacks->track_created_changed = 0;
+ m_callbacks->track_message_changed = 0;
+ m_callbacks->track_seen_changed = callback_track_seen_changed;
+ sp_playlist_add_callbacks(m_sp_playlist, m_callbacks, 0);
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(playlistDataChanged()));
+ connect(this, SIGNAL(isLoadedChanged()), this, SIGNAL(thisIsLoadedChanged()));
+ connect(this, SIGNAL(playlistDataChanged()), this , SIGNAL(seenCountChanged()));
+
+ metadataUpdated();
+}
+
+QSpotifyPlaylist::~QSpotifyPlaylist()
+{
+ emit playlistDestroyed();
+ g_playlistObjects.remove(m_sp_playlist);
+ sp_playlist_remove_callbacks(m_sp_playlist, m_callbacks, 0);
+ m_trackList->release();
+ sp_playlist_release(m_sp_playlist);
+ delete m_callbacks;
+}
+
+bool QSpotifyPlaylist::isLoaded()
+{
+ return sp_playlist_is_loaded(m_sp_playlist);
+}
+
+bool QSpotifyPlaylist::updateData()
+{
+ bool updated = false;
+
+ QString name = QString::fromUtf8(sp_playlist_name(m_sp_playlist));
+ if (m_name != name) {
+ m_name = name;
+ updated = true;
+ }
+
+ QString owner = QString::fromUtf8(sp_user_canonical_name(sp_playlist_owner(m_sp_playlist)));
+ if (m_owner != owner) {
+ m_owner = owner;
+ updated = true;
+ }
+
+ bool collab = sp_playlist_is_collaborative(m_sp_playlist);
+ if (m_collaborative != collab) {
+ m_collaborative = collab;
+ updated = true;
+ }
+
+ if (m_trackList->m_tracks.isEmpty()) {
+ int count = sp_playlist_num_tracks(m_sp_playlist);
+ for (int i = 0; i < count; ++i)
+ addTrack(sp_playlist_track(m_sp_playlist, i));
+ updated = true;
+ }
+
+ OfflineStatus os = OfflineStatus(sp_playlist_get_offline_status(QSpotifySession::instance()->spsession(), m_sp_playlist));
+ if (m_offlineStatus != os) {
+ m_offlineStatus = os;
+
+ if (m_offlineStatus != No) {
+ m_availableOffline = true;
+ emit availableOfflineChanged();
+ }
+
+ updated = true;
+ }
+
+ if (m_offlineStatus == Downloading) {
+ int dp = sp_playlist_get_offline_download_completed(QSpotifySession::instance()->spsession(), m_sp_playlist);
+ if (m_offlineDownloadProgress != dp) {
+ m_offlineDownloadProgress = dp;
+ updated = true;
+ }
+ }
+
+ return updated;
+}
+
+void QSpotifyPlaylist::addTrack(sp_track *track, int pos)
+{
+ QSpotifyTrack *qtrack = new QSpotifyTrack(track, this);
+ if (pos == -1)
+ m_trackList->m_tracks.append(qtrack);
+ else
+ m_trackList->m_tracks.insert(pos, qtrack);
+ m_tracksSet.insert(track);
+ connect(qtrack, SIGNAL(trackDataChanged()), this, SIGNAL(playlistDataChanged()));
+ if (m_type != Starred) {
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksAdded(QVector<sp_track*>)), qtrack, SLOT(onStarredListTracksAdded(QVector<sp_track*>)));
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksRemoved(QVector<sp_track*>)), qtrack, SLOT(onStarredListTracksRemoved(QVector<sp_track*>)));
+ }
+ if (m_type == Inbox) {
+ connect(qtrack, SIGNAL(seenChanged()), this, SIGNAL(seenCountChanged()));
+ }
+ qtrack->metadataUpdated();
+}
+
+bool QSpotifyPlaylist::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ metadataUpdated();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 1) {
+ // TracksMetadata updated
+ for (int i = 0; i < m_trackList->m_tracks.count(); ++i) {
+ m_trackList->m_tracks.at(i)->metadataUpdated();
+ }
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 2) {
+ // Playlist renamed
+ m_name = QString::fromUtf8(sp_playlist_name(m_sp_playlist));
+ emit dataChanged();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 3) {
+ // TracksAdded event
+ QSpotifyTracksAddedEvent *ev = static_cast<QSpotifyTracksAddedEvent *>(e);
+ QVector<sp_track*> tracks = ev->tracks();
+ if (m_trackList->m_tracks.count() + tracks.count() == sp_playlist_num_tracks(m_sp_playlist)) {
+ int pos = ev->position();
+ for (int i = 0; i < tracks.count(); ++i)
+ addTrack(tracks.at(i), pos++);
+ emit dataChanged();
+ if (m_type == Starred || m_type == Inbox)
+ emit tracksAdded(tracks);
+ m_trackList->setShuffle(m_trackList->isShuffle());
+ if (QSpotifySession::instance()->playQueue()->isCurrentTrackList(m_trackList))
+ QSpotifySession::instance()->playQueue()->tracksUpdated();
+ }
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 4) {
+ // TracksRemoved event
+ QSpotifyTracksRemovedEvent *ev = static_cast<QSpotifyTracksRemovedEvent *>(e);
+ QVector<int> tracks = ev->positions();
+ QVector<sp_track *> tracksSignal;
+ for (int i = 0; i < tracks.count(); ++i) {
+ int pos = tracks.at(i);
+ if (pos < 0 || pos >= m_trackList->m_tracks.count())
+ continue;
+ QSpotifyTrack *tr = m_trackList->m_tracks[pos];
+ tracksSignal.append(tr->m_sp_track);
+ m_tracksSet.remove(tr->m_sp_track);
+ tr->release();
+ m_trackList->m_tracks.replace(pos, 0);
+ }
+ m_trackList->m_tracks.removeAll(0);
+ emit dataChanged();
+ if (m_type == Starred)
+ emit tracksRemoved(tracksSignal);
+ if (QSpotifySession::instance()->playQueue()->isCurrentTrackList(m_trackList))
+ QSpotifySession::instance()->playQueue()->tracksUpdated();
+ e->accept();
+ m_trackList->setShuffle(m_trackList->isShuffle());
+ return true;
+ } else if (e->type() == QEvent::User + 5) {
+ // TracksMoved event
+ QSpotifyTracksMovedEvent *ev = static_cast<QSpotifyTracksMovedEvent *>(e);
+ QVector<int> positions = ev->positions();
+ int newpos = ev->newPosition();
+ QVector<QSpotifyTrack*> tracks;
+ for (int i = 0; i < positions.count(); ++i) {
+ tracks.append(m_trackList->m_tracks[positions.at(i)]);
+ m_trackList->m_tracks.replace(positions.at(i), 0);
+ }
+ for (int i = 0; i < tracks.count(); ++i)
+ m_trackList->m_tracks.insert(newpos++, tracks.at(i));
+ m_trackList->m_tracks.removeAll(0);
+ emit dataChanged();
+ if (QSpotifySession::instance()->playQueue()->isCurrentTrackList(m_trackList))
+ QSpotifySession::instance()->playQueue()->tracksUpdated();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 6) {
+ // TrackSeen event
+ if (m_type == Inbox) {
+ QSpotifyTrackSeenEvent *ev = static_cast<QSpotifyTrackSeenEvent*>(e);
+ m_trackList->m_tracks.at(ev->position())->updateSeen(ev->seen());
+ }
+ e->accept();
+ return true;
+ }
+ return QSpotifyObject::event(e);
+}
+
+void QSpotifyPlaylist::add(QSpotifyTrack *track)
+{
+ if (!track)
+ return;
+
+ sp_playlist_add_tracks(m_sp_playlist, const_cast<sp_track* const*>(&track->m_sp_track), 1, m_trackList->m_tracks.count(), QSpotifySession::instance()->spsession());
+}
+
+void QSpotifyPlaylist::remove(QSpotifyTrack *track)
+{
+ if (!track)
+ return;
+
+ int i = m_trackList->m_tracks.indexOf(track);
+ if (i > -1)
+ sp_playlist_remove_tracks(m_sp_playlist, &i, 1);
+}
+
+void QSpotifyPlaylist::addAlbum(QSpotifyAlbumBrowse *album)
+{
+ if (!album || !album->m_albumTracks)
+ return;
+
+ int c = album->m_albumTracks->m_tracks.count();
+ if (c < 1)
+ return;
+
+ const sp_track *tracks[c];
+ for (int i = 0; i < c; ++i)
+ tracks[i] = album->m_albumTracks->m_tracks.at(i)->sptrack();
+ sp_playlist_add_tracks(m_sp_playlist, const_cast<sp_track* const*>(tracks), c, m_trackList->m_tracks.count(), QSpotifySession::instance()->spsession());
+}
+
+void QSpotifyPlaylist::rename(const QString &name)
+{
+ if (name.trimmed().isEmpty())
+ return;
+
+ QString n = name;
+ if (n.size() > 255)
+ n.resize(255);
+
+ sp_playlist_rename(m_sp_playlist, n.toUtf8().constData());
+}
+
+int QSpotifyPlaylist::trackCount() const
+{
+ int c = 0;
+ for (int i = 0; i < m_trackList->m_tracks.count(); ++i) {
+ if (m_trackList->m_tracks.at(i)->error() == QSpotifyTrack::Ok)
+ ++c;
+ }
+ return c;
+}
+
+QList<QObject*> QSpotifyPlaylist::tracksAsQObject() const
+{
+ QList<QObject*> list;
+ if (m_type == Starred || m_type == Inbox) {
+ // Reverse order for StarredList to get the most recents first
+ for (int i = m_trackList->m_tracks.count() - 1; i >= 0 ; --i) {
+ QSpotifyTrack *t = m_trackList->m_tracks[i];
+ if (t->error() == QSpotifyTrack::Ok)
+ list.append((QObject*)(t));
+ }
+ } else {
+ for (int i = 0; i < m_trackList->m_tracks.count(); ++i) {
+ QSpotifyTrack *t = m_trackList->m_tracks[i];
+ if (t->error() == QSpotifyTrack::Ok)
+ list.append((QObject*)(t));
+ }
+ }
+ return list;
+}
+
+int QSpotifyPlaylist::totalDuration() const
+{
+ return m_trackList->totalDuration();
+}
+
+QString QSpotifyPlaylist::listSection() const
+{
+ if (m_type == Playlist)
+ return QLatin1String("p");
+ else
+ return QLatin1String("s");
+}
+
+void QSpotifyPlaylist::removeFromContainer()
+{
+ QSpotifySession::instance()->user()->removePlaylist(this);
+}
+
+bool QSpotifyPlaylist::isCurrentPlaylist() const
+{
+ return QSpotifySession::instance()->m_playQueue->m_implicitTracks == m_trackList;
+}
+
+void QSpotifyPlaylist::setCollaborative(bool c)
+{
+ sp_playlist_set_collaborative(m_sp_playlist, c);
+}
+
+void QSpotifyPlaylist::setAvailableOffline(bool offline)
+{
+ if (m_availableOffline == offline)
+ return;
+
+ m_availableOffline = offline;
+ sp_playlist_set_offline_mode(QSpotifySession::instance()->spsession(), m_sp_playlist, offline);
+ emit availableOfflineChanged();
+}
+
+void QSpotifyPlaylist::play()
+{
+ if (!m_trackList || m_trackList->m_tracks.isEmpty())
+ return;
+
+ int i = (m_type == Starred || m_type == Inbox) ? m_trackList->previousAvailable(m_trackList->m_tracks.count())
+ : m_trackList->nextAvailable(-1);
+ QSpotifySession::instance()->m_playQueue->playTrack(m_trackList->m_tracks.at(i));
+}
+
+void QSpotifyPlaylist::enqueue()
+{
+ int c = m_trackList->m_tracks.count();
+ if (m_type == Starred || m_type == Inbox) {
+ // Reverse order for StarredList to get the most recents first
+ QList<QSpotifyTrack *> tracks;
+ for (int i = c - 1; i >= 0 ; --i)
+ tracks.append(m_trackList->m_tracks.at(i));
+ QSpotifySession::instance()->playQueue()->enqueueTracks(tracks);
+ } else {
+ QSpotifySession::instance()->playQueue()->enqueueTracks(m_trackList->m_tracks);
+ }
+}
+
+int QSpotifyPlaylist::unseenCount() const
+{
+ if (m_type != Inbox)
+ return 0;
+
+ int c = 0;
+ for (int i = 0; i < m_trackList->m_tracks.count(); ++i) {
+ QSpotifyTrack *t = m_trackList->m_tracks.at(i);
+ if (t->error() == QSpotifyTrack::Ok && !t->seen())
+ ++c;
+ }
+ return c;
+}
diff --git a/libQtSpotify/qspotifyplaylist.h b/libQtSpotify/qspotifyplaylist.h
new file mode 100644
index 0000000..8dbc392
--- /dev/null
+++ b/libQtSpotify/qspotifyplaylist.h
@@ -0,0 +1,167 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYPLAYLIST_H
+#define QSPOTIFYPLAYLIST_H
+
+#include <QtCore/QSet>
+#include <QtCore/QVector>
+#include <QtCore/QMetaType>
+
+#include "qspotifyobject.h"
+#include "qspotifytracklist.h"
+
+#include <libspotify/api.h>
+
+struct sp_playlist;
+struct sp_playlist_callbacks;
+struct sp_track;
+class QSpotifyTrack;
+class QSpotifyAlbumBrowse;
+
+class QSpotifyPlaylist : public QSpotifyObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ name NOTIFY playlistDataChanged)
+ Q_PROPERTY(int trackCount READ trackCount NOTIFY playlistDataChanged)
+ Q_PROPERTY(int totalDuration READ totalDuration NOTIFY playlistDataChanged)
+ Q_PROPERTY(QList<QObject *> tracks READ tracksAsQObject NOTIFY playlistDataChanged)
+ Q_PROPERTY(bool isLoaded READ isLoaded NOTIFY thisIsLoadedChanged)
+ Q_PROPERTY(Type type READ type NOTIFY playlistDataChanged)
+ Q_PROPERTY(OfflineStatus offlineStatus READ offlineStatus NOTIFY playlistDataChanged)
+ Q_PROPERTY(QString listSection READ listSection NOTIFY thisIsLoadedChanged)
+ Q_PROPERTY(QString owner READ owner NOTIFY playlistDataChanged)
+ Q_PROPERTY(bool collaborative READ collaborative WRITE setCollaborative NOTIFY playlistDataChanged)
+ Q_PROPERTY(int offlineDownloadProgress READ offlineDownloadProgress NOTIFY playlistDataChanged)
+ Q_PROPERTY(bool availableOffline READ availableOffline WRITE setAvailableOffline NOTIFY availableOfflineChanged)
+ Q_PROPERTY(int unseenCount READ unseenCount NOTIFY seenCountChanged)
+ Q_ENUMS(Type)
+ Q_ENUMS(OfflineStatus)
+public:
+ enum Type {
+ Playlist,
+ Starred,
+ Inbox
+ };
+
+ enum OfflineStatus {
+ No = SP_PLAYLIST_OFFLINE_STATUS_NO,
+ Yes = SP_PLAYLIST_OFFLINE_STATUS_YES,
+ Downloading = SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING,
+ Waiting = SP_PLAYLIST_OFFLINE_STATUS_WAITING
+ };
+
+ virtual ~QSpotifyPlaylist();
+
+ bool isLoaded();
+
+ QString name() const { return m_name; }
+ int trackCount() const;
+ int totalDuration() const;
+ Type type() const { return m_type; }
+ OfflineStatus offlineStatus() const { return m_offlineStatus; }
+ QString owner() const { return m_owner; }
+ bool collaborative() const { return m_collaborative; }
+ void setCollaborative(bool c);
+ int offlineDownloadProgress() const { return m_offlineDownloadProgress; }
+ bool availableOffline() const { return m_availableOffline; }
+ void setAvailableOffline(bool offline);
+ QString listSection() const;
+ QList<QSpotifyTrack *> tracks() const { return m_trackList->m_tracks; }
+ QList<QObject *> tracksAsQObject() const;
+ int unseenCount() const;
+
+ bool contains(sp_track *t) const { return m_tracksSet.contains(t); }
+
+ Q_INVOKABLE void add(QSpotifyTrack *track);
+ Q_INVOKABLE void remove(QSpotifyTrack *track);
+
+ Q_INVOKABLE void addAlbum(QSpotifyAlbumBrowse *);
+
+ Q_INVOKABLE void rename(const QString &name);
+
+ Q_INVOKABLE void removeFromContainer();
+
+ Q_INVOKABLE bool isCurrentPlaylist() const;
+
+public Q_SLOTS:
+ void play();
+ void enqueue();
+
+Q_SIGNALS:
+ void playlistDestroyed();
+ void playlistDataChanged();
+ void thisIsLoadedChanged();
+ void tracksAdded(QVector<sp_track *>);
+ void tracksRemoved(QVector<sp_track *>);
+ void availableOfflineChanged();
+ void seenCountChanged();
+
+protected:
+ bool updateData();
+ bool event(QEvent *);
+
+private:
+ QSpotifyPlaylist(Type type, sp_playlist *playlist, bool incrRefCount = true);
+ void addTrack(sp_track *track, int pos = -1);
+
+ sp_playlist *m_sp_playlist;
+ sp_playlist_callbacks *m_callbacks;
+
+ QSpotifyTrackList *m_trackList;
+ QSet<sp_track *> m_tracksSet;
+
+ QString m_name;
+ Type m_type;
+ OfflineStatus m_offlineStatus;
+ QString m_owner;
+ bool m_collaborative;
+ int m_offlineDownloadProgress;
+ bool m_availableOffline;
+
+ QString m_uri;
+
+ friend class QSpotifyPlaylistContainer;
+ friend class QSpotifyUser;
+ friend class QSpotifyTrack;
+};
+
+#endif // QSPOTIFYPLAYLIST_H
diff --git a/libQtSpotify/qspotifyplaylistcontainer.cpp b/libQtSpotify/qspotifyplaylistcontainer.cpp
new file mode 100644
index 0000000..c7dff6e
--- /dev/null
+++ b/libQtSpotify/qspotifyplaylistcontainer.cpp
@@ -0,0 +1,212 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyplaylistcontainer.h"
+#include "qspotifyplaylist.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+#include <QtCore/QHash>
+#include <libspotify/api.h>
+
+static QHash<sp_playlistcontainer*, QSpotifyPlaylistContainer*> g_containerObjects;
+
+class QSpotifyPlaylistAddedEvent : public QEvent
+{
+public:
+ QSpotifyPlaylistAddedEvent(sp_playlist *playlist, int pos)
+ : QEvent(Type(User + 1))
+ , m_playlist(playlist)
+ , m_position(pos)
+ { }
+
+ sp_playlist *playlist() const { return m_playlist; }
+ int position() const { return m_position; }
+
+private:
+ sp_playlist *m_playlist;
+ int m_position;
+};
+
+class QSpotifyPlaylistRemovedEvent : public QEvent
+{
+public:
+ QSpotifyPlaylistRemovedEvent(int position)
+ : QEvent(Type(User + 2))
+ , m_position(position)
+ { }
+
+ int position() const { return m_position; }
+
+private:
+ int m_position;
+};
+
+class QSpotifyPlaylistMovedEvent : public QEvent
+{
+public:
+ QSpotifyPlaylistMovedEvent(int oldpos, int newpos)
+ : QEvent(Type(User + 3))
+ , m_position(oldpos)
+ , m_newposition(newpos)
+ { }
+
+ int position() const { return m_position; }
+ int newPosition() const { return m_newposition; }
+
+private:
+ int m_position;
+ int m_newposition;
+};
+
+static void callback_container_loaded(sp_playlistcontainer *pc, void *)
+{
+ QCoreApplication::postEvent(g_containerObjects.value(pc), new QEvent(QEvent::User));
+}
+
+static void callback_playlist_added(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *)
+{
+ QCoreApplication::postEvent(g_containerObjects.value(pc), new QSpotifyPlaylistAddedEvent(playlist, position));
+}
+
+static void callback_playlist_removed(sp_playlistcontainer *pc, sp_playlist *, int position, void *)
+{
+ QCoreApplication::postEvent(g_containerObjects.value(pc), new QSpotifyPlaylistRemovedEvent(position));
+}
+
+static void callback_playlist_moved(sp_playlistcontainer *pc, sp_playlist *, int position, int new_position, void *)
+{
+ QCoreApplication::postEvent(g_containerObjects.value(pc), new QSpotifyPlaylistMovedEvent(position, new_position));
+}
+
+QSpotifyPlaylistContainer::QSpotifyPlaylistContainer(sp_playlistcontainer *container)
+ : QSpotifyObject(true)
+{
+ m_container = container;
+ g_containerObjects.insert(container, this);
+ m_callbacks = new sp_playlistcontainer_callbacks;
+ m_callbacks->container_loaded = callback_container_loaded;
+ m_callbacks->playlist_added = callback_playlist_added;
+ m_callbacks->playlist_moved = callback_playlist_moved;
+ m_callbacks->playlist_removed = callback_playlist_removed;
+ sp_playlistcontainer_add_callbacks(m_container, m_callbacks, 0);
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(playlistContainerDataChanged()));
+
+ metadataUpdated();
+}
+
+QSpotifyPlaylistContainer::~QSpotifyPlaylistContainer()
+{
+ g_containerObjects.remove(m_container);
+ sp_playlistcontainer_remove_callbacks(m_container, m_callbacks, 0);
+ qDeleteAll(m_playlists);
+ sp_playlistcontainer_release(m_container);
+ delete m_callbacks;
+}
+
+bool QSpotifyPlaylistContainer::isLoaded()
+{
+ return sp_playlistcontainer_is_loaded(m_container);
+}
+
+bool QSpotifyPlaylistContainer::updateData()
+{
+ bool updated = false;
+
+ if (m_playlists.isEmpty()) {
+ int count = sp_playlistcontainer_num_playlists(m_container);
+ for (int i = 0; i < count; ++i)
+ addPlaylist(sp_playlistcontainer_playlist(m_container, i));
+ updated = true;
+ }
+
+ return updated;
+}
+
+void QSpotifyPlaylistContainer::addPlaylist(sp_playlist *playlist, int pos)
+{
+ QSpotifyPlaylist *pl = new QSpotifyPlaylist(QSpotifyPlaylist::Playlist, playlist);
+ if (pos == -1)
+ m_playlists.append(pl);
+ else
+ m_playlists.insert(pos, pl);
+ connect(pl, SIGNAL(playlistDataChanged()), this, SIGNAL(playlistContainerDataChanged()));
+}
+
+bool QSpotifyPlaylistContainer::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ metadataUpdated();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 1) {
+ // PlaylistAdded event
+ QSpotifyPlaylistAddedEvent *ev = static_cast<QSpotifyPlaylistAddedEvent *>(e);
+ addPlaylist(ev->playlist(), ev->position());
+ emit dataChanged();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 2) {
+ // PlaylistRemoved event
+ QSpotifyPlaylistRemovedEvent *ev = static_cast<QSpotifyPlaylistRemovedEvent *>(e);
+ int i = ev->position();
+ if (i >= 0 && i < m_playlists.count()) {
+ QSpotifyPlaylist *pl = m_playlists.takeAt(i);
+ delete pl;
+ emit dataChanged();
+ }
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 3) {
+ // PlaylistMoved event
+ QSpotifyPlaylistMovedEvent *ev = static_cast<QSpotifyPlaylistMovedEvent *>(e);
+ int i = ev->position();
+ int newpos = ev->newPosition();
+ if (i >= 0 && i < m_playlists.count()) {
+ QSpotifyPlaylist *pl = m_playlists.takeAt(i);
+ m_playlists.insert(newpos > i ? newpos - 1 : newpos, pl);
+ emit dataChanged();
+ }
+ e->accept();
+ return true;
+ }
+ return QSpotifyObject::event(e);
+}
diff --git a/libQtSpotify/qspotifyplaylistcontainer.h b/libQtSpotify/qspotifyplaylistcontainer.h
new file mode 100644
index 0000000..05645fa
--- /dev/null
+++ b/libQtSpotify/qspotifyplaylistcontainer.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYPLAYLISTCONTAINER_H
+#define QSPOTIFYPLAYLISTCONTAINER_H
+
+#include "qspotifyobject.h"
+#include <QMetaType>
+
+struct sp_playlistcontainer;
+struct sp_playlistcontainer_callbacks;
+struct sp_playlist;
+class QSpotifyPlaylist;
+
+class QSpotifyPlaylistContainer : public QSpotifyObject
+{
+ Q_OBJECT
+public:
+ ~QSpotifyPlaylistContainer();
+
+ bool isLoaded();
+
+ QList<QSpotifyPlaylist *> playlists() const { return m_playlists; }
+
+Q_SIGNALS:
+ void playlistContainerDataChanged();
+
+protected:
+ bool updateData();
+
+ bool event(QEvent *);
+
+private:
+ QSpotifyPlaylistContainer(sp_playlistcontainer *container);
+ void addPlaylist(sp_playlist *, int pos = -1);
+
+ sp_playlistcontainer *m_container;
+ sp_playlistcontainer_callbacks *m_callbacks;
+
+ QList<QSpotifyPlaylist *> m_playlists;
+
+ friend class QSpotifyUser;
+};
+
+#endif // QSPOTIFYPLAYLISTCONTAINER_H
diff --git a/libQtSpotify/qspotifyplayqueue.cpp b/libQtSpotify/qspotifyplayqueue.cpp
new file mode 100644
index 0000000..d190eb3
--- /dev/null
+++ b/libQtSpotify/qspotifyplayqueue.cpp
@@ -0,0 +1,291 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyplayqueue.h"
+
+#include "qspotifytrack.h"
+#include "qspotifytracklist.h"
+#include "qspotifysession.h"
+
+QSpotifyPlayQueue::QSpotifyPlayQueue()
+ : QObject()
+ , m_implicitTracks(0)
+ , m_currentExplicitTrack(0)
+ , m_currentTrackIndex(0)
+ , m_shuffle(false)
+ , m_repeat(false)
+{
+}
+
+QSpotifyPlayQueue::~QSpotifyPlayQueue()
+{
+ clear();
+}
+
+void QSpotifyPlayQueue::playTrack(QSpotifyTrack *track)
+{
+ if (m_currentExplicitTrack) {
+ m_currentExplicitTrack->release();
+ m_currentExplicitTrack = 0;
+ }
+
+ if (m_implicitTracks != track->m_trackList) {
+ if (m_implicitTracks)
+ m_implicitTracks->release();
+ m_implicitTracks = track->m_trackList;
+ m_implicitTracks->addRef();
+ }
+ m_implicitTracks->playTrackAtIndex(m_implicitTracks->m_tracks.indexOf(track));
+ m_implicitTracks->setShuffle(m_shuffle);
+
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::enqueueTrack(QSpotifyTrack *track)
+{
+ track->addRef();
+ m_explicitTracks.enqueue(track);
+
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::enqueueTracks(QList<QSpotifyTrack *> tracks)
+{
+ for (int i = 0; i < tracks.count(); ++i) {
+ QSpotifyTrack *t = tracks.at(i);
+ t->addRef();
+ m_explicitTracks.enqueue(t);
+ }
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::selectTrack(QSpotifyTrack *track)
+{
+ if (m_currentExplicitTrack == track || m_implicitTracks->m_currentTrack == track)
+ return;
+
+ if (m_currentExplicitTrack) {
+ m_currentExplicitTrack->release();
+ m_currentExplicitTrack = 0;
+ }
+
+ int explicitPos = m_explicitTracks.indexOf(track);
+ if (explicitPos != -1) {
+ m_explicitTracks.removeAt(explicitPos);
+ m_currentExplicitTrack = track;
+ if (m_currentExplicitTrack->isLoaded())
+ onTrackReady();
+ else
+ connect(m_currentExplicitTrack, SIGNAL(isLoadedChanged()), this, SLOT(onTrackReady()));
+ } else {
+ m_implicitTracks->playTrackAtIndex(m_implicitTracks->m_tracks.indexOf(track));
+ }
+
+ emit tracksChanged();
+}
+
+bool QSpotifyPlayQueue::isExplicitTrack(int index)
+{
+ return index > m_currentTrackIndex && index <= m_currentTrackIndex + m_explicitTracks.count();
+}
+
+void QSpotifyPlayQueue::playNext()
+{
+ if (m_currentExplicitTrack) {
+ m_currentExplicitTrack->release();
+ m_currentExplicitTrack = 0;
+ }
+
+ if (m_explicitTracks.isEmpty()) {
+ if (m_implicitTracks) {
+ if (!m_implicitTracks->next()) {
+ if (m_repeat) {
+ m_implicitTracks->play();
+ } else {
+ QSpotifySession::instance()->stop();
+ m_implicitTracks->release();
+ m_implicitTracks = 0;
+ }
+ }
+ } else {
+ QSpotifySession::instance()->stop();
+ }
+ } else {
+ m_currentExplicitTrack = m_explicitTracks.dequeue();
+ if (m_currentExplicitTrack->isLoaded())
+ onTrackReady();
+ else
+ connect(m_currentExplicitTrack, SIGNAL(isLoadedChanged()), this, SLOT(onTrackReady()));
+ }
+
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::playPrevious()
+{
+ if (m_currentExplicitTrack) {
+ m_currentExplicitTrack->release();
+ m_currentExplicitTrack = 0;
+ }
+
+ if (m_implicitTracks) {
+ if (!m_implicitTracks->previous()) {
+ if (m_repeat) {
+ m_implicitTracks->playLast();
+ } else {
+ QSpotifySession::instance()->stop();
+ m_implicitTracks->release();
+ m_implicitTracks = 0;
+ }
+ }
+ } else {
+ QSpotifySession::instance()->stop();
+ }
+
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::clear()
+{
+ if (m_currentExplicitTrack) {
+ m_currentExplicitTrack->release();
+ m_currentExplicitTrack = 0;
+ }
+
+ if (m_implicitTracks) {
+ m_implicitTracks->release();
+ m_implicitTracks = 0;
+ }
+
+ int c = m_explicitTracks.count();
+ for (int i = 0; i < c; ++i)
+ m_explicitTracks[i]->release();
+ m_explicitTracks.clear();
+}
+
+void QSpotifyPlayQueue::setShuffle(bool s)
+{
+ if (m_shuffle == s)
+ return;
+ m_shuffle = s;
+ if (m_implicitTracks)
+ m_implicitTracks->setShuffle(s);
+
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::setRepeat(bool r)
+{
+ if (m_repeat == r)
+ return;
+
+ m_repeat = r;
+
+ emit tracksChanged();
+}
+
+void QSpotifyPlayQueue::onTrackReady()
+{
+ disconnect(this, SLOT(onTrackReady()));
+ if (m_currentExplicitTrack)
+ QSpotifySession::instance()->play(m_currentExplicitTrack);
+}
+
+QList<QObject *> QSpotifyPlayQueue::tracks() const
+{
+ QList<QObject *> list;
+
+ if (!m_implicitTracks)
+ return list;
+
+ int currIndex = 0;
+
+ if (m_shuffle) {
+ for (int i = 0; i < m_implicitTracks->m_shuffleList.count(); ++i) {
+ QSpotifyTrack * t = m_implicitTracks->m_tracks.at(m_implicitTracks->m_shuffleList.at(i));
+ list.append((QObject*)t);
+ if (t == m_implicitTracks->m_currentTrack)
+ currIndex = i;
+ }
+ } else {
+ if (m_implicitTracks->m_reverse) {
+ int i = m_implicitTracks->previousAvailable(m_implicitTracks->m_tracks.count());
+ while (i >= 0) {
+ QSpotifyTrack * t = m_implicitTracks->m_tracks.at(i);
+ list.append((QObject*)t);
+ if (t == m_implicitTracks->m_currentTrack)
+ currIndex = m_implicitTracks->m_tracks.count() - 1 - i;
+ i = m_implicitTracks->previousAvailable(i);
+ }
+ } else {
+ int i = m_implicitTracks->nextAvailable(-1);
+ while (i < m_implicitTracks->m_tracks.count()) {
+ QSpotifyTrack * t = m_implicitTracks->m_tracks.at(i);
+ list.append((QObject*)t);
+ if (t == m_implicitTracks->m_currentTrack)
+ currIndex = i;
+ i = m_implicitTracks->nextAvailable(i);
+ }
+ }
+ }
+
+ if (m_currentExplicitTrack)
+ list.insert(++currIndex, (QObject*)m_currentExplicitTrack);
+ for (int i = 0; i < m_explicitTracks.count(); ++i)
+ list.insert(++currIndex, (QObject*)m_explicitTracks.at(i));
+
+ if (m_currentExplicitTrack)
+ m_currentTrackIndex = list.indexOf((QObject *)m_currentExplicitTrack);
+ else if (m_implicitTracks->m_currentTrack)
+ m_currentTrackIndex = list.indexOf((QObject *)m_implicitTracks->m_currentTrack);
+
+ return list;
+}
+
+bool QSpotifyPlayQueue::isCurrentTrackList(QSpotifyTrackList *tl)
+{
+ return m_implicitTracks == tl;
+}
+
+void QSpotifyPlayQueue::tracksUpdated()
+{
+ emit tracksChanged();
+}
diff --git a/libQtSpotify/qspotifyplayqueue.h b/libQtSpotify/qspotifyplayqueue.h
new file mode 100644
index 0000000..d8bca5e
--- /dev/null
+++ b/libQtSpotify/qspotifyplayqueue.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYPLAYQUEUE_H
+#define QSPOTIFYPLAYQUEUE_H
+
+#include <QtCore/QObject>
+#include <QtCore/QQueue>
+
+class QSpotifyTrackList;
+class QSpotifyTrack;
+
+class QSpotifyPlayQueue : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QList<QObject *> tracks READ tracks NOTIFY tracksChanged)
+ Q_PROPERTY(int currentIndex READ currentIndex NOTIFY tracksChanged)
+public:
+ QSpotifyPlayQueue();
+ ~QSpotifyPlayQueue();
+
+ void playTrack(QSpotifyTrack *track);
+ void enqueueTrack(QSpotifyTrack *track);
+ void enqueueTracks(QList<QSpotifyTrack *> tracks);
+ Q_INVOKABLE void selectTrack(QSpotifyTrack *track);
+
+ Q_INVOKABLE bool isExplicitTrack(int index);
+
+ void playNext();
+ void playPrevious();
+
+ void clear();
+
+ void setShuffle(bool s);
+ void setRepeat(bool r);
+
+ int currentIndex() const { return m_currentTrackIndex; }
+
+ QList<QObject *> tracks() const;
+
+ bool isCurrentTrackList(QSpotifyTrackList *tl);
+ void tracksUpdated();
+
+Q_SIGNALS:
+ void tracksChanged();
+
+private Q_SLOTS:
+ void onTrackReady();
+
+private:
+
+ QSpotifyTrackList *m_implicitTracks;
+ QQueue<QSpotifyTrack *> m_explicitTracks;
+ QSpotifyTrack *m_currentExplicitTrack;
+
+ mutable int m_currentTrackIndex;
+
+ bool m_shuffle;
+ bool m_repeat;
+
+ friend class QSpotifyPlaylist;
+};
+
+#endif // QSPOTIFYPLAYQUEUE_H
diff --git a/libQtSpotify/qspotifysearch.cpp b/libQtSpotify/qspotifysearch.cpp
new file mode 100644
index 0000000..3c87243
--- /dev/null
+++ b/libQtSpotify/qspotifysearch.cpp
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifysearch.h"
+
+#include "qspotifyplaylist.h"
+#include "qspotifytracklist.h"
+#include "qspotifysession.h"
+#include "qspotifytrack.h"
+#include "qspotifyuser.h"
+#include "qspotifyalbum.h"
+#include "qspotifyartist.h"
+
+#include <libspotify/api.h>
+
+static QHash<sp_search *, QSpotifySearch *> g_searchObjects;
+static QMutex g_mutex;
+
+static void callback_search_complete(sp_search *result, void *)
+{
+ QMutexLocker lock(&g_mutex);
+ QSpotifySearch *s = g_searchObjects.value(result);
+ if (s)
+ QCoreApplication::postEvent(s, new QEvent(QEvent::User));
+}
+
+QSpotifySearch::QSpotifySearch(QObject *parent)
+ : QObject(parent)
+ , m_sp_search(0)
+ , m_trackResults(0)
+ , m_busy(false)
+ , m_tracksLimit(100)
+ , m_albumsLimit(50)
+ , m_artistsLimit(50)
+{
+}
+
+QSpotifySearch::~QSpotifySearch()
+{
+ if (m_trackResults)
+ m_trackResults->release();
+ qDeleteAll(m_albumResults);
+ m_albumResults.clear();
+ qDeleteAll(m_artistResults);
+ m_artistResults.clear();
+ clearSearch();
+}
+
+void QSpotifySearch::setQuery(const QString &q)
+{
+ if (q == m_query)
+ return;
+
+ m_query = q;
+ emit queryChanged();
+}
+
+QList<QObject *> QSpotifySearch::tracks() const
+{
+ QList<QObject*> list;
+ if (m_trackResults != 0) {
+ int c = m_trackResults->m_tracks.count();
+ for (int i = 0; i < c; ++i)
+ list.append((QObject*)(m_trackResults->m_tracks[i]));
+ }
+ return list;
+}
+
+void QSpotifySearch::search()
+{
+ clearSearch();
+
+ m_busy = true;
+ emit busyChanged();
+
+ if (!m_query.isEmpty()) {
+ QMutexLocker lock(&g_mutex);
+ m_sp_search = sp_search_create(QSpotifySession::instance()->m_sp_session, m_query.toUtf8().constData(), 0, m_tracksLimit, 0, m_albumsLimit, 0, m_artistsLimit, callback_search_complete, 0);
+ g_searchObjects.insert(m_sp_search, this);
+ } else {
+ populateResults();
+ }
+}
+
+void QSpotifySearch::clearSearch()
+{
+ QMutexLocker lock(&g_mutex);
+ if (m_sp_search)
+ sp_search_release(m_sp_search);
+ g_searchObjects.remove(m_sp_search);
+ m_sp_search = 0;
+}
+
+bool QSpotifySearch::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ populateResults();
+ e->accept();
+ return true;
+ }
+ return QObject::event(e);
+}
+
+void QSpotifySearch::populateResults()
+{
+ if (m_trackResults) {
+ m_trackResults->release();
+ m_trackResults = 0;
+ }
+ qDeleteAll(m_albumResults);
+ m_albumResults.clear();
+ qDeleteAll(m_artistResults);
+ m_artistResults.clear();
+
+ if (m_sp_search) {
+ if (sp_search_error(m_sp_search) != SP_ERROR_OK)
+ return;
+
+ // Populate tracks
+ m_trackResults = new QSpotifyTrackList;
+ int c = sp_search_num_tracks(m_sp_search);
+ for (int i = 0; i < c; ++i) {
+ QSpotifyTrack *track = new QSpotifyTrack(sp_search_track(m_sp_search, i), m_trackResults);
+ m_trackResults->m_tracks.append(track);
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksAdded(QVector<sp_track*>)), track, SLOT(onStarredListTracksAdded(QVector<sp_track*>)));
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksRemoved(QVector<sp_track*>)), track, SLOT(onStarredListTracksRemoved(QVector<sp_track*>)));
+ }
+
+ // Populate albums
+ c = sp_search_num_albums(m_sp_search);
+ for (int i = 0; i < c; ++i) {
+ sp_album *a = sp_search_album(m_sp_search, i);
+ if (!sp_album_is_available(a))
+ continue;
+ QSpotifyAlbum *album = new QSpotifyAlbum(a);
+ m_albumResults.append((QObject *)album);
+ }
+
+ // Populate artists
+ c = sp_search_num_artists(m_sp_search);
+ for (int i = 0; i < c; ++i) {
+ QSpotifyArtist *artist = new QSpotifyArtist(sp_search_artist(m_sp_search, i));
+ m_artistResults.append((QObject *)artist);
+ }
+ }
+
+ m_busy = false;
+ emit busyChanged();
+
+ emit resultsChanged();
+}
diff --git a/libQtSpotify/qspotifysearch.h b/libQtSpotify/qspotifysearch.h
new file mode 100644
index 0000000..c6d15d0
--- /dev/null
+++ b/libQtSpotify/qspotifysearch.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYSEARCH_H
+#define QSPOTIFYSEARCH_H
+
+#include <QObject>
+
+struct sp_search;
+class QSpotifyTrackList;
+
+class QSpotifySearch : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
+ Q_PROPERTY(QList<QObject *> tracks READ tracks NOTIFY resultsChanged)
+ Q_PROPERTY(QList<QObject *> albums READ albums NOTIFY resultsChanged)
+ Q_PROPERTY(QList<QObject *> artists READ artists NOTIFY resultsChanged)
+ Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
+public:
+
+ QSpotifySearch(QObject *parent = 0);
+ ~QSpotifySearch();
+
+ QString query() const { return m_query; }
+ void setQuery(const QString &q);
+
+ QList<QObject *> tracks() const;
+ QList<QObject *> albums() const { return m_albumResults; }
+ QList<QObject *> artists() const { return m_artistResults; }
+
+ void setTracksLimit(int l) { m_tracksLimit = l; }
+ void setAlbumsLimit(int l) { m_albumsLimit = l; }
+ void setArtistsLimit(int l) { m_artistsLimit = l; }
+
+ QSpotifyTrackList *trackResults() const { return m_trackResults; }
+
+ bool busy() const { return m_busy; }
+
+ Q_INVOKABLE void search();
+
+ bool event(QEvent *);
+
+Q_SIGNALS:
+ void queryChanged();
+ void resultsChanged();
+ void busyChanged();
+
+private:
+ void clearSearch();
+ void populateResults();
+
+ sp_search *m_sp_search;
+
+ QString m_query;
+ QSpotifyTrackList *m_trackResults;
+ QList<QObject *> m_albumResults;
+ QList<QObject *> m_artistResults;
+ bool m_busy;
+
+ int m_tracksLimit;
+ int m_albumsLimit;
+ int m_artistsLimit;
+
+};
+
+#endif // QSPOTIFYSEARCH_H
diff --git a/libQtSpotify/qspotifysession.cpp b/libQtSpotify/qspotifysession.cpp
new file mode 100644
index 0000000..ea4cfc6
--- /dev/null
+++ b/libQtSpotify/qspotifysession.cpp
@@ -0,0 +1,1106 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifysession.h"
+#include "qspotifyuser.h"
+#include "qspotifytrack.h"
+#include "qspotifytracklist.h"
+#include "qspotifyplayqueue.h"
+#include "qspotifyalbum.h"
+#include "qspotifyartist.h"
+#include "spotify_key.h"
+
+#include <QtCore/QHash>
+#include <QtCore/QEvent>
+#include <QtCore/QCoreApplication>
+#include <QtMultimediaKit/QAudioOutput>
+#include <QtCore/QIODevice>
+#include <QtCore/QBuffer>
+#include <QtCore/QMutexLocker>
+#include <QtCore/QDebug>
+#include <QtGui/QDesktopServices>
+#include <QtNetwork/QNetworkConfigurationManager>
+
+#define BUFFER_SIZE 409600
+#define AUDIOSTREAM_UPDATE_INTERVAL 20
+
+class QSpotifyAudioThreadWorker;
+
+static QBuffer g_buffer;
+static QMutex g_mutex;
+static int g_readPos = 0;
+static int g_writePos = 0;
+static QSpotifyAudioThreadWorker *g_audioWorker;
+
+static QMutex g_imageRequestMutex;
+static QHash<QString, QWaitCondition *> g_imageRequestConditions;
+static QHash<QString, QImage> g_imageRequestImages;
+static QHash<sp_image *, QString> g_imageRequestObject;
+
+QSpotifySession *QSpotifySession::m_instance = 0;
+
+
+class QSpotifyConnectionErrorEvent : public QEvent
+{
+public:
+ QSpotifyConnectionErrorEvent(sp_error error, const QString &message)
+ : QEvent(Type(QEvent::User + 1))
+ , m_error(error)
+ , m_message(message)
+ { }
+
+ sp_error error() const { return m_error; }
+ QString message() const { return m_message; }
+
+private:
+ sp_error m_error;
+ QString m_message;
+};
+
+
+class QSpotifyStreamingStartedEvent : public QEvent
+{
+public:
+ QSpotifyStreamingStartedEvent(int channels, int sampleRate)
+ : QEvent(Type(QEvent::User + 3))
+ , m_channels(channels)
+ , m_sampleRate(sampleRate)
+ { }
+
+ int channels() const { return m_channels; }
+ int sampleRate() const { return m_sampleRate; }
+
+private:
+ int m_channels;
+ int m_sampleRate;
+};
+
+
+class QSpotifyTrackProgressEvent : public QEvent
+{
+public:
+ QSpotifyTrackProgressEvent(int delta)
+ : QEvent(Type(QEvent::User + 10))
+ , m_delta(delta)
+ { }
+
+ int delta() const { return m_delta; }
+
+private:
+ int m_delta;
+};
+
+class QSpotifyRequestImageEvent : public QEvent
+{
+public:
+ QSpotifyRequestImageEvent(const QString &id)
+ : QEvent(Type(User + 11))
+ , m_id(id)
+ { }
+
+ QString imageId() const { return m_id; }
+
+private:
+ QString m_id;
+};
+
+class QSpotifyReceiveImageEvent : public QEvent
+{
+public:
+ QSpotifyReceiveImageEvent(sp_image *image)
+ : QEvent(Type(User + 12))
+ , m_image(image)
+ { }
+
+ sp_image *image() const { return m_image; }
+
+private:
+ sp_image *m_image;
+};
+
+
+class QSpotifyAudioThreadWorker : public QObject
+{
+public:
+ QSpotifyAudioThreadWorker();
+
+ bool event(QEvent *);
+
+private:
+ void startStreaming(int channels, int sampleRate);
+ void updateAudioBuffer();
+
+ QAudioOutput *m_audioOutput;
+ QIODevice *m_iodevice;
+ int m_audioTimerID;
+ int m_timeCounter;
+ bool m_endOfTrack;
+ int m_previousElapsedTime;
+};
+
+QSpotifyAudioThreadWorker::QSpotifyAudioThreadWorker()
+ : QObject()
+ , m_audioOutput(0)
+ , m_iodevice(0)
+ , m_audioTimerID(0)
+ , m_timeCounter(0)
+ , m_endOfTrack(false)
+ , m_previousElapsedTime(0)
+{
+}
+
+bool QSpotifyAudioThreadWorker::event(QEvent *e)
+{
+ if (e->type() == QEvent::User + 3) {
+ // StreamingStarted Event
+ QSpotifyStreamingStartedEvent *ev = static_cast<QSpotifyStreamingStartedEvent *>(e);
+ startStreaming(ev->channels(), ev->sampleRate());
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 4) {
+ m_endOfTrack = true;
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 6) {
+ // Resume
+ if (m_audioOutput) {
+ m_audioTimerID = startTimer(AUDIOSTREAM_UPDATE_INTERVAL);
+ m_audioOutput->resume();
+ }
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 7) {
+ // Suspend
+ if (m_audioOutput) {
+ killTimer(m_audioTimerID);
+ m_audioOutput->suspend();
+ }
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 8) {
+ // Stop
+ QMutexLocker lock(&g_mutex);
+ killTimer(m_audioTimerID);
+ g_buffer.close();
+ g_buffer.setData(QByteArray());
+ g_readPos = 0;
+ g_writePos = 0;
+ if (m_audioOutput) {
+ m_audioOutput->suspend();
+ m_audioOutput->stop();
+ delete m_audioOutput;
+ m_audioOutput = 0;
+ m_iodevice = 0;
+ }
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 9) {
+ // Reset buffers
+ QMutexLocker lock(&g_mutex);
+ killTimer(m_audioTimerID);
+ m_audioOutput->suspend();
+ m_audioOutput->stop();
+ g_buffer.close();
+ g_buffer.setData(QByteArray());
+ g_buffer.open(QIODevice::ReadWrite);
+ g_readPos = 0;
+ g_writePos = 0;
+ m_audioOutput->reset();
+ m_iodevice = m_audioOutput->start();
+ m_audioOutput->suspend();
+ m_audioTimerID = startTimer(AUDIOSTREAM_UPDATE_INTERVAL);
+ m_timeCounter = 0;
+ m_previousElapsedTime = 0;
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::Timer) {
+ QTimerEvent *te = static_cast<QTimerEvent *>(e);
+ if (te->timerId() == m_audioTimerID) {
+ updateAudioBuffer();
+ e->accept();
+ return true;
+ }
+ }
+ return QObject::event(e);
+}
+
+void QSpotifyAudioThreadWorker::startStreaming(int channels, int sampleRate)
+{
+ if (!m_audioOutput) {
+ QAudioFormat af;
+ af.setChannelCount(channels);
+ af.setCodec("audio/pcm");
+ af.setSampleRate(sampleRate);
+ af.setSampleSize(16);
+ af.setSampleType(QAudioFormat::SignedInt);
+
+ QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
+ if (!info.isFormatSupported(af)) {
+ qWarning()<<"raw audio format not supported by backend, cannot play audio.";
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 5)));
+ return;
+ }
+
+ m_audioOutput = new QAudioOutput(af);
+ m_audioOutput->setBufferSize(BUFFER_SIZE);
+ qDebug() << m_audioOutput->bufferSize();
+ m_iodevice = m_audioOutput->start();
+ m_audioOutput->suspend();
+ m_audioTimerID = startTimer(AUDIOSTREAM_UPDATE_INTERVAL);
+ m_endOfTrack = false;
+ m_timeCounter = 0;
+ m_previousElapsedTime = 0;
+ }
+}
+
+void QSpotifyAudioThreadWorker::updateAudioBuffer()
+{
+ if (!m_audioOutput)
+ return;
+
+ if (m_audioOutput->state() == QAudio::SuspendedState)
+ m_audioOutput->resume();
+
+ if (m_endOfTrack && m_audioOutput->state() == QAudio::IdleState) {
+ killTimer(m_audioTimerID);
+ int elapsedTime = int(m_audioOutput->processedUSecs() / 1000);
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QSpotifyTrackProgressEvent(elapsedTime - m_previousElapsedTime));
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 4)));
+ m_previousElapsedTime = elapsedTime;
+ return;
+ } else {
+ g_mutex.lock();
+ int toRead = qMin(g_writePos - g_readPos, m_audioOutput->bytesFree());
+ g_buffer.seek(g_readPos);
+ char data[toRead];
+ int read = g_buffer.read(&data[0], toRead);
+ g_readPos += read;
+ g_mutex.unlock();
+
+ m_iodevice->write(&data[0], read);
+
+ }
+
+ m_timeCounter += AUDIOSTREAM_UPDATE_INTERVAL;
+ if (m_timeCounter >= 1000) {
+ m_timeCounter = 0;
+ int elapsedTime = int(m_audioOutput->processedUSecs() / 1000);
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QSpotifyTrackProgressEvent(elapsedTime - m_previousElapsedTime));
+ m_previousElapsedTime = elapsedTime;
+ }
+}
+
+
+class QSpotifyAudioThread : public QThread
+{
+public:
+ void run();
+};
+
+void QSpotifyAudioThread::run()
+{
+ g_audioWorker = new QSpotifyAudioThreadWorker;
+ exec();
+ delete g_audioWorker;
+}
+
+
+static void callback_logged_in(sp_session *, sp_error error)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QSpotifyConnectionErrorEvent(error, QString::fromUtf8(sp_error_message(error))));
+ if (error == SP_ERROR_OK)
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 14)));
+}
+
+static void callback_logged_out(sp_session *)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 15)));
+}
+
+static void callback_connection_error(sp_session *, sp_error error)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QSpotifyConnectionErrorEvent(error, QString::fromUtf8(sp_error_message(error))));
+}
+
+static void callback_notify_main_thread(sp_session *)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::User));
+}
+
+static void callback_metadata_updated(sp_session *)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 2)));
+}
+
+static void callback_userinfo_updated(sp_session* )
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 2)));
+}
+
+static int callback_music_delivery(sp_session *, const sp_audioformat *format, const void *frames, int num_frames)
+{
+ if (num_frames == 0)
+ return 0;
+
+ QMutexLocker locker(&g_mutex);
+
+ if (!g_buffer.isOpen()) {
+ g_buffer.open(QIODevice::ReadWrite);
+ QCoreApplication::postEvent(g_audioWorker,
+ new QSpotifyStreamingStartedEvent(format->channels, format->sample_rate));
+ }
+
+ int availableFrames = (BUFFER_SIZE - (g_writePos - g_readPos)) / (sizeof(int16_t) * format->channels);
+ int writtenFrames = qMin(num_frames, availableFrames);
+
+ if (writtenFrames == 0)
+ return 0;
+
+ g_buffer.seek(g_writePos);
+ g_writePos += g_buffer.write((const char *) frames, writtenFrames * sizeof(int16_t) * format->channels);
+
+ return writtenFrames;
+}
+
+static void callback_end_of_track(sp_session *)
+{
+ QCoreApplication::postEvent(g_audioWorker, new QEvent(QEvent::Type(QEvent::User + 4)));
+}
+
+static void callback_play_token_lost(sp_session *)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QEvent(QEvent::Type(QEvent::User + 13)));
+}
+
+static void callback_log_message(sp_session *, const char *data)
+{
+ fprintf(stderr, data);
+}
+
+QSpotifySession::QSpotifySession()
+ : QObject(0)
+ , m_timerID(0)
+ , m_connectionStatus(LoggedOut)
+ , m_connectionError(Ok)
+ , m_connectionRules(AllowSyncOverWifi | AllowNetworkIfRoaming)
+ , m_streamingQuality(Unknown)
+ , m_syncQuality(Unknown)
+ , m_syncOverMobile(false)
+ , m_user(0)
+ , m_pending_connectionRequest(false)
+ , m_isLoggedIn(false)
+ , m_explicitLogout(false)
+ , m_offlineMode(false)
+ , m_forcedOfflineMode(false)
+ , m_ignoreNextConnectionError(false)
+ , m_playQueue(new QSpotifyPlayQueue)
+ , m_currentTrack(0)
+ , m_isPlaying(false)
+ , m_currentTrackPosition(0)
+ , m_shuffle(false)
+ , m_repeat(false)
+{
+ connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp()));
+
+ m_networkConfManager = new QNetworkConfigurationManager;
+ connect(m_networkConfManager, SIGNAL(onlineStateChanged(bool)), this, SLOT(onOnlineChanged()));
+ connect(m_networkConfManager, SIGNAL(onlineStateChanged(bool)), this, SIGNAL(isOnlineChanged()));
+ connect(m_networkConfManager, SIGNAL(configurationChanged(QNetworkConfiguration)), this, SIGNAL(isOnlineChanged()));
+ connect(m_networkConfManager, SIGNAL(configurationChanged(QNetworkConfiguration)), this, SLOT(configurationChanged()));
+
+ m_audioThread = new QSpotifyAudioThread;
+ m_audioThread->start(QThread::HighestPriority);
+
+ // Resource management stuff
+ m_resourceSet = new ResourcePolicy::ResourceSet(QLatin1String("player"), 0, false, true);
+ m_audioResource = new ResourcePolicy::AudioResource(QLatin1String("player"));
+ m_audioResource->setProcessID(QCoreApplication::applicationPid());
+ m_audioResource->setStreamTag(QLatin1String("media.name"), QLatin1String("*"));
+ m_audioResource->setOptional(false);
+ m_resourceSet->addResourceObject(m_audioResource);
+ connect(m_resourceSet, SIGNAL(resourcesGranted(QList<ResourcePolicy::ResourceType>)), this, SLOT(resourceAcquiredHandler(QList<ResourcePolicy::ResourceType>)));
+ connect(m_resourceSet, SIGNAL(lostResources()), this, SLOT(resourceLostHandler()));
+}
+
+void QSpotifySession::init()
+{
+ m_sp_callbacks.logged_in = callback_logged_in;
+ m_sp_callbacks.logged_out = callback_logged_out;
+ m_sp_callbacks.metadata_updated = callback_metadata_updated;
+ m_sp_callbacks.connection_error = callback_connection_error;
+ m_sp_callbacks.message_to_user = 0;
+ m_sp_callbacks.notify_main_thread = callback_notify_main_thread;
+ m_sp_callbacks.music_delivery = callback_music_delivery;
+ m_sp_callbacks.play_token_lost = callback_play_token_lost;
+ m_sp_callbacks.log_message = callback_log_message;
+ m_sp_callbacks.end_of_track = callback_end_of_track;
+ m_sp_callbacks.streaming_error = 0;
+ m_sp_callbacks.userinfo_updated = callback_userinfo_updated;
+ m_sp_callbacks.start_playback = 0;
+ m_sp_callbacks.stop_playback = 0;
+ m_sp_callbacks.get_audio_buffer_stats = 0;
+ m_sp_callbacks.offline_status_updated = 0;
+
+ m_sp_config.api_version = SPOTIFY_API_VERSION;
+ m_sp_config.cache_location = "/home/user/MyDocs/.meespot";
+ m_sp_config.settings_location = "/home/user/MyDocs/.meespot";
+ m_sp_config.application_key = g_appkey;
+ m_sp_config.application_key_size = g_appkey_size;
+ m_sp_config.user_agent = "MeeSpot";
+ m_sp_config.callbacks = &m_sp_callbacks;
+ sp_error error = sp_session_create(&m_sp_config, &m_sp_session);
+ if (error != SP_ERROR_OK) {
+ fprintf(stderr, "failed to create session: %s\n",
+ sp_error_message(error));
+ } else {
+ QSettings settings;
+
+ // Remove stored login information from older version of MeeSpot
+ if (settings.contains("username")) {
+ settings.remove("username");
+ settings.remove("password");
+ }
+
+ m_offlineMode = settings.value("offlineMode", false).toBool();
+
+ checkNetworkAccess();
+
+ StreamingQuality quality = StreamingQuality(settings.value("streamingQuality", int(LowQuality)).toInt());
+ setStreamingQuality(quality);
+
+ StreamingQuality syncQuality = StreamingQuality(settings.value("syncQuality", int(HighQuality)).toInt());
+ setSyncQuality(syncQuality);
+
+ bool syncMobile = settings.value("syncOverMobile", false).toBool();
+ setSyncOverMobile(syncMobile);
+
+ QString storedLogin = getStoredLoginInformation();
+ if (!storedLogin.isEmpty()) {
+ login(storedLogin);
+ }
+
+ bool shuffle = settings.value("shuffle", false).toBool();
+ setShuffle(shuffle);
+
+ bool repeat = settings.value("repeat", false).toBool();
+ setRepeat(repeat);
+ }
+}
+
+QSpotifySession::~QSpotifySession()
+{
+}
+
+QSpotifySession *QSpotifySession::instance()
+{
+ if (!m_instance) {
+ m_instance = new QSpotifySession;
+ m_instance->init();
+ }
+ return m_instance;
+}
+
+void QSpotifySession::cleanUp()
+{
+ stop();
+ delete m_playQueue;
+ m_audioThread->quit();
+ m_audioThread->wait();
+ delete m_audioThread;
+ delete m_user;
+ logout(true);
+ sp_session_release(m_sp_session);
+ delete m_resourceSet;
+ delete m_networkConfManager;
+}
+
+bool QSpotifySession::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ processSpotifyEvents();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::Timer) {
+ QTimerEvent *te = static_cast<QTimerEvent *>(e);
+ if (te->timerId() == m_timerID) {
+ processSpotifyEvents();
+ e->accept();
+ return true;
+ }
+ } else if (e->type() == QEvent::User + 1) {
+ // ConnectionError event
+ QSpotifyConnectionErrorEvent *ev = static_cast<QSpotifyConnectionErrorEvent *>(e);
+ setConnectionError(ConnectionError(ev->error()), ev->message());
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 2) {
+ // Metadata event
+ emit metadataUpdated();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 4) {
+ // End of track event
+ playNext();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 5) {
+ stop();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 10) {
+ // Track progressed
+ QSpotifyTrackProgressEvent *ev = static_cast<QSpotifyTrackProgressEvent *>(e);
+ m_currentTrackPosition += ev->delta();
+ emit currentTrackPositionChanged();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 11) {
+ QSpotifyRequestImageEvent *ev = static_cast<QSpotifyRequestImageEvent *>(e);
+ sendImageRequest(ev->imageId());
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 12) {
+ QSpotifyReceiveImageEvent *ev = static_cast<QSpotifyReceiveImageEvent *>(e);
+ receiveImageResponse(ev->image());
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 13) {
+ // Play Token Lost
+ emit playTokenLost();
+ pause();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 14) {
+ // LoggedIn
+ onLoggedIn();
+ e->accept();
+ return true;
+ } else if (e->type() == QEvent::User + 15) {
+ // LoggedOut
+ onLoggedOut();
+ e->accept();
+ return true;
+ }
+ return QObject::event(e);
+}
+
+void QSpotifySession::processSpotifyEvents()
+{
+ if (m_timerID)
+ killTimer(m_timerID);
+ int nextTimeout;
+ do {
+ sp_session_process_events(m_sp_session, &nextTimeout);
+ // update connection state
+ setConnectionStatus(ConnectionStatus(sp_session_connectionstate(m_sp_session)));
+ if (m_offlineMode && m_connectionStatus == LoggedIn) {
+ setConnectionRule(AllowNetwork, true);
+ setConnectionRule(AllowNetwork, false);
+ }
+ } while (nextTimeout == 0);
+ m_timerID = startTimer(nextTimeout);
+}
+
+void QSpotifySession::setStreamingQuality(StreamingQuality q)
+{
+ if (m_streamingQuality == q)
+ return;
+
+ m_streamingQuality = q;
+ QSettings s;
+ s.setValue("streamingQuality", int(q));
+ sp_session_preferred_bitrate(m_sp_session, sp_bitrate(q));
+
+ emit streamingQualityChanged();
+}
+
+void QSpotifySession::setSyncQuality(StreamingQuality q)
+{
+ if (m_syncQuality == q)
+ return;
+
+ m_syncQuality = q;
+ QSettings s;
+ s.setValue("syncQuality", int(q));
+ sp_session_preferred_offline_bitrate(m_sp_session, sp_bitrate(q), false);
+
+ emit syncQualityChanged();
+}
+
+void QSpotifySession::onLoggedIn()
+{
+ if (m_user)
+ return;
+
+ m_isLoggedIn = true;
+ m_user = new QSpotifyUser(sp_session_user(m_sp_session));
+
+ m_pending_connectionRequest = false;
+ emit pendingConnectionRequestChanged();
+ emit isLoggedInChanged();
+
+ checkNetworkAccess();
+}
+
+void QSpotifySession::onLoggedOut()
+{
+ if (!m_explicitLogout)
+ return;
+
+ delete m_user;
+ m_user = 0;
+ m_explicitLogout = false;
+ m_isLoggedIn = false;
+
+ m_pending_connectionRequest = false;
+ emit pendingConnectionRequestChanged();
+ emit isLoggedInChanged();
+}
+
+void QSpotifySession::setConnectionStatus(ConnectionStatus status)
+{
+ if (m_connectionStatus == status)
+ return;
+
+ m_connectionStatus = status;
+ emit connectionStatusChanged();
+}
+
+void QSpotifySession::setConnectionError(ConnectionError error, const QString &message)
+{
+ if (error == Ok || m_offlineMode)
+ return;
+
+ if (m_pending_connectionRequest) {
+ m_pending_connectionRequest = false;
+ emit pendingConnectionRequestChanged();
+ }
+
+ if (error == UnableToContactServer && m_ignoreNextConnectionError) {
+ m_ignoreNextConnectionError = false;
+ return;
+ }
+
+ if (error == UnableToContactServer) {
+ setOfflineMode(true, true);
+ }
+
+ m_connectionError = error;
+ m_connectionErrorMessage = message;
+ if (error != Ok && m_currentTrack && !m_currentTrack->isAvailableOffline())
+ pause();
+ emit connectionErrorChanged();
+}
+
+void QSpotifySession::login(const QString &username, const QString &password)
+{
+ if (!isValid() || m_isLoggedIn || m_pending_connectionRequest)
+ return;
+
+ m_pending_connectionRequest = true;
+ emit pendingConnectionRequestChanged();
+ emit loggingIn();
+
+ if (password.isEmpty())
+ sp_session_relogin(m_sp_session);
+ else
+ sp_session_login(m_sp_session, username.toLatin1().constData(), password.toLatin1().constData(), true);
+}
+
+void QSpotifySession::logout(bool keepLoginInfo)
+{
+ if (!m_isLoggedIn || m_pending_connectionRequest)
+ return;
+
+ stop();
+ m_playQueue->clear();
+
+ if (!keepLoginInfo)
+ sp_session_forget_me(m_sp_session);
+
+ m_explicitLogout = true;
+
+ m_pending_connectionRequest = true;
+ emit pendingConnectionRequestChanged();
+ emit loggingOut();
+ sp_session_logout(m_sp_session);
+}
+
+void QSpotifySession::setShuffle(bool s)
+{
+ if (m_shuffle == s)
+ return;
+
+ QSettings settings;
+ settings.setValue("shuffle", s);
+ m_playQueue->setShuffle(s);
+ m_shuffle = s;
+ emit shuffleChanged();
+}
+
+void QSpotifySession::setRepeat(bool r)
+{
+ if (m_repeat == r)
+ return;
+
+ QSettings s;
+ s.setValue("repeat", r);
+ m_playQueue->setRepeat(r);
+ m_repeat = r;
+ emit repeatChanged();
+}
+
+void QSpotifySession::play(QSpotifyTrack *track)
+{
+ if (track->error() != QSpotifyTrack::Ok || !track->isAvailable() || m_currentTrack == track)
+ return;
+
+ if (m_currentTrack)
+ stop(true);
+
+ if (!track->seen())
+ track->setSeen(true);
+
+ sp_error error = sp_session_player_load(m_sp_session, track->m_sp_track);
+ if (error != SP_ERROR_OK) {
+ fprintf(stderr, "failed to load track: %s\n",
+ sp_error_message(error));
+ return;
+ }
+ m_currentTrack = track;
+ m_currentTrackPosition = 0;
+ emit currentTrackChanged();
+ emit currentTrackPositionChanged();
+
+ m_resourceSet->acquire();
+}
+
+void QSpotifySession::beginPlayBack()
+{
+ sp_session_player_play(m_sp_session, true);
+ m_isPlaying = true;
+ emit isPlayingChanged();
+
+ QCoreApplication::postEvent(g_audioWorker, new QEvent(QEvent::Type(QEvent::User + 6)));
+}
+
+void QSpotifySession::pause()
+{
+ if (!m_isPlaying)
+ return;
+
+ sp_session_player_play(m_sp_session, false);
+ m_isPlaying = false;
+ emit isPlayingChanged();
+
+ QCoreApplication::postEvent(g_audioWorker, new QEvent(QEvent::Type(QEvent::User + 7)));
+
+ m_resourceSet->release();
+}
+
+void QSpotifySession::resume()
+{
+ if (m_isPlaying || !m_currentTrack)
+ return;
+
+ m_resourceSet->acquire();
+}
+
+void QSpotifySession::stop(bool dontEmitSignals)
+{
+ if (!m_isPlaying && !m_currentTrack)
+ return;
+
+ sp_session_player_unload(m_sp_session);
+ m_isPlaying = false;
+ m_currentTrack = 0;
+ m_currentTrackPosition = 0;
+
+ if (!dontEmitSignals) {
+ emit isPlayingChanged();
+ emit currentTrackChanged();
+ emit currentTrackPositionChanged();
+ }
+
+ QCoreApplication::postEvent(g_audioWorker, new QEvent(QEvent::Type(QEvent::User + 8)));
+
+ m_resourceSet->release();
+}
+
+void QSpotifySession::seek(int offset)
+{
+ if (!m_currentTrack)
+ return;
+
+ sp_session_player_seek(m_sp_session, offset);
+
+ m_currentTrackPosition = offset;
+ emit currentTrackPositionChanged();
+
+ QCoreApplication::postEvent(g_audioWorker, new QEvent(QEvent::Type(QEvent::User + 9)));
+}
+
+void QSpotifySession::playNext()
+{
+ m_playQueue->playNext();
+}
+
+void QSpotifySession::playPrevious()
+{
+ m_playQueue->playPrevious();
+}
+
+void QSpotifySession::enqueue(QSpotifyTrack *track)
+{
+ m_playQueue->enqueueTrack(track);
+}
+
+void QSpotifySession::resourceAcquiredHandler(const QList<ResourcePolicy::ResourceType> &)
+{
+ beginPlayBack();
+}
+
+void QSpotifySession::resourceLostHandler()
+{
+ pause();
+}
+
+QString QSpotifySession::formatDuration(qint64 d) const
+{
+ d /= 1000;
+ int s = d % 60;
+ d /= 60;
+ int m = d % 60;
+ int h = d / 60;
+
+ QString r;
+ if (h > 0)
+ r += QString::number(h) + QLatin1String(":");
+ r += QLatin1String(m > 9 ? "" : "0") + QString::number(m) + QLatin1String(":");
+ r += QLatin1String(s > 9 ? "" : "0") + QString::number(s);
+
+ return r;
+}
+
+QString QSpotifySession::getStoredLoginInformation() const
+{
+ QString username;
+ char buffer[200];
+ int size = sp_session_remembered_user(m_sp_session, &buffer[0], 200);
+ if (size > 0) {
+ username = QString::fromLatin1(&buffer[0], size);
+ }
+ return username;
+}
+
+QImage QSpotifySession::requestSpotifyImage(const QString &id)
+{
+ g_imageRequestMutex.lock();
+ g_imageRequestConditions.insert(id, new QWaitCondition);
+ QCoreApplication::postEvent(this, new QSpotifyRequestImageEvent(id));
+ g_imageRequestConditions[id]->wait(&g_imageRequestMutex);
+ delete g_imageRequestConditions.take(id);
+
+ QImage im = g_imageRequestImages.take(id);
+
+ g_imageRequestMutex.unlock();
+
+ return im;
+}
+
+static void callback_image_loaded(sp_image *image, void *)
+{
+ QCoreApplication::postEvent(QSpotifySession::instance(), new QSpotifyReceiveImageEvent(image));
+}
+
+void QSpotifySession::sendImageRequest(const QString &id)
+{
+ sp_link *link = sp_link_create_from_string(id.toLatin1().constData());
+ sp_image *image = sp_image_create_from_link(m_sp_session, link);
+ sp_link_release(link);
+
+ g_imageRequestObject.insert(image, id);
+ sp_image_add_load_callback(image, callback_image_loaded, 0);
+}
+
+void QSpotifySession::receiveImageResponse(sp_image *image)
+{
+ sp_image_remove_load_callback(image, callback_image_loaded, 0);
+
+ QString id = g_imageRequestObject.take(image);
+ QImage im;
+ if (sp_image_error(image) == SP_ERROR_OK) {
+ size_t dataSize;
+ const void *data = sp_image_data(image, &dataSize);
+ im = QImage::fromData(reinterpret_cast<const uchar *>(data), dataSize, "JPG");
+ }
+
+ sp_image_release(image);
+
+ g_imageRequestMutex.lock();
+ g_imageRequestImages.insert(id, im);
+ g_imageRequestConditions[id]->wakeAll();
+ g_imageRequestMutex.unlock();
+}
+
+bool QSpotifySession::isOnline() const
+{
+ return m_networkConfManager->isOnline();
+}
+
+void QSpotifySession::onOnlineChanged()
+{
+ checkNetworkAccess();
+}
+
+void QSpotifySession::configurationChanged()
+{
+ checkNetworkAccess();
+}
+
+void QSpotifySession::checkNetworkAccess()
+{
+ if (!m_networkConfManager->isOnline()) {
+ sp_session_set_connection_type(m_sp_session, SP_CONNECTION_TYPE_NONE);
+ setOfflineMode(true, true);
+ } else {
+ bool wifi = false;
+ bool mobile = false;
+ bool roaming = false;
+ QList<QNetworkConfiguration> confs = m_networkConfManager->allConfigurations(QNetworkConfiguration::Active);
+ for (int i = 0; i < confs.count(); ++i) {
+ QString bearer = confs.at(i).bearerName();
+ if (bearer == QLatin1String("WLAN")) {
+ wifi = true;
+ break;
+ }
+ if (bearer == QLatin1String("2G")
+ || bearer == QLatin1String("CDMA2000")
+ || bearer == QLatin1String("WCDMA")
+ || bearer == QLatin1String("HSPA")
+ || bearer == QLatin1String("WiMAX")) {
+ mobile = true;
+ }
+ if (confs.at(i).isRoamingAvailable())
+ roaming = true;
+ }
+
+ sp_connection_type type;
+ if (wifi)
+ type = SP_CONNECTION_TYPE_WIFI;
+ else if (roaming)
+ type = SP_CONNECTION_TYPE_MOBILE_ROAMING;
+ else if (mobile)
+ type = SP_CONNECTION_TYPE_MOBILE;
+ else
+ type = SP_CONNECTION_TYPE_UNKNOWN;
+
+ sp_session_set_connection_type(m_sp_session, type);
+
+ if (m_forcedOfflineMode)
+ setOfflineMode(false, true);
+ else
+ setConnectionRule(AllowNetwork, !m_offlineMode);
+ }
+}
+
+void QSpotifySession::setConnectionRules(ConnectionRules r)
+{
+ if (m_connectionRules == r)
+ return;
+
+ m_connectionRules = r;
+ sp_session_set_connection_rules(m_sp_session, sp_connection_rules(int(m_connectionRules)));
+ emit connectionRulesChanged();
+}
+
+void QSpotifySession::setConnectionRule(ConnectionRule r, bool on)
+{
+ ConnectionRules oldRules = m_connectionRules;
+ if (on)
+ m_connectionRules |= r;
+ else
+ m_connectionRules &= ~r;
+
+ if (m_connectionRules != oldRules) {
+ sp_session_set_connection_rules(m_sp_session, sp_connection_rules(int(m_connectionRules)));
+ emit connectionRulesChanged();
+ }
+}
+
+void QSpotifySession::setOfflineMode(bool on, bool forced)
+{
+ if (m_offlineMode == on)
+ return;
+
+ m_offlineMode = on;
+
+ if (m_offlineMode && m_currentTrack && !m_currentTrack->isAvailableOffline())
+ stop();
+
+ if (!forced) {
+ QSettings s;
+ s.setValue("offlineMode", m_offlineMode);
+
+ if (m_offlineMode)
+ m_ignoreNextConnectionError = true;
+ }
+
+ m_forcedOfflineMode = forced && on;
+
+ setConnectionRule(AllowNetwork, !on);
+
+ emit offlineModeChanged();
+}
+
+void QSpotifySession::setSyncOverMobile(bool s)
+{
+ if (m_syncOverMobile == s)
+ return;
+
+ m_syncOverMobile = s;
+
+ QSettings settings;
+ settings.setValue("syncOverMobile", m_syncOverMobile);
+
+ setConnectionRule(AllowSyncOverMobile, s);
+ emit syncOverMobileChanged();
+}
diff --git a/libQtSpotify/qspotifysession.h b/libQtSpotify/qspotifysession.h
new file mode 100644
index 0000000..b789263
--- /dev/null
+++ b/libQtSpotify/qspotifysession.h
@@ -0,0 +1,285 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYSESSION_H
+#define QSPOTIFYSESSION_H
+
+#include <libspotify/api.h>
+#include <policy/resource-set.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QPair>
+#include <QtGui/QImage>
+
+class QSpotifyUser;
+class QSpotifyTrack;
+class QSpotifyAudioThread;
+class QAudioOutput;
+class QSpotifyPlayQueue;
+class QNetworkConfigurationManager;
+
+class QSpotifySession : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus NOTIFY connectionStatusChanged)
+ Q_PROPERTY(ConnectionError connectionError READ connectionError NOTIFY connectionErrorChanged)
+ Q_PROPERTY(QString connectionErrorMessage READ connectionErrorMessage NOTIFY connectionErrorChanged)
+ Q_PROPERTY(bool pendingConnectionRequest READ pendingConnectionRequest NOTIFY pendingConnectionRequestChanged)
+ Q_PROPERTY(QSpotifyUser *user READ user NOTIFY userChanged)
+ Q_PROPERTY(QSpotifyTrack *currentTrack READ currentTrack NOTIFY currentTrackChanged)
+ Q_PROPERTY(QSpotifyPlayQueue *playQueue READ playQueue NOTIFY userChanged)
+ Q_PROPERTY(bool hasCurrentTrack READ hasCurrentTrack NOTIFY currentTrackChanged)
+ Q_PROPERTY(int currentTrackPosition READ currentTrackPosition NOTIFY currentTrackPositionChanged)
+ Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY isPlayingChanged)
+ Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged)
+ Q_PROPERTY(bool repeat READ repeat WRITE setRepeat NOTIFY repeatChanged)
+ Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
+ Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
+ Q_PROPERTY(bool offlineMode READ offlineMode NOTIFY offlineModeChanged)
+ Q_PROPERTY(StreamingQuality streamingQuality READ streamingQuality WRITE setStreamingQuality NOTIFY streamingQualityChanged)
+ Q_PROPERTY(StreamingQuality syncQuality READ syncQuality WRITE setSyncQuality NOTIFY syncQualityChanged)
+ Q_PROPERTY(bool syncOverMobile READ syncOverMobile WRITE setSyncOverMobile NOTIFY syncOverMobileChanged)
+ Q_ENUMS(ConnectionStatus)
+ Q_ENUMS(ConnectionError)
+ Q_ENUMS(StreamingQuality)
+public:
+ enum ConnectionStatus {
+ LoggedOut = SP_CONNECTION_STATE_LOGGED_OUT,
+ LoggedIn = SP_CONNECTION_STATE_LOGGED_IN,
+ Disconnected = SP_CONNECTION_STATE_DISCONNECTED,
+ Undefined = SP_CONNECTION_STATE_UNDEFINED,
+ Offline = SP_CONNECTION_STATE_OFFLINE
+ };
+
+ enum ConnectionError {
+ Ok = SP_ERROR_OK,
+ ClientTooOld = SP_ERROR_CLIENT_TOO_OLD,
+ UnableToContactServer = SP_ERROR_UNABLE_TO_CONTACT_SERVER,
+ BadUsernameOrPassword = SP_ERROR_BAD_USERNAME_OR_PASSWORD,
+ UserBanned = SP_ERROR_USER_BANNED,
+ UserNeedsPremium = SP_ERROR_USER_NEEDS_PREMIUM,
+ OtherTransient = SP_ERROR_OTHER_TRANSIENT,
+ OtherPermanent = SP_ERROR_OTHER_PERMANENT
+ };
+
+ enum StreamingQuality {
+ Unknown = -1,
+ LowQuality = SP_BITRATE_96k,
+ HighQuality = SP_BITRATE_160k
+ };
+
+ enum ConnectionRule {
+ AllowNetwork = SP_CONNECTION_RULE_NETWORK,
+ AllowNetworkIfRoaming = SP_CONNECTION_RULE_NETWORK_IF_ROAMING,
+ AllowSyncOverMobile = SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE,
+ AllowSyncOverWifi = SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI
+ };
+ Q_DECLARE_FLAGS(ConnectionRules, ConnectionRule)
+
+ ~QSpotifySession();
+
+ static QSpotifySession *instance();
+
+ Q_INVOKABLE bool isValid() const { return m_sp_session != 0; }
+
+ Q_INVOKABLE QString formatDuration(qint64 d) const;
+
+ ConnectionStatus connectionStatus() const { return m_connectionStatus; }
+ void setConnectionStatus(ConnectionStatus status);
+
+ bool isLoggedIn() const { return m_isLoggedIn; }
+
+ bool offlineMode() const { return m_offlineMode; }
+ Q_INVOKABLE void setOfflineMode(bool on, bool forced = false);
+
+ ConnectionError connectionError() const { return m_connectionError; }
+ void setConnectionError(ConnectionError error, const QString &message);
+
+ QString connectionErrorMessage() const { return m_connectionErrorMessage; }
+
+ bool pendingConnectionRequest() const { return m_pending_connectionRequest; }
+
+ QSpotifyUser *user() const { return m_user; }
+
+ QSpotifyTrack *currentTrack() const { return m_currentTrack; }
+ bool hasCurrentTrack() const { return m_currentTrack != 0; }
+ int currentTrackPosition() const { return m_currentTrackPosition; }
+
+ StreamingQuality streamingQuality() const { return m_streamingQuality; }
+ void setStreamingQuality(StreamingQuality q);
+
+ StreamingQuality syncQuality() const { return m_syncQuality; }
+ void setSyncQuality(StreamingQuality q);
+
+ bool syncOverMobile() const { return m_syncOverMobile; }
+ Q_INVOKABLE void setSyncOverMobile(bool s);
+
+ bool isOnline() const;
+
+ void play(QSpotifyTrack *track);
+
+ bool isPlaying() const { return m_isPlaying; }
+
+ bool shuffle() const { return m_shuffle; }
+ void setShuffle(bool s);
+
+ bool repeat() const { return m_repeat; }
+ void setRepeat(bool r);
+
+ sp_session *spsession() const { return m_sp_session; }
+
+ QSpotifyPlayQueue *playQueue() const { return m_playQueue; }
+
+public Q_SLOTS:
+ void login(const QString &username, const QString &password = QString());
+ void logout(bool keepLoginInfo);
+
+ void pause();
+ void resume();
+ void stop(bool dontEmitSignals = false);
+ void seek(int offset);
+ void playNext();
+ void playPrevious();
+ void enqueue(QSpotifyTrack *track);
+
+Q_SIGNALS:
+ void connectionStatusChanged();
+ void connectionErrorChanged();
+ void userChanged();
+ void metadataUpdated();
+ void currentTrackChanged();
+ void pendingConnectionRequestChanged();
+ void isPlayingChanged();
+ void loggingIn();
+ void loggingOut();
+ void streamingQualityChanged();
+ void syncQualityChanged();
+ void currentTrackPositionChanged();
+ void shuffleChanged();
+ void repeatChanged();
+ void isOnlineChanged();
+ void playTokenLost();
+ void connectionRulesChanged();
+ void offlineModeChanged();
+ void syncOverMobileChanged();
+ void isLoggedInChanged();
+
+protected:
+ bool event(QEvent *);
+
+private Q_SLOTS:
+ void resourceAcquiredHandler(const QList<ResourcePolicy::ResourceType>&);
+ void resourceLostHandler();
+ void cleanUp();
+ void onOnlineChanged();
+ void configurationChanged();
+
+private:
+ QSpotifySession();
+ void init();
+ void checkNetworkAccess();
+ void processSpotifyEvents();
+ void beginPlayBack();
+
+ void onLoggedIn();
+ void onLoggedOut();
+
+ QString getStoredLoginInformation() const;
+
+ QImage requestSpotifyImage(const QString &id);
+ void sendImageRequest(const QString &id);
+ void receiveImageResponse(sp_image *image);
+
+ void setConnectionRules(ConnectionRules r);
+ void setConnectionRule(ConnectionRule r, bool on = true);
+
+ static QSpotifySession *m_instance;
+ int m_timerID;
+
+ sp_session *m_sp_session;
+ sp_session_callbacks m_sp_callbacks;
+ sp_session_config m_sp_config;
+
+ ConnectionStatus m_connectionStatus;
+ ConnectionError m_connectionError;
+ ConnectionRules m_connectionRules;
+ QString m_connectionErrorMessage;
+ StreamingQuality m_streamingQuality;
+ StreamingQuality m_syncQuality;
+ bool m_syncOverMobile;
+
+ mutable QSpotifyUser *m_user;
+
+ bool m_pending_connectionRequest;
+ bool m_isLoggedIn;
+ bool m_explicitLogout;
+
+ bool m_offlineMode;
+ bool m_forcedOfflineMode;
+ bool m_ignoreNextConnectionError;
+
+ QSpotifyPlayQueue *m_playQueue;
+ QSpotifyTrack *m_currentTrack;
+ bool m_isPlaying;
+ int m_currentTrackPosition;
+ bool m_shuffle;
+ bool m_repeat;
+
+ QSpotifyAudioThread *m_audioThread;
+
+ // Phone Resource Management
+ ResourcePolicy::ResourceSet *m_resourceSet;
+ ResourcePolicy::AudioResource *m_audioResource;
+ ResourcePolicy::ScaleButtonResource *m_scaleButtonResource;
+
+ // Network Management
+ QNetworkConfigurationManager *m_networkConfManager;
+
+ friend class QSpotifyUser;
+ friend class QSpotifyTrack;
+ friend class QSpotifyImageProvider;
+ friend class QSpotifySearch;
+ friend class QSpotifyPlaylist;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSpotifySession::ConnectionRules)
+
+#endif // QSPOTIFYSESSION_H
diff --git a/libQtSpotify/qspotifytoplist.cpp b/libQtSpotify/qspotifytoplist.cpp
new file mode 100644
index 0000000..e017941
--- /dev/null
+++ b/libQtSpotify/qspotifytoplist.cpp
@@ -0,0 +1,205 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifytoplist.h"
+
+#include "qspotifyplaylist.h"
+#include "qspotifytracklist.h"
+#include "qspotifysession.h"
+#include "qspotifytrack.h"
+#include "qspotifyuser.h"
+#include "qspotifyalbum.h"
+#include "qspotifyartist.h"
+
+#include <libspotify/api.h>
+
+static QHash<sp_toplistbrowse *, QSpotifyToplist *> g_toplistObjects;
+static QMutex g_mutex;
+
+class QSpotifyToplistCompleteEvent : public QEvent
+{
+public:
+ QSpotifyToplistCompleteEvent(sp_toplistbrowse *tlbrowse)
+ : QEvent(Type(QEvent::User))
+ , m_tlbrowse(tlbrowse)
+ { }
+
+ sp_toplistbrowse *toplistBrowse() const { return m_tlbrowse; }
+
+private:
+ sp_toplistbrowse *m_tlbrowse;
+};
+
+static void callback_toplistbrowse_complete(sp_toplistbrowse *result, void *)
+{
+ QMutexLocker lock(&g_mutex);
+ QSpotifyToplist *tl = g_toplistObjects.value(result);
+ if (tl)
+ QCoreApplication::postEvent(tl, new QSpotifyToplistCompleteEvent(result));
+}
+
+QSpotifyToplist::QSpotifyToplist(QObject *parent)
+ : QObject(parent)
+ , m_sp_browsetracks(0)
+ , m_sp_browseartists(0)
+ , m_sp_browsealbums(0)
+ , m_busy(false)
+ , m_trackResults(0)
+{
+}
+
+QSpotifyToplist::~QSpotifyToplist()
+{
+ clear();
+}
+
+QList<QObject *> QSpotifyToplist::tracks() const
+{
+ QList<QObject*> list;
+ if (m_trackResults != 0) {
+ int c = m_trackResults->m_tracks.count();
+ for (int i = 0; i < c; ++i)
+ list.append((QObject*)(m_trackResults->m_tracks[i]));
+ }
+ return list;
+}
+
+void QSpotifyToplist::updateResults()
+{
+ QDateTime currentTime = QDateTime::currentDateTime();
+
+ if (m_busy || (m_lastUpdate.isValid() && m_lastUpdate.secsTo(currentTime) < 7200))
+ return;
+
+ m_lastUpdate = currentTime;
+
+ clear();
+
+ m_busy = true;
+ emit busyChanged();
+
+ QMutexLocker lock(&g_mutex);
+ m_sp_browsetracks = sp_toplistbrowse_create(QSpotifySession::instance()->spsession(), SP_TOPLIST_TYPE_TRACKS, SP_TOPLIST_REGION_EVERYWHERE, NULL, callback_toplistbrowse_complete, 0);
+ g_toplistObjects.insert(m_sp_browsetracks, this);
+ m_sp_browseartists = sp_toplistbrowse_create(QSpotifySession::instance()->spsession(), SP_TOPLIST_TYPE_ARTISTS, SP_TOPLIST_REGION_EVERYWHERE, NULL, callback_toplistbrowse_complete, 0);
+ g_toplistObjects.insert(m_sp_browseartists, this);
+ m_sp_browsealbums = sp_toplistbrowse_create(QSpotifySession::instance()->spsession(), SP_TOPLIST_TYPE_ALBUMS, SP_TOPLIST_REGION_EVERYWHERE, NULL, callback_toplistbrowse_complete, 0);
+ g_toplistObjects.insert(m_sp_browsealbums, this);
+
+}
+
+void QSpotifyToplist::clear()
+{
+ if (m_trackResults)
+ m_trackResults->release();
+ m_trackResults = 0;
+ qDeleteAll(m_albumResults);
+ m_albumResults.clear();
+ qDeleteAll(m_artistResults);
+ m_artistResults.clear();
+ emit resultsChanged();
+
+ QMutexLocker lock(&g_mutex);
+ if (m_sp_browsetracks)
+ sp_toplistbrowse_release(m_sp_browsetracks);
+ g_toplistObjects.remove(m_sp_browsetracks);
+ m_sp_browsetracks = 0;
+ if (m_sp_browseartists)
+ sp_toplistbrowse_release(m_sp_browseartists);
+ g_toplistObjects.remove(m_sp_browseartists);
+ m_sp_browseartists = 0;
+ if (m_sp_browsealbums)
+ sp_toplistbrowse_release(m_sp_browsealbums);
+ g_toplistObjects.remove(m_sp_browsealbums);
+ m_sp_browsealbums = 0;
+}
+
+bool QSpotifyToplist::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ QSpotifyToplistCompleteEvent *ev = static_cast<QSpotifyToplistCompleteEvent *>(e);
+ populateResults(ev->toplistBrowse());
+ e->accept();
+ return true;
+ }
+ return QObject::event(e);
+}
+
+void QSpotifyToplist::populateResults(sp_toplistbrowse *tl)
+{
+ if (sp_toplistbrowse_error(tl) != SP_ERROR_OK)
+ return;
+
+ if (tl == m_sp_browsetracks) {
+ m_trackResults = new QSpotifyTrackList;
+ int c = sp_toplistbrowse_num_tracks(tl);
+ for (int i = 0; i < c; ++i) {
+ QSpotifyTrack *track = new QSpotifyTrack(sp_toplistbrowse_track(tl, i), m_trackResults);
+ m_trackResults->m_tracks.append(track);
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksAdded(QVector<sp_track*>)), track, SLOT(onStarredListTracksAdded(QVector<sp_track*>)));
+ connect(QSpotifySession::instance()->user()->starredList(), SIGNAL(tracksRemoved(QVector<sp_track*>)), track, SLOT(onStarredListTracksRemoved(QVector<sp_track*>)));
+ }
+ }
+
+ if (tl == m_sp_browseartists) {
+ int c = sp_toplistbrowse_num_artists(tl);
+ for (int i = 0; i < c; ++i) {
+ QSpotifyArtist *artist = new QSpotifyArtist(sp_toplistbrowse_artist(tl, i));
+ m_artistResults.append((QObject *)artist);
+ }
+ }
+
+ if (tl == m_sp_browsealbums) {
+ int c = sp_toplistbrowse_num_albums(tl);
+ for (int i = 0; i < c; ++i) {
+ sp_album *a = sp_toplistbrowse_album(tl, i);
+ QSpotifyAlbum *album = new QSpotifyAlbum(a);
+ m_albumResults.append((QObject *)album);
+ }
+ }
+
+ if (m_trackResults && m_artistResults.count() > 0 && m_albumResults.count() > 0) {
+ m_busy = false;
+ emit busyChanged();
+ }
+
+ emit resultsChanged();
+}
diff --git a/libQtSpotify/qspotifytoplist.h b/libQtSpotify/qspotifytoplist.h
new file mode 100644
index 0000000..429730a
--- /dev/null
+++ b/libQtSpotify/qspotifytoplist.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYTOPLIST_H
+#define QSPOTIFYTOPLIST_H
+
+#include <QObject>
+#include <QDateTime>
+
+struct sp_toplistbrowse;
+class QSpotifyTrackList;
+
+class QSpotifyToplist : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QList<QObject *> tracks READ tracks NOTIFY resultsChanged)
+ Q_PROPERTY(QList<QObject *> albums READ albums NOTIFY resultsChanged)
+ Q_PROPERTY(QList<QObject *> artists READ artists NOTIFY resultsChanged)
+ Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
+
+public:
+
+ QSpotifyToplist(QObject *parent = 0);
+ ~QSpotifyToplist();
+
+ QList<QObject *> tracks() const;
+ QList<QObject *> albums() const { return m_albumResults; }
+ QList<QObject *> artists() const { return m_artistResults; }
+
+ bool busy() const { return m_busy; }
+
+ Q_INVOKABLE void updateResults();
+
+ bool event(QEvent *);
+
+Q_SIGNALS:
+ void resultsChanged();
+ void busyChanged();
+
+private:
+ void clear();
+ void populateResults(sp_toplistbrowse *tl);
+
+ sp_toplistbrowse *m_sp_browsetracks;
+ sp_toplistbrowse *m_sp_browseartists;
+ sp_toplistbrowse *m_sp_browsealbums;
+
+ bool m_busy;
+
+ QSpotifyTrackList *m_trackResults;
+ QList<QObject *> m_albumResults;
+ QList<QObject *> m_artistResults;
+
+ QDateTime m_lastUpdate;
+
+};
+
+#endif // QSPOTIFYTOPLIST_H
diff --git a/libQtSpotify/qspotifytrack.cpp b/libQtSpotify/qspotifytrack.cpp
new file mode 100644
index 0000000..6dac538
--- /dev/null
+++ b/libQtSpotify/qspotifytrack.cpp
@@ -0,0 +1,316 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifytrack.h"
+#include "qspotifysession.h"
+#include "qspotifyuser.h"
+#include "qspotifyplaylist.h"
+#include "qspotifytracklist.h"
+#include "qspotifyartist.h"
+#include "qspotifyalbum.h"
+#include "qspotifyplayqueue.h"
+#include <libspotify/api.h>
+
+#include <QDebug>
+
+QSpotifyTrack::QSpotifyTrack(sp_track *track, QSpotifyPlaylist *playlist)
+ : QSpotifyObject(true)
+ , m_playlist(playlist)
+ , m_trackList(playlist->m_trackList)
+ , m_album(0)
+ , m_discNumber(0)
+ , m_duration(0)
+ , m_discIndex(0)
+ , m_isAvailable(false)
+ , m_numArtists(0)
+ , m_popularity(0)
+ , m_seen(true)
+ , m_isCurrentPlayingTrack(false)
+{
+ sp_track_add_ref(track);
+ m_sp_track = track;
+ m_error = Error(sp_track_error(m_sp_track));
+
+ connect(QSpotifySession::instance(), SIGNAL(currentTrackChanged()), this, SLOT(onSessionCurrentTrackChanged()));
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(trackDataChanged()));
+
+ metadataUpdated();
+}
+
+QSpotifyTrack::QSpotifyTrack(sp_track *track, QSpotifyTrackList *tracklist)
+ : QSpotifyObject(true)
+ , m_playlist(0)
+ , m_trackList(tracklist)
+ , m_album(0)
+ , m_discNumber(0)
+ , m_duration(0)
+ , m_discIndex(0)
+ , m_isAvailable(false)
+ , m_numArtists(0)
+ , m_popularity(0)
+ , m_seen(true)
+ , m_isCurrentPlayingTrack(false)
+{
+ sp_track_add_ref(track);
+ m_sp_track = track;
+ m_error = Error(sp_track_error(m_sp_track));
+
+ connect(QSpotifySession::instance(), SIGNAL(currentTrackChanged()), this, SLOT(onSessionCurrentTrackChanged()));
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(trackDataChanged()));
+
+ metadataUpdated();
+}
+
+QSpotifyTrack::~QSpotifyTrack()
+{
+ stop();
+ sp_track_release(m_sp_track);
+ qDeleteAll(m_artists);
+ delete m_album;
+}
+
+bool QSpotifyTrack::isLoaded()
+{
+ return sp_track_is_loaded(m_sp_track);
+}
+
+bool QSpotifyTrack::updateData()
+{
+ bool updated = false;
+
+ Error error = Error(sp_track_error(m_sp_track));
+ if (m_error != error) {
+ m_error = error;
+ updated = true;
+ }
+
+ if (m_error == Ok) {
+ QString name = QString::fromUtf8(sp_track_name(m_sp_track));
+ int discNumber = sp_track_disc(m_sp_track);
+ int duration = sp_track_duration(m_sp_track);
+ int discIndex = sp_track_index(m_sp_track);
+ bool isAvailable = sp_track_is_available(QSpotifySession::instance()->m_sp_session, m_sp_track);
+ int numArtists = sp_track_num_artists(m_sp_track);
+ int popularity = sp_track_popularity(m_sp_track);
+ if (m_playlist && m_playlist->type() == QSpotifyPlaylist::Inbox) {
+ int tindex = m_trackList->m_tracks.indexOf(this);
+
+ if (tindex > -1) {
+ bool seen = sp_playlist_track_seen(m_playlist->m_sp_playlist, tindex);
+ if (m_seen != seen)
+ updateSeen(seen);
+
+ QString crea = QString::fromUtf8(sp_user_canonical_name(sp_playlist_track_creator(m_playlist->m_sp_playlist, tindex)));
+ if (m_creator != crea) {
+ m_creator = crea;
+ updated = true;
+ }
+
+ int cd = sp_playlist_track_create_time(m_playlist->m_sp_playlist, tindex);
+ QDateTime dt = QDateTime::fromTime_t(cd);
+ if (m_creationDate != dt) {
+ m_creationDate = dt;
+ updated = true;
+ }
+ }
+ }
+
+
+ if (m_name != name) {
+ m_name = name;
+ updated = true;
+ }
+ if (m_discNumber != discNumber) {
+ m_discNumber = discNumber;
+ updated = true;
+ }
+ if (m_duration != duration) {
+ m_duration = duration;
+ m_durationString = QSpotifySession::instance()->formatDuration(m_duration);
+ updated = true;
+ }
+ if (m_discIndex != discIndex) {
+ m_discIndex = discIndex;
+ updated = true;
+ }
+ if (m_isAvailable != isAvailable) {
+ m_isAvailable = isAvailable;
+ updated = true;
+ }
+ if (m_numArtists != numArtists) {
+ m_numArtists = numArtists;
+ updated = true;
+ }
+ if (m_popularity != popularity) {
+ m_popularity = popularity;
+ updated = true;
+ }
+
+ if (m_artists.isEmpty()) {
+ int count = sp_track_num_artists(m_sp_track);
+ for (int i = 0; i < count; ++i) {
+ sp_artist *artist = sp_track_artist(m_sp_track, i);
+ QSpotifyArtist *a = new QSpotifyArtist(artist);
+ m_artists.append(a);
+ m_artistsString += a->name();
+ if (i != count - 1)
+ m_artistsString += QLatin1String(", ");
+ }
+ updated = true;
+ }
+ if (!m_album) {
+ m_album = new QSpotifyAlbum(sp_track_album(m_sp_track));
+ updated = true;
+ m_albumString = m_album->name();
+ }
+ }
+
+ return updated;
+}
+
+QString QSpotifyTrack::artists() const
+{
+ return m_artistsString;
+}
+
+QString QSpotifyTrack::album() const
+{
+ return m_albumString;
+}
+
+QString QSpotifyTrack::albumCoverId() const
+{
+ if (!m_album)
+ return QString();
+
+ return m_album->coverId();
+}
+
+bool QSpotifyTrack::isStarred() const
+{
+ return QSpotifySession::instance()->user()->starredList()->contains(m_sp_track);
+}
+
+void QSpotifyTrack::setIsStarred(bool v)
+{
+ sp_track_set_starred(QSpotifySession::instance()->m_sp_session, const_cast<sp_track* const*>(&m_sp_track), 1, v);
+}
+
+void QSpotifyTrack::play()
+{
+ QSpotifySession::instance()->m_playQueue->playTrack(this);
+}
+
+void QSpotifyTrack::pause()
+{
+ if (isCurrentPlayingTrack())
+ QSpotifySession::instance()->pause();
+}
+
+void QSpotifyTrack::resume()
+{
+ if (isCurrentPlayingTrack())
+ QSpotifySession::instance()->resume();
+}
+
+void QSpotifyTrack::stop()
+{
+ if (isCurrentPlayingTrack())
+ QSpotifySession::instance()->stop();
+}
+
+void QSpotifyTrack::seek(int offset)
+{
+ if (!isCurrentPlayingTrack())
+ play();
+ QSpotifySession::instance()->seek(offset);
+}
+
+void QSpotifyTrack::enqueue()
+{
+ QSpotifySession::instance()->enqueue(this);
+}
+
+void QSpotifyTrack::removeFromPlaylist()
+{
+ if (m_playlist)
+ m_playlist->remove(this);
+}
+
+void QSpotifyTrack::onSessionCurrentTrackChanged()
+{
+ bool newValue = QSpotifySession::instance()->currentTrack() == this;
+ if (m_isCurrentPlayingTrack != newValue) {
+ m_isCurrentPlayingTrack = newValue;
+ emit isCurrentPlayingTrackChanged();
+ }
+}
+
+void QSpotifyTrack::onStarredListTracksAdded(QVector<sp_track *> v)
+{
+ if (v.contains(m_sp_track))
+ emit isStarredChanged();
+}
+
+void QSpotifyTrack::onStarredListTracksRemoved(QVector<sp_track *> v)
+{
+ if (v.contains(m_sp_track))
+ emit isStarredChanged();
+}
+
+void QSpotifyTrack::updateSeen(bool s)
+{
+ m_seen = s;
+ emit seenChanged();
+}
+
+void QSpotifyTrack::setSeen(bool s)
+{
+ if (!m_playlist)
+ return;
+
+ sp_playlist_track_set_seen(m_playlist->m_sp_playlist, m_trackList->m_tracks.indexOf(this), s);
+}
+
+bool QSpotifyTrack::isAvailableOffline() const
+{
+ return m_playlist && m_playlist->offlineStatus() == QSpotifyPlaylist::Yes;
+}
diff --git a/libQtSpotify/qspotifytrack.h b/libQtSpotify/qspotifytrack.h
new file mode 100644
index 0000000..9c8022b
--- /dev/null
+++ b/libQtSpotify/qspotifytrack.h
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYTRACK_H
+#define QSPOTIFYTRACK_H
+
+#include <libspotify/api.h>
+#include "qspotifyobject.h"
+#include <QtCore/QVector>
+#include <QtCore/QDateTime>
+
+class QSpotifyPlaylist;
+class QSpotifyTrackList;
+class QSpotifyArtist;
+class QSpotifyAlbum;
+
+class QSpotifyTrack : public QSpotifyObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ name NOTIFY trackDataChanged)
+ Q_PROPERTY(QString artists READ artists NOTIFY trackDataChanged)
+ Q_PROPERTY(QString album READ album NOTIFY trackDataChanged)
+ Q_PROPERTY(QString albumCoverId READ albumCoverId NOTIFY trackDataChanged)
+ Q_PROPERTY(int discNumber READ discNumber NOTIFY trackDataChanged)
+ Q_PROPERTY(QString duration READ durationString NOTIFY trackDataChanged)
+ Q_PROPERTY(int durationMs READ duration NOTIFY trackDataChanged)
+ Q_PROPERTY(Error error READ error NOTIFY trackDataChanged)
+ Q_PROPERTY(int discIndex READ discIndex NOTIFY trackDataChanged)
+ Q_PROPERTY(bool isAvailable READ isAvailable NOTIFY trackDataChanged)
+ Q_PROPERTY(bool isStarred READ isStarred WRITE setIsStarred NOTIFY isStarredChanged)
+ Q_PROPERTY(int popularity READ popularity NOTIFY trackDataChanged)
+ Q_PROPERTY(bool isCurrentPlayingTrack READ isCurrentPlayingTrack NOTIFY isCurrentPlayingTrackChanged)
+ Q_PROPERTY(bool seen READ seen WRITE setSeen NOTIFY seenChanged)
+ Q_PROPERTY(QString creator READ creator NOTIFY trackDataChanged)
+ Q_PROPERTY(QDateTime creationDate READ creationDate NOTIFY trackDataChanged)
+ Q_PROPERTY(QSpotifyAlbum *albumObject READ albumObject NOTIFY trackDataChanged)
+ Q_PROPERTY(QSpotifyArtist *artistObject READ artistObject NOTIFY trackDataChanged)
+ Q_ENUMS(Error)
+public:
+ enum Error {
+ Ok = SP_ERROR_OK,
+ IsLoading = SP_ERROR_IS_LOADING,
+ OtherPermanent = SP_ERROR_OTHER_PERMANENT
+ };
+
+ ~QSpotifyTrack();
+
+ bool isLoaded();
+
+ QString artists() const;
+ QString album() const;
+ QString albumCoverId() const;
+ int numArtists() const { return m_numArtists; }
+ int discNumber() const { return m_discNumber; }
+ int duration() const { return m_duration; }
+ QString durationString() const { return m_durationString; }
+ Error error() const { return m_error; }
+ int discIndex() const { return m_discIndex; }
+ bool isAvailable() const { return m_isAvailable; }
+ bool isStarred() const;
+ void setIsStarred(bool v);
+ QString name() const { return m_name; }
+ int popularity() const { return m_popularity; }
+ QSpotifyAlbum *albumObject() const { return m_album; }
+ QSpotifyArtist *artistObject() const { return m_artists.at(0); }
+ bool seen() const { return m_seen; }
+ void setSeen(bool s);
+ QString creator() const { return m_creator; }
+ QDateTime creationDate() const { return m_creationDate; }
+
+ bool isCurrentPlayingTrack() const { return m_isCurrentPlayingTrack; }
+
+ bool isAvailableOffline() const;
+
+ sp_track *sptrack() const { return m_sp_track; }
+
+ void updateSeen(bool s);
+
+public Q_SLOTS:
+ void play();
+ void pause();
+ void resume();
+ void stop();
+ void seek(int offset);
+ void enqueue();
+ void removeFromPlaylist();
+
+Q_SIGNALS:
+ void isCurrentPlayingTrackChanged();
+ void trackDataChanged();
+ void isStarredChanged();
+ void seenChanged();
+
+protected:
+ bool updateData();
+
+private Q_SLOTS:
+ void onSessionCurrentTrackChanged();
+ void onStarredListTracksAdded(QVector<sp_track *>);
+ void onStarredListTracksRemoved(QVector<sp_track *>);
+
+private:
+ QSpotifyTrack(sp_track *track, QSpotifyPlaylist *playlist);
+ QSpotifyTrack(sp_track *track, QSpotifyTrackList *tracklist);
+
+ sp_track *m_sp_track;
+ QSpotifyPlaylist *m_playlist;
+ QSpotifyTrackList *m_trackList;
+
+ QSpotifyAlbum *m_album;
+ QList<QSpotifyArtist *> m_artists;
+ QString m_albumString;
+ QString m_artistsString;
+ int m_discNumber;
+ int m_duration;
+ QString m_durationString;
+ Error m_error;
+ int m_discIndex;
+ bool m_isAvailable;
+ QString m_name;
+ int m_numArtists;
+ int m_popularity;
+ bool m_seen;
+ QString m_creator;
+ QDateTime m_creationDate;
+
+ bool m_isCurrentPlayingTrack;
+
+ friend class QSpotifyPlaylist;
+ friend class QSpotifySession;
+ friend class QSpotifySearch;
+ friend class QSpotifyPlayQueue;
+ friend class QSpotifyUser;
+ friend class QSpotifyAlbumBrowse;
+ friend class QSpotifyArtistBrowse;
+ friend class QSpotifyToplist;
+};
+
+#endif // QSPOTIFYTRACK_H
diff --git a/libQtSpotify/qspotifytracklist.cpp b/libQtSpotify/qspotifytracklist.cpp
new file mode 100644
index 0000000..857fc5c
--- /dev/null
+++ b/libQtSpotify/qspotifytracklist.cpp
@@ -0,0 +1,218 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifytracklist.h"
+
+#include "qspotifytrack.h"
+#include "qspotifysession.h"
+
+QSpotifyTrackList::QSpotifyTrackList(bool reverse)
+ : QObject()
+ , m_reverse(reverse)
+ , m_currentIndex(0)
+ , m_currentTrack(0)
+ , m_shuffle(false)
+ , m_shuffleIndex(0)
+ , m_refCount(1)
+{
+}
+
+QSpotifyTrackList::~QSpotifyTrackList()
+{
+ int c = m_tracks.count();
+ for (int i = 0; i < c; ++i)
+ m_tracks[i]->release();
+}
+
+void QSpotifyTrackList::play()
+{
+ if (m_tracks.count() == 0)
+ return;
+
+ if (m_shuffle)
+ playTrackAtIndex(m_shuffleList.first());
+ else
+ playTrackAtIndex(m_reverse ? previousAvailable(m_tracks.count()) : nextAvailable(-1));
+}
+
+bool QSpotifyTrackList::playTrackAtIndex(int i)
+{
+ if (i < 0 || i >= m_tracks.count()) {
+ m_currentTrack->release();
+ m_currentTrack = 0;
+ m_currentIndex = 0;
+ return false;
+ }
+
+ if (m_shuffle)
+ m_shuffleIndex = m_shuffleList.indexOf(i);
+ m_currentTrack = m_tracks.at(i);
+ m_currentTrack->addRef();
+ m_currentIndex = i;
+ playCurrentTrack();
+ return true;
+}
+
+bool QSpotifyTrackList::next()
+{
+ if (m_shuffle) {
+ if (m_shuffleIndex + 1 >= m_shuffleList.count()) {
+ m_currentTrack->release();
+ m_currentTrack = 0;
+ return false;
+ }
+ return playTrackAtIndex(m_shuffleList.at(m_shuffleIndex + 1));
+ } else {
+ int index = m_tracks.indexOf(m_currentTrack);
+ if (index == -1) {
+ int newi = qMin(m_currentIndex, m_tracks.count() - 1);
+ return playTrackAtIndex(m_reverse ? previousAvailable(newi) : nextAvailable(newi - 1));
+ }
+ return playTrackAtIndex(m_reverse ? previousAvailable(index) : nextAvailable(index));
+ }
+}
+
+bool QSpotifyTrackList::previous()
+{
+ if (m_shuffle) {
+ if (m_shuffleIndex - 1 < 0) {
+ m_currentTrack->release();
+ m_currentTrack = 0;
+ return false;
+ }
+ return playTrackAtIndex(m_shuffleList.at(m_shuffleIndex - 1));
+ } else {
+ int index = m_tracks.indexOf(m_currentTrack);
+ if (index == -1) {
+ int newi = qMin(m_currentIndex, m_tracks.count() - 1);
+ return playTrackAtIndex(m_reverse ? nextAvailable(newi - 1) : previousAvailable(newi));
+ }
+ return playTrackAtIndex(m_reverse ? nextAvailable(index) : previousAvailable(index));
+ }
+}
+
+void QSpotifyTrackList::playLast()
+{
+ if (m_tracks.count() == 0)
+ return;
+
+ if (m_shuffle)
+ playTrackAtIndex(m_shuffleList.last());
+ else
+ playTrackAtIndex(m_reverse ? nextAvailable(-1) : previousAvailable(m_tracks.count()));
+}
+
+void QSpotifyTrackList::playCurrentTrack()
+{
+ if (!m_currentTrack)
+ return;
+
+ if (m_currentTrack->isLoaded())
+ onTrackReady();
+ else
+ connect(m_currentTrack, SIGNAL(isLoadedChanged()), this, SLOT(onTrackReady()));
+}
+
+void QSpotifyTrackList::onTrackReady()
+{
+ disconnect(this, SLOT(onTrackReady()));
+ QSpotifySession::instance()->play(m_currentTrack);
+}
+
+void QSpotifyTrackList::setShuffle(bool s)
+{
+ m_shuffle = s;
+
+ m_shuffleList.clear();
+ m_shuffleIndex = 0;
+ bool currentTrackStillExists = m_currentTrack && m_tracks.contains(m_currentTrack);
+
+ if (m_shuffle) {
+ qsrand(QTime::currentTime().msec());
+ int currentTrackIndex = 0;
+ if (currentTrackStillExists) {
+ currentTrackIndex = m_tracks.indexOf(m_currentTrack);
+ m_shuffleList.append(currentTrackIndex);
+ }
+ QList<int> indexes;
+ for (int i = 0; i < m_tracks.count(); ++i) {
+ if ((currentTrackStillExists && i == currentTrackIndex) || !m_tracks.at(i)->isAvailable())
+ continue;
+ indexes.append(i);
+ }
+ while (!indexes.isEmpty()) {
+ int i = indexes.takeAt(indexes.count() == 1 ? 0 : (qrand() % (indexes.count() - 1)));
+ m_shuffleList.append(i);
+ }
+ }
+}
+
+void QSpotifyTrackList::release()
+{
+ --m_refCount;
+ if (m_refCount == 0)
+ deleteLater();
+}
+
+int QSpotifyTrackList::totalDuration() const
+{
+ qint64 total = 0;
+ for (int i = 0; i < m_tracks.count(); ++i)
+ total += m_tracks.at(i)->duration();
+
+ return total;
+}
+
+int QSpotifyTrackList::nextAvailable(int i)
+{
+ do {
+ ++i;
+ } while (i < m_tracks.count() && !m_tracks.at(i)->isAvailable());
+ return i;
+}
+
+int QSpotifyTrackList::previousAvailable(int i)
+{
+ do {
+ --i;
+ } while (i > -1 && !m_tracks.at(i)->isAvailable());
+ return i;
+}
diff --git a/libQtSpotify/qspotifytracklist.h b/libQtSpotify/qspotifytracklist.h
new file mode 100644
index 0000000..0e51a6c
--- /dev/null
+++ b/libQtSpotify/qspotifytracklist.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYTRACKLIST_H
+#define QSPOTIFYTRACKLIST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QList>
+
+class QSpotifyTrack;
+
+class QSpotifyTrackList : public QObject
+{
+ Q_OBJECT
+public:
+ QSpotifyTrackList(bool reverse = false);
+
+ QList<QSpotifyTrack *> tracks() const { return m_tracks; }
+
+ void play();
+ bool playTrackAtIndex(int i);
+ bool next();
+ bool previous();
+ void playLast();
+
+ int totalDuration() const;
+
+ bool isShuffle() const { return m_shuffle; }
+ void setShuffle(bool s);
+
+ void addRef() { ++m_refCount; }
+ void release();
+
+private Q_SLOTS:
+ void onTrackReady();
+
+private:
+ ~QSpotifyTrackList();
+ void playCurrentTrack();
+
+ int nextAvailable(int i);
+ int previousAvailable(int i);
+
+ bool m_reverse;
+
+ QList<QSpotifyTrack *> m_tracks;
+
+ int m_currentIndex;
+ QSpotifyTrack *m_currentTrack;
+
+ bool m_shuffle;
+ QList<int> m_shuffleList;
+ int m_shuffleIndex;
+
+ int m_refCount;
+
+ friend class QSpotifyTrack;
+ friend class QSpotifyPlaylist;
+ friend class QSpotifySearch;
+ friend class QSpotifyPlayQueue;
+ friend class QSpotifyAlbumBrowse;
+ friend class QSpotifyArtistBrowse;
+ friend class QSpotifyUser;
+ friend class QSpotifyToplist;
+};
+
+#endif // QSPOTIFYTRACKLIST_H
diff --git a/libQtSpotify/qspotifyuser.cpp b/libQtSpotify/qspotifyuser.cpp
new file mode 100644
index 0000000..d24b6b8
--- /dev/null
+++ b/libQtSpotify/qspotifyuser.cpp
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qspotifyuser.h"
+#include "qspotifyplaylistcontainer.h"
+#include "qspotifysession.h"
+#include "qspotifyplaylist.h"
+#include "qspotifytrack.h"
+#include "qspotifyalbumbrowse.h"
+#include "qspotifyalbum.h"
+
+#include <libspotify/api.h>
+
+QSpotifyUser::QSpotifyUser(sp_user *user)
+ : QSpotifyObject(true)
+ , m_playlistContainer(0)
+ , m_starredList(0)
+ , m_inbox(0)
+{
+ sp_user_add_ref(user);
+ m_sp_user = user;
+ m_canonicalName = QString::fromUtf8(sp_user_canonical_name(m_sp_user));
+
+ connect(this, SIGNAL(dataChanged()), this, SIGNAL(userDataChanged()));
+
+ metadataUpdated();
+}
+
+QSpotifyUser::~QSpotifyUser()
+{
+ delete m_playlistContainer;
+ sp_user_release(m_sp_user);
+}
+
+bool QSpotifyUser::isLoaded()
+{
+ return sp_user_is_loaded(m_sp_user);
+}
+
+bool QSpotifyUser::updateData()
+{
+ QString canonicalName = QString::fromUtf8(sp_user_canonical_name(m_sp_user));
+ QString displayName = QString::fromUtf8(sp_user_display_name(m_sp_user));
+ QString fullName = QString::fromUtf8(sp_user_full_name(m_sp_user));
+ QString picture = QString::fromUtf8(sp_user_picture(m_sp_user));
+
+ bool updated = false;
+ if (m_canonicalName != canonicalName) {
+ m_canonicalName = canonicalName;
+ updated = true;
+ }
+ if (m_displayName != displayName) {
+ m_displayName = displayName;
+ updated = true;
+ }
+ if (m_fullName != fullName) {
+ m_fullName = fullName;
+ updated = true;
+ }
+ if (m_picture != picture) {
+ m_picture = picture;
+ updated = true;
+ }
+
+ return updated;
+}
+
+QSpotifyPlaylistContainer *QSpotifyUser::playlistContainer() const
+{
+ if (!m_playlistContainer) {
+ sp_playlistcontainer *pc;
+ if (QSpotifySession::instance()->user() == this) {
+ pc = sp_session_playlistcontainer(QSpotifySession::instance()->m_sp_session);
+ sp_playlistcontainer_add_ref(pc);
+ } else {
+ pc = sp_session_publishedcontainer_for_user_create(QSpotifySession::instance()->m_sp_session, m_canonicalName.toLatin1().constData());
+ }
+ m_playlistContainer = new QSpotifyPlaylistContainer(pc);
+ connect(m_playlistContainer, SIGNAL(playlistContainerDataChanged()), this, SIGNAL(playlistsChanged()));
+ }
+ return m_playlistContainer;
+}
+
+QSpotifyPlaylist *QSpotifyUser::starredList() const
+{
+ if (!m_starredList) {
+ sp_playlist *sl;
+ if (QSpotifySession::instance()->user() == this) {
+ sl = sp_session_starred_create(QSpotifySession::instance()->m_sp_session);
+ } else {
+ sl = sp_session_starred_for_user_create(QSpotifySession::instance()->m_sp_session, m_canonicalName.toLatin1().constData());
+ }
+ m_starredList = new QSpotifyPlaylist(QSpotifyPlaylist::Starred, sl, false);
+ connect(m_starredList, SIGNAL(playlistDataChanged()), this, SIGNAL(playlistsChanged()));
+ }
+ return m_starredList;
+}
+
+QSpotifyPlaylist *QSpotifyUser::inbox() const
+{
+ if (QSpotifySession::instance()->user() != this)
+ return 0;
+
+ if (!m_inbox) {
+ sp_playlist *in;
+ in = sp_session_inbox_create(QSpotifySession::instance()->m_sp_session);
+ m_inbox = new QSpotifyPlaylist(QSpotifyPlaylist::Inbox, in, false);
+ connect(m_inbox, SIGNAL(playlistDataChanged()), this, SIGNAL(playlistsChanged()));
+ }
+ return m_inbox;
+}
+
+QList<QObject*> QSpotifyUser::playlistsAsQObject() const
+{
+ QList<QSpotifyPlaylist*> pls = playlists();
+ QList<QObject*> list;
+ list.append((QObject*)inbox());
+ list.append((QObject*)starredList());
+ for (int i = 0; i < pls.count(); ++i) {
+ if (pls.at(i)->isLoaded() && sp_playlistcontainer_playlist_type(m_playlistContainer->m_container, i) == SP_PLAYLIST_TYPE_PLAYLIST)
+ list.append((QObject*)(pls[i]));
+ }
+ return list;
+}
+
+QList<QSpotifyPlaylist *> QSpotifyUser::playlists() const
+{
+ return playlistContainer()->playlists();
+}
+
+
+bool QSpotifyUser::createPlaylist(const QString &name)
+{
+ if (name.trimmed().isEmpty())
+ return false;
+
+ QString n = name;
+ if (n.size() > 255)
+ n.resize(255);
+ sp_playlist *pl = sp_playlistcontainer_add_new_playlist(m_playlistContainer->m_container, n.toUtf8().constData());
+ return pl != 0;
+}
+
+bool QSpotifyUser::createPlaylistFromTrack(QSpotifyTrack *track)
+{
+ if (!track)
+ return false;
+
+ sp_playlist *pl = sp_playlistcontainer_add_new_playlist(m_playlistContainer->m_container, track->name().toUtf8().constData());
+ if (pl == 0)
+ return false;
+ sp_playlist_add_tracks(pl, const_cast<sp_track* const*>(&track->m_sp_track), 1, 0, QSpotifySession::instance()->spsession());
+ return true;
+}
+
+bool QSpotifyUser::createPlaylistFromAlbum(QSpotifyAlbumBrowse *album)
+{
+ if (!album || !album->m_albumTracks || album->m_albumTracks->m_tracks.count() < 1)
+ return false;
+
+ QString playlistName = album->album()->artist() + QLatin1String(" - ") + album->album()->name();
+ sp_playlist *pl = sp_playlistcontainer_add_new_playlist(m_playlistContainer->m_container, playlistName.toUtf8().constData());
+ if (pl == 0)
+ return false;
+
+ int c = album->m_albumTracks->m_tracks.count();
+ const sp_track *tracks[c];
+ for (int i = 0; i < c; ++i)
+ tracks[i] = album->m_albumTracks->m_tracks.at(i)->sptrack();
+ sp_playlist_add_tracks(pl, const_cast<sp_track* const*>(tracks), c, 0, QSpotifySession::instance()->spsession());
+ return true;
+}
+
+void QSpotifyUser::removePlaylist(QSpotifyPlaylist *playlist)
+{
+ if (!playlist)
+ return;
+
+ int i = m_playlistContainer->m_playlists.indexOf(playlist);
+ if (i > -1)
+ sp_playlistcontainer_remove_playlist(m_playlistContainer->m_container, i);
+}
+
+bool QSpotifyUser::ownsPlaylist(QSpotifyPlaylist *playlist)
+{
+ if (!playlist)
+ return false;
+ return playlist->owner() == m_canonicalName;
+}
+
+bool QSpotifyUser::canModifyPlaylist(QSpotifyPlaylist *playlist)
+{
+ if (!playlist)
+ return false;
+ return ownsPlaylist(playlist) || playlist->collaborative();
+}
diff --git a/libQtSpotify/qspotifyuser.h b/libQtSpotify/qspotifyuser.h
new file mode 100644
index 0000000..947918b
--- /dev/null
+++ b/libQtSpotify/qspotifyuser.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QSPOTIFYUSER_H
+#define QSPOTIFYUSER_H
+
+#include "qspotifyobject.h"
+
+struct sp_user;
+class QSpotifyPlaylistContainer;
+class QSpotifyPlaylist;
+class QSpotifyTrack;
+class QSpotifyAlbumBrowse;
+
+class QSpotifyUser : public QSpotifyObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString canonicalName READ canonicalName NOTIFY userDataChanged)
+ Q_PROPERTY(QString displayName READ displayName NOTIFY userDataChanged)
+ Q_PROPERTY(QString fullName READ fullName NOTIFY userDataChanged)
+ Q_PROPERTY(QString picture READ picture NOTIFY userDataChanged)
+ Q_PROPERTY(QList<QObject *> playlists READ playlistsAsQObject NOTIFY playlistsChanged)
+public:
+ ~QSpotifyUser();
+
+ bool isLoaded();
+
+ QString canonicalName() const { return m_canonicalName; }
+ QString displayName() const { return m_displayName; }
+ QString fullName() const { return m_fullName; }
+ QString picture() const { return m_picture; }
+
+ QSpotifyPlaylistContainer *playlistContainer() const;
+ QSpotifyPlaylist *starredList() const;
+ QSpotifyPlaylist *inbox() const;
+
+ QList<QSpotifyPlaylist *> playlists() const;
+ QList<QObject *> playlistsAsQObject() const;
+
+ Q_INVOKABLE bool createPlaylist(const QString &name);
+ Q_INVOKABLE bool createPlaylistFromTrack(QSpotifyTrack *track);
+ Q_INVOKABLE bool createPlaylistFromAlbum(QSpotifyAlbumBrowse *album);
+ Q_INVOKABLE void removePlaylist(QSpotifyPlaylist *playlist);
+ Q_INVOKABLE bool ownsPlaylist(QSpotifyPlaylist *playlist);
+ Q_INVOKABLE bool canModifyPlaylist(QSpotifyPlaylist *playlist);
+
+Q_SIGNALS:
+ void userDataChanged();
+ void playlistsChanged();
+
+protected:
+ bool updateData();
+
+private:
+ QSpotifyUser(sp_user *user);
+
+ sp_user *m_sp_user;
+
+ QString m_canonicalName;
+ QString m_displayName;
+ QString m_fullName;
+ QString m_picture;
+
+ mutable QSpotifyPlaylistContainer *m_playlistContainer;
+ mutable QSpotifyPlaylist *m_starredList;
+ mutable QSpotifyPlaylist *m_inbox;
+
+ friend class QSpotifySession;
+ friend class QSpotifyPlaylist;
+};
+
+#endif // QSPOTIFYUSER_H
diff --git a/libQtSpotify/spotify_key.h b/libQtSpotify/spotify_key.h
new file mode 100644
index 0000000..4ecdf3a
--- /dev/null
+++ b/libQtSpotify/spotify_key.h
@@ -0,0 +1,9 @@
+#ifndef SPOTIFY_KEY_H
+#define SPOTIFY_KEY_H
+
+#error
+#error "You need to replace the content of this file with a libspotify API key provided by Spotify."
+#error "Please see https://developer.spotify.com/en/libspotify/application-key/"
+#error
+
+#endif // SPOTIFY_KEY_H
diff --git a/libspotify/.gitignore b/libspotify/.gitignore
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/libspotify/.gitignore
@@ -0,0 +1 @@
+
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..99aa952
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+#include <QtGui/QApplication>
+#include <QtDeclarative>
+#include <MDeclarativeCache>
+
+#include <QtSpotify>
+#include <qspotify_qmlplugin.h>
+
+Q_DECL_EXPORT int main(int argc, char *argv[])
+{
+ QApplication::setOrganizationName("MeeSpot");
+ QApplication::setOrganizationDomain("qt.nokia.com");
+ QApplication::setApplicationName("MeeSpot");
+
+ QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, QLatin1String("/home/user/MyDocs/.meespotconf"));
+
+ QApplication *app = MDeclarativeCache::qApplication(argc, argv);
+ QDeclarativeView *view = MDeclarativeCache::qDeclarativeView();
+
+ registerQmlTypes();
+ view->rootContext()->setContextProperty(QLatin1String("spotifySession"), QSpotifySession::instance());
+ view->engine()->addImageProvider(QLatin1String("spotify"), new QSpotifyImageProvider);
+
+ view->setSource(QUrl("qrc:/qml/main.qml"));
+ view->showFullScreen();
+
+ return app->exec();
+}
diff --git a/qml/AboutDialog.qml b/qml/AboutDialog.qml
new file mode 100644
index 0000000..6087105
--- /dev/null
+++ b/qml/AboutDialog.qml
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Dialog {
+ id: genericDialog
+
+ property string titleText: "About MeeSpot"
+
+ property Style platformStyle: SelectionDialogStyle {}
+
+ //private
+ property bool __drawFooterLine: false
+
+ title: Item {
+ id: header
+ height: genericDialog.platformStyle.titleBarHeight
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+
+ Item {
+ id: labelField
+
+ anchors.fill: parent
+
+ Item {
+ id: labelWrapper
+ anchors.left: labelField.left
+ anchors.right: labelField.left
+
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: genericDialog.platformStyle.titleBarLineMargin
+
+ //anchors.verticalCenter: labelField.verticalCenter
+
+ height: titleLabel.height
+
+ Label {
+ id: titleLabel
+ x: genericDialog.platformStyle.titleBarIndent
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_XLARGE
+ text: genericDialog.titleText
+ }
+
+ }
+ }
+
+ Rectangle {
+ id: headerLine
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.bottom: header.bottom
+
+ height: 1
+
+ color: "#4D4D4D"
+ }
+
+ }
+
+ content: Column {
+ width: parent.width
+
+ Item {
+ width: parent.width
+ height: 38
+ }
+
+ Label {
+ text: "Version 1.1.0"
+ font.pixelSize: UI.FONT_LSMALL
+ }
+
+ Label {
+ text: "Copyright \u00a9 2011 Yoann Lopes"
+ font.pixelSize: UI.FONT_LSMALL
+ }
+
+ Label {
+ text: "Contact: yoann.lopes@gmail.com"
+ font.pixelSize: UI.FONT_LSMALL
+ }
+
+ Item {
+ width: parent.width
+ height: 38
+ }
+
+ Item {
+ id: contentField
+ width: parent.width
+ height: spotifyCoreLogo.height
+
+ Image {
+ id: spotifyCoreLogo
+ source: "images/spotify-core-logo-128x128.png"
+ }
+
+ Label {
+ anchors.verticalCenter: spotifyCoreLogo.verticalCenter
+ anchors.left: spotifyCoreLogo.right
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+ height: parent.height
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_XSMALL
+ color: UI.LIST_SUBTITLE_COLOR_INVERTED
+ text: "This product uses SPOTIFY CORE but is not endorsed, certified or otherwise approved in any way by Spotify. Spotify is the registered trade mark of the Spotify Group."
+ }
+ }
+ }
+
+ buttons: Item {
+ id: buttonColFiller
+ width: parent.width
+ height: childrenRect.height
+
+ anchors.top: parent.top
+
+ Button {
+ id: acceptButton
+ text: "Close"
+ onClicked: accept()
+ __dialogButton: true
+ platformStyle: ButtonStyle {inverted: true}
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.topMargin: 38
+ }
+ }
+
+}
diff --git a/qml/AdvancedTextField.qml b/qml/AdvancedTextField.qml
new file mode 100644
index 0000000..2e8d2b0
--- /dev/null
+++ b/qml/AdvancedTextField.qml
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+
+TextField {
+ id: input
+ property alias showBusy: spinner.running
+ platformStyle: TextFieldStyle {
+ backgroundSelected: "image://theme/" + appWindow.themeColor + "-meegotouch-textedit-background-selected"
+ }
+
+ Image {
+ id: editIcon
+ source: input.text.length > 0 ? "image://theme/icon-m-input-clear" : "image://theme/icon-m-common-search"
+ anchors.right: parent.right
+ anchors.rightMargin: (parent.height - height) / 2
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: iconMouseArea.pressed && input.text.length > 0 ? 0.5 : 1.0
+ visible: !spinner.visible
+ }
+
+ BusyIndicator {
+ id: spinner
+ anchors.right: parent.right
+ anchors.rightMargin: (parent.height - height) / 2
+ anchors.verticalCenter: parent.verticalCenter
+ platformStyle: BusyIndicatorStyle { size: "small"; inverted: false }
+ visible: running
+ }
+
+ MouseArea {
+ id: iconMouseArea
+ anchors.fill: editIcon
+ onClicked: { input.text = ""; input.forceActiveFocus() }
+ z: 2
+ enabled: !spinner.visible
+ }
+}
diff --git a/qml/AlbumDelegate.qml b/qml/AlbumDelegate.qml
new file mode 100644
index 0000000..81b05be
--- /dev/null
+++ b/qml/AlbumDelegate.qml
@@ -0,0 +1,168 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: listItem
+
+ signal clicked
+ signal pressAndHold
+ property alias pressed: mouseArea.pressed
+ property alias name: mainText.text
+ property alias artist: subText.text
+ property alias albumCover: cover.spotifyId
+ property bool available: true
+ property bool showIndex: false
+
+ property int titleSize: UI.LIST_TILE_SIZE
+ property string titleFont: UI.FONT_FAMILY_BOLD
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+
+ property int subtitleSize: UI.LIST_SUBTILE_SIZE
+ property string subtitleFont: UI.FONT_FAMILY_LIGHT
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ height: UI.LIST_ITEM_HEIGHT
+ width: parent.width
+
+ SequentialAnimation {
+ id: backAnimation
+ property bool animEnded: false
+ running: mouseArea.pressed
+
+ ScriptAction { script: backAnimation.animEnded = false }
+ PauseAnimation { duration: 200 }
+ ParallelAnimation {
+ NumberAnimation { target: background; property: "opacity"; to: 0.4; duration: 300 }
+ ColorAnimation { target: mainText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: subText; property: "color"; to: "black"; duration: 300 }
+ NumberAnimation { target: coverContainer; property: "opacity"; to: 0.2; duration: 300 }
+ }
+ PauseAnimation { duration: 100 }
+ ScriptAction { script: { backAnimation.animEnded = true; listItem.pressAndHold(); } }
+ onRunningChanged: {
+ if (!running) {
+ coverContainer.opacity = 1.0
+ mainText.color = listItem.titleColor
+ subText.color = listItem.subtitleColor
+ }
+ }
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : 0.0
+ color: "#22FFFFFF"
+ }
+
+ Label {
+ id: indexText
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: listItem.showIndex ? 48 : 0
+ text: (index + 1) + ". "
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.pixelSize: UI.FONT_SMALL
+ horizontalAlignment: Text.AlignRight
+ visible: listItem.showIndex
+ }
+
+ Rectangle {
+ id: coverContainer
+ width: 64; height: width
+ anchors.left: indexText.right
+ anchors.verticalCenter: parent.verticalCenter
+ color: "#202020"
+ opacity: listItem.available ? 1.0 : 0.3
+
+ SpotifyImage {
+ id: cover
+ anchors.fill: parent
+ }
+ }
+
+ Column {
+ anchors.left: coverContainer.right
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: listItem.available ? 1.0 : 0.3
+
+ Label {
+ id: mainText
+ anchors.left: parent.left
+ anchors.right: parent.right
+ font.family: listItem.titleFont
+ font.weight: Font.Bold
+ font.pixelSize: listItem.titleSize
+ color: listItem.titleColor
+ elide: Text.ElideRight
+ }
+
+ Label {
+ id: subText
+ anchors.left: parent.left
+ anchors.right: parent.right
+ font.family: listItem.subtitleFont
+ font.pixelSize: listItem.subtitleSize
+ font.weight: Font.Light
+ color: listItem.subtitleColor
+ elide: Text.ElideRight
+ visible: text != ""
+ }
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ if (!backAnimation.animEnded)
+ listItem.clicked();
+ }
+ }
+}
diff --git a/qml/AlbumHeader.qml b/qml/AlbumHeader.qml
new file mode 100644
index 0000000..bcdd1a9
--- /dev/null
+++ b/qml/AlbumHeader.qml
@@ -0,0 +1,154 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Column {
+ id: albumHeader
+ signal moreClicked
+ property alias albumName: title.text
+ property alias artistName: artistText.text
+ property alias trackCount: trackCountText.text
+ property alias timing: timingText.text
+ property alias year: yearText.text
+ property alias coverId: cover.spotifyId
+
+ width: parent ? parent.width : 0
+ spacing: UI.MARGIN_XLARGE
+
+ Item {
+ width: parent.width
+ height: UI.HEADER_DEFAULT_HEIGHT_PORTRAIT
+
+ Label {
+ anchors.left: parent.left
+ anchors.right: moreIcon.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.verticalCenter: parent.verticalCenter
+ id: title
+ color: theme.inverted ? UI.COLOR_INVERTED_FOREGROUND : UI.COLOR_FOREGROUND
+ font.pixelSize: UI.FONT_LARGE
+ font.family: UI.FONT_FAMILY_LIGHT
+ elide: Text.ElideRight
+ opacity: moreButton.pressed ? 0.4 : 1.0
+ }
+
+ Image {
+ id: moreIcon
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ source: "image://theme/icon-s-music-video-description"
+ opacity: moreButton.pressed ? 0.4 : 1.0
+
+ MouseArea {
+ id: moreButton
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: { albumHeader.moreClicked(); }
+ }
+ }
+
+ Separator {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: 160
+
+ SpotifyImage {
+ id: cover
+ height: parent.height
+ width: height
+ anchors.left: parent.left
+ }
+
+ Column {
+ id: desc
+ anchors.left: cover.right
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+
+ Label {
+ id: artistText
+ height: 50
+ width: parent.width
+ font.family: UI.FONT_FAMILY_BOLD
+ font.weight: Font.Bold
+ font.pixelSize: UI.FONT_LSMALL
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Label {
+ id: trackCountText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ }
+ Label {
+ id: timingText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ }
+ Label {
+ id: yearText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ }
+ }
+ }
+
+ Separator {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+}
diff --git a/qml/AlbumMenu.qml b/qml/AlbumMenu.qml
new file mode 100644
index 0000000..e00eae0
--- /dev/null
+++ b/qml/AlbumMenu.qml
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+
+MyMenu {
+ id: trackMenu
+
+ property variant albumBrowse: null
+ property bool playVisible: true
+ property bool artistVisible: true
+ property variant playlists: spotifySession.user ? spotifySession.user.playlists : null
+
+ layoutContentHeight: layout.height
+
+ NotificationBanner {
+ id: banner
+ }
+
+ SelectionDialog {
+ id: selectionDialog
+
+ property alias dialogModel: playlistsModel
+
+ ListModel {
+ id: playlistsModel
+ }
+
+ titleText: "Playlists"
+ parent: trackMenu.parent
+ model: playlistsModel
+ onAccepted: {
+ var playlistItem = model.get(selectionDialog.selectedIndex);
+ if (playlistItem.object) {
+ banner.text = "Album added to " + playlistItem.name;
+ playlistItem.object.addAlbum(albumBrowse);
+ } else {
+ if (spotifySession.user.createPlaylistFromAlbum(albumBrowse)) {
+ banner.text = "Album added to new playlist";
+ } else {
+ banner.text = "Could not add album to new playlist";
+ }
+ }
+ banner.show();
+ }
+ }
+
+ MyMenuLayout {
+ id:layout
+
+ MyMenuItem {
+ text: "Play";
+ visible: playVisible
+ onClicked: { albumBrowse.play() }
+ }
+ MyMenuItem {
+ text: "Add to queue";
+ onClicked: { albumBrowse.enqueue() }
+ }
+ MyMenuItem {
+ id: starItem
+ onClicked: {
+ var newstar = !albumBrowse.isStarred ;
+ albumBrowse.isStarred = newstar;
+ if (newstar)
+ banner.text = "Album starred";
+ else
+ banner.text = "Album unstarred";
+ banner.show();
+ }
+ }
+ MyMenuItem {
+ text: "Artist";
+ onClicked: {
+ mainPage.tabs.currentTab.push(Qt.resolvedUrl("ArtistPage.qml"), { artist: albumBrowse.tracks[0].artistObject })
+ }
+ visible: artistVisible
+ }
+ MyMenuItem {
+ text: "Add to playlist";
+ onClicked: { selectionDialog.selectedIndex = -1; selectionDialog.open(); }
+ }
+ }
+
+ onPlaylistsChanged: {
+ selectionDialog.dialogModel.clear();
+
+ if (playlists === null)
+ return;
+
+ for (var i in trackMenu.playlists) {
+ if (trackMenu.playlists[i].type == SpotifyPlaylist.Playlist && spotifySession.user.canModifyPlaylist(trackMenu.playlists[i]))
+ selectionDialog.dialogModel.append({"name": trackMenu.playlists[i].name, "object": trackMenu.playlists[i] })
+ }
+ selectionDialog.dialogModel.append({"name": "New playlist" });
+
+ selectionDialog.model = 0;
+ selectionDialog.model = selectionDialog.dialogModel;
+ }
+
+ onStatusChanged: {
+ if (status == DialogStatus.Opening && albumBrowse) {
+ starItem.text = albumBrowse.isStarred ? "Unstar" : "Star";
+ }
+ }
+}
diff --git a/qml/AlbumPage.qml b/qml/AlbumPage.qml
new file mode 100644
index 0000000..44ed13e
--- /dev/null
+++ b/qml/AlbumPage.qml
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ id: albumPage
+
+ property variant album
+ orientationLock: PageOrientation.LockPortrait
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ onStatusChanged: if (status == PageStatus.Active) browse.album = albumPage.album
+
+ SpotifyAlbumBrowse {
+ id: browse
+ }
+
+ TrackMenu {
+ id: menu
+ deleteVisible: false
+ albumVisible: false
+ }
+
+ AlbumMenu {
+ id: albumMenu
+ playVisible: false
+ artistVisible: !browse.hasMultipleArtists
+ }
+
+ Component {
+ id: albumDelegate
+ AlbumTrackDelegate {
+ name: modelData.name
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ available: modelData.isAvailable
+ onClicked: modelData.play()
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+
+ Component {
+ id: compilationDelegate
+ TrackDelegate {
+ name: modelData.name
+ artistAndAlbum: modelData.artists
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ available: modelData.isAvailable
+ onClicked: modelData.play()
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+
+ ListView {
+ id: tracks
+ anchors.fill: parent
+
+ model: browse.tracks
+ header: AlbumHeader {
+ albumName: album ? album.name : ""
+ artistName: album ? album.artist : ""
+ trackCount: tracks.count > 0 ? (tracks.count + (tracks.count > 1 ? " songs" : " song")) : ""
+ timing: browse.totalDuration > 0 ? spotifySession.formatDuration(browse.totalDuration) : ""
+ year: tracks.count > 0 && album.year > 0 ? album.year : ""
+ coverId: album.coverId
+ onMoreClicked: { albumMenu.albumBrowse = browse; albumMenu.open() }
+ }
+
+ onCountChanged: {
+ delegate = browse.hasMultipleArtists ? compilationDelegate : albumDelegate
+ }
+ }
+
+ ScrollDecorator { flickableItem: tracks }
+}
diff --git a/qml/AlbumTrackDelegate.qml b/qml/AlbumTrackDelegate.qml
new file mode 100644
index 0000000..b2396ca
--- /dev/null
+++ b/qml/AlbumTrackDelegate.qml
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: listItem
+
+ signal clicked
+ signal pressAndHold
+ property alias pressed: mouseArea.pressed
+ property alias name: mainText.text
+ property alias duration: timing.text
+ property bool highlighted: false
+ property bool starred: false
+ property bool available: true
+
+ property color highlightColor: UI.SPOTIFY_COLOR
+
+ property int titleSize: UI.LIST_TILE_SIZE
+ property string titleFont: UI.FONT_FAMILY_BOLD
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+
+ property int subtitleSize: UI.LIST_SUBTILE_SIZE
+ property string subtitleFont: UI.FONT_FAMILY_LIGHT
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ height: UI.LIST_ITEM_HEIGHT_SMALL
+ width: parent.width
+
+ SequentialAnimation {
+ id: backAnimation
+ property bool animEnded: false
+ running: mouseArea.pressed
+
+ ScriptAction { script: backAnimation.animEnded = false }
+ PauseAnimation { duration: 200 }
+ ParallelAnimation {
+ NumberAnimation { target: background; property: "opacity"; to: 0.4; duration: 300 }
+ ColorAnimation { target: mainText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: timing; property: "color"; to: "black"; duration: 300 }
+ NumberAnimation { target: iconItem; property: "opacity"; to: 0.2; duration: 300 }
+ }
+ PauseAnimation { duration: 100 }
+ ScriptAction { script: { backAnimation.animEnded = true; listItem.pressAndHold(); } }
+ onRunningChanged: {
+ if (!running) {
+ iconItem.opacity = 1.0
+ mainText.color = highlighted ? listItem.highlightColor : listItem.titleColor
+ timing.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ }
+ }
+ }
+
+ onHighlightedChanged: {
+ mainText.color = highlighted ? listItem.highlightColor : listItem.titleColor
+ timing.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : 0.0
+ color: "#22FFFFFF"
+ }
+
+ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: listItem.available ? 1.0 : 0.3
+
+ Label {
+ id: mainText
+ anchors.left: parent.left
+ anchors.right: iconItem.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: listItem.titleFont
+ font.weight: Font.Bold
+ font.pixelSize: listItem.titleSize
+ color: highlighted ? listItem.highlightColor : listItem.titleColor
+ elide: Text.ElideRight
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+
+ Image {
+ id: iconItem
+ anchors.right: timing.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: -2
+ width: 34; height: width
+ smooth: true
+ visible: listItem.starred
+ source: "image://theme/icon-m-toolbar-favorite-mark-white"
+ }
+
+ Label {
+ id: timing
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: listItem.subtitleFont
+ font.weight: Font.Light
+ font.pixelSize: listItem.subtitleSize
+ color: highlighted ? listItem.highlightColor : listItem.subtitleColor
+ anchors.right: parent.right
+ visible: text != ""
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ if (!backAnimation.animEnded)
+ listItem.clicked();
+ }
+ }
+}
diff --git a/qml/ArtistDelegate.qml b/qml/ArtistDelegate.qml
new file mode 100644
index 0000000..1be3419
--- /dev/null
+++ b/qml/ArtistDelegate.qml
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: listItem
+
+ signal clicked
+ property alias pressed: mouseArea.pressed
+ property alias name: mainText.text
+ property alias portrait: portraitImage.spotifyId
+ property bool showIndex: false
+
+ property int titleSize: UI.LIST_TILE_SIZE
+ property string titleFont: UI.FONT_FAMILY_BOLD
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+
+ property int subtitleSize: UI.LIST_SUBTILE_SIZE
+ property string subtitleFont: UI.FONT_FAMILY_LIGHT
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ height: UI.LIST_ITEM_HEIGHT
+ width: parent.width
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : 0.0
+ color: "#22FFFFFF"
+ }
+
+ Label {
+ id: indexText
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: listItem.showIndex ? 48 : 0
+ text: (index + 1) + ". "
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.pixelSize: UI.FONT_SMALL
+ horizontalAlignment: Text.AlignRight
+ visible: listItem.showIndex
+ }
+
+ Rectangle {
+ id: portraitContainer
+ width: 64; height: width
+ anchors.left: indexText.right
+ anchors.verticalCenter: parent.verticalCenter
+ color: "#202020"
+
+ SpotifyImage {
+ id: portraitImage
+ anchors.fill: parent
+ defaultImage: "image://theme/icon-m-toolbar-contact-selected"
+ }
+ }
+
+ Label {
+ id: mainText
+ anchors.left: portraitContainer.right
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: listItem.titleFont
+ font.weight: Font.Bold
+ font.pixelSize: listItem.titleSize
+ color: listItem.titleColor
+ elide: Text.ElideRight
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: listItem.clicked()
+ }
+}
diff --git a/qml/ArtistHeader.qml b/qml/ArtistHeader.qml
new file mode 100644
index 0000000..739a3eb
--- /dev/null
+++ b/qml/ArtistHeader.qml
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Column {
+ id: albumHeader
+ property alias albumCount: albumCountText.text
+ property alias singleCount: singleCountText.text
+ property alias compilationCount: compilationsText.text
+ property alias appearsOnCount: appearsOnText.text
+ property alias artistPictureId: coverImage.spotifyId
+
+ property bool busy: false
+
+ width: parent ? parent.width : 0
+ spacing: UI.MARGIN_XLARGE
+
+ Item {
+ id: descContainer
+ width: parent.width
+ height: 160
+
+ Rectangle {
+ id: cover
+ height: parent.height
+ width: height
+ anchors.left: parent.left
+ color: "#202020"
+ visible: albumCount.length > 0 || singleCount.length > 0 || compilationCount.length > 0
+ SpotifyImage {
+ id: coverImage
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectCrop
+ clip: true
+ }
+ Image {
+ visible: coverImage.source == ""
+ anchors.fill: parent
+ smooth: true
+ source: "image://theme/icon-l-contact-avatar-placeholder"
+ }
+ }
+
+ Column {
+ id: desc
+ anchors.left: cover.right
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+
+ Label {
+ id: albumCountText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ visible: text.length > 0
+ }
+ Label {
+ id: singleCountText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ visible: text.length > 0
+ }
+ Label {
+ id: compilationsText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ visible: text.length > 0
+ }
+ Label {
+ id: appearsOnText
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ visible: text.length > 0
+ }
+ }
+ }
+
+ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 1
+ }
+}
diff --git a/qml/ArtistPage.qml b/qml/ArtistPage.qml
new file mode 100644
index 0000000..06c2aef
--- /dev/null
+++ b/qml/ArtistPage.qml
@@ -0,0 +1,263 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ id: artistPage
+
+ property variant artist
+ orientationLock: PageOrientation.LockPortrait
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ onStatusChanged: if (status == PageStatus.Active) browse.artist = artistPage.artist
+
+ SpotifyArtistBrowse {
+ id: browse
+ }
+
+ TrackMenu {
+ id: menu
+ deleteVisible: false
+ }
+
+ AlbumMenu {
+ id: albumMenu
+ playVisible: true
+ artistVisible: false
+ albumBrowse: SpotifyAlbumBrowse {
+ id: menuAlbumBrowse
+ onTracksChanged: albumMenu.open()
+ }
+ }
+
+ Column {
+ id: header
+ width: parent.width
+ anchors.top: parent.top
+
+ Selector {
+ id: selector
+ title: artist ? artist.name : ""
+ titleFontFamily: UI.FONT_FAMILY_LIGHT
+ titleFontWeight: Font.Light
+ titleFontSize: UI.FONT_LARGE
+ selectedIndex: 1
+ model: ListModel {
+ ListElement { name: "Top hits" }
+ ListElement { name: "Music" }
+ ListElement { name: "Biography" }
+ ListElement { name: "Related artists" }
+ }
+ }
+
+ Separator {
+ width: parent.width
+ }
+ }
+
+ Item {
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+ anchors.topMargin: UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ clip: true
+ visible: !browse.busy
+
+ ListView {
+ id: artistView
+ anchors.fill: parent
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ Component {
+ id: trackComponent
+ TrackDelegate {
+ name: modelData.name
+ artistAndAlbum: modelData.album
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ coverId: modelData.albumCoverId
+ showIndex: true
+ available: modelData.isAvailable
+ onClicked: modelData.play()
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+
+ Component {
+ id: albumComponent
+ AlbumDelegate {
+ name: modelData.name
+ artist: modelData.sectionType == "Appears on" ? modelData.artist : (modelData.year > 0 ? modelData.year : "")
+ albumCover: modelData.coverId
+ onClicked: {
+ mainPage.tabs.currentTab.push(Qt.resolvedUrl("AlbumPage.qml"), { album: modelData })
+ }
+ onPressAndHold: {
+ menuAlbumBrowse.album = modelData;
+ if (menuAlbumBrowse.totalDuration > 0)
+ albumMenu.open()
+ }
+ }
+ }
+
+ Component {
+ id: artistComponent
+ ArtistDelegate {
+ name: modelData.name
+ portrait: modelData.pictureId
+ onClicked: { mainPage.tabs.currentTab.push(Qt.resolvedUrl("ArtistPage.qml"), { artist: modelData }) }
+ }
+ }
+
+ Component {
+ id: bioComponent
+ Label {
+ width: parent ? parent.width : 0
+ height: paintedHeight + UI.MARGIN_XLARGE
+ text: "<style type=text/css> a { text-decoration: none; color:" + color + "} </style>" + modelData
+ textFormat: Text.RichText
+ wrapMode: Text.WordWrap
+ font.pixelSize: UI.FONT_LSMALL
+ }
+ }
+
+ Component {
+ id: headerComponent
+ ArtistHeader {
+ albumCount: browse.albumCount > 0 ? (browse.albumCount + (browse.albumCount > 1 ? " albums" : " album")) : ""
+ singleCount: browse.singleCount > 0 ? (browse.singleCount + (browse.singleCount > 1 ? " singles" : " single")) : ""
+ compilationCount: browse.compilationCount > 0 ? (browse.compilationCount + (browse.compilationCount > 1 ? " compilations" : " compilation")) : ""
+ appearsOnCount: browse.appearsOnCount > 0 ? ("Appears on " + browse.appearsOnCount + (browse.appearsOnCount > 1 ? " other albums" : " other album")) : ""
+ artistPictureId: artist.pictureId.length > 0 ? artist.pictureId : browse.pictureId
+ busy: browse.busy
+ }
+ }
+
+ Component {
+ id: sectionComponent
+ Item {
+ width: parent.width
+ height: sectionText.height
+
+ Separator {
+ anchors.left: parent.left
+ anchors.right: sectionText.left
+ anchors.rightMargin: UI.MARGIN_XLARGE * 2
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Label {
+ id: sectionText
+ anchors.right: parent.right
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: UI.FONT_FAMILY_BOLD
+ font.pixelSize: UI.FONT_SMALL
+ font.weight: Font.Bold
+ color: "#808080"
+ text: section
+ }
+ }
+ }
+
+ section.property: "sectionType"
+
+ Connections {
+ target: selector
+ onSelectedIndexChanged: artistView.updateResults()
+ }
+
+ Connections {
+ target: browse
+ onDataChanged: artistView.updateResults()
+ }
+
+ function updateResults() {
+ artistView.model = 0
+ artistView.delegate = null
+ artistView.header = null
+ artistView.section.delegate = null
+ if (selector.selectedIndex === 0) {
+ artistView.model = browse.topTracks
+ artistView.delegate = trackComponent
+ } else if (selector.selectedIndex == 1) {
+ artistView.header = headerComponent
+ artistView.section.delegate = sectionComponent
+ artistView.delegate = albumComponent
+ artistView.model = browse.albums
+ } else if (selector.selectedIndex == 2) {
+ artistView.model = browse.biography
+ artistView.delegate = bioComponent
+ } else if (selector.selectedIndex == 3) {
+ artistView.model = browse.similarArtists
+ artistView.delegate = artistComponent
+ }
+ artistView.positionViewAtBeginning()
+ }
+ }
+
+ ScrollDecorator { flickableItem: artistView }
+ }
+
+ Label {
+ anchors.centerIn: parent
+ visible: selector.selectedIndex == 2 && artistView.count === 0 && !browse.busy
+ text: "No biography available"
+ }
+
+ BusyIndicator {
+ id: busy
+ running: browse.busy
+ visible: running
+ anchors.centerIn: parent
+ platformStyle: BusyIndicatorStyle { size: "medium" }
+ }
+}
diff --git a/qml/FullControls.qml b/qml/FullControls.qml
new file mode 100644
index 0000000..f0346da
--- /dev/null
+++ b/qml/FullControls.qml
@@ -0,0 +1,468 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+import QtSpotify 1.0
+
+Column {
+ id: fullControls
+ width: parent.width
+ spacing: UI.MARGIN_XLARGE
+
+ property bool albumRequested: false
+ property bool artistRequested: false
+
+ Connections {
+ target: spotifySession
+ onCurrentTrackChanged: {
+ if (infoDialog.status != DialogStatus.Closed)
+ infoDialog.reject()
+ }
+ }
+
+ Connections {
+ target: player
+ onInOpenPositionChanged: {
+ if (!player.inOpenPosition) {
+ if (albumRequested) {
+ albumRequested = false;
+ mainPage.tabs.currentTab.push(Qt.resolvedUrl("AlbumPage.qml"), { album: spotifySession.currentTrack.albumObject })
+ }
+ if (artistRequested) {
+ artistRequested = false;
+ mainPage.tabs.currentTab.push(Qt.resolvedUrl("ArtistPage.qml"), { artist: spotifySession.currentTrack.artistObject })
+ }
+ }
+ }
+ }
+
+ SelectionDialog {
+ id: infoDialog
+ parent: player.parent
+ titleText: "Choose"
+ model: ListModel {
+ ListElement { name: "Album" }
+ ListElement { name: "Artist" }
+ }
+ onAccepted: {
+ player.openRequested = false;
+ if (model.get(infoDialog.selectedIndex).name == "Album") {
+ albumRequested = true;
+ } else {
+ artistRequested = true;
+ }
+ }
+ }
+
+ Flipable {
+ id: flipable
+ width: parent.width
+ height: width
+
+ property bool flipped: false
+
+ transform: Rotation {
+ id: rotation
+ origin.x: flipable.width/2
+ origin.y: flipable.height/2
+ axis.x: 0; axis.y: 1; axis.z: 0
+ angle: 0
+ }
+
+ states: State {
+ name: "back"
+ PropertyChanges { target: rotation; angle: 180 }
+ when: flipable.flipped
+ }
+
+ transitions: Transition {
+ NumberAnimation { target: rotation; property: "angle"; duration: 350 }
+ }
+
+ front: Rectangle {
+ anchors.fill: parent
+ color: "#202020"
+
+ ListView {
+ id: coverList
+ anchors.fill: parent
+ boundsBehavior: Flickable.StopAtBounds
+ orientation: ListView.Horizontal
+ snapMode: ListView.SnapOneItem
+ highlightRangeMode: ListView.StrictlyEnforceRange
+ cacheBuffer: width * 2
+ highlightMoveSpeed: -1
+ highlightMoveDuration: 0
+ clip: true
+ pressDelay: 90
+
+ currentIndex: spotifySession.playQueue.currentIndex
+ onMovingChanged: {
+ if (!moving)
+ spotifySession.playQueue.selectTrack(model[currentIndex])
+ }
+
+ model: spotifySession.playQueue.tracks
+ delegate: SpotifyImage {
+ width: coverList.width
+ height: width
+ spotifyId: modelData.albumCoverId
+ fillMode: Image.PreserveAspectCrop
+ clip: true
+ smooth: true
+ opacity: imageMouseArea.pressed ? 0.4 : 1.0
+
+ MouseArea {
+ id: imageMouseArea
+ anchors.fill: parent
+ onClicked: flipable.flipped = true
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: detailsColumn.height + UI.MARGIN_XLARGE
+ color: moreMouseArea.pressed ? "#DD202020" : "#BA202020"
+
+ Column {
+ id: detailsColumn
+ anchors.left: parent.left
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 8
+
+ Label {
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.weight: Font.Bold
+ font.pixelSize: UI.FONT_DEFAULT
+ color: UI.COLOR_INVERTED_FOREGROUND
+ elide: Text.ElideRight
+ opacity: details.opacity
+ text: spotifySession.currentTrack ? spotifySession.currentTrack.name : ""
+ }
+
+ Item {
+ id: details
+ width: parent.width
+ height: column.height
+ opacity: moreMouseArea.pressed ? 0.4 : 1.0
+ Column {
+ id: column
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.right: moreIcon.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ Label {
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_LSMALL
+ color: UI.LIST_SUBTITLE_COLOR_INVERTED
+ elide: Text.ElideRight
+ anchors.left: parent.left
+ anchors.right: parent.right
+ text: spotifySession.currentTrack ? spotifySession.currentTrack.artists : ""
+ }
+ Label {
+ width: parent.width
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_LSMALL
+ color: UI.LIST_SUBTITLE_COLOR_INVERTED
+ elide: Text.ElideRight
+ anchors.left: parent.left
+ anchors.right: parent.right
+ text: spotifySession.currentTrack ? spotifySession.currentTrack.album : ""
+ }
+ }
+ Image {
+ id: moreIcon
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ source: "image://theme/icon-s-description-inverse"
+ visible: !spotifySession.offlineMode
+ }
+ MouseArea {
+ id: moreMouseArea
+ anchors.fill: parent
+ onClicked: { infoDialog.selectedIndex = -1; infoDialog.open(); }
+ enabled: moreIcon.visible
+ }
+ }
+ }
+ }
+ }
+
+ back: Item {
+ anchors.fill: parent
+ clip: true
+
+ ViewHeader {
+ id: queueHeader
+ anchors.left: parent.left
+ anchors.right: parent.right
+ contentMargins: UI.MARGIN_XLARGE
+ text: "Play queue"
+ color: "black"
+ contentOpacity: queueHeaderMouse.pressed ? 0.4 : 1.0
+ z: 500
+
+ MouseArea {
+ id: queueHeaderMouse
+ anchors.fill: parent
+ onClicked: flipable.flipped = false
+ }
+ }
+
+ ListView {
+ id: queueList
+ anchors.top: queueHeader.bottom
+ anchors.topMargin: UI.MARGIN_XLARGE
+ anchors.left: parent.left
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.bottom: parent.bottom
+ cacheBuffer: UI.LIST_ITEM_HEIGHT
+ model: spotifySession.playQueue.tracks
+ delegate: TrackDelegate {
+ property bool isExplicit: spotifySession.playQueue.isExplicitTrack(index)
+ name: modelData.name
+ backgroundOpacity: isExplicit ? 0.7 : 0.0
+ titleColor: isExplicit ? "#f5e8b9" : UI.LIST_TITLE_COLOR_INVERTED
+ subtitleColor: isExplicit ? "#e0d3a5" : UI.LIST_SUBTITLE_COLOR_INVERTED
+ artistAndAlbum: modelData.artists + " | " + modelData.album
+ duration: modelData.duration
+ highlighted: index == spotifySession.playQueue.currentIndex
+ onClicked: if (!highlighted) spotifySession.playQueue.selectTrack(modelData)
+ pressAndHoldEnabled: false
+ }
+
+ Connections {
+ target: spotifySession.playQueue
+ onTracksChanged: queueList.positionViewAtIndex(spotifySession.playQueue.currentIndex, ListView.Center)
+ }
+ }
+
+ ScrollDecorator {
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ flickableItem: queueList
+ }
+ }
+ }
+
+ Column {
+ anchors.left: parent.left
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.right: parent.right
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ spacing: UI.MARGIN_XLARGE
+
+ Item {
+ id: controls
+ width: parent.width
+ height: 90
+
+ Image {
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ source: "image://theme/icon-m-toolbar-mediacontrol-previous-white"
+ opacity: previous.pressed ? 0.4 : 1.0
+
+ MouseArea {
+ id: previous
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: spotifySession.playPrevious()
+ }
+ }
+ Image {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ source: spotifySession.isPlaying ? "image://theme/icon-m-toolbar-mediacontrol-pause-white"
+ : "image://theme/icon-m-toolbar-mediacontrol-play-white"
+ opacity: play.pressed ? 0.4 : 1.0
+
+ MouseArea {
+ id: play
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: spotifySession.isPlaying ? spotifySession.pause() : spotifySession.resume()
+ }
+ }
+ Image {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ source: "image://theme/icon-m-toolbar-mediacontrol-next-white"
+ opacity: next.pressed ? 0.4 : 1.0
+
+ MouseArea {
+ id: next
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: spotifySession.playNext()
+ }
+ }
+ }
+
+ Column {
+ id: seeker
+ width: parent.width
+ spacing: 8
+
+ Slider {
+ id: slider
+ width: parent.width
+ minimumValue: 0
+ maximumValue: spotifySession.currentTrack ? spotifySession.currentTrack.durationMs : 0
+ stepSize: 1000
+ valueIndicatorVisible: spotifySession.currentTrack ? true : false
+ valueIndicatorMargin : 40
+ enabled: spotifySession.currentTrack ? true : false
+ platformStyle: SliderStyle {
+ grooveItemElapsedBackground: "qrc:/qml/images/meegotouch-slider-elapsed-background-horizontal.png"
+ handleBackground: "qrc:/qml/images/meegotouch-slider-handle-background-horizontal.png"
+ handleBackgroundPressed: "qrc:/qml/images/meegotouch-slider-handle-background-pressed-horizontal.png"
+ mouseMarginBottom: -25
+ mouseMarginLeft: -25
+ mouseMarginRight: -25
+ mouseMarginTop: -25
+ }
+
+ function formatValue(v) {
+ return spotifySession.formatDuration(v);
+ }
+
+ onPressedChanged: {
+ if (!slider.pressed) {
+ spotifySession.seek(slider.value)
+ }
+ }
+
+ Connections {
+ target: spotifySession
+ onCurrentTrackPositionChanged: {
+ if (!slider.pressed)
+ slider.value = spotifySession.currentTrackPosition;
+ }
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: trackPos.height + 8
+
+ Label {
+ id: trackPos
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_LSMALL
+ color: UI.LIST_SUBTITLE_COLOR_INVERTED
+ anchors.left: parent.left
+ anchors.leftMargin: 5
+ anchors.top: parent.top
+ text: slider.valueIndicatorText
+ visible: spotifySession.currentTrack ? true : false
+ }
+ Label {
+ font.family: UI.FONT_FAMILY
+ font.pixelSize: UI.FONT_LSMALL
+ color: UI.LIST_SUBTITLE_COLOR_INVERTED
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+ anchors.top: parent.top
+ text: spotifySession.currentTrack ? spotifySession.currentTrack.duration : ""
+ }
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: favIcon.height
+
+ Image {
+ id: favIcon
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: enabled ? (starArea.pressed ? 0.4 : 1.0) : 0.2
+ source: spotifySession.currentTrack ? (spotifySession.currentTrack.isStarred ? "image://theme/icon-m-toolbar-favorite-mark-white"
+ : "image://theme/icon-m-toolbar-favorite-unmark-white")
+ : "image://theme/icon-m-toolbar-favorite-unmark-white";
+ enabled: !spotifySession.offlineMode
+
+ MouseArea {
+ id: starArea
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: spotifySession.currentTrack.isStarred = !spotifySession.currentTrack.isStarred
+ }
+ }
+ Image {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ source: spotifySession.shuffle ? "images/icon-m-toolbar-shuffle-white-selected.png" : "image://theme/icon-m-toolbar-shuffle-white";
+ opacity: shuffleArea.pressed ? 0.4 : 1.0
+ MouseArea {
+ id: shuffleArea
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: spotifySession.shuffle = !spotifySession.shuffle
+ }
+ }
+ Image {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ source: spotifySession.repeat ? "images/icon-m-toolbar-repeat-white-selected.png" : "image://theme/icon-m-toolbar-repeat-white";
+ opacity: repeatArea.pressed ? 0.4 : 1.0
+ MouseArea {
+ id: repeatArea
+ anchors.fill: parent
+ anchors.margins: -15
+ onClicked: spotifySession.repeat = !spotifySession.repeat
+ }
+ }
+ }
+ }
+}
diff --git a/qml/HeaderSearchField.qml b/qml/HeaderSearchField.qml
new file mode 100644
index 0000000..3713057
--- /dev/null
+++ b/qml/HeaderSearchField.qml
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import "UIConstants.js" as UI
+import com.meego 1.0
+
+Item {
+ id: root
+
+ property alias showDuration: hideTimer.interval
+ property alias text: searchField.text
+ signal returnPressed
+
+ width: parent.width
+ state: "hidden"
+
+ function show() {
+ root.state = "visible"
+ hideTimer.restart();
+ }
+
+ Timer {
+ id: hideTimer
+ interval: 4500
+ repeat: false
+ onTriggered: root.state = "hidden"
+ }
+
+ AdvancedTextField {
+ id: searchField
+ placeholderText: "Search"
+ width: parent.width
+ y: UI.MARGIN_XLARGE
+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
+ platformSipAttributes: SipAttributes {
+ actionKeyLabel: "Close"
+ actionKeyEnabled: true
+ }
+ Keys.onReturnPressed: { root.returnPressed() }
+
+ onActiveFocusChanged: {
+ if (activeFocus)
+ hideTimer.stop();
+ else {
+ if (text.length === 0)
+ hideTimer.restart();
+ }
+ }
+ }
+
+ Separator {
+ id: separator
+ width: parent.width
+ anchors.bottom: parent.bottom
+ }
+
+ states: [
+ State {
+ name: "hidden"
+ PropertyChanges {
+ target: searchField
+ opacity: 0.0
+ }
+ PropertyChanges {
+ target: separator
+ opacity: 0.0
+ }
+ PropertyChanges {
+ target: root
+ height: 0
+ }
+ },
+ State {
+ name: "visible"
+ PropertyChanges {
+ target: searchField
+ opacity: 1.0
+ }
+ PropertyChanges {
+ target: separator
+ opacity: 1.0
+ }
+ PropertyChanges {
+ target: root
+ height: searchField.height + UI.MARGIN_XLARGE * 2 + separator.height
+ }
+ }
+
+ ]
+
+ transitions: [
+ Transition {
+ to: "hidden"
+ SequentialAnimation {
+ NumberAnimation { target: searchField; properties: "opacity"; duration: 300 }
+ NumberAnimation { target: root; properties: "height"; duration: 300 }
+ PropertyAction { target: separator; properties: "opacity" }
+ }
+ },
+ Transition {
+ to: "visible"
+ SequentialAnimation {
+ PropertyAction { target: separator; properties: "opacity" }
+ PropertyAction { target: root; properties: "height" }
+ NumberAnimation { target:searchField; properties: "opacity"; duration: 300 }
+ }
+ }
+ ]
+}
diff --git a/qml/InboxTrackDelegate.qml b/qml/InboxTrackDelegate.qml
new file mode 100644
index 0000000..bf3be42
--- /dev/null
+++ b/qml/InboxTrackDelegate.qml
@@ -0,0 +1,221 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: listItem
+
+ signal clicked
+ signal pressAndHold
+ property alias pressed: mouseArea.pressed
+ property alias name: mainText.text
+ property alias artistAndAlbum: subText.text
+ property alias creatorAndDate: thirdText.text
+ property alias duration: timing.text
+ property bool highlighted: false
+ property bool starred: false
+ property bool available: true
+ property bool seen: true
+ property bool pressAndHoldEnabled: true
+
+ property color highlightColor: UI.SPOTIFY_COLOR
+
+ property int titleSize: UI.LIST_TILE_SIZE
+ property string titleFont: UI.FONT_FAMILY_BOLD
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+
+ property int subtitleSize: UI.LIST_SUBTILE_SIZE
+ property string subtitleFont: UI.FONT_FAMILY_LIGHT
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ property real backgroundOpacity: 0.0
+
+ height: UI.LIST_ITEM_HEIGHT + thirdText.height
+ width: parent.width
+
+ SequentialAnimation {
+ id: backAnimation
+ property bool animEnded: false
+ running: mouseArea.pressed && listItem.pressAndHoldEnabled
+
+ ScriptAction { script: backAnimation.animEnded = false }
+ PauseAnimation { duration: 200 }
+ ParallelAnimation {
+ NumberAnimation { target: background; property: "opacity"; to: 0.4; duration: 300 }
+ ColorAnimation { target: mainText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: subText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: thirdText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: timing; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: seenMarker; property: "color"; to: "black"; duration: 300 }
+ NumberAnimation { target: iconItem; property: "opacity"; to: 0.2; duration: 300 }
+ }
+ PauseAnimation { duration: 100 }
+ ScriptAction { script: { backAnimation.animEnded = true; listItem.pressAndHold(); } }
+ onRunningChanged: {
+ if (!running) {
+ iconItem.opacity = 1.0
+ mainText.color = highlighted ? listItem.highlightColor : listItem.titleColor
+ subText.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ thirdText.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ timing.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ seenMarker.color = UI.SPOTIFY_COLOR
+ }
+ }
+ }
+
+ onHighlightedChanged: {
+ mainText.color = highlighted ? listItem.highlightColor : listItem.titleColor
+ subText.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ timing.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ thirdText.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : backgroundOpacity
+ color: "#22FFFFFF"
+ }
+
+ Rectangle {
+ id: seenMarker
+ visible: !listItem.seen
+ anchors.verticalCenter: parent.verticalCenter
+ height: listItem.height - UI.MARGIN_XLARGE - 8
+ width: 6
+ color: UI.SPOTIFY_COLOR
+ anchors.left: parent.left
+ anchors.leftMargin: -UI.MARGIN_XLARGE / 2 - width / 2 - 1
+ }
+
+ Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: listItem.available ? 1.0 : 0.3
+
+ Item {
+ height: mainText.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Label {
+ id: mainText
+ anchors.left: parent.left
+ anchors.right: iconItem.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ font.family: listItem.titleFont
+ font.weight: Font.Bold
+ font.pixelSize: listItem.titleSize
+ color: highlighted ? listItem.highlightColor : listItem.titleColor
+ elide: Text.ElideRight
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ Image {
+ id: iconItem
+ anchors.right: parent.right
+ anchors.bottom: mainText.bottom
+ anchors.bottomMargin: 2
+ width: 34; height: width
+ smooth: true
+ visible: listItem.starred
+ source: "image://theme/icon-m-toolbar-favorite-mark-white"
+ }
+ }
+
+ Item {
+ height: subText.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Label {
+ id: subText
+ anchors.left: parent.left
+ anchors.right: timing.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ font.family: listItem.subtitleFont
+ font.pixelSize: listItem.subtitleSize
+ font.weight: Font.Light
+ color: highlighted ? listItem.highlightColor : listItem.subtitleColor
+ elide: Text.ElideRight
+ visible: text != ""
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ Label {
+ id: timing
+ font.family: listItem.subtitleFont
+ font.weight: Font.Light
+ font.pixelSize: listItem.subtitleSize
+ color: highlighted ? listItem.highlightColor : listItem.subtitleColor
+ anchors.right: parent.right
+ visible: text != ""
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ }
+
+ Label {
+ id: thirdText
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 30
+ verticalAlignment: Text.AlignBottom
+ font.family: listItem.subtitleFont
+ font.pixelSize: UI.FONT_SMALL
+ font.weight: Font.Light
+ color: highlighted ? listItem.highlightColor : listItem.subtitleColor
+ elide: Text.ElideRight
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ if (!backAnimation.animEnded)
+ listItem.clicked();
+ }
+ }
+}
diff --git a/qml/LoginPage.qml b/qml/LoginPage.qml
new file mode 100644
index 0000000..29d466d
--- /dev/null
+++ b/qml/LoginPage.qml
@@ -0,0 +1,216 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ orientationLock: PageOrientation.LockPortrait
+
+ NotificationBanner {
+ id: errorBanner
+ }
+
+ Connections {
+ target: spotifySession
+ onConnectionErrorChanged: {
+ if (spotifySession.connectionError != SpotifySession.Ok) {
+ errorBanner.text = spotifySession.connectionErrorMessage;
+ errorBanner.show();
+ }
+ }
+ }
+
+ Image {
+ id: logo
+ source: "images/meespot-logo.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.topMargin: username.activeFocus || password.activeFocus ? -25 : 100
+ }
+
+ Column {
+ id: fields
+ visible: !spotifySession.pendingConnectionRequest && spotifySession.connectionStatus == SpotifySession.LoggedOut
+ spacing: UI.DEFAULT_MARGIN
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: UI.PADDING_XXLARGE
+ anchors.rightMargin: UI.PADDING_XXLARGE
+ anchors.top: logo.bottom
+
+ TextField {
+ id: username
+ placeholderText: "Username"
+ width: parent.width
+ platformStyle: TextFieldStyle {
+ backgroundSelected: "image://theme/" + appWindow.themeColor + "-meegotouch-textedit-background-selected"
+ }
+ platformSipAttributes: SipAttributes {
+ actionKeyLabel: (username.text.length > 0 && password.text.length > 0) ? "Log in" : "Next"
+ actionKeyEnabled: true
+ }
+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
+ Keys.onReturnPressed: {
+ if (username.text.length > 0 && password.text.length > 0 && termsCheck.checked)
+ login();
+ else
+ password.forceActiveFocus();
+ }
+ }
+ TextField {
+ id: password
+ placeholderText: "Password"
+ echoMode: TextInput.Password
+ width: parent.width
+ platformStyle: TextFieldStyle {
+ backgroundSelected: "image://theme/" + appWindow.themeColor + "-meegotouch-textedit-background-selected"
+ }
+ platformSipAttributes: SipAttributes {
+ actionKeyLabel: (username.text.length > 0 && password.text.length > 0 && termsCheck.checked) ? "Log in" : "Next"
+ actionKeyEnabled: true
+ }
+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
+ Keys.onReturnPressed: {
+ if (username.text.length > 0 && password.text.length > 0 && termsCheck.checked)
+ login();
+ else
+ username.forceActiveFocus();
+ }
+ }
+
+ Item {
+ height: termsText.height
+ width: parent.width
+
+ CheckBox {
+ id: termsCheck
+ anchors.verticalCenter: parent.verticalCenter
+ platformStyle: CheckBoxStyle {
+ backgroundSelected: "image://theme/" + appWindow.themeColor + "-meegotouch-button-checkbox-inverted-background-selected"
+ backgroundPressed: "image://theme/" + appWindow.themeColor + "-meegotouch-button-checkbox-inverted-background-pressed"
+ backgroundDisabled: "image://theme/" + appWindow.themeColor + "-meegotouch-button-checkbox-inverted-background-disabled"
+ }
+ }
+ Label {
+ id: termsText
+ anchors.left: termsCheck.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ text: "<style type=text/css> a { text-decoration: underline; color:" + UI.SPOTIFY_COLOR + "} </style>I have read and accepted the Spotify® <a href='http://www.spotify.com/legal/end-user-agreement/'>Terms and Conditions of Use</a> and <a href='http://www.spotify.com/legal/mobile-terms-and-conditions/'>Mobile Terms of Use</a>."
+ wrapMode: Text.WordWrap
+ font.pixelSize: UI.FONT_LSMALL
+ onLinkActivated: Qt.openUrlExternally(link)
+ }
+ }
+
+ Item {
+ height: UI.DEFAULT_MARGIN
+ width: 1
+ }
+ Button {
+ id: button
+ text: "Log in"
+ platformStyle: ButtonStyle {
+ pressedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-pressed" + (position ? "-" + position : "")
+ disabledBackground: "image://theme/" + (position ? appWindow.themeColor + "-" : "") + "meegotouch-button-inverted-background-disabled" + (position ? "-" + position : "")
+ checkedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-selected" + (position ? "-" + position : "")
+ checkedDisabledBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-disabled-selected" + (position ? "-" + position : "")
+ }
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: UI.PADDING_XXLARGE
+ anchors.rightMargin: UI.PADDING_XXLARGE
+ enabled: username.text.length > 0 && password.text.length > 0 && termsCheck.checked
+
+ onClicked: {
+ login();
+ }
+ }
+
+ Item {
+ height: UI.DEFAULT_MARGIN * 3
+ width: 1
+ }
+
+ Label {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ font.pixelSize: UI.FONT_LSMALL
+ onLinkActivated: Qt.openUrlExternally(link)
+ horizontalAlignment: Text.AlignHCenter
+ text: "<style type=text/css> a { text-decoration: underline; color:" + UI.SPOTIFY_COLOR + "} </style>Need a Spotify® Premium account?<br>Get one at <a href='http://www.spotify.com'>www.spotify.com</a>."
+ }
+ }
+
+ function login() {
+ spotifySession.login(username.text, password.text);
+ password.text = ""
+ }
+
+ Column {
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: UI.DEFAULT_MARGIN * 2
+ visible: !fields.visible
+ spacing: UI.DEFAULT_MARGIN * 2
+
+ Label {
+ id: loggingText
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "Logging in"
+
+ Connections {
+ target: spotifySession
+ onLoggingIn: loggingText.text = "Logging in"
+ onLoggingOut: loggingText.text = "Logging out"
+ }
+ }
+
+ BusyIndicator {
+ anchors.horizontalCenter: parent.horizontalCenter
+ platformStyle: BusyIndicatorStyle { size: "medium" }
+ running: parent.visible
+ }
+ }
+}
diff --git a/qml/MainPage.qml b/qml/MainPage.qml
new file mode 100644
index 0000000..cfe6302
--- /dev/null
+++ b/qml/MainPage.qml
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ id: mainPage
+ tools: player.state == "open" ? null : mainTools
+ orientationLock: PageOrientation.LockPortrait
+
+ property alias tabs: tabGroup
+ property alias searchTabAlias: searchTab
+
+ NotificationBanner {
+ id: errorBanner
+ }
+
+ Connections {
+ target: spotifySession
+ onConnectionErrorChanged: {
+ if (spotifySession.connectionError != SpotifySession.Ok) {
+ errorBanner.text = spotifySession.connectionErrorMessage;
+ errorBanner.show();
+ }
+ }
+ onPlayTokenLost: {
+ if (spotifySession.isPlaying) {
+ errorBanner.text = "Playback has been paused because your account is used somewhere else";
+ errorBanner.show();
+ }
+ }
+ }
+
+ TabGroup {
+ id: tabGroup
+ enabled: !currentTab.busy
+ y: player.hidden ? 0 : screen.currentOrientation == Screen.Portrait ? UI.HEADER_DEFAULT_HEIGHT_PORTRAIT
+ : UI.HEADER_DEFAULT_HEIGHT_LANDSCAPE
+ height: parent.height - y
+ Behavior on y { NumberAnimation { easing.type: Easing.OutQuart; duration: 500 } }
+
+ currentTab: playlistsTab
+
+ PageStack {
+ id: playlistsTab
+ Component.onCompleted: push(Qt.resolvedUrl("PlaylistPage.qml"))
+ }
+ PageStack {
+ id: searchTab
+ }
+ PageStack {
+ id: toplistTab
+ }
+ PageStack {
+ id: settingsTab
+ }
+
+ Connections {
+ target: spotifySession
+ onUserChanged: {
+ if (spotifySession.isLoggedIn)
+ tabGroup.currentTab = playlistsTab
+ }
+ }
+ }
+
+ Player {
+ id: player
+ }
+
+ property Item mainTools : ToolBarLayout {
+ ToolIcon {
+ iconId: enabled ? "toolbar-back" : "toolbar-back-dimmed"
+ onClicked: { tabGroup.currentTab.pop(); }
+ enabled: tabGroup.currentTab.depth > 1 && !tabGroup.currentTab.busy
+ }
+
+ ButtonRow {
+ TabButton {
+ tab: playlistsTab
+ iconSource: theme.inverted ? "image://theme/icon-m-toolbar-list-white"
+ : "image://theme/icon-m-toolbar-list"
+ }
+ TabButton {
+ tab: searchTab
+ iconSource: theme.inverted ? "image://theme/icon-m-toolbar-search-white"
+ : "image://theme/icon-m-toolbar-search"
+ onClicked: { mainPage.checkSearchPage() }
+ }
+ TabButton {
+ tab: toplistTab
+ iconSource: theme.inverted ? "image://theme/icon-m-toolbar-home-white"
+ : "image://theme/icon-m-toolbar-home"
+ onClicked: {
+ if (toplistTab.depth === 0)
+ toplistTab.push(Qt.resolvedUrl("ToplistPage.qml"))
+ }
+ }
+ TabButton {
+ tab: settingsTab
+ iconSource: theme.inverted ? "image://theme/icon-m-toolbar-settings-white"
+ : "image://theme/icon-m-toolbar-settings"
+ onClicked: { if (settingsTab.depth === 0) settingsTab.push(Qt.resolvedUrl("SettingsPage.qml")) }
+ }
+ }
+ }
+
+ function checkSearchPage() {
+ if (searchTab.depth === 0) searchTab.push(Qt.resolvedUrl("SearchPage.qml"))
+ }
+
+}
diff --git a/qml/MyFader.qml b/qml/MyFader.qml
new file mode 100644
index 0000000..1e15dc9
--- /dev/null
+++ b/qml/MyFader.qml
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+
+// Background dimming
+Rectangle {
+ id: faderBackground
+
+ property double dim: 0.9
+ property int fadeInDuration: 250
+ property int fadeOutDuration: 250
+
+ property int fadeInDelay: 0
+ property int fadeOutDelay: 0
+
+ property int fadeInEasingType: Easing.InQuint
+ property int fadeOutEasingType: Easing.OutQuint
+
+ property url background: ""
+
+ property Item visualParent: null
+ property Item originalParent: parent
+
+ // widen the edges to avoid artefacts during rotation
+ anchors.topMargin: -1
+ anchors.rightMargin: -1
+ anchors.bottomMargin: -1
+ anchors.leftMargin: -1
+
+ // opacity is passed to child elements - that is not, what we want
+ // so we need to use alpha value here
+ property double alpha: dim
+
+ signal privateClicked
+
+ //Deprecated, TODO Remove the following two lines on w13
+ signal clicked
+ onClicked: privateClicked()
+
+ // we need the possibility to fetch the red, green, blue components from a color
+ // see http://bugreports.qt.nokia.com/browse/QTBUG-14731
+ color: background != "" ? "transparent" : Qt.rgba(0.0, 0.0, 0.0, alpha)
+ state: 'hidden'
+
+ anchors.fill: parent
+
+ // eat mouse events
+ MouseArea {
+ id: mouseEventEater
+ anchors.fill: parent
+ enabled: faderBackground.alpha != 0.0
+ onClicked: { parent.privateClicked() }
+ }
+
+ Component {
+ id: backgroundComponent
+ BorderImage {
+ id: backgroundImage
+ source: background
+
+ width: faderBackground.width
+ height: faderBackground.height
+
+ opacity: faderBackground.alpha
+ }
+ }
+ Loader {id: backgroundLoader}
+
+ onAlphaChanged: {
+ if (background && faderBackground.alpha && backgroundLoader.sourceComponent == undefined) {
+ backgroundLoader.sourceComponent = backgroundComponent;
+ }
+ if (!faderBackground.alpha) {
+ backgroundLoader.sourceComponent = undefined;
+ }
+ }
+
+ function findRoot() {
+ var next = parent;
+
+ if (next != null) {
+ while (next.parent) {
+ if(next.objectName == "appWindowContent" || next.objectName == "windowContent"){
+ break
+ }
+
+ next = next.parent;
+ }
+ }
+ return next;
+ }
+
+
+ states: [
+ State {
+ name: "visible"
+ PropertyChanges {
+ target: faderBackground
+ alpha: dim
+ }
+ },
+ State {
+ name: "hidden"
+ PropertyChanges {
+ target: faderBackground
+ alpha: 0.0
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "hidden"; to: "visible"
+ //reparent fader whenever it is going to be visible
+ SequentialAnimation {
+ ScriptAction {script: {
+ //console.log("=============00=============");
+ // the algorithm works in the following way:
+ // First: Check if visualParent property is set; if yes, center the fader in visualParent
+ // Second: If not, center inside window content element
+ // Third: If no window was found, use root window
+ originalParent = faderBackground.parent;
+ if (visualParent != null) {
+ faderBackground.parent = visualParent
+ } else {
+ var root = findRoot();
+ if (root != null) {
+ faderBackground.parent = root;
+ } else {
+ // console.log("Error: Cannot find root");
+ }
+ }
+ }
+ }
+ PauseAnimation { duration: fadeInDelay }
+
+ NumberAnimation {
+ properties: "alpha"
+ duration: faderBackground.fadeInDuration
+ easing.type: faderBackground.fadeInEasingType;
+ }
+ }
+ },
+ Transition {
+ from: "visible"; to: "hidden"
+ SequentialAnimation {
+ PauseAnimation { duration: fadeOutDelay }
+
+ NumberAnimation {
+ properties: "alpha"
+ duration: faderBackground.fadeOutDuration
+ easing.type: faderBackground.fadeOutEasingType;
+ }
+ ScriptAction {script: {
+ faderBackground.parent = originalParent;
+ }
+ }
+ }
+ }
+ ]
+}
+
+
+
diff --git a/qml/MyMenu.qml b/qml/MyMenu.qml
new file mode 100644
index 0000000..d567182
--- /dev/null
+++ b/qml/MyMenu.qml
@@ -0,0 +1,361 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import com.meego 1.0
+
+MyPopup {
+ id: root
+
+ // Common API
+ default property alias content: contentField.children
+
+ // Common API inherited from Popup:
+ /*
+ function open()
+ function close()
+
+ property QDeclarativeItem* visualParent
+ property int status
+ */
+
+ property int layoutContentHeight
+
+ // platformStyle API
+ property Style platformStyle: MenuStyle{}
+ property alias style: root.platformStyle // Deprecated
+ property alias platformTitle: titleBar.children
+ property alias title: titleBar.children // Deprecated
+ property alias __footer: footerBar.children
+
+ // private api
+ property int __statusBarDelta: visualParent ? 0 :
+ __findItem( "appWindowContent") != null ? 0 :
+ __findItem( "pageStackWindow") != null && __findItem( "pageStackWindow").showStatusBar ? 36 : 0
+
+ property string __animationChief: "abstractMenu"
+ property int __pressDelay: platformStyle.pressDelay
+
+ // This item will find the object with the given objectName ... or will return
+ function __findItem( objectName ) {
+ var next = parent;
+
+ if (next != null) {
+ while (next) {
+ if(next.objectName == objectName){
+ return next;
+ }
+
+ next = next.parent;
+ }
+ }
+
+ return null;
+ }
+
+ __dim: platformStyle.dim
+ __fadeInDuration: platformStyle.fadeInDuration
+ __fadeOutDuration: platformStyle.fadeOutDuration
+ __fadeInDelay: platformStyle.fadeInDelay
+ __fadeOutDelay: platformStyle.fadeOutDelay
+ __faderBackground: platformStyle.faderBackground
+ __fadeInEasingType: platformStyle.fadeInEasingType
+ __fadeOutEasingType: platformStyle.fadeOutEasingType
+
+ anchors.fill: parent
+
+ // When application is minimized menu is closed.
+ Connections {
+ target: platformWindow
+ onActiveChanged: {
+ if(!platformWindow.active)
+ close()
+ }
+ }
+
+ // This is needed for menus which are not instantiated inside the
+ // content window of the PageStackWindow:
+ Item {
+ id: roundedCorners
+ visible: root.status != DialogStatus.Closed && !visualParent
+ && __findItem( "pageStackWindow") != null && __findItem( "pageStackWindow").platformStyle.cornersVisible
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: parent.height - __statusBarDelta - 2
+ anchors.bottom: parent.bottom
+ z: 10001
+
+ // compensate for the widening of the edges of the fader (which avoids artefacts during rotation)
+ anchors.topMargin: +1
+ anchors.rightMargin: +1
+ anchors.bottomMargin: +1
+ anchors.leftMargin: +1
+
+ Image {
+ anchors.top : parent.top
+ anchors.left: parent.left
+ source: "image://theme/meegotouch-applicationwindow-corner-top-left"
+ }
+ Image {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ source: "image://theme/meegotouch-applicationwindow-corner-top-right"
+ }
+ Image {
+ anchors.bottom : parent.bottom
+ anchors.left: parent.left
+ source: "image://theme/meegotouch-applicationwindow-corner-bottom-left"
+ }
+ Image {
+ anchors.bottom : parent.bottom
+ anchors.right: parent.right
+ source: "image://theme/meegotouch-applicationwindow-corner-bottom-right"
+ }
+ }
+
+ // Shadows:
+ Image {
+ anchors.top : menuItem.top
+ anchors.right: menuItem.left
+ anchors.bottom : menuItem.bottom
+ source: "image://theme/meegotouch-menu-shadow-left"
+ visible: root.status != DialogStatus.Closed
+ }
+ Image {
+ anchors.bottom : menuItem.top
+ anchors.left: menuItem.left
+ anchors.right : menuItem.right
+ source: "image://theme/meegotouch-menu-shadow-top"
+ visible: root.status != DialogStatus.Closed
+ }
+ Image {
+ anchors.top : menuItem.top
+ anchors.left: menuItem.right
+ anchors.bottom : menuItem.bottom
+ source: "image://theme/meegotouch-menu-shadow-right"
+ visible: root.status != DialogStatus.Closed
+ }
+ Image {
+ anchors.top : menuItem.bottom
+ anchors.left: menuItem.left
+ anchors.right : menuItem.right
+ source: "image://theme/meegotouch-menu-shadow-bottom"
+ visible: root.status != DialogStatus.Closed
+ }
+
+ Item {
+ id: menuItem
+ //ToDo: add support for layoutDirection Qt::RightToLeft
+ x: platformStyle.leftMargin
+ width: parent.width - platformStyle.leftMargin - platformStyle.rightMargin // ToDo: better width heuristic
+ height: (screen.currentOrientation == 1) || (screen.currentOrientation == 4) ?
+ /* Portrait */ titleBar.height + flickableContent.height + footerBar.height :
+ /* Landscape */ parent.height - platformStyle.topMargin - platformStyle.bottomMargin - __statusBarDelta
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+
+ state: statesWrapper.state
+
+ BorderImage {
+ id: backgroundImage
+ source: "images/meegotouch-menu-background-inverted.png"
+ anchors.fill : parent
+ border { left: 22; top: 22;
+ right: 22; bottom: 22 }
+ }
+
+ // this item contains the whole menu (content rectangle)
+ Item {
+ id: backgroundRect
+ anchors.fill: parent
+
+ Item {
+ id: titleBar
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ height: childrenRect.height
+ }
+
+ Item {
+ // Required to have the ScrollDecorator+Flickable handled
+ // by the column as a single item while keeping the
+ // ScrollDecorator working
+ id: flickableContent
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.top: backgroundRect.top
+ anchors.topMargin: titleBar.height
+ property int maxHeight : visualParent
+ ? visualParent.height - platformStyle.topMargin - __statusBarDelta
+ - footerBar.height - titleBar.height
+ : root.parent
+ ? root.parent.height - platformStyle.topMargin - __statusBarDelta
+ - footerBar.height - titleBar.height
+ : 350
+
+ height: root.layoutContentHeight + platformStyle.topPadding + platformStyle.bottomPadding < maxHeight
+ ? root.layoutContentHeight + platformStyle.topPadding + platformStyle.bottomPadding
+ : maxHeight
+
+ Flickable {
+ id: flickable
+ anchors.fill: parent
+ contentWidth: parent.width
+ contentHeight: root.layoutContentHeight + platformStyle.topPadding + platformStyle.bottomPadding
+ interactive: contentHeight > flickable.height
+ flickableDirection: Flickable.VerticalFlick
+ pressDelay: __pressDelay
+ clip: true
+
+ Item {
+ id: contentRect
+ height: root.layoutContentHeight
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: platformStyle.topPadding
+ anchors.bottomMargin: platformStyle.bottomPadding
+ anchors.leftMargin: platformStyle.leftPadding
+ anchors.rightMargin: platformStyle.rightPadding
+
+ Item {
+ id: contentField
+ anchors.fill: contentRect
+
+ function closeMenu() { root.close(); }
+ }
+ }
+ }
+ ScrollDecorator {
+ id: scrollDecorator
+ flickableItem: flickable
+ }
+ }
+
+ Item {
+ id: footerBar
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.top: backgroundRect.top
+ anchors.topMargin: titleBar.height + flickableContent.height
+ height: childrenRect.height
+ }
+
+ }
+ }
+
+ onPrivateClicked: close() // "reject()"
+
+ StateGroup {
+ id: statesWrapper
+
+ state: "hidden"
+
+ states: [
+ State {
+ name: "visible"
+ when: root.__animationChief == "abstractMenu" && (root.status == DialogStatus.Opening || root.status == DialogStatus.Open)
+ PropertyChanges {
+ target: menuItem
+ opacity: 1.0
+ }
+ },
+ State {
+ name: "hidden"
+ when: root.__animationChief == "abstractMenu" && (root.status == DialogStatus.Closing || root.status == DialogStatus.Closed)
+ PropertyChanges {
+ target: menuItem
+ opacity: 0.0
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "visible"; to: "hidden"
+ SequentialAnimation {
+ ScriptAction {script: {
+ __fader().state = "hidden";
+ root.status = DialogStatus.Closing;
+ }
+ }
+
+ NumberAnimation {target: menuItem; property: "opacity";
+ from: 0.0; to: 1.0; duration: 0}
+
+ NumberAnimation {target: menuItem; property: "anchors.bottomMargin";
+ easing.type: Easing.InOutQuint;
+ from: 0; to: -menuItem.height; duration: 350}
+
+ NumberAnimation {target: menuItem; property: "opacity";
+ from: 1.0; to: 0.0; duration: 0}
+
+ ScriptAction {script: {
+ status = DialogStatus.Closed;
+ }
+ }
+ }
+ },
+ Transition {
+ from: "hidden"; to: "visible"
+ SequentialAnimation {
+ ScriptAction {script: {
+ __fader().state = "visible";
+ root.status = DialogStatus.Opening;
+ }
+ }
+
+ NumberAnimation {target: menuItem; property: "anchors.bottomMargin";
+ easing.type: Easing.InOutQuint;
+ from: -menuItem.height; to: 0; duration: 350}
+
+ ScriptAction {script: {
+ status = DialogStatus.Open;
+ }
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/qml/MyMenuItem.qml b/qml/MyMenuItem.qml
new file mode 100644
index 0000000..e3d181c
--- /dev/null
+++ b/qml/MyMenuItem.qml
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+// MenuItem is a component that is used in menus.
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: root
+
+ // Common API
+ property string text
+ signal clicked
+ property alias pressed: mouseArea.pressed
+
+ // platformStyle API
+ property Style platformStyle: MyMenuItemStyle{
+ position: root.parent.visibleChildren == 1 ? ""
+ : root.parent.firstVisible == root ? "vertical-top"
+ : root.parent.lastVisible == root ? "vertical-bottom"
+ : "vertical-center"
+ }
+ property alias style: root.platformStyle // Deprecated
+
+ width: parent ? parent.width: 0
+ height: ( root.platformStyle.height == 0 ) ?
+ root.platformStyle.topMargin + menuText.paintedHeight + root.platformStyle.bottomMargin :
+ root.platformStyle.topMargin + root.platformStyle.height + root.platformStyle.bottomMargin
+/*
+ Rectangle {
+ id: backgroundRec
+ // ToDo: remove hardcoded values
+ color: pressed ? "darkgray" : "transparent"
+ anchors.fill : root
+ opacity : 0.5
+ }
+*/
+ BorderImage {
+ id: backgroundImage
+ source: // !enabled ? root.platformStyle.disabledBackground :
+ pressed ? root.platformStyle.pressedBackground
+ : root.platformStyle.background
+ anchors.fill : root
+ border { left: 22; top: 22;
+ right: 22; bottom: 22 }
+ }
+
+ Text {
+ id: menuText
+ text: parent.text
+ elide: Text.ElideRight
+ font.family : root.platformStyle.fontFamily
+ font.pixelSize : root.platformStyle.fontPixelSize
+ font.weight: root.platformStyle.fontWeight
+ color: root.platformStyle.textColor
+
+ anchors.topMargin : root.platformStyle.topMargin
+ anchors.bottomMargin : root.platformStyle.bottomMargin
+ anchors.leftMargin : root.platformStyle.leftMargin
+ anchors.rightMargin : root.platformStyle.rightMargin
+
+ anchors.top : root.platformStyle.centered ? undefined : root.top
+ anchors.bottom : root.platformStyle.centered ? undefined : root.bottom
+ anchors.left : root.left
+ anchors.right : root.right
+// anchors.centerIn : parent.centerIn
+ anchors.verticalCenter : root.platformStyle.centered ? parent.verticalCenter : undefined
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: { if (parent.enabled) parent.clicked();}
+ }
+
+ onClicked: if (parent) parent.closeLayout();
+ onVisibleChanged: if (root.parent) root.parent.relayout();
+}
diff --git a/qml/MyMenuItemStyle.qml b/qml/MyMenuItemStyle.qml
new file mode 100644
index 0000000..0d04f4a
--- /dev/null
+++ b/qml/MyMenuItemStyle.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Style {
+ id: root
+ // Font
+ property string fontFamily: UI.FONT_FAMILY
+ property int fontPixelSize: 26 // UI.FONT_DEFAULT_SIZE
+ property int fontCapitalization: Font.MixedCase
+ property int fontWeight: Font.Bold
+ property int height: 80
+
+ // Text Color
+ property color textColor: inverted ? UI.COLOR_BUTTON_INVERTED_FOREGROUND : UI.COLOR_BUTTON_FOREGROUND
+ property color pressedTextColor: UI.COLOR_INVERTED_FOREGROUND
+ property color disabledTextColor: UI.COLOR_DISABLED_FOREGROUND
+ property color checkedTextColor: UI.COLOR_INVERTED_FOREGROUND
+
+ property real leftMargin: 24
+ property real rightMargin: 24
+ property real topMargin: 0
+ property real bottomMargin: 0
+ property bool centered: true
+
+ property string position: ""
+
+ property url background: "images/meegotouch-list-inverted-background" + (position ? "-" + position : "") + ".png"
+ property url pressedBackground: "images/meegotouch-list-inverted-background-pressed" + (position ? "-" + position : "") + ".png"
+// TODO: Add disabled state once the graphics are available
+// property url disabledBackground: "image://theme/meegotouch-list" + __invertedString + "-background-disabled" + (position ? "-" + position : "")
+}
diff --git a/qml/MyMenuLayout.qml b/qml/MyMenuLayout.qml
new file mode 100644
index 0000000..183da65
--- /dev/null
+++ b/qml/MyMenuLayout.qml
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import com.meego 1.0
+
+Item {
+ id: root
+ anchors.left: parent!==undefined?parent.left:undefined
+ anchors.right: parent!==undefined?parent.right:undefined
+ height: menuItemColumn.height
+
+ default property alias menuChildren: menuItemColumn.children
+
+ Column {
+ id: menuItemColumn
+
+ property int visibleChildren: 0
+ property Item firstVisible: null
+ property Item lastVisible: null
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ //height: childrenRect.height
+
+ function closeLayout() {
+ root.parent.closeMenu();
+ }
+
+ function relayout() {
+ visibleChildren = 0;
+ firstVisible = null;
+ lastVisible = null;
+ for (var i = 0; i < children.length; i++) {
+ if (children[i].visible) {
+ if (firstVisible === null) {
+ firstVisible = children[i];
+ }
+ lastVisible = children[i];
+ visibleChildren = visibleChildren + 1;
+ }
+ }
+ }
+ }
+ Component.onCompleted: menuItemColumn.relayout()
+
+}
diff --git a/qml/MyPopup.qml b/qml/MyPopup.qml
new file mode 100644
index 0000000..d4b9d00
--- /dev/null
+++ b/qml/MyPopup.qml
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import com.meego 1.0
+
+Item {
+ id: root
+
+ // api
+ property alias visualParent: fader.visualParent
+
+ // possible states: Opening, Open, Closing, Closed
+ // Opening and Closing are used during animation (when the dialog fades/moves/pops/whatever in)
+ property int status: DialogStatus.Closed
+
+ // private api
+ property double __dim: 0.9
+ property int __fadeInDuration
+ property int __fadeOutDuration
+ property int __fadeInDelay
+ property int __fadeOutDelay
+ property int __fadeInEasingType
+ property int __fadeOutEasingType
+ property string __faderBackground
+
+ function open() {
+ if (status == DialogStatus.Closed)
+ status = DialogStatus.Opening;
+ }
+
+ function close() {
+ if (status == DialogStatus.Open)
+ status = DialogStatus.Closing;
+ }
+
+ signal privateClicked
+
+ //Deprecated, TODO Remove the following two lines on w13
+ signal clicked
+ onClicked: privateClicked()
+
+ QtObject {
+ id: parentCache
+ property QtObject oldParent: null
+ }
+
+ Component.onCompleted: {
+ parentCache.oldParent = parent;
+ fader.parent = parent;
+ parent = fader;
+ }
+
+ //if this is not given, application may crash in some cases
+ Component.onDestruction: {
+ if (parentCache.oldParent != null) {
+ parent = parentCache.oldParent
+ fader.parent = root
+ }
+ }
+
+ MyFader {
+ id: fader
+ dim: root.__dim
+ fadeInDuration: root.__fadeInDuration
+ fadeOutDuration: root.__fadeOutDuration
+ fadeInDelay: root.__fadeInDelay
+ fadeOutDelay: root.__fadeOutDelay
+ fadeInEasingType: root.__fadeInEasingType
+ fadeOutEasingType: root.__fadeOutEasingType
+
+
+ background: root.__faderBackground
+ onPrivateClicked: root.privateClicked();
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: root.status == DialogStatus.Opening || root.status == DialogStatus.Closing
+ z: Number.MAX_VALUE
+ }
+ }
+
+ function __fader() {
+ return fader;
+ }
+
+}
diff --git a/qml/MySheet.qml b/qml/MySheet.qml
new file mode 100644
index 0000000..d748f3c
--- /dev/null
+++ b/qml/MySheet.qml
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import com.meego 1.0
+
+Item {
+ id: root
+
+ width: parent ? parent.width : 0
+ height: parent ? parent.height : 0
+
+ property alias title: titleBar.children
+ property alias content: contentField.children
+ property alias buttons: buttonRow.children
+ property Item visualParent
+ property int status: DialogStatus.Closed
+
+ property alias acceptButtonText: acceptButton.text
+ property alias rejectButtonText: rejectButton.text
+
+ property alias acceptButton: acceptButton
+ property alias rejectButton: rejectButton
+
+ signal accepted
+ signal rejected
+
+ property QtObject platformStyle: SheetStyle {}
+
+ //Deprecated, TODO Remove this on w13
+ property alias style: root.platformStyle
+
+ function reject() {
+ close();
+ rejected();
+ }
+
+ function accept() {
+ close();
+ accepted();
+ }
+
+ visible: status != DialogStatus.Closed;
+
+ function open() {
+ parent = visualParent || __findParent();
+ sheet.state = "";
+ }
+
+ function close() {
+ sheet.state = "closed";
+ }
+
+ function __findParent() {
+ var next = parent;
+ while (next && next.parent
+ && next.objectName != "appWindowContent"
+ && next.objectName != "windowContent") {
+ next = next.parent;
+ }
+ return next;
+ }
+
+ function getButton(name) {
+ for (var i=0; i<buttons.length; ++i) {
+ if (buttons[i].objectName == name)
+ return buttons[i];
+ }
+ return undefined;
+ }
+
+ MouseArea {
+ id: blockMouseInput
+ anchors.fill: parent
+ }
+
+ Item {
+ id: sheet
+
+ //when the sheet is part of a page do nothing
+ //when the sheet is a direct child of a PageStackWindow, consider the status bar
+ property int statusBarOffset: (typeof __isPage != "undefined") ? 0
+ : (typeof __statusBarHeight == "undefined") ? 0
+ : __statusBarHeight
+
+ width: parent.width
+ height: parent.height - statusBarOffset
+
+ y: statusBarOffset
+
+ clip: true
+
+ property int transitionDurationIn: 300
+ property int transitionDurationOut: 450
+
+ state: "closed"
+
+ function transitionStarted() {
+ status = (state == "closed") ? DialogStatus.Closing : DialogStatus.Opening;
+ }
+
+ function transitionEnded() {
+ status = (state == "closed") ? DialogStatus.Closed : DialogStatus.Open;
+ }
+
+ states: [
+ // Closed state.
+ State {
+ name: "closed"
+ PropertyChanges { target: sheet; y: height; }
+ }
+ ]
+
+ transitions: [
+ // Transition between open and closed states.
+ Transition {
+ from: ""; to: "closed"; reversible: false
+ SequentialAnimation {
+ ScriptAction { script: if (sheet.state == "closed") { sheet.transitionStarted(); } else { sheet.transitionEnded(); } }
+ PropertyAnimation { properties: "y"; easing.type: Easing.InOutQuint; duration: sheet.transitionDurationOut }
+ ScriptAction { script: if (sheet.state == "closed") { sheet.transitionEnded(); } else { sheet.transitionStarted(); } }
+ }
+ },
+ Transition {
+ from: "closed"; to: ""; reversible: false
+ SequentialAnimation {
+ ScriptAction { script: if (sheet.state == "") { sheet.transitionStarted(); } else { sheet.transitionEnded(); } }
+ PropertyAnimation { properties: "y"; easing.type: Easing.OutQuint; duration: sheet.transitionDurationIn }
+ ScriptAction { script: if (sheet.state == "") { sheet.transitionEnded(); } else { sheet.transitionStarted(); } }
+ }
+ }
+ ]
+
+ BorderImage {
+ source: platformStyle.background
+ width: parent.width
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+ Item {
+ id: contentField
+ anchors.fill: parent
+ }
+ }
+
+ Item {
+ id: header
+ width: parent.width
+ height: headerBackground.height
+ BorderImage {
+ id: headerBackground
+ border {
+ left: platformStyle.headerBackgroundMarginLeft
+ right: platformStyle.headerBackgroundMarginRight
+ top: platformStyle.headerBackgroundMarginTop
+ bottom: platformStyle.headerBackgroundMarginBottom
+ }
+ source: platformStyle.headerBackground
+ width: header.width
+ }
+ Item {
+ id: buttonRow
+ anchors.fill: parent
+ SheetButton {
+ id: rejectButton
+ objectName: "rejectButton"
+ anchors.left: parent.left
+ anchors.leftMargin: root.platformStyle.rejectButtonLeftMargin
+ anchors.verticalCenter: parent.verticalCenter
+ visible: text != ""
+ onClicked: close()
+ }
+ SheetButton {
+ id: acceptButton
+ objectName: "acceptButton"
+ anchors.right: parent.right
+ anchors.rightMargin: root.platformStyle.acceptButtonRightMargin
+ anchors.verticalCenter: parent.verticalCenter
+ platformStyle: SheetButtonAccentStyle {
+ background: "image://theme/" + appWindow.themeColor + "-meegotouch-sheet-button-accent-inverted-background"
+ pressedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-sheet-button-accent-inverted-background-pressed"
+ disabledBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-sheet-button-accent-inverted-background-disabled"
+ }
+ visible: text != ""
+ onClicked: close()
+ }
+ Component.onCompleted: {
+ acceptButton.clicked.connect(accepted)
+ rejectButton.clicked.connect(rejected)
+ }
+ }
+ Item {
+ id: titleBar
+ anchors.fill: parent
+ }
+ }
+ }
+}
diff --git a/qml/NotificationBanner.qml b/qml/NotificationBanner.qml
new file mode 100644
index 0000000..62ce8b1
--- /dev/null
+++ b/qml/NotificationBanner.qml
@@ -0,0 +1,254 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import com.meego 1.0
+
+/*
+ Class: InfoBanner
+ The InfoBanner component is used to display information to the user. The number of lines of text
+ shouldn't exceed 3.
+*/
+
+Item {
+ id: root
+
+ /*
+ * Property: iconSource
+ * [url] The path to the icon image
+ */
+ property url iconSource: ""
+
+ /*
+ * Property: text
+ * [string] Text to be displayed in InfoBanner
+ */
+ property alias text: text.text
+
+ /*
+ * Property: timerEnabled
+ * [bool=true] Enable/disable timer that dismisses InfoBanner
+ */
+ property bool timerEnabled: true
+
+ /*
+ * Property: timerShowTime
+ * [int=3000ms] For setting how long InfoBanner stays visible to user before being dismissed
+ */
+ property alias timerShowTime: sysBannerTimer.interval
+
+ /*
+ * Property: topMargin
+ * [int=8 pix] Allows user to customize top margin if needed
+ */
+ property alias topMargin: root.y
+
+ /*
+ * Property: leftMargin
+ * [int=8 pix] Allows user to customize left margin if needed
+ */
+ property alias leftMargin: root.x
+
+ /*
+ * Function: show
+ * Show InfoBanner
+ */
+ function show() {
+ parent = __findParent();
+ animationShow.running = true;
+ if (root.timerEnabled)
+ sysBannerTimer.restart();
+ }
+
+ function __findParent() {
+ var next = parent;
+ while (next && next.parent && next.objectName != "appWindowContent") {
+ next = next.parent;
+ }
+ return next;
+ }
+
+ /*
+ * Function: hide
+ * Hide InfoBanner
+ */
+ function hide() {
+ animationHide.running = true;
+ }
+
+ implicitHeight: internal.getBannerHeight()
+ implicitWidth: internal.getBannerWidth()
+ x:8; y:8
+ scale: 0
+
+ BorderImage {
+ source: "image://theme/meegotouch-notification-system-background"
+ anchors.fill: root
+ horizontalTileMode: BorderImage.Stretch
+ verticalTileMode: BorderImage.Stretch
+ border { left: 10; top: 10; right: 10; bottom: 10 }
+ opacity: 1
+ }
+
+ Image {
+ id: image
+ anchors { left: parent.left; leftMargin: 16; top: parent.top; topMargin: 16 }
+ source: root.iconSource
+ visible: root.iconSource != ""
+ }
+
+ Text {
+ id: text
+ width: internal.getTextWidth()
+ anchors { left: (image.visible ? image.right : parent.left); leftMargin: (image.visible ? 14:16);
+ top: parent.top; topMargin: internal.getTopMargin(); bottom: parent.bottom }
+ color: "white"
+ wrapMode: Text.Wrap
+ verticalAlignment: Text.AlignHCenter
+ font.pixelSize: 24
+ font.family: "Nokia Pure Text"
+ font.letterSpacing: -1.2
+ maximumLineCount: 3
+ elide: Text.ElideRight
+ }
+
+ QtObject {
+ id: internal
+
+ function getBannerHeight() {
+ if (image.visible) {
+ if (text.lineCount <= 2)
+ return 80;
+ else
+ return 80; //106
+ } else {
+ if (text.lineCount <= 1)
+ return 80; //64
+ else if (text.lineCount <= 2)
+ return 80;
+ else
+ return 80; //106
+ }
+ }
+
+ function getBannerWidth() {
+ if ( screen.currentOrientation==Screen.Portrait || screen.currentOrientation==Screen.PortraitInverted ) {
+ // In portrait mode, the width of the banner is equal to the width of parent minus left
+ // and right margins in-between banner and parent.
+ return parent.width-root.x*2;
+ } else {
+ if (image.visible) {
+ // If an icon image is specified...
+ if ((image.width+text.paintedWidth+46) <= parent.width*0.54 && text.lineCount <= 1) {
+ // 46 is the sum of all horizontal margins within the banner. The above condition basically
+ // says that if there's only one line of text, and the sum of width of icon, text, and required
+ // margins is less then 54% of the screen width, banner width should be 54% of the screen.
+ return parent.width*0.54;
+ } else {
+ return parent.width-root.x*2;
+ }
+ } else {
+ // If no icon image specified...
+ if ((text.paintedWidth+32) <= parent.width*0.54 && text.lineCount <= 1) {
+ // 32 is the sum of all horizontal margins within the banner. The above condition basically
+ // says that if there's only one line of text, and the sum of width of text and required
+ // margins is less then 54% of the screen width, banner width should be 54% of the screen.
+ return parent.width-root.x*2;
+ } else {
+ return parent.width-root.x*2;
+ }
+ }
+ }
+ }
+
+ function getTopMargin() {
+ if (text.lineCount <= 1 && !image.visible) {
+ // If there's only one line of text and no icon image, top and bottom margins are equal.
+ return (root.height-text.paintedHeight)/2;
+ } else {
+ // In all other cases, top margin is 4 px more than bottom margin.
+ return (root.height-text.paintedHeight)/2 + 2;
+ }
+ }
+
+ function getTextWidth() {
+ // 46(32 when there's no icon) is sum of all margins within banner. root.x*2 is sum of margins outside banner.
+ // Text element width is dertermined by substracting parent width(screen width) by all the margins and
+ // icon width(if applicable).
+ return image.visible ? (parent.width-root.x*2-46-image.width) : (parent.width-root.x*2-32);
+ }
+
+ function getScaleValue() {
+ // When banner is displayed, as part of transition effect, it'll first be enlarged to the point where its width
+ // is equal to screen width. root.x*2/root.width calculates the amount of expanding required, where root.x*2 is
+ // equal to screen.displayWidth minus banner.width
+ return root.x*2/root.width + 1;
+ }
+ }
+
+ Timer {
+ id: sysBannerTimer
+ repeat: false
+ running: false
+ interval: 3000
+ onTriggered: hide()
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: hide()
+ }
+
+ SequentialAnimation {
+ id: animationShow
+ NumberAnimation { target: root; property: "scale"; from: 0; to: internal.getScaleValue(); duration: 200; easing.type: Easing.OutQuad}
+ NumberAnimation { target: root; property: "scale"; from: internal.getScaleValue(); to: 1; duration: 200 }
+ }
+
+ NumberAnimation {
+ id: animationHide
+ target: root; property: "scale"; to: 0; duration: 200; easing.type: Easing.InExpo
+ }
+
+ Component.onCompleted: {
+ //__owner = parent;
+ }
+}
+
diff --git a/qml/Player.qml b/qml/Player.qml
new file mode 100644
index 0000000..f2ecdde
--- /dev/null
+++ b/qml/Player.qml
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+
+Rectangle {
+ id: player
+ width: parent.width
+ height: screen.currentOrientation == Screen.Portrait ? 818 : 444
+ color: "black"
+ y: -height
+
+ property bool openRequested: false
+ property bool hidden: true
+ property bool inOpenPosition: y > -quickControls.y
+
+ property bool openedOnce: false
+ onOpenRequestedChanged: if (openRequested) openedOnce = true
+
+ MouseArea {
+ anchors.fill: parent
+ }
+
+ FullControls {
+ id: fullControls
+ anchors.top: parent.top
+ anchors.bottom: quickControls.top
+ }
+
+ QuickControls {
+ id: quickControls
+ anchors.bottom: parent.bottom
+ }
+
+ states: [
+ State {
+ when: openRequested && !hidden
+ name: "open"
+ PropertyChanges { target: player; y: 0 }
+ },
+ State {
+ when: !openRequested && !hidden
+ name: "small"
+ PropertyChanges { target: player; y: -quickControls.y }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation { properties: "y"; easing.type: Easing.OutQuart; duration: 500 }
+ }
+
+ Connections {
+ target: spotifySession
+ onCurrentTrackChanged: hidden = !spotifySession.hasCurrentTrack
+ }
+
+ onHiddenChanged: {
+ if (hidden)
+ openRequested = false;
+ }
+}
diff --git a/qml/PlaylistDelegate.qml b/qml/PlaylistDelegate.qml
new file mode 100644
index 0000000..0caefc8
--- /dev/null
+++ b/qml/PlaylistDelegate.qml
@@ -0,0 +1,270 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: listItem
+
+ signal clicked
+ signal pressAndHold
+ property alias pressed: mouseArea.pressed
+ property alias title: mainText.text
+ property alias subtitle: subText.text
+ property alias extraText: timing.text
+ property alias icon: iconImage.source
+ property int offlineStatus: 0
+ property bool availableOffline: false
+ property int downloadProgress: 0
+ property int unseens: 0
+
+ property int titleSize: UI.LIST_TILE_SIZE
+ property string titleFont: UI.FONT_FAMILY_BOLD
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+
+ property int subtitleSize: UI.LIST_SUBTILE_SIZE
+ property string subtitleFont: UI.FONT_FAMILY_LIGHT
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ height: UI.LIST_ITEM_HEIGHT
+ width: parent.width
+
+ SequentialAnimation {
+ id: backAnimation
+ property bool animEnded: false
+ running: mouseArea.pressed
+
+ ScriptAction { script: backAnimation.animEnded = false }
+ PauseAnimation { duration: 200 }
+ ParallelAnimation {
+ NumberAnimation { target: background; property: "opacity"; to: 0.4; duration: 300 }
+ ColorAnimation { target: mainText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: subText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: timing; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: waitingText; property: "color"; to: "black"; duration: 300 }
+ NumberAnimation { target: iconItem; property: "opacity"; to: 0.2; duration: 300 }
+ NumberAnimation { target: unseenBox; property: "opacity"; to: 0.2; duration: 300 }
+ NumberAnimation { target: offlineStatusIcon; property: "opacity"; to: 0.2; duration: 300 }
+ NumberAnimation { target: downloadBar; property: "opacity"; to: 0.2; duration: 300 }
+ }
+ PauseAnimation { duration: 100 }
+ ScriptAction { script: { backAnimation.animEnded = true; listItem.pressAndHold(); } }
+ onRunningChanged: {
+ if (!running) {
+ iconItem.opacity = 1.0
+ unseenBox.opacity = 1.0
+ offlineStatusIcon.opacity = 1.0
+ downloadBar.opacity = 1.0
+ mainText.color = listItem.titleColor
+ subText.color = listItem.subtitleColor
+ timing.color = listItem.subtitleColor
+ waitingText.color = listItem.subtitleColor
+ }
+ }
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : 0.0
+ color: "#22FFFFFF"
+ }
+
+ Row {
+ anchors.fill: parent
+ spacing: UI.LIST_ITEM_SPACING
+
+ Item {
+ id: iconItem
+ anchors.verticalCenter: parent.verticalCenter
+ visible: iconImage.source !== "" ? true : false
+ width: 40
+ height: 40
+
+ Image {
+ id: iconImage
+ anchors.centerIn: parent
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - (iconItem.visible ? iconItem.width + UI.LIST_ITEM_SPACING : 0)
+
+ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: mainText.height
+
+ Label {
+ id: mainText
+ anchors.left: parent.left
+ anchors.right: parent.right
+ font.family: listItem.titleFont
+ font.weight: Font.Bold
+ font.pixelSize: listItem.titleSize
+ color: listItem.titleColor
+ elide: Text.ElideRight
+ }
+
+ BorderImage {
+ id: unseenBox
+ source: "images/icon-m-common-green.png"
+ anchors.verticalCenter: parent.verticalCenter
+ x: mainText.paintedWidth + UI.MARGIN_XLARGE
+ visible: listItem.unseens > 0
+ width: unseenText.width > 14 ? unseenText.width + 14 : 28
+ border.left: 10
+ border.right: 10
+ border.top: 10
+ border.bottom: 10
+
+ Label {
+ id: unseenText
+ anchors.centerIn: parent
+ font.family: listItem.titleFont
+ font.pixelSize: listItem.subtitleSize
+ font.bold: true
+ color: listItem.titleColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: listItem.unseens
+ }
+ }
+ }
+
+ Item {
+ height: subText.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Label {
+ id: offlineStatusIcon
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ height: visible ? 24 : 0; width: height
+ verticalAlignment: Text.AlignVCenter
+ color: UI.SPOTIFY_COLOR
+ font.bold: true
+ font.pixelSize: 32
+ text: "\u2022"
+ visible: listItem.availableOffline
+ }
+
+ Item {
+ anchors.left: offlineStatusIcon.right
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ height: subText.height
+ visible: listItem.offlineStatus == SpotifyPlaylist.Yes || listItem.offlineStatus == SpotifyPlaylist.No
+
+ Label {
+ id: subText
+ font.family: listItem.subtitleFont
+ font.pixelSize: listItem.subtitleSize
+ font.weight: Font.Light
+ color: listItem.subtitleColor
+ anchors.left: parent.left
+ anchors.right: timing.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ elide: Text.ElideRight
+ visible: text != ""
+ }
+
+ Label {
+ id: timing
+ font.family: listItem.subtitleFont
+ font.weight: Font.Light
+ font.pixelSize: listItem.subtitleSize
+ color: listItem.subtitleColor
+ anchors.right: parent.right
+ visible: text != ""
+ }
+ }
+
+ ProgressBar {
+ id: downloadBar
+ anchors.left: offlineStatusIcon.right
+ anchors.right: parent.right
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 3
+ platformStyle: ProgressBarStyle {
+ knownTexture: "qrc:/qml/images/meegotouch-progressindicator-bar-known-texture.png"
+ barMask: "image://theme/meegotouch-progressindicator-bar-mask"
+ }
+ visible: listItem.offlineStatus == SpotifyPlaylist.Downloading
+ minimumValue: 0
+ maximumValue: 100
+ value: listItem.downloadProgress
+ }
+
+ Label {
+ id: waitingText
+ anchors.left: offlineStatusIcon.right
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ visible: listItem.offlineStatus == SpotifyPlaylist.Waiting
+ font.family: listItem.subtitleFont
+ font.pixelSize: listItem.subtitleSize
+ font.weight: Font.Light
+ color: listItem.subtitleColor
+ text: "Waiting for sync"
+ }
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ if (!backAnimation.animEnded)
+ listItem.clicked();
+ }
+ }
+}
diff --git a/qml/PlaylistMenu.qml b/qml/PlaylistMenu.qml
new file mode 100644
index 0000000..b1c0855
--- /dev/null
+++ b/qml/PlaylistMenu.qml
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+import "Utilities.js" as Utilities
+
+MyMenu {
+ id: playlistMenu
+
+ property variant playlist: null
+ property bool userOwnsPlaylist: spotifySession.user ? spotifySession.user.ownsPlaylist(playlist) : false
+
+ layoutContentHeight: layout.height
+
+ NotificationBanner {
+ id: banner
+ }
+
+ PlaylistNameSheet{
+ id: renameSheet
+ title: "Rename playlist"
+ onAccepted: playlist.rename(Utilities.trim(renameSheet.playlistName))
+ }
+
+ QueryDialog {
+ id: confirmDeleteDialog
+ parent: playlistMenu.parent
+ titleText: userOwnsPlaylist ? "Delete playlist?" : "Unsubscribe from playlist?"
+ message: playlist ? playlist.name : ""
+ acceptButtonText: "Yes"
+ rejectButtonText: "No"
+ onAccepted: { playlist.removeFromContainer() }
+ }
+
+ MyMenuLayout {
+ id: layout
+
+ MyMenuItem {
+ text: "Play";
+ onClicked: { playlist.play() }
+ visible: (playlist && playlist.trackCount > 0) ? true : false
+ }
+ MyMenuItem {
+ text: "Add to queue";
+ onClicked: { playlist.enqueue() }
+ visible: (playlist && playlist.trackCount > 0) ? true : false
+ }
+ MyMenuItem {
+ text: "Rename"
+ onClicked: { renameSheet.playlistName = playlist.name; renameSheet.open() }
+ visible: ((playlist && playlist.type == SpotifyPlaylist.Playlist && userOwnsPlaylist) ? true : false)
+ && !spotifySession.offlineMode
+ }
+ MyMenuItem {
+ id: collabItem
+ onClicked: { playlist.collaborative = !playlist.collaborative }
+ visible: ((playlist && playlist.type == SpotifyPlaylist.Playlist && userOwnsPlaylist) ? true : false)
+ && !spotifySession.offlineMode
+ }
+ MyMenuItem {
+ id: offlineItem
+ onClicked: { playlist.availableOffline = !playlist.availableOffline }
+ }
+ MyMenuItem {
+ id: deleteItem
+ onClicked: { confirmDeleteDialog.open(); }
+ visible: ((playlist && playlist.type == SpotifyPlaylist.Playlist) ? true : false)
+ && !spotifySession.offlineMode
+ }
+ }
+
+ onStatusChanged: {
+ if (status == DialogStatus.Opening && playlist) {
+ collabItem.text = (playlist.collaborative ? "Unset" : "Set") + " collaborative";
+ deleteItem.text = userOwnsPlaylist ? "Delete" : "Unsubscribe";
+ offlineItem.text = (playlist.availableOffline ? "Unset" : "Set") + " offline mode"
+ }
+ }
+}
diff --git a/qml/PlaylistNameSheet.qml b/qml/PlaylistNameSheet.qml
new file mode 100644
index 0000000..0a2d088
--- /dev/null
+++ b/qml/PlaylistNameSheet.qml
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+MySheet {
+ id: renameSheet
+
+ property alias title: label.text
+ property alias playlistName: field.text
+
+ acceptButtonText: "Save"
+ rejectButtonText: "Cancel"
+ platformStyle: SheetStyle {
+ headerBackground: "images/meegotouch-sheet-header-inverted-background.png"
+ }
+
+ content: Column {
+ anchors.fill: parent
+ spacing: 20
+
+ Item {
+ width: 1; height: 1
+ }
+
+ Label {
+ id: label
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ font.pixelSize: UI.FONT_LARGE
+ }
+
+ TextField {
+ id: field
+ width: parent.width
+ inputMethodHints: Qt.ImhNoPredictiveText
+ platformStyle: TextFieldStyle {
+ backgroundSelected: "image://theme/" + appWindow.themeColor + "-meegotouch-textedit-background-selected"
+ }
+ platformSipAttributes: SipAttributes {
+ actionKeyLabel: "Save"
+ actionKeyEnabled: true
+ }
+ Keys.onReturnPressed: { label.focus = true; renameSheet.accept(); }
+ }
+ }
+
+ onStatusChanged: if (status == DialogStatus.Opening) field.forceActiveFocus()
+}
diff --git a/qml/PlaylistPage.qml b/qml/PlaylistPage.qml
new file mode 100644
index 0000000..2052a83
--- /dev/null
+++ b/qml/PlaylistPage.qml
@@ -0,0 +1,194 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ id: playlistPage
+ orientationLock: PageOrientation.LockPortrait
+ pageStack: parent
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ Connections {
+ target: spotifySession
+ onOfflineModeChanged: {
+ if (spotifySession.offlineMode)
+ pageStack.pop(null);
+ }
+ onConnectionStatusChanged: {
+ if (spotifySession.connectionStatus != SpotifySession.LoggedIn)
+ pageStack.pop(null);
+ }
+ }
+
+ PlaylistMenu {
+ id: menu
+ }
+
+ PlaylistNameSheet {
+ id: newPlaylistSheet
+ title: "Playlist name"
+ onAccepted: { spotifySession.user.createPlaylist(newPlaylistSheet.playlistName); }
+ }
+
+ ListView {
+ id: playlists
+ anchors.fill: parent
+
+ model: spotifySession.user ? spotifySession.user.playlists : 0
+
+ delegate: PlaylistDelegate {
+ title: (modelData.type == SpotifyPlaylist.Playlist ? modelData.name
+ : (modelData.type == SpotifyPlaylist.Starred ? "Starred"
+ : "Inbox"))
+ subtitle: modelData.trackCount + " song" + (modelData.trackCount > 1 ? "s" : "")
+ + ((spotifySession.user ? spotifySession.user.ownsPlaylist(modelData) : false) ? "" : " | by " + modelData.owner)
+ extraText: spotifySession.formatDuration(modelData.totalDuration)
+ visible: modelData.isLoaded
+ icon: modelData.collaborative ? "images/icon-m-collaborative-playlist.png" : staticIcon
+ offlineStatus: modelData.offlineStatus
+ availableOffline: modelData.availableOffline
+ downloadProgress: modelData.offlineDownloadProgress
+ unseens: modelData.unseenCount
+ enabled: opacity == 1.0
+ opacity: !spotifySession.offlineMode || modelData.offlineStatus == SpotifyPlaylist.Yes ? 1.0 : 0.3
+
+ onClicked: {
+ if (modelData.trackCount > 0)
+ pageStack.push(Qt.resolvedUrl("TracklistPage.qml"), { playlist: modelData })
+ }
+ onPressAndHold: { menu.playlist = modelData; menu.open(); }
+
+ property string staticIcon
+ Component.onCompleted: {
+ if (modelData.type == SpotifyPlaylist.Playlist)
+ staticIcon = "image://theme/icon-m-music-video-all-songs";
+ else if (modelData.type == SpotifyPlaylist.Starred)
+ staticIcon = theme.inverted ? "image://theme/icon-m-common-favorite-mark-inverse" : "image://theme/icon-m-common-favorite-mark";
+ else if (modelData.type == SpotifyPlaylist.Inbox)
+ staticIcon = "image://theme/icon-m-toolbar-directory-move-to-white-selected";
+ }
+ }
+
+ header: ViewHeader {
+ text: "Playlists" + (spotifySession.offlineMode ? " (Offline mode)" : "")
+ }
+
+ footer: Item {
+ height: visible ? (UI.LIST_ITEM_HEIGHT + separator.height) : 0
+ width: parent.width
+ visible: !spotifySession.offlineMode
+
+ Separator {
+ id: separator
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: row
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : 0.0
+ color: "#22FFFFFF"
+ }
+
+ Row {
+ id: row
+ width: parent.width
+ anchors.top: separator.bottom
+ anchors.bottom: parent.bottom
+ spacing: UI.LIST_ITEM_SPACING
+
+ Item {
+ id: iconItem
+ anchors.verticalCenter: parent.verticalCenter
+ visible: iconImage.source !== "" ? true : false
+ width: 40
+ height: 40
+
+ Image {
+ id: iconImage
+ anchors.centerIn: parent
+ source: theme.inverted ? "image://theme/icon-m-input-add" : "image://theme/icon-m-common-add"
+ opacity: 0.4
+ }
+ }
+
+ Label {
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: UI.FONT_FAMILY_BOLD
+ font.weight: Font.Bold
+ font.pixelSize: UI.LIST_TILE_SIZE
+ color: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+ opacity: 0.4
+ text: "New playlist"
+ }
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ newPlaylistSheet.playlistName = "";
+ newPlaylistSheet.open();
+ }
+ }
+ }
+
+ section.criteria: ViewSection.FirstCharacter
+ section.property: "listSection"
+ section.delegate: Separator {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ visible: section == "p"
+ }
+ }
+
+ ScrollDecorator { flickableItem: playlists }
+}
diff --git a/qml/QuickControls.qml b/qml/QuickControls.qml
new file mode 100644
index 0000000..1e3d7f6
--- /dev/null
+++ b/qml/QuickControls.qml
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: smallPlayer
+ width: parent.width
+ height: screen.currentOrientation == Screen.Portrait ? UI.HEADER_DEFAULT_HEIGHT_PORTRAIT : UI.HEADER_DEFAULT_HEIGHT_LANDSCAPE
+
+ MouseArea {
+ id: opener
+ anchors.fill: parent
+ onClicked: player.openRequested ? player.openRequested = false : player.openRequested = true
+ }
+
+ Image {
+ id: background
+ anchors.fill: parent
+ source: player.openRequested ? "images/player-quickcontrols-back-open.png" : "images/player-quickcontrols-back-closed.png"
+ opacity: opener.pressed ? 0.5 : 1.0
+ }
+
+ SequentialAnimation {
+ running: !player.openRequested && !player.hidden && !player.openedOnce
+ loops: 5
+ PauseAnimation { duration: 2000 }
+ ParallelAnimation {
+ NumberAnimation { target: arrowIcon; property: "opacity"; from: 0.0; to: 1.0; duration: 500 }
+ NumberAnimation { target: quickControls; property: "opacity"; from: 1.0; to: 0.0; duration: 500 }
+ }
+ NumberAnimation { target: background; property: "opacity"; from: 1.0; to: 0.7; duration: 375 }
+ NumberAnimation { target: background; property: "opacity"; from: 0.7; to: 1.0; duration: 375 }
+ NumberAnimation { target: background; property: "opacity"; from: 1.0; to: 0.7; duration: 375 }
+ NumberAnimation { target: background; property: "opacity"; from: 0.7; to: 1.0; duration: 375 }
+ ParallelAnimation {
+ NumberAnimation { target: arrowIcon; property: "opacity"; from: 1.0; to: 0.0; duration: 500 }
+ NumberAnimation { target: quickControls; property: "opacity"; from: 0.0; to: 1.0; duration: 500 }
+ }
+ PauseAnimation { duration: 8000 }
+ }
+
+ Image {
+ id: arrowIcon
+ anchors.centerIn: parent
+ source: player.openRequested ? "image://theme/icon-m-toolbar-up-selected" : "image://theme/icon-m-toolbar-down-selected"
+ opacity: player.openRequested ? 1.0 : 0.0
+ }
+
+ Item {
+ id: quickControls
+ anchors.fill: parent
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ opacity: player.openRequested ? 0.0 : 1.0
+
+ SpotifyImage {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ id: cover
+ spotifyId: spotifySession.currentTrack ? spotifySession.currentTrack.albumCoverId : ""
+ width: screen.currentOrientation == Screen.Portrait ? 48 : 0; height: width
+ visible: screen.currentOrientation == Screen.Portrait
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: 1
+ anchors.right: controls.left
+ anchors.left: cover.right
+ anchors.leftMargin: UI.MARGIN_XLARGE - 1
+ Label {
+ font.family: UI.FONT_FAMILY
+ font.weight: Font.Bold
+ font.pixelSize: UI.FONT_DEFAULT
+ anchors.left: parent.left
+ anchors.right: parent.right
+ color: UI.COLOR_INVERTED_FOREGROUND
+ elide: Text.ElideRight
+ text: spotifySession.currentTrack ? spotifySession.currentTrack.name : ""
+ }
+ Label {
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.weight: Font.Light
+ font.pixelSize: UI.FONT_LSMALL
+ anchors.left: parent.left
+ anchors.right: parent.right
+ color: UI.COLOR_INVERTED_FOREGROUND
+ elide: Text.ElideRight
+ text: spotifySession.currentTrack ? spotifySession.currentTrack.artists + " | " + spotifySession.currentTrack.album : ""
+ visible: screen.currentOrientation == Screen.Portrait
+ }
+ }
+
+ Row {
+ id: controls
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ spacing: -10
+
+ Item {
+ width: 64; height: 64
+ anchors.verticalCenter: parent.verticalCenter
+ Image {
+ anchors.centerIn: parent
+ source: "image://theme/icon-m-toolbar-mediacontrol-previous-selected"
+ opacity: previous.pressed ? 0.4 : 1.0
+ }
+ MouseArea {
+ id: previous
+ anchors.fill: parent
+ onClicked: spotifySession.playPrevious()
+ }
+ }
+
+ Item {
+ width: 64; height: 64
+ anchors.verticalCenter: parent.verticalCenter
+ Image {
+ anchors.centerIn: parent
+ source: spotifySession.isPlaying ? "image://theme/icon-m-toolbar-mediacontrol-pause-selected"
+ : "image://theme/icon-m-toolbar-mediacontrol-play-selected"
+ opacity: play.pressed ? 0.4 : 1.0
+ }
+ MouseArea {
+ id: play
+ anchors.fill: parent
+ onClicked: spotifySession.isPlaying ? spotifySession.pause() : spotifySession.resume()
+ }
+ }
+
+ Item {
+ width: 64; height: 64
+ anchors.verticalCenter: parent.verticalCenter
+ Image {
+ anchors.centerIn: parent
+ source: "image://theme/icon-m-toolbar-mediacontrol-next-selected"
+ opacity: next.pressed ? 0.4 : 1.0
+ }
+ MouseArea {
+ id: next
+ anchors.fill: parent
+ onClicked: spotifySession.playNext()
+ }
+ }
+ }
+ }
+}
diff --git a/qml/SearchPage.qml b/qml/SearchPage.qml
new file mode 100644
index 0000000..35ce002
--- /dev/null
+++ b/qml/SearchPage.qml
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+import "Utilities.js" as Utilities
+
+Page {
+ orientationLock: PageOrientation.LockPortrait
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ pageStack: parent
+ enabled: !spotifySession.offlineMode
+
+ Connections {
+ target: spotifySession
+ onOfflineModeChanged: {
+ if (spotifySession.offlineMode)
+ pageStack.pop(null);
+ }
+ onConnectionStatusChanged: {
+ if (spotifySession.connectionStatus != SpotifySession.LoggedIn)
+ pageStack.pop(null);
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: spotifySession.offlineMode
+ color: "#DD000000"
+ z: 500
+
+ Label {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "Search is not available in offline mode"
+ font.pixelSize: UI.FONT_XLARGE
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.weight: Font.Light
+ wrapMode: Text.WordWrap
+ width: parent.width - UI.MARGIN_XLARGE * 2
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+
+ SpotifySearch {
+ id: search
+ }
+
+ TrackMenu {
+ id: menu
+ deleteVisible: false
+ }
+
+ AlbumMenu {
+ id: albumMenu
+ playVisible: true
+ artistVisible: true
+ albumBrowse: SpotifyAlbumBrowse {
+ id: menuAlbumBrowse
+ onTracksChanged: albumMenu.open()
+ }
+ }
+
+ Column {
+ id: header
+ width: parent.width
+ anchors.top: parent.top
+ spacing: UI.MARGIN_XLARGE
+
+ Column {
+ width: parent.width
+ Selector {
+ id: selector
+ title: "Search"
+ titleFontFamily: UI.FONT_FAMILY_LIGHT
+ titleFontWeight: Font.Light
+ titleFontSize: UI.FONT_LARGE
+ selectedIndex: 0
+ model: ListModel {
+ ListElement { name: "Tracks" }
+ ListElement { name: "Albums" }
+ ListElement { name: "Artists" }
+ }
+ }
+ Separator {
+ width: parent.width
+ }
+ }
+
+ AdvancedTextField {
+ id: searchField
+ placeholderText: "Search"
+ width: parent.width
+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
+ showBusy: search.busy
+ platformSipAttributes: SipAttributes {
+ actionKeyLabel: "Close"
+ actionKeyEnabled: true
+ }
+ onTextChanged: {
+ search.query = Utilities.trim(text)
+ search.search()
+ }
+ Keys.onReturnPressed: { results.focus = true }
+ }
+
+ Separator {
+ width: parent.width
+ }
+ }
+
+ Item {
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+ anchors.topMargin: UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ clip: true
+
+ ListView {
+ id: results
+ anchors.fill: parent
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ onMovementStarted: results.focus = true
+
+ Component {
+ id: trackComponent
+ TrackDelegate {
+ name: modelData.name
+ artistAndAlbum: modelData.artists + " | " + modelData.album
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ available: modelData.isAvailable
+ onClicked: modelData.play()
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+ Component {
+ id: albumComponent
+ AlbumDelegate {
+ name: modelData.name
+ artist: modelData.artist
+ albumCover: modelData.coverId
+ onClicked: { mainPage.tabs.currentTab.push(Qt.resolvedUrl("AlbumPage.qml"), { album: modelData }) }
+ onPressAndHold: {
+ menuAlbumBrowse.album = modelData;
+ if (menuAlbumBrowse.totalDuration > 0)
+ albumMenu.open()
+ }
+ }
+ }
+ Component {
+ id: artistComponent
+ ArtistDelegate {
+ name: modelData.name
+ portrait: modelData.pictureId
+ onClicked: { mainPage.tabs.currentTab.push(Qt.resolvedUrl("ArtistPage.qml"), { artist: modelData }) }
+ }
+ }
+
+ Connections {
+ target: selector
+ onSelectedIndexChanged: results.updateResults()
+ }
+
+ Connections {
+ target: search
+ onResultsChanged: results.updateResults()
+ }
+
+ function updateResults() {
+ results.model = 0
+ results.delegate = null
+ if (selector.selectedIndex === 0) {
+ results.delegate = trackComponent
+ results.model = search.tracks
+ } else if (selector.selectedIndex == 1) {
+ results.delegate = albumComponent
+ results.model = search.albums
+ } else if (selector.selectedIndex == 2) {
+ results.delegate = artistComponent
+ results.model = search.artists
+ }
+ }
+ }
+
+ ScrollDecorator { flickableItem: results }
+ }
+}
diff --git a/qml/Selector.qml b/qml/Selector.qml
new file mode 100644
index 0000000..9308ca3
--- /dev/null
+++ b/qml/Selector.qml
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: selector
+
+ property alias title: titleText.text
+ property alias model: dialog.model
+ property alias selectedIndex: dialog.selectedIndex
+
+ property alias titleFontFamily: titleText.font.family
+ property alias titleFontWeight: titleText.font.weight
+ property alias titleFontSize: titleText.font.pixelSize
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ width: parent.width
+ height: UI.LIST_ITEM_HEIGHT
+
+ SelectionDialog {
+ id: dialog
+ titleText: selector.title
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : 0.0
+ color: "#22FFFFFF"
+ Behavior on opacity { NumberAnimation { duration: 50 } }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.right: icon.left
+
+ Label {
+ id: titleText
+ width: parent.width
+ font.family: UI.FONT_FAMILY_BOLD
+ font.weight: Font.Bold
+ font.pixelSize: UI.LIST_TILE_SIZE
+ elide: Text.ElideRight
+ color: titleColor
+ }
+ Label {
+ id: selectedValue
+ width: parent.width
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.pixelSize: UI.LIST_SUBTILE_SIZE
+ font.weight: Font.Light
+ elide: Text.ElideRight
+ color: subtitleColor
+ text: dialog.model.get(dialog.selectedIndex).name
+ }
+ }
+
+ Image {
+ id: icon
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ source: theme.inverted ? "image://theme/icon-m-textinput-combobox-arrow"
+ : "image://theme/icon-m-common-combobox-arrow"
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: {
+ dialog.open()
+ }
+ }
+}
diff --git a/qml/Separator.qml b/qml/Separator.qml
new file mode 100644
index 0000000..8785fd7
--- /dev/null
+++ b/qml/Separator.qml
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+
+Image {
+ source: theme.inverted ? "image://theme/meegotouch-groupheader-inverted-background"
+ : "image://theme/meegotouch-groupheader-background"
+}
diff --git a/qml/SettingsPage.qml b/qml/SettingsPage.qml
new file mode 100644
index 0000000..8e930bd
--- /dev/null
+++ b/qml/SettingsPage.qml
@@ -0,0 +1,232 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ id: settingsPage
+ orientationLock: PageOrientation.LockPortrait
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ ListView {
+ id: settingsFlickable
+ anchors.fill: parent
+
+ model: 1
+ delegate: Column {
+ id: settingsContainer
+ width: settingsPage.width
+ spacing: UI.MARGIN_XLARGE
+
+ ViewHeader {
+ id: header
+ text: "Settings"
+ }
+
+ Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Column {
+ width: parent.width
+
+ Item {
+ width: parent.width
+ height: UI.LIST_ITEM_HEIGHT
+
+ Label {
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: UI.FONT_FAMILY_BOLD
+ font.weight: Font.Bold
+ font.pixelSize: UI.LIST_TILE_SIZE
+ color: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+ text: "Offline mode"
+ }
+
+ Switch {
+ platformStyle: SwitchStyle {
+ switchOn: "image://theme/" + appWindow.themeColor + "-meegotouch-switch-on-inverted"
+ }
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ checked: spotifySession.offlineMode
+ onCheckedChanged: spotifySession.setOfflineMode(checked)
+ }
+ }
+
+ Label {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.pixelSize: UI.LIST_SUBTILE_SIZE
+ font.weight: Font.Light
+ color: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+ text: "When offline, only the playlists you've made available for offline listening will be playable."
+ }
+
+ Item {
+ width: parent.width
+ height: UI.MARGIN_XLARGE * 2
+ }
+ }
+
+ Selector {
+ title: "Stream"
+ model: ListModel {
+ ListElement { name: "Low bandwidth"; value: SpotifySession.LowQuality }
+ ListElement { name: "High bandwidth"; value: SpotifySession.HighQuality }
+ }
+ selectedIndex: spotifySession.streamingQuality == SpotifySession.LowQuality ? 0 : 1
+ onSelectedIndexChanged: spotifySession.streamingQuality = model.get(selectedIndex).value
+ }
+
+ Selector {
+ title: "Offline Sync"
+ model: ListModel {
+ ListElement { name: "Low Quality"; value: SpotifySession.LowQuality }
+ ListElement { name: "High Quality"; value: SpotifySession.HighQuality }
+ }
+ selectedIndex: spotifySession.syncQuality == SpotifySession.LowQuality ? 0 : 1
+ onSelectedIndexChanged: spotifySession.syncQuality = model.get(selectedIndex).value
+ }
+
+ Item {
+ width: parent.width
+ height: UI.LIST_ITEM_HEIGHT
+
+ Label {
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: UI.FONT_FAMILY_BOLD
+ font.weight: Font.Bold
+ font.pixelSize: UI.LIST_TILE_SIZE
+ color: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+ text: "Sync over 2G/3G"
+ }
+
+ Switch {
+ platformStyle: SwitchStyle {
+ switchOn: "image://theme/" + appWindow.themeColor + "-meegotouch-switch-on-inverted"
+ }
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ checked: spotifySession.syncOverMobile
+ onCheckedChanged: spotifySession.setSyncOverMobile(checked)
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: UI.MARGIN_XLARGE * 2
+
+ Separator {
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: UI.MARGIN_XLARGE
+ }
+
+ Button {
+ id: buttonAbout
+ text: "About"
+ platformStyle: ButtonStyle {
+ pressedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-pressed" + (position ? "-" + position : "")
+ disabledBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-disabled" + (position ? "-" + position : "")
+ checkedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-selected" + (position ? "-" + position : "")
+ checkedDisabledBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-disabled-selected" + (position ? "-" + position : "")
+ }
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: UI.PADDING_XXLARGE
+ anchors.rightMargin: UI.PADDING_XXLARGE
+
+ onClicked: {
+ aboutDialog.open();
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: UI.MARGIN_XLARGE
+ }
+
+ Button {
+ id: button
+ platformStyle: ButtonStyle {
+ pressedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-pressed" + (position ? "-" + position : "")
+ disabledBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-disabled" + (position ? "-" + position : "")
+ checkedBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-selected" + (position ? "-" + position : "")
+ checkedDisabledBackground: "image://theme/" + appWindow.themeColor + "-meegotouch-button-inverted-background-disabled-selected" + (position ? "-" + position : "")
+ }
+ text: "Log out " + (spotifySession.user ? spotifySession.user.canonicalName : "")
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: UI.PADDING_XXLARGE
+ anchors.rightMargin: UI.PADDING_XXLARGE
+
+ onClicked: {
+ spotifySession.logout(false);
+ }
+ }
+
+ Item {
+ width: parent.width
+ height: UI.MARGIN_XLARGE * 2
+ }
+ }
+ }
+ }
+
+ AboutDialog {
+ id: aboutDialog
+ }
+
+ ScrollDecorator { flickableItem: settingsFlickable }
+}
diff --git a/qml/SpotifyImage.qml b/qml/SpotifyImage.qml
new file mode 100644
index 0000000..27d6c5e
--- /dev/null
+++ b/qml/SpotifyImage.qml
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+
+Image {
+ property string spotifyId: ""
+ property string defaultImage: ""
+
+ source: spotifyId.length > 0 ? ("image://spotify/" + spotifyId) : defaultImage
+ asynchronous: true
+
+}
diff --git a/qml/ToplistPage.qml b/qml/ToplistPage.qml
new file mode 100644
index 0000000..018c42b
--- /dev/null
+++ b/qml/ToplistPage.qml
@@ -0,0 +1,307 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+import "Utilities.js" as Utilities
+
+Page {
+ orientationLock: PageOrientation.LockPortrait
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+ pageStack: parent
+ enabled: !spotifySession.offlineMode
+
+ onStatusChanged: if (status == PageStatus.Active && !spotifySession.offlineMode) toplist.updateResults()
+
+ Connections {
+ target: spotifySession
+ onOfflineModeChanged: {
+ if (spotifySession.offlineMode)
+ pageStack.pop(null);
+ else
+ toplist.updateResults()
+ }
+ onConnectionStatusChanged: {
+ if (spotifySession.connectionStatus != SpotifySession.LoggedIn)
+ pageStack.pop(null);
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: spotifySession.offlineMode
+ color: "#DD000000"
+ z: 500
+
+ Label {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "Not available in offline mode"
+ font.pixelSize: UI.FONT_XLARGE
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.weight: Font.Light
+ wrapMode: Text.WordWrap
+ width: parent.width - UI.MARGIN_XLARGE * 2
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+
+ SpotifyToplist {
+ id: toplist
+ }
+
+ TrackMenu {
+ id: menu
+ deleteVisible: false
+ }
+
+ AlbumMenu {
+ id: albumMenu
+ playVisible: true
+ artistVisible: true
+ albumBrowse: SpotifyAlbumBrowse {
+ id: menuAlbumBrowse
+ onTracksChanged: albumMenu.open()
+ }
+ }
+
+ Column {
+ id: whatsNew
+ width: parent.width
+ spacing: UI.MARGIN_XLARGE
+
+ ViewHeader {
+ text: "New releases"
+ }
+
+ SpotifySearch {
+ id: searchNew
+ query: "tag:new"
+ Component.onCompleted: search()
+ }
+
+ ListView {
+ id: newAlbumsView
+ width: parent.width
+ height: 112
+ orientation: ListView.Horizontal
+ model: searchNew.albums
+ clip: true
+ snapMode: ListView.SnapToItem
+ cacheBuffer: height * 2
+ pressDelay: 50
+
+ delegate: SpotifyImage {
+ spotifyId: modelData.coverId
+ height: newAlbumsView.height
+ width: height
+ clip: true
+ opacity: newAlbumMouse.pressed ? 0.3 : 1.0
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: newAlbumName.height + 2
+ color: "#BA202020"
+
+ Column {
+ id: newAlbumName
+ anchors.left: parent.left
+ anchors.leftMargin: 4
+ anchors.right: parent.right
+ anchors.rightMargin: 4
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: -3
+ Label {
+ text: modelData.name
+ font.pixelSize: UI.FONT_XSMALL
+ width: parent.width
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+ Label {
+ text: modelData.artist
+ font.pixelSize: UI.FONT_XXSMALL
+ width: parent.width
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+ }
+
+ MouseArea {
+ id: newAlbumMouse
+ anchors.fill: parent
+ onClicked: { mainPage.tabs.currentTab.push(Qt.resolvedUrl("AlbumPage.qml"), { album: modelData }) }
+ onPressAndHold: {
+ menuAlbumBrowse.album = modelData;
+ if (menuAlbumBrowse.totalDuration > 0)
+ albumMenu.open()
+ }
+ }
+ }
+ }
+ }
+
+ Column {
+ id: header
+ width: parent.width
+ anchors.top: whatsNew.bottom
+ anchors.topMargin: UI.MARGIN_XLARGE
+
+ Separator {
+ width: parent.width
+ }
+
+ Selector {
+ id: selector
+ title: "Top lists"
+ titleFontFamily: UI.FONT_FAMILY_LIGHT
+ titleFontWeight: Font.Light
+ titleFontSize: UI.FONT_LARGE
+ selectedIndex: 0
+ model: ListModel {
+ ListElement { name: "Tracks" }
+ ListElement { name: "Albums" }
+ ListElement { name: "Artists" }
+ }
+ }
+
+ Separator {
+ width: parent.width
+ }
+ }
+
+ Item {
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+ anchors.topMargin: UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ clip: true
+
+ ListView {
+ id: results
+ anchors.fill: parent
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ Component {
+ id: trackComponent
+ TrackDelegate {
+ name: modelData.name
+ artistAndAlbum: modelData.artists + " | " + modelData.album
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ coverId: modelData.albumCoverId
+ showIndex: true
+ available: modelData.isAvailable
+ onClicked: modelData.play()
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+ Component {
+ id: albumComponent
+ AlbumDelegate {
+ name: modelData.name
+ artist: modelData.artist
+ albumCover: modelData.coverId
+ showIndex: true
+ onClicked: { mainPage.tabs.currentTab.push(Qt.resolvedUrl("AlbumPage.qml"), { album: modelData }) }
+ onPressAndHold: {
+ menuAlbumBrowse.album = modelData;
+ if (menuAlbumBrowse.totalDuration > 0)
+ albumMenu.open()
+ }
+ }
+ }
+ Component {
+ id: artistComponent
+ ArtistDelegate {
+ name: modelData.name
+ portrait: modelData.pictureId
+ showIndex: true
+ onClicked: { mainPage.tabs.currentTab.push(Qt.resolvedUrl("ArtistPage.qml"), { artist: modelData }) }
+ }
+ }
+
+ Connections {
+ target: selector
+ onSelectedIndexChanged: results.updateResults()
+ }
+
+ Connections {
+ target: toplist
+ onResultsChanged: results.updateResults()
+ }
+
+ function updateResults() {
+ results.model = 0
+ results.delegate = null
+ if (selector.selectedIndex === 0) {
+ results.delegate = trackComponent
+ results.model = toplist.tracks
+ } else if (selector.selectedIndex == 1) {
+ results.delegate = albumComponent
+ results.model = toplist.albums
+ } else if (selector.selectedIndex == 2) {
+ results.delegate = artistComponent
+ results.model = toplist.artists
+ }
+ }
+ }
+
+ ScrollDecorator { flickableItem: results }
+ }
+
+ BusyIndicator {
+ anchors.centerIn: parent
+ visible: toplist.busy && results.count === 0
+ running: visible
+ BusyIndicatorStyle { size: "medium" }
+ }
+}
diff --git a/qml/TrackDelegate.qml b/qml/TrackDelegate.qml
new file mode 100644
index 0000000..b7fdfd8
--- /dev/null
+++ b/qml/TrackDelegate.qml
@@ -0,0 +1,220 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Item {
+ id: listItem
+
+ signal clicked
+ signal pressAndHold
+ property alias pressed: mouseArea.pressed
+ property alias name: mainText.text
+ property alias artistAndAlbum: subText.text
+ property alias duration: timing.text
+ property alias coverId: coverImage.spotifyId
+ property bool highlighted: false
+ property bool starred: false
+ property bool available: true
+ property bool pressAndHoldEnabled: true
+ property bool showIndex: false
+
+ property color highlightColor: UI.SPOTIFY_COLOR
+
+ property int titleSize: UI.LIST_TILE_SIZE
+ property string titleFont: UI.FONT_FAMILY_BOLD
+ property color titleColor: theme.inverted ? UI.LIST_TITLE_COLOR_INVERTED : UI.LIST_TITLE_COLOR
+
+ property int subtitleSize: UI.LIST_SUBTILE_SIZE
+ property string subtitleFont: UI.FONT_FAMILY_LIGHT
+ property color subtitleColor: theme.inverted ? UI.LIST_SUBTITLE_COLOR_INVERTED : UI.LIST_SUBTITLE_COLOR
+
+ property real backgroundOpacity: 0.0
+
+ height: UI.LIST_ITEM_HEIGHT
+ width: parent.width
+
+ SequentialAnimation {
+ id: backAnimation
+ property bool animEnded: false
+ running: mouseArea.pressed && listItem.pressAndHoldEnabled
+
+ ScriptAction { script: backAnimation.animEnded = false }
+ PauseAnimation { duration: 200 }
+ ParallelAnimation {
+ NumberAnimation { target: background; property: "opacity"; to: 0.4; duration: 300 }
+ ColorAnimation { target: mainText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: subText; property: "color"; to: "black"; duration: 300 }
+ ColorAnimation { target: timing; property: "color"; to: "black"; duration: 300 }
+ NumberAnimation { target: iconItem; property: "opacity"; to: 0.2; duration: 300 }
+ NumberAnimation { target: coverContainer; property: "opacity"; to: 0.2; duration: 300 }
+ }
+ PauseAnimation { duration: 100 }
+ ScriptAction { script: { backAnimation.animEnded = true; listItem.pressAndHold(); } }
+ onRunningChanged: {
+ if (!running) {
+ coverContainer.opacity = available ? 1.0 : 0.2
+ iconItem.opacity = 1.0
+ mainText.color = highlighted ? listItem.highlightColor : listItem.titleColor
+ subText.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ timing.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ }
+ }
+ }
+
+ onHighlightedChanged: {
+ mainText.color = highlighted ? listItem.highlightColor : listItem.titleColor
+ subText.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ timing.color = highlighted ? listItem.highlightColor : listItem.subtitleColor
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ // Fill page porders
+ anchors.leftMargin: -UI.MARGIN_XLARGE
+ anchors.rightMargin: -UI.MARGIN_XLARGE
+ opacity: mouseArea.pressed ? 1.0 : backgroundOpacity
+ color: "#22FFFFFF"
+ }
+
+ Label {
+ id: indexText
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: listItem.showIndex ? 48 : 0
+ text: (index + 1) + ". "
+ font.family: UI.FONT_FAMILY_LIGHT
+ font.pixelSize: UI.FONT_SMALL
+ horizontalAlignment: Text.AlignRight
+ visible: listItem.showIndex
+ }
+
+ Rectangle {
+ id: coverContainer
+ width: coverImage.spotifyId.length > 0 ? 64 : 0; height: width
+ anchors.left: indexText.right
+ anchors.verticalCenter: parent.verticalCenter
+ color: "#202020"
+ opacity: listItem.available ? 1.0 : 0.2
+
+ SpotifyImage {
+ id: coverImage
+ anchors.fill: parent
+ }
+ }
+
+ Column {
+ anchors.left: coverContainer.right
+ anchors.leftMargin: coverImage.spotifyId.length > 0 ? UI.MARGIN_XLARGE : 0
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: listItem.available ? 1.0 : 0.3
+
+ Item {
+ height: mainText.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Label {
+ id: mainText
+ anchors.left: parent.left
+ anchors.right: iconItem.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ font.family: listItem.titleFont
+ font.weight: Font.Bold
+ font.pixelSize: listItem.titleSize
+ color: highlighted ? listItem.highlightColor : listItem.titleColor
+ elide: Text.ElideRight
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ Image {
+ id: iconItem
+ anchors.right: parent.right
+ anchors.bottom: mainText.bottom
+ anchors.bottomMargin: 2
+ width: 34; height: width
+ smooth: true
+ visible: listItem.starred
+ source: "image://theme/icon-m-toolbar-favorite-mark-white"
+ }
+ }
+
+ Item {
+ height: subText.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Label {
+ id: subText
+ anchors.left: parent.left
+ anchors.right: timing.left
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ font.family: listItem.subtitleFont
+ font.pixelSize: listItem.subtitleSize
+ font.weight: Font.Light
+ color: highlighted ? listItem.highlightColor : listItem.subtitleColor
+ elide: Text.ElideRight
+ visible: text != ""
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ Label {
+ id: timing
+ font.family: listItem.subtitleFont
+ font.weight: Font.Light
+ font.pixelSize: listItem.subtitleSize
+ color: highlighted ? listItem.highlightColor : listItem.subtitleColor
+ anchors.right: parent.right
+ visible: text != ""
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ if (!backAnimation.animEnded)
+ listItem.clicked();
+ }
+ }
+}
diff --git a/qml/TrackMenu.qml b/qml/TrackMenu.qml
new file mode 100644
index 0000000..d0a042b
--- /dev/null
+++ b/qml/TrackMenu.qml
@@ -0,0 +1,166 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+
+MyMenu {
+ id: trackMenu
+
+
+ property variant track: null
+ property bool deleteVisible: false
+ property bool albumVisible: true
+ property bool markSeenVisible: false
+
+ property variant playlists: spotifySession.user ? spotifySession.user.playlists : null
+
+ layoutContentHeight: layout.height
+
+ NotificationBanner {
+ id: banner
+ }
+
+ SelectionDialog {
+ id: selectionDialog
+
+ property alias dialogModel: playlistsModel
+
+ ListModel {
+ id: playlistsModel
+ }
+
+ titleText: "Playlists"
+ parent: trackMenu.parent
+ model: playlistsModel
+ onAccepted: {
+ var playlistItem = model.get(selectionDialog.selectedIndex);
+ if (playlistItem.object) {
+ banner.text = "Track added to " + playlistItem.name;
+ playlistItem.object.add(track);
+ } else {
+ if (spotifySession.user.createPlaylistFromTrack(track)) {
+ banner.text = "Track added to new playlist";
+ } else {
+ banner.text = "Could not add track to new playlist";
+ }
+ }
+ banner.show();
+ }
+ }
+
+ QueryDialog {
+ id: confirmDeleteDialog
+ parent: trackMenu.parent
+ titleText: "Delete track?"
+ message: track ? track.name : ""
+ acceptButtonText: "Yes"
+ rejectButtonText: "No"
+ onAccepted: { track.removeFromPlaylist() }
+ }
+
+ MyMenuLayout {
+ id:layout
+
+ MyMenuItem {
+ id: seenItem
+ onClicked: { track.seen = !track.seen }
+ visible: trackMenu.markSeenVisible && !spotifySession.offlineMode;
+ }
+ MyMenuItem {
+ text: "Add to queue";
+ onClicked: { track.enqueue() }
+ }
+ MyMenuItem {
+ id: starItem
+ onClicked: { track.isStarred = !track.isStarred }
+ visible: !spotifySession.offlineMode
+ }
+ MyMenuItem {
+ text: "Album";
+ visible: trackMenu.albumVisible && !spotifySession.offlineMode
+ onClicked: {
+ mainPage.tabs.currentTab.push(Qt.resolvedUrl("AlbumPage.qml"), { album: track.albumObject })
+ }
+ }
+ MyMenuItem {
+ text: "Artist";
+ visible: !spotifySession.offlineMode
+ onClicked: {
+ mainPage.tabs.currentTab.push(Qt.resolvedUrl("ArtistPage.qml"), { artist: track.artistObject })
+ }
+ }
+ MyMenuItem {
+ text: "Add to playlist";
+ visible: !spotifySession.offlineMode
+ onClicked: { selectionDialog.selectedIndex = -1; selectionDialog.open(); }
+ }
+ MyMenuItem {
+ text: "Delete";
+ visible: trackMenu.deleteVisible && !spotifySession.offlineMode;
+ onClicked: { confirmDeleteDialog.open(); }
+ }
+ }
+
+ onPlaylistsChanged: {
+ selectionDialog.dialogModel.clear();
+
+ if (playlists === null)
+ return;
+
+ for (var i in trackMenu.playlists) {
+ if (trackMenu.playlists[i].type == SpotifyPlaylist.Playlist && spotifySession.user.canModifyPlaylist(trackMenu.playlists[i]))
+ selectionDialog.dialogModel.append({"name": trackMenu.playlists[i].name, "object": trackMenu.playlists[i] })
+ }
+ selectionDialog.dialogModel.append({"name": "New playlist" });
+
+ selectionDialog.model = 0;
+ selectionDialog.model = selectionDialog.dialogModel;
+ }
+
+ onStatusChanged: {
+ if (status == DialogStatus.Opening && track) {
+ starItem.text = track.isStarred ? "Unstar" : "Star";
+ seenItem.text = "Mark as " + (track.seen ? "unseen" : "seen");
+ }
+ }
+}
diff --git a/qml/TracklistPage.qml b/qml/TracklistPage.qml
new file mode 100644
index 0000000..10edebb
--- /dev/null
+++ b/qml/TracklistPage.qml
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+import "UIConstants.js" as UI
+
+Page {
+ id: tracklistPage
+ property variant playlist
+ orientationLock: PageOrientation.LockPortrait
+ anchors.rightMargin: UI.MARGIN_XLARGE
+ anchors.leftMargin: UI.MARGIN_XLARGE
+
+ TrackMenu {
+ id: menu
+ deleteVisible: playlist && spotifySession.user ? (playlist.type != SpotifyPlaylist.Starred && spotifySession.user.canModifyPlaylist(playlist))
+ : false
+ markSeenVisible: playlist && playlist.type == SpotifyPlaylist.Inbox
+ }
+
+ Component {
+ id: trackDelegate
+ TrackDelegate {
+ name: modelData.name
+ artistAndAlbum: modelData.artists + " | " + modelData.album
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ available: modelData.isAvailable
+ onClicked: {
+ modelData.play()
+ }
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+
+ Component {
+ id: inboxDelegate
+ InboxTrackDelegate {
+ name: modelData.name
+ artistAndAlbum: modelData.artists + " | " + modelData.album
+ creatorAndDate: modelData.creator + " | " + Qt.formatDateTime(modelData.creationDate)
+ duration: modelData.duration
+ highlighted: modelData.isCurrentPlayingTrack
+ starred: modelData.isStarred
+ available: modelData.isAvailable
+ onClicked: {
+ modelData.play()
+ }
+ seen: modelData.seen
+ onPressAndHold: { menu.track = modelData; menu.open(); }
+ }
+ }
+
+ ListView {
+ id: tracks
+ anchors.fill: parent
+
+ highlightMoveDuration: 1
+ model: playlist.tracks
+ header: ViewHeader {
+ text: (playlist.type == SpotifyPlaylist.Playlist ? playlist.name
+ : (playlist.type == SpotifyPlaylist.Starred ? "Starred"
+ : "Inbox"))
+ }
+
+ Component.onCompleted: {
+ tracks.delegate = playlist.type == SpotifyPlaylist.Inbox ? inboxDelegate : trackDelegate
+// if (playlist.isCurrentPlaylist()) {
+// for (var i in playlist.tracks) {
+// if (playlist.tracks[i].isCurrentPlayingTrack) {
+// positionViewAtIndex(i, ListView.Center)
+// return;
+// }
+// }
+// }
+ }
+ }
+
+ Connections {
+ target: playlist
+ onPlaylistDestroyed: {
+ playlistsTab.pop(null);
+ }
+ }
+
+ ScrollDecorator { flickableItem: tracks }
+}
diff --git a/qml/UIConstants.js b/qml/UIConstants.js
new file mode 100644
index 0000000..553fdcf
--- /dev/null
+++ b/qml/UIConstants.js
@@ -0,0 +1,182 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+.pragma library
+
+var FONT_FAMILY = "Nokia Pure Text";
+var FONT_DEFAULT_SIZE = 24; // DEPRECATED
+
+var FONT_XLARGE = 32;
+var FONT_LARGE = 28;
+var FONT_SLARGE = 26;
+var FONT_DEFAULT = 24;
+var FONT_LSMALL = 22;
+var FONT_SMALL = 20;
+var FONT_XSMALL = 18;
+var FONT_XXSMALL = 16;
+
+var SPOTIFY_COLOR = "#7AB800";
+var SPOTIFY_ORANGE = "#EC5810";
+
+var COLOR_FOREGROUND = "#191919"; // Text color
+var COLOR_SECONDARY_FOREGROUND = "#8c8c8c"; // Secondary text
+var COLOR_BACKGROUND = "#E0E1E2"; // Background
+var COLOR_SELECT = "#4591ff"; //Selected item background
+
+var COLOR_INVERTED_FOREGROUND = "#ffffff"; // Text color
+var COLOR_INVERTED_SECONDARY_FOREGROUND = "#8c8c8c"; // Secondary text
+var COLOR_INVERTED_BACKGROUND = "#000000"; // Background
+
+var COLOR_DISABLED_FOREGROUND = "#b2b2b4";
+
+var COLOR_BUTTON_FOREGROUND = "#000000" //text color
+var COLOR_BUTTON_INVERTED_FOREGROUND = "#ffffff" //inverted text color
+var COLOR_BUTTON_SECONDARY_FOREGROUND = "#8c8c8c" //secondary text
+var COLOR_BUTTON_DISABLED_FOREGROUND = "#B2B2B4" //disabled text
+var COLOR_BUTTON_BACKGROUND = "#000000" //background
+
+var SIZE_ICON_DEFAULT = 32;
+var SIZE_ICON_LARGE = 48;
+
+var CORNER_MARGINS = 22;
+
+var MARGIN_DEFAULT = 0;
+var MARGIN_XLARGE = 16;
+
+// Distance in pixels from the widget bounding box inside which a release
+// event would still be accepted and trigger the widget
+var RELEASE_MISS_DELTA = 30;
+
+var OPACITY_ENABLED = 1.0;
+var OPACITY_DISABLED = 0.5;
+var SIZE_BUTTON = 64;
+
+var PADDING_XSMALL = 2;
+var PADDING_SMALL = 4;
+var PADDING_MEDIUM = 6;
+var PADDING_LARGE = 8;
+var PADDING_DOUBLE = 12;
+var PADDING_XLARGE = 16;
+var PADDING_XXLARGE = 24;
+
+var SCROLLDECORATOR_SHORT_MARGIN = 8;
+var SCROLLDECORATOR_LONG_MARGIN = 4;
+
+var TOUCH_EXPANSION_MARGIN = -4;
+
+var BUTTON_WIDTH = 322;
+var BUTTON_HEIGHT = 51;
+var BUTTON_LABEL_MARGIN = 10;
+
+var FIELD_DEFAULT_HEIGHT = 52;
+
+//Common UI layouts
+var DEFAULT_MARGIN = 16;
+var BUTTON_SPACING = 6;
+var HEADER_DEFAULT_HEIGHT_PORTRAIT = 72;
+var HEADER_DEFAULT_HEIGHT_LANDSCAPE = 46;
+var HEADER_DEFAULT_TOP_SPACING_PORTRAIT = 20;
+var HEADER_DEFAULT_BOTTOM_SPACING_PORTRAIT = 20;
+var HEADER_DEFAULT_TOP_SPACING_LANDSCAPE = 16;
+var HEADER_DEFAULT_BOTTOM_SPACING_LANDSCAPE = 14;
+var LIST_ITEM_HEIGHT_SMALL = 64;
+var LIST_ITEM_HEIGHT_DEFAULT = 80;
+
+/* Margins */
+var INDENT_DEFAULT = 16;
+var CORNER_MARGINS = 22;
+var MARGIN_DEFAULT = 0;
+var MARGIN_XLARGE = 16;
+
+// ListDelegate
+var LIST_ITEM_MARGIN = 18
+var LIST_ITEM_SPACING = 16
+var LIST_ITEM_HEIGHT = 80
+var LIST_ICON_SIZE = 64
+var LIST_TILE_SIZE = 26
+var LIST_TITLE_COLOR = "#282828"
+var LIST_TITLE_COLOR_INVERTED = "#ffffff"
+var LIST_SUBTILE_SIZE = 22
+var LIST_SUBTITLE_COLOR = "#505050"
+var LIST_SUBTITLE_COLOR_INVERTED = "#b8b8b8"
+
+/* Font properties */
+var FONT_FAMILY = "Nokia Pure Text";
+var FONT_FAMILY_BOLD = "Nokia Pure Text";
+var FONT_FAMILY_LIGHT = "Nokia Pure Text Light";
+var FONT_DEFAULT_SIZE = 24;
+var FONT_LIGHT_SIZE = 22;
+
+/* TUMBLER properties */
+var TUMBLER_COLOR_TEXT = "#FFFFFF";
+var TUMBLER_COLOR_LABEL = "#8C8C8C";
+var TUMBLER_COLOR = "#000000";
+var TUMBLER_OPACITY_FULL = 1.0;
+var TUMBLER_OPACITY = 0.4;
+var TUMBLER_OPACITY_LOW = 0.1;
+var TUMBLER_FLICK_VELOCITY = 700;
+var TUMBLER_ROW_HEIGHT = 64;
+var TUMBLER_LABEL_HEIGHT = 54;
+var TUMBLER_MARGIN = 16;
+var TUMBLER_BORDER_MARGIN = 1;
+var TUMBLER_WIDTH = 344;
+var TUMBLER_HEIGHT_PORTRAIT = 256;
+var TUMBLER_HEIGHT_LANDSCAPE = 192;
+
+/* Button styles */
+// Normal
+var COLOR_BUTTON_FOREGROUND = "#191919"; // Text color
+var COLOR_BUTTON_SECONDARY_FOREGROUND = "#8c8c8c"; // Pressed
+var COLOR_BUTTON_DISABLED_FOREGROUND = "#b2b2b4"; // Disabled
+// Inverted
+var COLOR_BUTTON_INVERTED_FOREGROUND = "#FFFFFF";
+var COLOR_BUTTON_INVERTED_SECONDARY_FOREGROUND = "#8c8c8c"; // Pressed
+var COLOR_BUTTON_INVERTED_DISABLED_FOREGROUND = "#f5f5f5"; // Disabled
+
+var SIZE_BUTTON = 51;
+var SIZE_SMALL_BUTTON = 43;
+var WIDTH_SMALL_BUTTON = 122;
+var WIDTH_TUMBLER_BUTTON = 222;
+
+var FONT_BOLD_BUTTON = true;
+
+var INFO_BANNER_OPACITY = 0.9
+var INFO_BANNER_LETTER_SPACING = -1.2
+
diff --git a/qml/Utilities.js b/qml/Utilities.js
new file mode 100644
index 0000000..d38788d
--- /dev/null
+++ b/qml/Utilities.js
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+.pragma library
+
+function lTrim(value) {
+
+ var re = /\s*((\S+\s*)*)/;
+ return value.replace(re, "$1");
+
+}
+
+function rTrim(value) {
+
+ var re = /((\s*\S+)*)\s*/;
+ return value.replace(re, "$1");
+
+}
+
+function trim(value) {
+
+ return lTrim(rTrim(value));
+
+}
diff --git a/qml/ViewHeader.qml b/qml/ViewHeader.qml
new file mode 100644
index 0000000..435bbf4
--- /dev/null
+++ b/qml/ViewHeader.qml
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import "UIConstants.js" as UI
+
+Rectangle {
+ property alias text: title.text
+ property alias secondaryText: title2.text
+ property real contentOpacity: 1.0
+ property int contentMargins: 0
+
+ width: parent ? parent.width : 0
+ height: screen.currentOrientation == Screen.Portrait ? UI.HEADER_DEFAULT_HEIGHT_PORTRAIT : UI.HEADER_DEFAULT_HEIGHT_LANDSCAPE
+ anchors.horizontalCenter: parent.horizontalCenter
+ color: "transparent"
+
+ Column {
+ anchors.left: parent.left
+ anchors.leftMargin: contentMargins
+ anchors.right: parent.right
+ anchors.rightMargin: contentMargins
+ anchors.verticalCenter: parent.verticalCenter
+
+ Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ id: title
+ verticalAlignment: Text.AlignVCenter
+ color: theme.inverted ? UI.COLOR_INVERTED_FOREGROUND : UI.COLOR_FOREGROUND
+ font.pixelSize: UI.FONT_LARGE
+ font.family: UI.FONT_FAMILY_LIGHT
+ elide: Text.ElideRight
+ opacity: contentOpacity
+ }
+ Label {
+ id: title2
+ anchors.left: text.length > 0 ? parent.left : undefined
+ anchors.right: text.length > 0 ? parent.right : undefined
+ verticalAlignment: Text.AlignVCenter
+ color: theme.inverted ? UI.COLOR_INVERTED_SECONDARY_FOREGROUND : UI.LIST_SUBTITLE_COLOR
+ font.pixelSize: UI.FONT_SMALL
+ elide: Text.ElideRight
+ opacity: contentOpacity
+ }
+ }
+
+ Separator {
+ anchors.left: parent.left
+ anchors.leftMargin: contentMargins
+ anchors.right: parent.right
+ anchors.rightMargin: contentMargins
+ anchors.bottom: parent.bottom
+ }
+}
diff --git a/qml/images/icon-m-collaborative-playlist.png b/qml/images/icon-m-collaborative-playlist.png
new file mode 100644
index 0000000..43fcd44
--- /dev/null
+++ b/qml/images/icon-m-collaborative-playlist.png
Binary files differ
diff --git a/qml/images/icon-m-common-green.png b/qml/images/icon-m-common-green.png
new file mode 100644
index 0000000..97a2a78
--- /dev/null
+++ b/qml/images/icon-m-common-green.png
Binary files differ
diff --git a/qml/images/icon-m-toolbar-repeat-white-selected.png b/qml/images/icon-m-toolbar-repeat-white-selected.png
new file mode 100644
index 0000000..75fbf3d
--- /dev/null
+++ b/qml/images/icon-m-toolbar-repeat-white-selected.png
Binary files differ
diff --git a/qml/images/icon-m-toolbar-shuffle-white-selected.png b/qml/images/icon-m-toolbar-shuffle-white-selected.png
new file mode 100644
index 0000000..2478592
--- /dev/null
+++ b/qml/images/icon-m-toolbar-shuffle-white-selected.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background-pressed-vertical-bottom.png b/qml/images/meegotouch-list-inverted-background-pressed-vertical-bottom.png
new file mode 100644
index 0000000..64753f3
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background-pressed-vertical-bottom.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background-pressed-vertical-center.png b/qml/images/meegotouch-list-inverted-background-pressed-vertical-center.png
new file mode 100644
index 0000000..81b096c
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background-pressed-vertical-center.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background-pressed-vertical-top.png b/qml/images/meegotouch-list-inverted-background-pressed-vertical-top.png
new file mode 100644
index 0000000..d08e746
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background-pressed-vertical-top.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background-vertical-bottom.png b/qml/images/meegotouch-list-inverted-background-vertical-bottom.png
new file mode 100644
index 0000000..8a85637
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background-vertical-bottom.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background-vertical-center.png b/qml/images/meegotouch-list-inverted-background-vertical-center.png
new file mode 100644
index 0000000..15f5ed7
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background-vertical-center.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background-vertical-top.png b/qml/images/meegotouch-list-inverted-background-vertical-top.png
new file mode 100644
index 0000000..2b2f14c
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background-vertical-top.png
Binary files differ
diff --git a/qml/images/meegotouch-list-inverted-background.png b/qml/images/meegotouch-list-inverted-background.png
new file mode 100644
index 0000000..43ea2db
--- /dev/null
+++ b/qml/images/meegotouch-list-inverted-background.png
Binary files differ
diff --git a/qml/images/meegotouch-menu-background-inverted.png b/qml/images/meegotouch-menu-background-inverted.png
new file mode 100644
index 0000000..9d0efc4
--- /dev/null
+++ b/qml/images/meegotouch-menu-background-inverted.png
Binary files differ
diff --git a/qml/images/meegotouch-progressindicator-bar-known-texture.png b/qml/images/meegotouch-progressindicator-bar-known-texture.png
new file mode 100644
index 0000000..d485fa1
--- /dev/null
+++ b/qml/images/meegotouch-progressindicator-bar-known-texture.png
Binary files differ
diff --git a/qml/images/meegotouch-sheet-header-inverted-background.png b/qml/images/meegotouch-sheet-header-inverted-background.png
new file mode 100644
index 0000000..3c1540e
--- /dev/null
+++ b/qml/images/meegotouch-sheet-header-inverted-background.png
Binary files differ
diff --git a/qml/images/meegotouch-slider-elapsed-background-horizontal.png b/qml/images/meegotouch-slider-elapsed-background-horizontal.png
new file mode 100644
index 0000000..e1c5e76
--- /dev/null
+++ b/qml/images/meegotouch-slider-elapsed-background-horizontal.png
Binary files differ
diff --git a/qml/images/meegotouch-slider-handle-background-horizontal.png b/qml/images/meegotouch-slider-handle-background-horizontal.png
new file mode 100644
index 0000000..589a227
--- /dev/null
+++ b/qml/images/meegotouch-slider-handle-background-horizontal.png
Binary files differ
diff --git a/qml/images/meegotouch-slider-handle-background-pressed-horizontal.png b/qml/images/meegotouch-slider-handle-background-pressed-horizontal.png
new file mode 100644
index 0000000..ddd37d3
--- /dev/null
+++ b/qml/images/meegotouch-slider-handle-background-pressed-horizontal.png
Binary files differ
diff --git a/qml/images/meespot-logo.png b/qml/images/meespot-logo.png
new file mode 100644
index 0000000..c9eebca
--- /dev/null
+++ b/qml/images/meespot-logo.png
Binary files differ
diff --git a/qml/images/player-quickcontrols-back-closed.png b/qml/images/player-quickcontrols-back-closed.png
new file mode 100644
index 0000000..2ad1af8
--- /dev/null
+++ b/qml/images/player-quickcontrols-back-closed.png
Binary files differ
diff --git a/qml/images/player-quickcontrols-back-open.png b/qml/images/player-quickcontrols-back-open.png
new file mode 100644
index 0000000..7db7cb6
--- /dev/null
+++ b/qml/images/player-quickcontrols-back-open.png
Binary files differ
diff --git a/qml/images/spotify-core-logo-128x128.png b/qml/images/spotify-core-logo-128x128.png
new file mode 100644
index 0000000..e52ecc4
--- /dev/null
+++ b/qml/images/spotify-core-logo-128x128.png
Binary files differ
diff --git a/qml/main.qml b/qml/main.qml
new file mode 100644
index 0000000..6c0a52e
--- /dev/null
+++ b/qml/main.qml
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Yoann Lopes (yoann.lopes@nokia.com)
+**
+** This file is part of the MeeSpot project.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its
+** contributors may be used to endorse or promote products derived from
+** this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+** FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+
+import QtQuick 1.1
+import com.meego 1.0
+import QtSpotify 1.0
+
+PageStackWindow {
+ id: appWindow
+ property string themeColor
+ style: PageStackWindowStyle {
+ background: theme.inverted ? "image://theme/meegotouch-video-background"
+ : "image://theme/meegotouch-applicationpage-background"
+ backgroundFillMode: Image.Stretch
+ }
+
+ initialPage: spotifySession.isLoggedIn ? mainPage : loginPage
+
+ Component {
+ id: mainPage
+ MainPage {
+ id: mainPageItem
+ onToolsChanged: appWindow.pageStack.toolBar.setTools(mainPageItem.tools, "replace")
+ }
+ }
+
+ Component {
+ id: loginPage
+ LoginPage { }
+ }
+
+ Component.onCompleted: {
+ theme.inverted = true;
+ themeColor = "color2"
+ if (!spotifySession.isOnline && (!spotifySession.user || !spotifySession.offlineMode))
+ openConnection();
+ }
+
+ function openConnection() {
+ var xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://m.google.com"); //force opening a connection
+ xhr.send();
+ }
+
+ Connections {
+ target: spotifySession
+ onIsOnlineChanged: {
+ if (!spotifySession.isOnline && (!spotifySession.user || !spotifySession.offlineMode))
+ openConnection();
+ }
+ onOfflineModeChanged: {
+ if (!spotifySession.isOnline && (!spotifySession.user || !spotifySession.offlineMode))
+ openConnection();
+ }
+ onPendingConnectionRequestChanged: {
+ if (!spotifySession.pendingConnectionRequest && spotifySession.isLoggedIn) {
+ pageStack.replace(mainPage)
+ } else if (spotifySession.pendingConnectionRequest && spotifySession.isLoggedIn) {
+ pageStack.replace(loginPage)
+ }
+ }
+ }
+}
diff --git a/qtc_packaging/debian_harmattan/README b/qtc_packaging/debian_harmattan/README
new file mode 100644
index 0000000..5f0c464
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/README
@@ -0,0 +1,6 @@
+The Debian Package meespot
+----------------------------
+
+No comment
+
+ -- Yoann Lopes <yoann.lopes@gmail.com> Tue, 4 Oct 2011 12:42:43 +0200
diff --git a/qtc_packaging/debian_harmattan/changelog b/qtc_packaging/debian_harmattan/changelog
new file mode 100644
index 0000000..c5846f8
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/changelog
@@ -0,0 +1,12 @@
+meespot (1.1.0) unstable; urgency=low
+
+ * Now based on libspotify 9.
+ * Support for offline mode.
+
+ -- Yoann Lopes <yoann.lopes@gmail.com> Sat, 15 Oct 2011 16:18:12 +0200
+
+meespot (1.0.0) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Yoann Lopes <yoann.lopes@gmail.com> Tue, 4 Oct 2011 12:42:43 +0200
diff --git a/qtc_packaging/debian_harmattan/compat b/qtc_packaging/debian_harmattan/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/compat
@@ -0,0 +1 @@
+7
diff --git a/qtc_packaging/debian_harmattan/control b/qtc_packaging/debian_harmattan/control
new file mode 100644
index 0000000..c577761
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/control
@@ -0,0 +1,15 @@
+Source: meespot
+Section: user/other
+Priority: optional
+Maintainer: Yoann Lopes <yoann.lopes@gmail.com>
+Build-Depends: debhelper (>= 5), libqt4-dev
+Standards-Version: 3.7.3
+Homepage: <insert the upstream URL, if relevant>
+
+Package: meespot
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Spotify client for Nokia Meego.
+ MeeSpot brings Spotify music streaming to the N9. You can listen to your playlists or search for new tracks, albums and artists. You can synchronise your playlists for offline listening when no Internet connection is available. You need a Spotify Premium account to use the application, if you don't have one already, get one at www.spotify.com.\n\nThis product uses SPOTIFY CORE but is not endorsed, certified or otherwise approved in any way by Spotify. Spotify is the registered trade mark of the Spotify Group.
+XSBC-Maemo-Display-Name: MeeSpot
+XB-Maemo-Icon-26: iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAPOgAADxIBymR1aAAAEiRJREFUeJzlmnmUVNWdxz9vq627eqObHZq12QIiEeMaiEcFEo2JiToniQOSYJYx2xgSndE5E7M6E43JZLLohJEwWRFEUESNUTRRA4oGhIZGlm6WbpZeq7q2996988d7r+pV9VbVgcwf8zvnnnr1lnt/v+/97e/B/3NSzsekT+371HXBQGKFglVh6InpAEEjPqmUOdJm+VEA04oclOg96Uzk0Q/M/q8nzzWvwwZg6/7PLjT07tuDRsf1QT0xKmO3Y4s4lujGknGktM4lnyiKjq6Uo6uVaGo5AW0EaStyKm3WbDGtyoffP/MnO4c173Ae2tq4cmVN9ODPhUxhinYydjuW6B7OVMMmXa0koI3AUEegKiE6YtM/+f5Za9aUOk9JAGxtXLkyHGy9J2z0TI6b+zHt9lLXOy9kaCMoN2aSNCuOJNNjvlkKEEUD8EzTh7aVh04vMUUHCfMIQqaHx+15IlUJEjEmY6g1xFMjn1nSsGlpMc8NCcDmvTd/t7Ks5WudqdfPuV2fL1IUnerQRXT3Trz/g3N+d9eg9w52cfOem78bLW/+WsJsJmW1nVsuzzOF9NFEjHpi8fr7Pzh3YBAGBGDDno98p6rs2F29mcOk7bPnh8vzTEGtlrLAFLp6J3z3I3M33N3fPQMC8Ow7C2XCbCFlnTp/HP4NKKSPImJM5NppO/uVtd+T699c+nRGfW6plPb55e5vRIqiERDXbLvpwm3L+lwrPLHu9Q8trypvebQjsaukRaT0DvJ+8q8NymQ/jCkDXy+VaiIL6IpPXHHrRZvW+s/rhTeGjPZ701YXYgim84SSzn9vIMk9L/u5vx/KCqg4x/6B8tcDkra6CBll9wJ5AORNs/bP1y+viB57tDO5DyEzg04ohSubBCFA2M45IXPnPDD6CC8LV+4LgKqCouZAUDX3WM0Ho1ggVCVAdXg2PbEJK5a/Z0sWhOzja7Z/eEG05tAbsfRRMnbPwIK7QtkW2LYj+JjoYi6beh8Ro45IsBZF0Yrjaggy7QTJTDvxVCu7mn9AZ3I/scwRVBV0wweImgNiMEACWgXR4CRiHVPfvXLR47vAZwJqIL7KtNOkrIGFh5yaWxZYlsKcUZ/ivTO+iaoY50DkfDK0CEY4QkV4AmOrf0lP8hjHO17heOcfaTr7SzTN1RQNNH1ozUhZPYT0NGogvgr4LPgAUIzOG1JWD/ZQtu+qdsaCIKOZP/Fz50X4/qgiPIHZ425h9rhbqGuez4G2x2iL70DTJQb5JqKo/c+RsnrQjM4bKARA05JjetM9CDE4E9K1d8uEiuBowkbNORKvNLqw/pNcMHE5Ta1PcvD0Fo50PY6m5zRCkQNrQUWwZoz3P3vLwzvGy1j6+JALS+HYfjIGmaTG/PGfYsmF9xIwIgCcaj/E6c6j7GrczNnuFjpjJzGtJJ3x43l2Kn3RQVMMqqPjiEbqqKmcwNjamTTUX8aomilURUcXBUgs1caWv9xOa/yPaDoYAbIOtZCiwfHcfvHx/MDyH6/UyqQ5dMrrAZCKQzoJmYTGtJHLGBGZwamOIxxseY3u3tMoipVzULi7opDn/KVvTiHIRQ8BuhqmOjqB+tHzGVk9mWWXfZ5gIDIob/uOP8ETu29DD0Ag7AujBRQ2avn8ZWcV8JlAxkoMGfvBF+9de0Oz2Xf8SVK9TyLcxNEIg6b5wpjqqCbQf+7pE1y6x7aZpD3RxJmDTUgBuw5s5cKGZcyZsphpExaiqn0jTciowTQdsIXwhcwCyliJ7LEC8P2nFy9JhV/aJuUQDsADQEAmBWYarDSYGcgkyQIAubjtT2zyV80HwG8SXpj1QLEtZx3bhpBexaz6xaz60H8SMMJ50+x4Zx1P7f4iRghCZT4eCkhRVELJ9y798rIXn9EBFC12q2kPLXyWQQANtKDziw6oUBGaQGVoPHWV07m0YQUBvZxIsIqQEUXXggPOadopOuPHyFi9nOjYw8mO3ew7sZXuRGt2TZHNO7rYd2oT//DQJnQ1zNyp11AdHUNj61OkZStGBFQJlgB1ABMAQViL3Qo4AAhE5VDevxAEd6McO1bgugXfYUrdlYypnl38RC4ZWoiRldMBGD9iPgCLYnfwzqk/crr7AI0nnuVUdxOa6uyqqjlrCjvJ7ubNKCoEI47de9rlmdJAiZFAVILrA2wt02CXUvi5ub43wODyhk+XLPhgVBOt5+JoPQCXz7idp968j6a27cRTZ1A10FXHPABQQA+BGgA0kCoInFA4ENlapgFcAKQtinKAfhISbAm2gHKjuFA1XKouH88nrnyYtq4D/KV5M281P86p7v3ORbdu0AKg6qDozrmhAJCuyTsABJINdrI0pmzPOZnQ3ttGT+IMFZE6MlaKE2cO8Oe9T3Ck9S90xFpJprrp7G3tEwMVFEBlRMV4xtXNYFxtA9MnLKSmYhzTxi3os+boqhmMrlrNkgtW09rZyHN7H2Bv62YU3UIP+Jye6oZVhfy63L98ONng4gff/kO97Ew2lwaABbbpDDMFFcygOlJPd89ZTp45TDzVlStpCwqWLBP+8tnORZhQIMpV82+lYcJ7mDftfYSD5f3yEEud4Wcv3sSpxG6MoAtAQbU4EFWH6/mnq5oVxwlKWVTTIp/73CKKCqe6DnCi/QDCcszDCOXivwdCnyzIc6bClwcIsESMp3f+mG07H2ZMzXTmTlnM3y/9BkpBgh8N1TGhegGtsd1588pBdt4j4QrsOEEphyyC+kzgDqmC1EANgqY6Nqj6nJO/pu+PPA1Q3EwwmxVqIGyLE12NHNvZSFfvWRZdcAvzp1/lk1XSHjuBZYPualOxfRLbD4AlJFYJYdBZ3EFaqs4satABYlx0FgE9yoL665hUdyGGFiISrKSqbDSGFkRTc5WjaafJWAmSmRid8ZOc6Gikqe1VTncf4vCpN7MmIWzYcWQ9rx1cj5RQHq5m7Mh6WuONqEYaIwSW7Yv7RaBgCR8AUgzPBLx8AGB05UyWzvkKs8YsJhKoQFX7dNv6kKEFMbQgZcFqaqMTmT7mEhbPuQ1bmGx760e0nNnNnpbnSZpxFEDoDiAp0UlLRyd60El7Ud0NKUEG6QfAGoYJePbr5QLvm/FZFk6+scRJ+idNNfjAgi+TMns52XGAl/f/ij+8vQa/B1ADoBiOyUkVhOK6pSLlsPwmIIQcsg/Qh6SvcLGhLDB4X6C79wwZK4VlZxBSoqkaAT1E0IgQCUb7ODiAkFHGlFELmDJqARXhOnYcepwz8WZsmUYzXK/ven4hQKU4B+jJDK613LlltOxOlfjqy9cMFSaQijK+Yh4hrZKeeCd7D7+GROYVRNmo4fHoL4KE07+fWDuTkVX1rFjydcaOmNpn2YyV5EfPL2d/+++dNphaRLXZD1WGRvPA9W1KNgqUmgl66bDEiQZpK0Zj65+wTadwUQJuH8ADwF+augh4IOTKYZvm9r0cPbOXphO7uHzODSycsZT5Uxdllw3oYSbXvpvG9t87u+0OIWGwxKeQ7D4m8Ff4AKk4qqga7voqKF5t4e/t97M7/jDo5QFCQFeqjS07fsb2PRu4YMoiVt/0CADJTJyDbTsR0rF31eOlBOE9mbMAWEJSZDXcB4DsUAFfL04qebe62ZnqO+cu6DGuug5MunmEm9L2pM/y8r4N1Dw3krrKcRzvbGT3sZcxygDhgl9k6PNTfhhMqPtthZklye9lcu6OCemYQk10AtPrFnLTxXcztrqvDQ9EXYkzHG/fT/PZvbzStJGmttdRPR8hYOvbP0FVnYJHMxyv760/HABkQt0P2UxQSFHsBD6nlW1hWTC2Yg5XTL2ZK6Z/lOoypzqUJQTmynAtleOvYM74K7j6Xct59KV7ePXQZuKpdkezvDxfAwxn54VXaxQvd5ZsNxFwfEBGNooAs4Z8yue4hA3SdoSvK5vM3cs2UBWpGwYrfSmgh7j9qu9x1eyP88ye/2bn0a0krE4HBLcZknWgonT7B0dmcAFQBT1DPu8TXgqnCgxrVVw85Xquv+AOqsK1paViRdC0UfOZOuohdh25jn979mOgyFwztsDPlEqqoAc8DUjKdbahrJADFs/56o6tce+SjcwZd1nulnMsvJ8WTLqaX69q45m317Jux79i2gmn4eECUUz56ycFBZGU6/Ieu3GtLm3Rz0dQnre33K6vrTFjxCXcd8Omv0amYdOrh57kR9vvwNZ7c36hRCeoqTobl1v57wVUdKzCr8B8am9bTsY3s/Y9rLryfko2unNEl079AFJKHty+Ek0yLBBUdMCRNQuARM1Phjxv76q/ZUJVYCwrL/8W46sbzrW5l0SXTr0O5Q8BLDKoOMnQQC9D+yPpK6uyR0JqCKlhCaenbgowbciYkE7BjXNW88itu5hUOxsp5f/5+Pkn9vCuEdeQTjj8mbbTE7BssjL0Nzw5PcqZQEo0qwG93nb749lmhAXSVvnIgs+5pebwtj6W6CaW6MayTQACRoiKsioiwbJhzVcerOSWi77EkWffpstsQxGy375jIamqjpoS2QZoFgA9KR6zgvqdJumc13e/AAnICAE9NGxP/9Y7f+bHT3yD1s5m0mYKgEiwnMmjZvDuhiuZO3khF0y7uOR5Z4xewLJZt/E/b3wbNZjLqhVfv7KQNEVHT4rHvP9ZAMJCXZtWlDs1Vce2LUcD3ETnvdNuHJbNnzhzlF+/8FNe2/97ziZOODvkrthlxtnV0sYbR7YTMSp45MvbGF83ueQ1FjV8mFcOP0lLandejtAfaaqOoiiEhbrWO5d369/9e+jmxAj1t2kziZmSiLTC4im38MWrHyiZsU9+/xqOnN2HFnRydy3s1uye1/E6wZYDciYOVaGRrL7xAS6dfdVgU/eh7kQ7K341z3k5YuR6BH5SUAjoYSJnxS2/WZ36nXc+77bfrE6tV5Nir6YEkDYElDKunfWxgrJv6HHybDPNnY3oEedVefY37LzC8o6NMBiR3HF35jQ/3PzPtJw6VNJ6leERSDuXrPVHuhZAS4q9fuH7AACgdtjfUFARFkytmcek2pkleWfLNln7/ENoIZkVXg+DFnLeJueNkDO8e/QwnE60sO6FH5QcFVRhOCC4QBSSoqgE4uLrfeQtPLHxXnOD0S42YStcXH8tQT1cEiM7DmznxX2Po4cKhDYc9dQMp6T1jrUA6AFHM/Sg8/tS4+aSAZhcM9fJVIVPOzzhUQh1i8d++9XM+qEAUABt093pFXNGXcF1828riYk3D/2J+5+4A7XMJBCFQJm7uyFXaJ1sRef9VwMOUEY5zjNRUMtM9h97q6S1b7rwCxgi4rTk7Jz8qqJhaEHWfyl9U1+96B8ABVCs4+Y6p2lZwu4ffIGkjKEauTe1quqbtY9e5gqZLCjuswdb3y5p7Wkj5xLWo476i5zwmqoT6RD39Cd8IQB5LD78L6/dv27LQy858XDo0RU7y/6Tb2RVXdELqrWByL3uNTs8EN44vB1hW0WtjZQE9RCVwdqsI1TR0BSdsg55z2+/kvlWMQBI368E7H9c+f271mx88E/F7EBrZwvvnNmdU3X/G+GhihQlB4AHwrGOJmLJ7qI1IGiEGVcxNdupUhWNQLs9qPCFAHjCC8AE4kDX1z7zwztHjpp87dMvP9bmvEDpfzy7ez1CzzgOz/+uvtgy1fuazO0un062cPjU/gHXKxxSwLwJV6ChE+jQt2xdlQls/Kr17aGWHQgAG0gBCSAG9Cz/6OrP3PfgVzbuPfR6uj8VbOtpzjm5YTQpvXcIntYILNpjbUWbAFLS293ZmdkrvvDMN61Vxa5b+LFd4cds/pRD2/lK49u/WLN+c1w5ri+65Jq8HuKmNx8hbrejBXIhr9RODeSKMDsFc0ZfQsOYeUU99+Cae35x96d/+rlje+RuII2zgUM2+wtf4Xp+wDMD75wAAkAGSP30exvXHD147MA171/0wUsvWjxj0qgZuqJJNF1FVaXznRqUJny2kslpQkakGKwAO9J6wHrljRcOvvjcyxue2rBjK47gFo4GF1W9DMaiF8A0d5ThgBB0R5ScCVlAJ47JJK+/S7tUr+Wjiq5UmJqYBWCFlXcNxYz0CjATMm2y6eqGj1dPrpqX7u2NZV564dXnf/nIcy/gbIblrpUht9vtQNI9zri/BSlRaQB417wSJoCjMYZ7HPEBIHCcZtpd3HRHqTWk+36JIFCDA3IZEHYHOLtr4gBg+tbscX8zOABZFAHA/wLnuy2TjtzmUAAAAABJRU5ErkJggg==
diff --git a/qtc_packaging/debian_harmattan/copyright b/qtc_packaging/debian_harmattan/copyright
new file mode 100644
index 0000000..003dac1
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/copyright
@@ -0,0 +1,44 @@
+This package was debianized by Yoann Lopes <yoann.lopes@gmail.com> on
+Tue, 4 Oct 2011 12:42:43 +0200.
+
+Upstream Author(s):
+
+ Yoann Lopes <yoann.lopes@gmail.com>
+
+Copyright:
+
+ Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+ All rights reserved.
+
+License:
+
+Redistribution and use in source and binary forms, with
+or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the
+ above copyright notice, this list of conditions and
+ the following disclaimer.
+* Redistributions in binary form must reproduce
+ above copyright notice, this list of conditions and
+ the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+* Neither the name of Nokia Corporation and its Subsidiary(-ies)
+ nor the names of its contributors may be used to endorse or
+ promote products derived from this software without
+ specific rior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
diff --git a/qtc_packaging/debian_harmattan/manifest.aegis b/qtc_packaging/debian_harmattan/manifest.aegis
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/manifest.aegis
diff --git a/qtc_packaging/debian_harmattan/postrm b/qtc_packaging/debian_harmattan/postrm
new file mode 100755
index 0000000..1e8a129
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/postrm
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -e
+if [ "$1" = "remove" ]; then
+ rm -R /home/user/MyDocs/.meespot
+ rm -R /home/user/MyDocs/.meespotconf
+fi
diff --git a/qtc_packaging/debian_harmattan/rules b/qtc_packaging/debian_harmattan/rules
new file mode 100755
index 0000000..92d1b1c
--- /dev/null
+++ b/qtc_packaging/debian_harmattan/rules
@@ -0,0 +1,90 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+
+
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # qmake PREFIX=/usr# Uncomment this line for use without Qt Creator
+
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+
+ # Add here commands to compile the package.
+ # $(MAKE) # Uncomment this line for use without Qt Creator
+ #docbook-to-man debian/meespot.sgml > meespotify.1
+
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ $(MAKE) clean
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/meespot.
+ $(MAKE) INSTALL_ROOT="$(CURDIR)"/debian/meespot install
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs
+ dh_installdocs
+ dh_installexamples
+# dh_install
+# dh_installmenu
+# dh_installdebconf
+# dh_installlogrotate
+# dh_installemacsen
+# dh_installpam
+# dh_installmime
+# dh_python
+# dh_installinit
+# dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_makeshlibs
+ dh_installdeb
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/res.qrc b/res.qrc
new file mode 100644
index 0000000..dc8b1d9
--- /dev/null
+++ b/res.qrc
@@ -0,0 +1,67 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/main.qml</file>
+ <file>qml/MainPage.qml</file>
+ <file>qml/LoginPage.qml</file>
+ <file>qml/UIConstants.js</file>
+ <file>qml/PlaylistPage.qml</file>
+ <file>qml/TracklistPage.qml</file>
+ <file>qml/PlaylistDelegate.qml</file>
+ <file>qml/TrackDelegate.qml</file>
+ <file>qml/Player.qml</file>
+ <file>qml/QuickControls.qml</file>
+ <file>qml/images/player-quickcontrols-back-open.png</file>
+ <file>qml/images/player-quickcontrols-back-closed.png</file>
+ <file>qml/FullControls.qml</file>
+ <file>qml/NotificationBanner.qml</file>
+ <file>qml/SettingsPage.qml</file>
+ <file>qml/ViewHeader.qml</file>
+ <file>qml/Selector.qml</file>
+ <file>qml/SearchPage.qml</file>
+ <file>qml/images/meegotouch-slider-elapsed-background-horizontal.png</file>
+ <file>qml/images/meegotouch-slider-handle-background-horizontal.png</file>
+ <file>qml/images/meegotouch-slider-handle-background-pressed-horizontal.png</file>
+ <file>qml/SpotifyImage.qml</file>
+ <file>qml/AdvancedTextField.qml</file>
+ <file>qml/Utilities.js</file>
+ <file>qml/images/icon-m-toolbar-repeat-white-selected.png</file>
+ <file>qml/images/icon-m-toolbar-shuffle-white-selected.png</file>
+ <file>qml/TrackMenu.qml</file>
+ <file>qml/images/icon-m-collaborative-playlist.png</file>
+ <file>qml/PlaylistMenu.qml</file>
+ <file>qml/MyMenuLayout.qml</file>
+ <file>qml/MyMenuItem.qml</file>
+ <file>qml/images/meegotouch-sheet-header-inverted-background.png</file>
+ <file>qml/MyMenu.qml</file>
+ <file>qml/MyPopup.qml</file>
+ <file>qml/MyFader.qml</file>
+ <file>qml/PlaylistNameSheet.qml</file>
+ <file>qml/AlbumPage.qml</file>
+ <file>qml/AlbumHeader.qml</file>
+ <file>qml/AlbumTrackDelegate.qml</file>
+ <file>qml/AlbumMenu.qml</file>
+ <file>qml/ArtistPage.qml</file>
+ <file>qml/ArtistHeader.qml</file>
+ <file>qml/AlbumDelegate.qml</file>
+ <file>qml/ArtistDelegate.qml</file>
+ <file>qml/images/spotify-core-logo-128x128.png</file>
+ <file>qml/images/meegotouch-progressindicator-bar-known-texture.png</file>
+ <file>qml/AboutDialog.qml</file>
+ <file>qml/images/meespot-logo.png</file>
+ <file>qml/MyMenuItemStyle.qml</file>
+ <file>qml/images/meegotouch-list-inverted-background-vertical-top.png</file>
+ <file>qml/images/meegotouch-list-inverted-background-pressed-vertical-bottom.png</file>
+ <file>qml/images/meegotouch-list-inverted-background-pressed-vertical-center.png</file>
+ <file>qml/images/meegotouch-list-inverted-background-pressed-vertical-top.png</file>
+ <file>qml/images/meegotouch-list-inverted-background-vertical-bottom.png</file>
+ <file>qml/images/meegotouch-list-inverted-background-vertical-center.png</file>
+ <file>qml/images/meegotouch-menu-background-inverted.png</file>
+ <file>qml/MySheet.qml</file>
+ <file>qml/images/meegotouch-list-inverted-background.png</file>
+ <file>qml/images/icon-m-common-green.png</file>
+ <file>qml/InboxTrackDelegate.qml</file>
+ <file>qml/HeaderSearchField.qml</file>
+ <file>qml/Separator.qml</file>
+ <file>qml/ToplistPage.qml</file>
+ </qresource>
+</RCC>
diff --git a/splash.png b/splash.png
new file mode 100755
index 0000000..c691bc9
--- /dev/null
+++ b/splash.png
Binary files differ