diff --git a/README.md b/README.md index a980554..d279102 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Features include: * Browse and search tracks, sets and users. * Play and download tracks. + * Folder-based playback of local music. * Support for multiple SoundCloud accounts. - * Support for user-defined credentials and access scopes for SoundCloud. + * Support for user-defined credentials for SoundCloud. * Support for additional services via plugins. diff --git a/app/app.pro b/app/app.pro index 22ed020..8fcad3a 100644 --- a/app/app.pro +++ b/app/app.pro @@ -1,9 +1,7 @@ TEMPLATE = app TARGET = musikloud2 -#DEFINES += MUSIKLOUD_DEBUG - -QT += network sql xml +QT += network script sql INCLUDEPATH += \ src/audioplayer \ @@ -13,43 +11,51 @@ INCLUDEPATH += \ HEADERS += \ src/audioplayer/audioplayer.h \ - src/audioplayer/trackmodel.h \ src/base/artist.h \ src/base/categorymodel.h \ src/base/categorynamemodel.h \ src/base/clipboard.h \ src/base/comment.h \ src/base/concurrenttransfersmodel.h \ - src/base/database.h \ src/base/json.h \ - src/base/localtrack.h \ + src/base/loggerverbositymodel.h \ src/base/networkproxytypemodel.h \ src/base/playlist.h \ src/base/resources.h \ src/base/searchhistorymodel.h \ src/base/selectionmodel.h \ src/base/servicemodel.h \ - src/base/settings.h \ src/base/track.h \ - src/base/transfer.h \ + src/base/trackmodel.h \ + src/base/transfermodel.h \ + src/base/transferprioritymodel.h \ src/base/transfers.h \ src/base/utils.h \ + src/plugins/externalresourcesrequest.h \ + src/plugins/externalserviceplugin.h \ + src/plugins/javascriptresourcesrequest.h \ + src/plugins/javascriptresourcesrequestglobalobject.h \ + src/plugins/javascriptserviceplugin.h \ src/plugins/pluginartist.h \ src/plugins/pluginartistmodel.h \ src/plugins/plugincategorymodel.h \ src/plugins/plugincomment.h \ src/plugins/plugincommentmodel.h \ + src/plugins/pluginconfigmodel.h \ + src/plugins/pluginmanager.h \ src/plugins/pluginnavmodel.h \ src/plugins/pluginplaylist.h \ src/plugins/pluginplaylistmodel.h \ src/plugins/pluginsearchtypemodel.h \ - src/plugins/pluginsettingsmodel.h \ + src/plugins/pluginsettings.h \ src/plugins/pluginstreammodel.h \ src/plugins/plugintrack.h \ src/plugins/plugintrackmodel.h \ src/plugins/plugintransfer.h \ - src/plugins/resourcesplugins.h \ src/plugins/resourcesrequest.h \ + src/plugins/serviceplugin.h \ + src/plugins/servicepluginconfig.h \ + src/plugins/xmlhttprequest.h \ src/soundcloud/soundcloud.h \ src/soundcloud/soundcloudaccountmodel.h \ src/soundcloud/soundcloudartist.h \ @@ -68,35 +74,41 @@ HEADERS += \ SOURCES += \ src/audioplayer/audioplayer.cpp \ - src/audioplayer/trackmodel.cpp \ src/base/artist.cpp \ src/base/categorymodel.cpp \ src/base/clipboard.cpp \ src/base/comment.cpp \ src/base/json.cpp \ - src/base/localtrack.cpp \ src/base/playlist.cpp \ src/base/resources.cpp \ src/base/searchhistorymodel.cpp \ src/base/selectionmodel.cpp \ - src/base/settings.cpp \ src/base/track.cpp \ - src/base/transfer.cpp \ + src/base/trackmodel.cpp \ + src/base/transfermodel.cpp \ src/base/transfers.cpp \ src/base/utils.cpp \ + src/plugins/externalresourcesrequest.cpp \ + src/plugins/externalserviceplugin.cpp \ + src/plugins/javascriptresourcesrequest.cpp \ + src/plugins/javascriptresourcesrequestglobalobject.cpp \ + src/plugins/javascriptserviceplugin.cpp \ src/plugins/pluginartist.cpp \ src/plugins/pluginartistmodel.cpp \ src/plugins/plugincategorymodel.cpp \ src/plugins/plugincomment.cpp \ src/plugins/plugincommentmodel.cpp \ + src/plugins/pluginconfigmodel.cpp \ + src/plugins/pluginmanager.cpp \ src/plugins/pluginplaylist.cpp \ src/plugins/pluginplaylistmodel.cpp \ + src/plugins/pluginsettings.cpp \ src/plugins/pluginstreammodel.cpp \ src/plugins/plugintrack.cpp \ src/plugins/plugintrackmodel.cpp \ src/plugins/plugintransfer.cpp \ - src/plugins/resourcesplugins.cpp \ - src/plugins/resourcesrequest.cpp \ + src/plugins/servicepluginconfig.cpp \ + src/plugins/xmlhttprequest.cpp \ src/soundcloud/soundcloud.cpp \ src/soundcloud/soundcloudaccountmodel.cpp \ src/soundcloud/soundcloudartist.cpp \ @@ -112,33 +124,39 @@ SOURCES += \ src/soundcloud/soundcloudtrackmodel.cpp \ src/soundcloud/soundcloudtransfer.cpp -maemo5 { - QT += maemo5 webkit +maemo5 { + QT += dbus maemo5 webkit + CONFIG += mobility12 + MOBILITY += multimedia LIBS += -L/usr/lib -lqsoundcloud CONFIG += link_prl PKGCONFIG += libqsoundcloud INCLUDEPATH += \ + src/dbus \ src/maemo5 \ src/maemo5/plugins \ src/maemo5/soundcloud HEADERS += \ - src/base/transfermodel.h \ - src/base/transferprioritymodel.h \ + src/dbus/dbusservice.h \ src/maemo5/aboutdialog.h \ src/maemo5/accountdelegate.h \ src/maemo5/artistdelegate.h \ src/maemo5/categoriesdialog.h \ + src/maemo5/categorydelegate.h \ src/maemo5/commentdelegate.h \ - src/maemo5/dialog.h \ + src/maemo5/customcommanddialog.h \ + src/maemo5/database.h \ src/maemo5/definitions.h \ + src/maemo5/dialog.h \ src/maemo5/drawing.h \ src/maemo5/filterbox.h \ src/maemo5/image.h \ src/maemo5/imagecache.h \ src/maemo5/listview.h \ + src/maemo5/logger.h \ src/maemo5/mainwindow.h \ src/maemo5/navdelegate.h \ src/maemo5/networkproxydialog.h \ @@ -147,13 +165,16 @@ maemo5 { src/maemo5/nowplayingdelegate.h \ src/maemo5/nowplayingwindow.h \ src/maemo5/playlistdelegate.h \ + src/maemo5/pluginsettingsdialog.h \ src/maemo5/qwebviewselectionsuppressor.h \ src/maemo5/screen.h \ src/maemo5/searchhistorydialog.h \ + src/maemo5/settings.h \ src/maemo5/settingsdialog.h \ src/maemo5/stackedwindow.h \ src/maemo5/textbrowser.h \ src/maemo5/trackdelegate.h \ + src/maemo5/transfer.h \ src/maemo5/transferswindow.h \ src/maemo5/valueselector.h \ src/maemo5/valueselectoraction.h \ @@ -165,12 +186,6 @@ maemo5 { src/maemo5/plugins/pluginplaylistswindow.h \ src/maemo5/plugins/pluginplaylistwindow.h \ src/maemo5/plugins/pluginsearchdialog.h \ - src/maemo5/plugins/pluginsettingscheckbox.h \ - src/maemo5/plugins/pluginsettingsdialog.h \ - src/maemo5/plugins/pluginsettingslineedit.h \ - src/maemo5/plugins/pluginsettingsselector.h \ - src/maemo5/plugins/pluginsettingsslider.h \ - src/maemo5/plugins/pluginsettingsspinbox.h \ src/maemo5/plugins/plugintrackswindow.h \ src/maemo5/plugins/plugintrackwindow.h \ src/maemo5/plugins/pluginview.h \ @@ -188,17 +203,20 @@ maemo5 { src/maemo5/soundcloud/soundcloudview.h SOURCES += \ - src/base/transfermodel.cpp \ + src/dbus/dbusservice.cpp \ src/maemo5/aboutdialog.cpp \ src/maemo5/accountdelegate.cpp \ src/maemo5/artistdelegate.cpp \ src/maemo5/categoriesdialog.cpp \ + src/maemo5/categorydelegate.cpp \ src/maemo5/commentdelegate.cpp \ + src/maemo5/customcommanddialog.cpp \ src/maemo5/dialog.cpp \ src/maemo5/filterbox.cpp \ src/maemo5/image.cpp \ src/maemo5/imagecache.cpp \ src/maemo5/listview.cpp \ + src/maemo5/logger.cpp \ src/maemo5/main.cpp \ src/maemo5/mainwindow.cpp \ src/maemo5/navdelegate.cpp \ @@ -208,12 +226,15 @@ maemo5 { src/maemo5/nowplayingdelegate.cpp \ src/maemo5/nowplayingwindow.cpp \ src/maemo5/playlistdelegate.cpp \ + src/maemo5/pluginsettingsdialog.cpp \ src/maemo5/screen.cpp \ src/maemo5/searchhistorydialog.cpp \ + src/maemo5/settings.cpp \ src/maemo5/settingsdialog.cpp \ src/maemo5/stackedwindow.cpp \ src/maemo5/textbrowser.cpp \ src/maemo5/trackdelegate.cpp \ + src/maemo5/transfer.cpp \ src/maemo5/transferswindow.cpp \ src/maemo5/valueselector.cpp \ src/maemo5/valueselectoraction.cpp \ @@ -225,12 +246,6 @@ maemo5 { src/maemo5/plugins/pluginplaylistswindow.cpp \ src/maemo5/plugins/pluginplaylistwindow.cpp \ src/maemo5/plugins/pluginsearchdialog.cpp \ - src/maemo5/plugins/pluginsettingscheckbox.cpp \ - src/maemo5/plugins/pluginsettingsdialog.cpp \ - src/maemo5/plugins/pluginsettingslineedit.cpp \ - src/maemo5/plugins/pluginsettingsselector.cpp \ - src/maemo5/plugins/pluginsettingsslider.cpp \ - src/maemo5/plugins/pluginsettingsspinbox.cpp \ src/maemo5/plugins/plugintrackswindow.cpp \ src/maemo5/plugins/plugintrackwindow.cpp \ src/maemo5/plugins/pluginview.cpp \ @@ -247,171 +262,308 @@ maemo5 { src/maemo5/soundcloud/soundcloudtrackwindow.cpp \ src/maemo5/soundcloud/soundcloudview.cpp + dbus_service.files = dbus/maemo5/org.marxoft.musikloud2.service + dbus_service.path = /usr/share/dbus-1/services + + dbus_interface.files = dbus/maemo5/org.marxoft.musikloud2.xml + dbus_interface.path = /usr/share/dbus-1/interfaces + desktopfile.files = desktop/maemo5/musikloud2.desktop desktopfile.path = /usr/share/applications/hildon icon.files = desktop/maemo5/64/musikloud2.png icon.path = /usr/share/icons/hicolor/64x64/apps + target.path = /opt/musikloud2/bin + INSTALLS += \ + dbus_service \ + dbus_interface \ desktopfile \ - icon + icon \ + target -} else:contains(MEEGO_EDITION,harmattan) { - LIBS += -L../../qsoundcloud/lib -lqsoundcloud - - QT += declarative opengl - CONFIG += \ - link_pkgconfig \ - qdeclarative-boostable \ - libtuiclient \ - shareuiinterface-maemo-meegotouch \ - share-ui-common \ - mdatauri +} else:symbian { + QT += declarative + CONFIG += mobility qtcomponents + MOBILITY += multimedia + + TARGET.UID3 = 0xE77ABF1C + TARGET.CAPABILITY += NetworkServices ReadUserData WriteUserData + TARGET.EPOCHEAPSIZE = 0x20000 0x8000000 + TARGET.EPOCSTACKSIZE = 0x14000 + + VERSION = 0.2.0 + ICON = desktop/symbian/musikloud2.svg + + MMP_RULES += "DEBUGGABLE_UDEBONLY" + + LIBS += -lqsoundcloud + LIBS += -L\\epoc32\\release\\armv5\\lib -lremconcoreapi + LIBS += -L\\epoc32\\release\\armv5\\lib -lremconinterfacebase INCLUDEPATH += \ - src/harmattan + MW_LAYER_SYSTEMINCLUDE \ + src/symbian HEADERS += \ - src/harmattan/activecolormodel.h \ - src/harmattan/cookiejar.h \ - src/harmattan/definitions.h \ - src/harmattan/maskeditem.h \ - src/harmattan/maskeffect.h \ - src/harmattan/networkaccessmanagerfactory.h \ - src/harmattan/screenorientationmodel.h \ - src/harmattan/shareui.h - + src/symbian/database.h \ + src/symbian/definitions.h \ + src/symbian/logger.h \ + src/symbian/maskeditem.h \ + src/symbian/maskeffect.h \ + src/symbian/mediakeycaptureitem.h \ + src/symbian/screenorientationmodel.h \ + src/symbian/settings.h \ + src/symbian/transfer.h + SOURCES += \ - src/harmattan/cookiejar.cpp \ - src/harmattan/main.cpp \ - src/harmattan/maskeditem.cpp \ - src/harmattan/maskeffect.cpp \ - src/harmattan/networkaccessmanagerfactory.cpp \ - src/harmattan/shareui.cpp - - base_qml.files = $$files(src/harmattan/qml/*.qml) - base_qml.path = /opt/musikloud2/qml - - plugins_qml.files = $$files(src/harmattan/qml/plugins/*.qml) - plugins_qml.path = /opt/musikloud2/qml/plugins - - soundcloud_qml.files = $$files(src/harmattan/qml/soundcloud/*.qml) - soundcloud_qml.path = /opt/musikloud2/qml/soundcloud + src/symbian/logger.cpp \ + src/symbian/main.cpp \ + src/symbian/maskeditem.cpp \ + src/symbian/maskeffect.cpp \ + src/symbian/mediakeycaptureitem.cpp \ + src/symbian/settings.cpp \ + src/symbian/transfer.cpp - images.files = $$files(src/harmattan/qml/images/*.*) - images.path = /opt/musikloud2/qml/images - - desktop.files = desktop/harmattan/musikloud2.desktop - desktop.path = /usr/share/applications - - icon.files = desktop/harmattan/80/musikloud2.png - icon.path = /usr/share/icons/hicolor/80x80/apps - - contentaction.files = desktop/harmattan/musikloud2.xml - contentaction.path = /usr/share/contentaction - - splash.files = desktop/harmattan/splash/*.png - splash.path = /opt/musikloud2/splash + qml_base.sources = \ + src/symbian/qml/AboutPage.qml \ + src/symbian/qml/AboutPluginPage.qml \ + src/symbian/qml/AboutPluginsPage.qml \ + src/symbian/qml/AccountDelegate.qml \ + src/symbian/qml/AddFolderPage.qml \ + src/symbian/qml/AddUrlPage.qml \ + src/symbian/qml/AppWindow.qml \ + src/symbian/qml/ArtistDelegate.qml \ + src/symbian/qml/Avatar.qml \ + src/symbian/qml/BackToolButton.qml \ + src/symbian/qml/CategorySettingsPage.qml \ + src/symbian/qml/CommentDelegate.qml \ + src/symbian/qml/DualTextDelegate.qml \ + src/symbian/qml/EditPage.qml \ + src/symbian/qml/EditCategoryPage.qml \ + src/symbian/qml/FileBrowserPage.qml \ + src/symbian/qml/GeneralSettingsPage.qml \ + src/symbian/qml/HeaderLabel.qml \ + src/symbian/qml/KeyNavFlickable.qml \ + src/symbian/qml/LoggingSettingsPage.qml \ + src/symbian/qml/LogPage.qml \ + src/symbian/qml/main.qml \ + src/symbian/qml/MainPage.qml \ + src/symbian/qml/MyButton.qml \ + src/symbian/qml/MyContextMenu.qml \ + src/symbian/qml/MyDialog.qml \ + src/symbian/qml/MyFlickable.qml \ + src/symbian/qml/MyInfoBanner.qml \ + src/symbian/qml/MyListItem.qml \ + src/symbian/qml/MyListItemText.qml \ + src/symbian/qml/MyListView.qml \ + src/symbian/qml/MyMenu.qml \ + src/symbian/qml/MyPage.qml \ + src/symbian/qml/MyQueryDialog.qml \ + src/symbian/qml/MySelectionDialog.qml \ + src/symbian/qml/MyStatusBar.qml \ + src/symbian/qml/MySwitch.qml \ + src/symbian/qml/MyTextField.qml \ + src/symbian/qml/MyToolButton.qml \ + src/symbian/qml/NetworkSettingsPage.qml \ + src/symbian/qml/NowPlayingButton.qml \ + src/symbian/qml/NowPlayingPage.qml \ + src/symbian/qml/NowPlayingTrackPage.qml \ + src/symbian/qml/PlaybackQueueDelegate.qml \ + src/symbian/qml/PlaybackQueuePage.qml \ + src/symbian/qml/PlaybackSettingsPage.qml \ + src/symbian/qml/PlaylistDelegate.qml \ + src/symbian/qml/PlayFolderPage.qml \ + src/symbian/qml/PlayUrlPage.qml \ + src/symbian/qml/PluginSettingsPage.qml \ + src/symbian/qml/PluginsSettingsPage.qml \ + src/symbian/qml/PopupLoader.qml \ + src/symbian/qml/SettingsPage.qml \ + src/symbian/qml/TextDelegate.qml \ + src/symbian/qml/TextInputPage.qml \ + src/symbian/qml/Thumbnail.qml \ + src/symbian/qml/TrackDelegate.qml \ + src/symbian/qml/TransferDelegate.qml \ + src/symbian/qml/TransfersPage.qml \ + src/symbian/qml/ValueDialog.qml \ + src/symbian/qml/ValueListItem.qml \ + src/symbian/qml/ValueSelector.qml \ + src/symbian/qml/VolumeControl.qml + + qml_base.path = !:/Private/e77abf1c/qml + + qml_plugins.sources = \ + src/symbian/qml/plugins/PluginArtistPage.qml \ + src/symbian/qml/plugins/PluginArtistsPage.qml \ + src/symbian/qml/plugins/PluginCategoriesPage.qml \ + src/symbian/qml/plugins/PluginCommentsPage.qml \ + src/symbian/qml/plugins/PluginDownloadPage.qml \ + src/symbian/qml/plugins/PluginPlaylistPage.qml \ + src/symbian/qml/plugins/PluginPlaylistsPage.qml \ + src/symbian/qml/plugins/PluginSearchPage.qml \ + src/symbian/qml/plugins/PluginTrackPage.qml \ + src/symbian/qml/plugins/PluginTracksPage.qml \ + src/symbian/qml/plugins/PluginView.qml + + qml_plugins.path = !:/Private/e77abf1c/qml/plugins + + qml_soundcloud.sources = \ + src/symbian/qml/soundcloud/SoundCloudAccountsPage.qml \ + src/symbian/qml/soundcloud/SoundCloudArtistPage.qml \ + src/symbian/qml/soundcloud/SoundCloudArtistsPage.qml \ + src/symbian/qml/soundcloud/SoundCloudAuthPage.qml \ + src/symbian/qml/soundcloud/SoundCloudCommentPage.qml \ + src/symbian/qml/soundcloud/SoundCloudCommentsPage.qml \ + src/symbian/qml/soundcloud/SoundCloudDownloadPage.qml \ + src/symbian/qml/soundcloud/SoundCloudPlaylistPage.qml \ + src/symbian/qml/soundcloud/SoundCloudPlaylistsPage.qml \ + src/symbian/qml/soundcloud/SoundCloudSearchPage.qml \ + src/symbian/qml/soundcloud/SoundCloudTrackPage.qml \ + src/symbian/qml/soundcloud/SoundCloudTracksPage.qml \ + src/symbian/qml/soundcloud/SoundCloudView.qml + + qml_soundcloud.path = !:/Private/e77abf1c/qml/soundcloud + + images.sources = \ + src/symbian/qml/images/artist.jpg \ + src/symbian/qml/images/avatar-frame.png \ + src/symbian/qml/images/avatar-mask.png \ + src/symbian/qml/images/close.png \ + src/symbian/qml/images/download.png \ + src/symbian/qml/images/folder.png \ + src/symbian/qml/images/musikloud2.png \ + src/symbian/qml/images/no.png \ + src/symbian/qml/images/pause-active.png \ + src/symbian/qml/images/play-active.png \ + src/symbian/qml/images/repeat.png \ + src/symbian/qml/images/shuffle.png \ + src/symbian/qml/images/stop-active.png \ + src/symbian/qml/images/track.jpg \ + src/symbian/qml/images/track-large.jpg \ + src/symbian/qml/images/up.png \ + src/symbian/qml/images/upload.png \ + src/symbian/qml/images/volume.png \ + src/symbian/qml/images/volume-mute.png \ + src/symbian/qml/images/yes.png + + images.path = !:/Private/e77abf1c/qml/images + + vendorinfo += "%{\"Stuart Howarth\"}" ":\"Stuart Howarth\"" + qtcomponentsdep = "; Default dependency to Qt Quick Components for Symbian library" \ + "(0x200346DE), 1, 1, 0, {\"Qt Quick components for Symbian\"}" - INSTALLS += \ - base_qml \ - plugins_qml \ - soundcloud_qml \ - images \ - desktop \ - icon \ - contentaction \ - splash + musikloud2_deployment.pkg_prerules += vendorinfo qtcomponentsdep + DEPLOYMENT.display_name = MusiKloud2 + + DEPLOYMENT += \ + musikloud2_deployment \ + qml_base \ + qml_plugins \ + qml_soundcloud \ + images + } else:unix { - QT += qml quick widgets + greaterThan(QT_MAJOR_VERSION,4) { + QT += multimedia widgets + } + else { + CONFIG += mobility + MOBILITY += multimedia + } LIBS += -L/usr/lib -lqsoundcloud CONFIG += link_prl PKGCONFIG += libqsoundcloud - INCLUDEPATH += src/desktop-qml + INCLUDEPATH += \ + src/desktop \ + src/desktop/plugins \ + src/desktop/soundcloud HEADERS += \ - src/base/transfermodel.h \ - src/base/transferprioritymodel.h \ - src/desktop-qml/definitions.h - + src/desktop/aboutdialog.h \ + src/desktop/categorysettingspage.h \ + src/desktop/customcommanddialog.h \ + src/desktop/database.h \ + src/desktop/definitions.h \ + src/desktop/drawing.h \ + src/desktop/generalsettingspage.h \ + src/desktop/image.h \ + src/desktop/imagecache.h \ + src/desktop/itemmetadata.h \ + src/desktop/logger.h \ + src/desktop/mainwindow.h \ + src/desktop/networksettingspage.h \ + src/desktop/page.h \ + src/desktop/playbackqueuepage.h \ + src/desktop/pluginsettingspage.h \ + src/desktop/pluginssettingspage.h \ + src/desktop/searchdialog.h \ + src/desktop/settings.h \ + src/desktop/settingsdialog.h \ + src/desktop/settingspage.h \ + src/desktop/transfer.h \ + src/desktop/transferdelegate.h \ + src/desktop/transferspage.h \ + src/desktop/treeview.h \ + src/desktop/plugins/plugindownloaddialog.h \ + src/desktop/plugins/plugintrackspage.h \ + src/desktop/soundcloud/soundclouddownloaddialog.h \ + src/desktop/soundcloud/soundcloudtrackspage.h + SOURCES += \ - src/base/transfermodel.cpp \ - src/desktop-qml/main.cpp - - base_qml.files = $$files(src/desktop-qml/qml/*.qml) - base_qml.path = /opt/musikloud2/qml + src/desktop/aboutdialog.cpp \ + src/desktop/categorysettingspage.cpp \ + src/desktop/customcommanddialog.cpp \ + src/desktop/generalsettingspage.cpp \ + src/desktop/image.cpp \ + src/desktop/imagecache.cpp \ + src/desktop/itemmetadata.cpp \ + src/desktop/logger.cpp \ + src/desktop/main.cpp \ + src/desktop/mainwindow.cpp \ + src/desktop/networksettingspage.cpp \ + src/desktop/page.cpp \ + src/desktop/playbackqueuepage.cpp \ + src/desktop/pluginsettingspage.cpp \ + src/desktop/pluginssettingspage.cpp \ + src/desktop/searchdialog.cpp \ + src/desktop/settings.cpp \ + src/desktop/settingsdialog.cpp \ + src/desktop/settingspage.cpp \ + src/desktop/transfer.cpp \ + src/desktop/transferdelegate.cpp \ + src/desktop/transferspage.cpp \ + src/desktop/treeview.cpp \ + src/desktop/plugins/plugindownloaddialog.cpp \ + src/desktop/plugins/plugintrackspage.cpp \ + src/desktop/soundcloud/soundclouddownloaddialog.cpp \ + src/desktop/soundcloud/soundcloudtrackspage.cpp - plugins_qml.files = $$files(src/desktop-qml/qml/plugins/*.qml) - plugins_qml.path = /opt/musikloud2/qml/plugins - - soundcloud_qml.files = $$files(src/desktop-qml/qml/soundcloud/*.qml) - soundcloud_qml.path = /opt/musikloud2/qml/soundcloud + desktop.files = desktop/desktop/musikloud2.desktop + desktop.path = /usr/share/applications - desktopfile.files = desktop/desktop/musikloud2.desktop - desktopfile.path = /usr/share/applications - icon64.files = desktop/desktop/64/musikloud2.png - icon64.path = /usr/share/icons/hicolor/64/apps + icon64.path = /usr/share/icons/hicolor/64x64/apps icon48.files = desktop/desktop/48/musikloud2.png icon48.path = /usr/share/icons/hicolor/48x48/apps icon22.files = desktop/desktop/22/musikloud2.png - icon22.path = /usr/share/icons/hicolor/22/apps + icon22.path = /usr/share/icons/hicolor/22x22/apps icon16.files = desktop/desktop/16/musikloud2.png - icon16.path = /usr/share/icons/hicolor/16/apps + icon16.path = /usr/share/icons/hicolor/16x16/apps + + target.path = /usr/bin INSTALLS += \ - base_qml \ - plugins_qml \ - soundcloud_qml \ - desktopfile \ + desktop \ icon64 \ icon48 \ icon22 \ - icon16 -} - -unix:!symbian { - QT += dbus - - INCLUDEPATH += src/dbus - - HEADERS += src/dbus/dbusservice.h - SOURCES += src/dbus/dbusservice.cpp - - dbus_service.files = dbus/org.marxoft.musikloud2.service - dbus_service.path = /usr/share/dbus-1/services - - dbus_interface.files = dbus/org.marxoft.musikloud2.xml - dbus_interface.path = /usr/share/dbus-1/interfaces - - target.path = /opt/musikloud2/bin - - INSTALLS += dbus_service dbus_interface -} - -greaterThan(QT_MAJOR_VERSION,4) { - QT += multimedia -} else:maemo5 { - CONFIG += mobility12 - MOBILITY += multimedia -} else { - CONFIG += mobility - MOBILITY += multimedia + icon16 \ + target } - -contains(DEFINES,FETCH_LOCAL_METADATA) { - greaterThan(QT_MAJOR_VERSION,4) { - QT += docgallery - } else { - MOBILITY += gallery - } -} - -INSTALLS += target diff --git a/app/dbus/org.marxoft.musikloud2.service b/app/dbus/maemo5/org.marxoft.musikloud2.service similarity index 100% rename from app/dbus/org.marxoft.musikloud2.service rename to app/dbus/maemo5/org.marxoft.musikloud2.service diff --git a/app/dbus/org.marxoft.musikloud2.xml b/app/dbus/maemo5/org.marxoft.musikloud2.xml similarity index 57% rename from app/dbus/org.marxoft.musikloud2.xml rename to app/dbus/maemo5/org.marxoft.musikloud2.xml index 630afb9..944fc65 100644 --- a/app/dbus/org.marxoft.musikloud2.xml +++ b/app/dbus/maemo5/org.marxoft.musikloud2.xml @@ -1,7 +1,11 @@ - + + + + + diff --git a/app/debian/changelog b/app/debian/changelog new file mode 100644 index 0000000..43c7474 --- /dev/null +++ b/app/debian/changelog @@ -0,0 +1,39 @@ +musikloud2 (0.2.0) unstable; urgency=low + * Add sleep timer function. + * UI improvements. + + -- Stuart Howarth Sat, 14 Jan 2017 22:04:34 +0000 + +musikloud2 (0.1.0) unstable; urgency=low + * New plugin API adding support for Qt/C++ and JavaScript plugins. + * Support for playing remote URLs. + * Support for downloading tracks in 'now playing' window. + * Option to stop playback after current track. + * Save and optionally restore playback queue. + * Search folders recursively when playing a folder. + + -- Stuart Howarth Tue, 03 Jan 2017 23:56:24 +0000 + +musikloud2 (0.0.4) unstable; urgency=low + * UI tweaks. + * Register DBus interface (org.marxoft.musikloud2). + * Fix crashing when clearing stacked windows. + + -- Stuart Howarth Thu, 01 Oct 2015 16:25:41 +0000 + +musikloud2 (0.0.3) unstable; urgency=low + * Fix fetching of further results from the SoundCloud API. + * Fix unneccessary fetching of further results when loading plugin categories. + + -- Stuart Howarth Sun, 09 Aug 2015 14:37:15 +0000 + +musikloud2 (0.0.2) unstable; urgency=low + * Enable fetching of more results when retrieving plugin categories. + * Better adaptation of the 'Now playing' UI for non-seekable streams. + + -- Stuart Howarth Mon, 29 Jun 2015 21:11:42 +0000 + +musikloud2 (0.0.1) unstable; urgency=low + * Initial release. + + -- Stuart Howarth Mon, 29 Jun 2015 01:03:12 +0000 diff --git a/plugins/internetradio/debian_maemo5/compat b/app/debian/compat similarity index 100% rename from plugins/internetradio/debian_maemo5/compat rename to app/debian/compat diff --git a/app/debian/control b/app/debian/control new file mode 100644 index 0000000..6f6ca61 --- /dev/null +++ b/app/debian/control @@ -0,0 +1,51 @@ +Source: musikloud2 +Section: user/multimedia +Priority: optional +Maintainer: Stuart Howarth +Homepage: http://marxoft.co.uk/projects/musikloud2 +Build-Depends: debhelper (>= 5), libqt4-dev, libqtm-12-dev, qsoundcloud-dev +Standards-Version: 3.7.3 + +Package: musikloud2 +Architecture: armel +Depends: ${shlibs:Depends}, ${misc:Depends}, libqtm-12, qsoundcloud +Description: A SoundCloud client and music player + MusiKloud2 is a SoundCloud client and music player that can be extended to support other + services via plugins. +XB-Maemo-Display-Name: MusiKloud2 +XB-Maemo-Icon-26: + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI + WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wYTFjYP41harQAAABl0RVh0Q29tbWVudABDcmVhdGVk + IHdpdGggR0lNUFeBDhcAAAcXSURBVGje7ZpriF1XFcd/a+99zj2Pe+dxZ+5kMk2TycOkSapjgsFK + aaVWi48iooWCClaKfmixBBEEqX4QGqTgFwtJP7YqRQmlqH0pRbFKSEuatBEqxSRNY6aTZDKTzOM+ + zzl7++HeTDLMM9NkZGAWnA+Xe8/Z67/22v/1X+seyaxz42WLY2WZAG2xwoyXLT9/bpRqw64oAKGv + +Nk3ixgHVBuWan2l7UEzaxQr3FYBrAJYBbAKYOnmcFgE55ZO4WZ5HG066XDorIFxNXRaw9gybckw + p6MBPOOhlP7/A8hQZChSJ1jn6GwMsaZ2gp7G+/TWTlFMBikmQxSTIUrJIGfUrey95SCl9iLiR8hy + A7AOrLNYayGrs6H8Dluqb7Gl8habq8eJ7ASCQ5xDmC5XnGiqmaN86SzFOI/2AhC1fAAcsPPyX/jM + xd+xrvYufY3TKCBDyMRgReMWiqnLyOpj1KoVjB+hjb+8ALZMHGLXxGs4UdRVeH33OyiEmu/cW6St + GPLKkRHK9ZgwjFrnQW4yC7lWColaONJzBEApoafT58t39PCbnw5wzw5oVEZIk/qi2GnJANKsqQg9 + fWNkeGYd1joeeeBjPPzFIuMjZ8mS2oIg1NKcd3z2do+BTQGf31W44bT7hU+v45Gv3cLY6BA2S24c + AOegI6/YtTnH3QMFHKDVQp2TQzmLdinKpjTEY8zroKJjZJ4+8O7dfdzaUWVyfJQ0TXBz/NbMR484 + R5o5jFGICNVGxg8+18Hr/6qS2ZlHrEmXFuUsymUkohnO9TIc9TGaX894YR2SL5Llu9l68kW2n351 + TgBR6PP49/dw8myV3//tAsPlHLkgRqTpy5wABDAaNvRo6incsc3j0LsNquVLdPZ2kg/VrLFwCKNe + keH8ei60b2akfROV4kbycUQhjijEMZvyMVEU4cVthJVj2FMWEaY5dK31lQr0lQrcOVDi8QPHOHGh + RhAXEW2mA7hyeyOx3Lc75OyIZc/WgGMn62xYG3H8A8sT393KgZfGmGMtzuV6efnjj9G/ZRulrk7W + xRFxGBCGIUEQ4HkexhiMMVjtUw4XT7lKCfse3c1jT/6ToUkhKhSnYm+mNt+mdBdg+4aQc5crpC1y + sdbhHNQT10yrOczTio19JQZ2bKVUKuF5HlprlGpuuVJXD0sC13xePP3u/dbt7P3lEfwwBncNgMw6 + OiLLg/d0kc3i5aKWECHIBRQKBeI4nubwkp43i63tiugvOc7XJnBxAM6hcI5aI+Hb93Z8hEc37xSR + qeumjFICnzUdikZtHGebKaIcgE3Zc1ueJFue0YprXUuxNcWApF7GufRqHehuUzi3PI4nQM1BOlWC + rm9hEYdNGzjbnAsZgIlKyk3a9VYBdKTWUfv7r7GvPoUrX8JvVMH4tMrNou3DC5cRpbjisBFgsmoZ + Gqmh5Lq8WlQqOOdIq2Wq+x/Cvfk8eLlmMcI1BaBb/C7U6g0Gz42jzRpENHIlhcLA59mX/0scLqyu + nXVk4tFQPmWJSRZY24lQ+/P+pvN+eE3D0oyWtsmiyWN4ZIL3zozhh21Iq/00AL7ncfTEGHF8gT07 + uudlmrReY8uJ35J/7Vl+NPEOCR7SiufsLSbYN54H482qk8Zy3XhGz0u7V+zJp19C5brwcm0o0a1D + LAKiyIUF/np0jHKljtFqRkwcUOQibQe+wpeOPErP5WOkViE2m5fdnYPG1ruQZLqqVM5SlYC3N95P + qT3GGDMniCzL+MX+P/HGv8foKG3A5CJQAiKtSiygvYD2zoBnXv6AOM6zqbeIiOAbjSjBM0LXoV/R + fv4IDS+cRdZafKPI+WZaHfAFvPt/yOToIMXjL4BzaCxn8hs5vOVB+j5xJ5v611MozJTlSZLwjzff + 4+nnXmd4Mkdf/ycJ8iWU9qYCJqMTmfvJMxenxusiCmtTxi9doK2tANVBCNZgR47yvcpBBsZemTUV + DlfXsC/7Bp29m/Bz8RQIhyNLEyaqFdLz/yEuDzJGjomol/YwJt/WRRAV0NrjWipMM8uZDy+j/ZiO + 4lrijl6CfBfaixARwpyw76HumWrUOYuIor1rbZNpCtvAWei+i1Pn3uZT/JGEq/nsScaE9Xiqeh+2 + eydp1I/zwmm74JwlCBLSwm3Ukzo5mxFpg/YClBeQiiGVmWm4fruHNjmMF6BNgGgzo8qbebuXVnQR + QRvDi50P48bP8tX6H6bOxeFGP0/Uvo7ft4Oe3q2E+VKLIWSWOVyriRbX/F4EmaenajoroASZ44xd + 11QizAW80PdjDo4+QKHyPpOpMBr1UOztJG5fSy4qoow/vxbSN7ZIXhcApT068p2kuZ2kyWZiZ8lr + jfFCtAlQelkmlUsHICKI8fC0wc/lpxUruZla5EYPtqTZB35kfb/6/8AqgFUAqwBWPgAjNF+cgJX3 + socAstJft/kf0E+q1YJmRAgAAAAASUVORK5CYII= diff --git a/plugins/podcasts/debian/rules b/app/debian/rules similarity index 84% rename from plugins/podcasts/debian/rules rename to app/debian/rules index 9669b2e..f3ac343 100755 --- a/plugins/podcasts/debian/rules +++ b/app/debian/rules @@ -16,7 +16,7 @@ configure: configure-stamp configure-stamp: dh_testdir - #qmake + # qmake PREFIX=/usr# Uncomment this line for use without Qt Creator touch configure-stamp @@ -28,8 +28,8 @@ build-stamp: configure-stamp # Add here commands to compile the package. qmake - $(MAKE) - #docbook-to-man debian/musikloud2-podcasts.sgml > musikloud2-podcasts.1 + $(MAKE) # Uncomment this line for use without Qt Creator + #docbook-to-man debian/musikloud2.sgml > musikloud2.1 touch $@ @@ -49,8 +49,8 @@ install: build dh_clean -k dh_installdirs - # Add here commands to install the package into debian/musikloud2-podcasts. - $(MAKE) INSTALL_ROOT="$(CURDIR)"/debian/musikloud2-podcasts install + # Add here commands to install the package into debian/musikloud2. + $(MAKE) INSTALL_ROOT="$(CURDIR)"/debian/musikloud2 install # Build architecture-independent files here. @@ -83,7 +83,7 @@ binary-arch: build install # dh_perl # dh_makeshlibs dh_installdeb - # dh_shlibdeps # Uncomment this line for use without Qt Creator +# dh_shlibdeps # Uncomment this line for use without Qt Creator dh_gencontrol dh_md5sums dh_builddeb diff --git a/app/desktop/harmattan/80/musikloud2.png b/app/desktop/harmattan/80/musikloud2.png deleted file mode 100644 index fe2ad8b..0000000 Binary files a/app/desktop/harmattan/80/musikloud2.png and /dev/null differ diff --git a/app/desktop/harmattan/musikloud2.desktop b/app/desktop/harmattan/musikloud2.desktop deleted file mode 100644 index f796ab7..0000000 --- a/app/desktop/harmattan/musikloud2.desktop +++ /dev/null @@ -1,13 +0,0 @@ -[Desktop Entry] -Encoding=UTF-8 -Version=1.0 -Type=Application -Terminal=false -Name=MusiKloud2 -X-logical-id=musikloud2 -X-translation-catalog=musikloud2 -Exec=/usr/bin/invoker --type=d -s --splash=/opt/musikloud2/splash/splash-portrait.png --splash-landscape=/opt/musikloud2/splash/splash-landscape.png /opt/musikloud2/bin/musikloud2 -Icon=/usr/share/icons/hicolor/80x80/apps/musikloud2.png -MimeType=x-maemo-highlight/soundcloud-url; -X-Maemo-Service=org.marxoft.musikloud2 -X-Maemo-Method=org.marxoft.musikloud2.showResource diff --git a/app/desktop/harmattan/musikloud2.xml b/app/desktop/harmattan/musikloud2.xml deleted file mode 100644 index 14ab976..0000000 --- a/app/desktop/harmattan/musikloud2.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/desktop/harmattan/splash/splash-landscape.png b/app/desktop/harmattan/splash/splash-landscape.png deleted file mode 100644 index 726a3cd..0000000 Binary files a/app/desktop/harmattan/splash/splash-landscape.png and /dev/null differ diff --git a/app/desktop/harmattan/splash/splash-portrait.png b/app/desktop/harmattan/splash/splash-portrait.png deleted file mode 100644 index 4d4524f..0000000 Binary files a/app/desktop/harmattan/splash/splash-portrait.png and /dev/null differ diff --git a/app/desktop/symbian/musikloud2.svg b/app/desktop/symbian/musikloud2.svg new file mode 100755 index 0000000..e4bb86f --- /dev/null +++ b/app/desktop/symbian/musikloud2.svg @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/audioplayer/audioplayer.cpp b/app/src/audioplayer/audioplayer.cpp index ba6dca8..203a843 100644 --- a/app/src/audioplayer/audioplayer.cpp +++ b/app/src/audioplayer/audioplayer.cpp @@ -16,54 +16,199 @@ #include "audioplayer.h" #include "definitions.h" -#include "localtrack.h" +#include "logger.h" +#include "plugintrack.h" #include "resources.h" #include "settings.h" +#include "soundcloudtrack.h" #include "utils.h" +#include #include +#include #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif + +AudioPlayerMetaData::AudioPlayerMetaData(QObject *parent) : + QObject(parent) +{ + connect(AudioPlayer::instance(), SIGNAL(metaDataChanged()), this, SIGNAL(changed())); +} + +QString AudioPlayerMetaData::artist() { + return AudioPlayer::instance()->metaData(Artist).toString(); +} + +QString AudioPlayerMetaData::artistId() { + return AudioPlayer::instance()->metaData(ArtistId).toString(); +} + +QString AudioPlayerMetaData::date() { + return AudioPlayer::instance()->metaData(Date).toString(); +} + +QString AudioPlayerMetaData::description() { + return AudioPlayer::instance()->metaData(Description).toString(); +} + +qint64 AudioPlayerMetaData::duration() { + return AudioPlayer::instance()->metaData(Duration).toLongLong(); +} + +QString AudioPlayerMetaData::durationString() { + return AudioPlayer::instance()->metaData(DurationString).toString(); +} + +QString AudioPlayerMetaData::format() { + return AudioPlayer::instance()->metaData(Format).toString(); +} + +QString AudioPlayerMetaData::genre() { + return AudioPlayer::instance()->metaData(Genre).toString(); +} + +QString AudioPlayerMetaData::id() { + return AudioPlayer::instance()->metaData(Id).toString(); +} + +bool AudioPlayerMetaData::isAvailable() { + return AudioPlayer::instance()->currentTrack() ? true : false; +} + +bool AudioPlayerMetaData::isDownloadable() { + return AudioPlayer::instance()->metaData(Downloadable).toBool(); +} + +QUrl AudioPlayerMetaData::largeThumbnailUrl() { + return AudioPlayer::instance()->metaData(LargeThumbnailUrl).toString(); +} + +QUrl AudioPlayerMetaData::thumbnailUrl() { + return AudioPlayer::instance()->metaData(ThumbnailUrl).toString(); +} + +QString AudioPlayerMetaData::service() { + return AudioPlayer::instance()->metaData(Service).toString(); +} + +qint64 AudioPlayerMetaData::size() { + return AudioPlayer::instance()->metaData(Size).toLongLong(); +} + +QString AudioPlayerMetaData::sizeString() { + return AudioPlayer::instance()->metaData(SizeString).toString(); +} + +QUrl AudioPlayerMetaData::streamUrl() { + return AudioPlayer::instance()->metaData(StreamUrl).toString(); +} + +QString AudioPlayerMetaData::title() { + return AudioPlayer::instance()->metaData(Title).toString(); +} + +QUrl AudioPlayerMetaData::url() { + return AudioPlayer::instance()->metaData(Url).toString(); +} AudioPlayer* AudioPlayer::self = 0; AudioPlayer::AudioPlayer(QObject *parent) : QObject(parent), m_player(new QMediaPlayer(this)), + m_metaData(0), m_queue(new TrackModel(this)), m_soundcloudModel(0), m_pluginModel(0), - m_index(0), + m_index(-1), m_shuffleIndex(0), m_repeat(false), m_shuffle(false), + m_sleepTimerDuration(30), + m_sleepTimerRemaining(0), m_stopAfterCurrentTrack(false), - m_status(Stopped) + m_status(Stopped), + m_metaDataSet(false) { - if (!self) { - self = this; - } + m_sleepTimer.setInterval(60000); connect(m_player, SIGNAL(bufferStatusChanged(int)), this, SLOT(onBufferStatusChanged(int))); connect(m_player, SIGNAL(durationChanged(qint64)), this, SLOT(onDurationChanged(qint64))); connect(m_player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(onError(QMediaPlayer::Error))); + connect(m_player, SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged())); connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(onMediaStatusChanged(QMediaPlayer::MediaStatus))); connect(m_player, SIGNAL(positionChanged(qint64)), this, SIGNAL(positionChanged(qint64))); connect(m_player, SIGNAL(seekableChanged(bool)), this, SLOT(onSeekableChanged())); connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(onStateChanged(QMediaPlayer::State))); + connect(m_player, SIGNAL(volumeChanged(int)), this, SIGNAL(volumeChanged(int))); + connect(this, SIGNAL(currentIndexChanged(int)), m_queue, SLOT(onCurrentIndexChanged())); connect(m_queue, SIGNAL(countChanged(int)), this, SIGNAL(queueCountChanged(int))); + connect(&m_sleepTimer, SIGNAL(timeout()), this, SLOT(onSleepTimerTimeout())); } AudioPlayer::~AudioPlayer() { - if (self == this) { - self = 0; - } + saveQueue(); + self = 0; } AudioPlayer* AudioPlayer::instance() { - return self; + return self ? self : self = new AudioPlayer; +} + +AudioPlayerMetaData* AudioPlayer::metaData() { + if (!m_metaData) { + m_metaData = new AudioPlayerMetaData(this); + } + + return m_metaData; +} + +QVariant AudioPlayer::metaData(AudioPlayerMetaData::MetaData key) const { + const MKTrack *track = currentTrack(); + + if (!track) { + return QVariant(); + } + + switch (key) { + case AudioPlayerMetaData::Artist: + return track->artist(); + case AudioPlayerMetaData::ArtistId: + return track->id(); + case AudioPlayerMetaData::Date: + return track->date(); + case AudioPlayerMetaData::Description: + return track->description(); + case AudioPlayerMetaData::Downloadable: + return track->isDownloadable(); + case AudioPlayerMetaData::Duration: + return track->duration(); + case AudioPlayerMetaData::DurationString: + return track->durationString(); + case AudioPlayerMetaData::Format: + return track->format(); + case AudioPlayerMetaData::Genre: + return track->genre(); + case AudioPlayerMetaData::Id: + return track->id(); + case AudioPlayerMetaData::LargeThumbnailUrl: + return track->largeThumbnailUrl(); + case AudioPlayerMetaData::ThumbnailUrl: + return track->largeThumbnailUrl(); + case AudioPlayerMetaData::Service: + return track->service(); + case AudioPlayerMetaData::Size: + return track->size(); + case AudioPlayerMetaData::SizeString: + return track->sizeString(); + case AudioPlayerMetaData::StreamUrl: + return track->streamUrl(); + case AudioPlayerMetaData::Title: + return track->title(); + case AudioPlayerMetaData::Url: + return track->url(); + default: + return QVariant(); + } } int AudioPlayer::bufferStatus() const { @@ -74,30 +219,17 @@ int AudioPlayer::currentIndex() const { return m_index; } -void AudioPlayer::setCurrentIndex(int i) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::setCurrentIndex" << i; -#endif +void AudioPlayer::setCurrentIndex(int i, bool autoPlay) { if ((i >= 0) && (i < queueCount())) { m_index = i; + m_metaDataSet = false; + autoPlay = (autoPlay) || (isPlaying()); + stop(); emit currentIndexChanged(i); + emit metaDataChanged(); - if (MKTrack *track = currentTrack()) { - stop(); - - if (!track->streamUrl().isEmpty()) { - m_player->setMedia(track->streamUrl()); - m_player->play(); - } - else if (track->service() == Resources::SOUNDCLOUD) { - initSoundCloudModel(); - m_soundcloudModel->get(track->id()); - } - else { - initPluginModel(); - m_pluginModel->setService(track->service()); - m_pluginModel->list(track->id()); - } + if (autoPlay) { + play(); } } } @@ -119,10 +251,12 @@ QString AudioPlayer::errorString() const { } void AudioPlayer::setErrorString(const QString &e) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::setErrorString" << e; -#endif m_errorString = e; + + if (!e.isEmpty()) { + Logger::log("AudioPlayer::error(). " + e); + emit error(e); + } } bool AudioPlayer::isPaused() const { @@ -156,8 +290,18 @@ void AudioPlayer::setPlaying(bool p) { } } +void AudioPlayer::togglePlaying() { + setPlaying(!isPlaying()); +} + bool AudioPlayer::isStopped() const { - return status() == Stopped; + switch (status()) { + case Stopped: + case Failed: + return true; + default: + return false; + } } qint64 AudioPlayer::position() const { @@ -189,9 +333,6 @@ bool AudioPlayer::repeatEnabled() const { } void AudioPlayer::setRepeatEnabled(bool r) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::setRepeatEnabled" << r; -#endif if (r != repeatEnabled()) { m_repeat = r; emit repeatEnabledChanged(r); @@ -203,9 +344,6 @@ bool AudioPlayer::shuffleEnabled() const { } void AudioPlayer::setShuffleEnabled(bool s) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::setShuffleEnabled" << s; -#endif if (s != shuffleEnabled()) { m_shuffle = s; emit shuffleEnabledChanged(s); @@ -216,14 +354,60 @@ void AudioPlayer::setShuffleEnabled(bool s) { } } +int AudioPlayer::sleepTimerDuration() const { + return m_sleepTimerDuration; +} + +void AudioPlayer::setSleepTimerDuration(int d) { + if ((d != sleepTimerDuration()) && (d > 0)) { + m_sleepTimerDuration = d; + emit sleepTimerDurationChanged(d); + } +} + +QString AudioPlayer::sleepTimerDurationString() const { + return Utils::formatMSecs(sleepTimerDuration() * 60000); +} + +bool AudioPlayer::sleepTimerEnabled() const { + return m_sleepTimer.isActive(); +} + +void AudioPlayer::setSleepTimerEnabled(bool e) { + if (e != sleepTimerEnabled()) { + if (e) { + setSleepTimerRemaining(sleepTimerDuration()); + m_sleepTimer.start(); + } + else { + setSleepTimerRemaining(0); + m_sleepTimer.stop(); + } + + emit sleepTimerEnabledChanged(e); + } +} + +int AudioPlayer::sleepTimerRemaining() const { + return m_sleepTimerRemaining; +} + +void AudioPlayer::setSleepTimerRemaining(int r) { + if ((r != sleepTimerRemaining()) && (r >= 0)) { + m_sleepTimerRemaining = r; + emit sleepTimerRemainingChanged(r); + } +} + +QString AudioPlayer::sleepTimerRemainingString() const { + return Utils::formatMSecs(sleepTimerRemaining() * 60000); +} + bool AudioPlayer::stopAfterCurrentTrack() const { return m_stopAfterCurrentTrack; } void AudioPlayer::setStopAfterCurrentTrack(bool s) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::setStopAfterCurrentTrack" << s; -#endif if (s != stopAfterCurrentTrack()) { m_stopAfterCurrentTrack = s; emit stopAfterCurrentTrackChanged(s); @@ -235,30 +419,27 @@ AudioPlayer::Status AudioPlayer::status() const { } void AudioPlayer::setStatus(AudioPlayer::Status s) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::setStatus" << s; -#endif if (s != status()) { m_status = s; emit statusChanged(s); } } -bool AudioPlayer::addFolder(const QString &folder) { - QList urls; - QDir dir(folder); - - foreach (QString fileName, dir.entryList(SUPPORTED_AUDIO_FORMATS, QDir::Files)) { - urls << QUrl::fromLocalFile(dir.absoluteFilePath(fileName)); - } - - addUrls(urls); - return !urls.isEmpty(); +int AudioPlayer::volume() const { + return m_player->volume(); +} + +void AudioPlayer::setVolume(int v) { + m_player->setVolume(v); } void AudioPlayer::addTrack(MKTrack *track) { m_queue->append(new MKTrack(track, m_queue)); + if (currentIndex() < 0) { + setCurrentIndex(0, false); + } + if (shuffleEnabled()) { shuffleTracks(); } @@ -269,27 +450,32 @@ void AudioPlayer::addTracks(const QList &tracks) { m_queue->append(new MKTrack(track, m_queue)); } + if (currentIndex() < 0) { + setCurrentIndex(0, false); + } + if (shuffleEnabled()) { shuffleTracks(); } } void AudioPlayer::addTracks(const QVariantList &tracks) { - foreach (QVariant v, tracks) { + foreach (const QVariant &v, tracks) { if (MKTrack *track = qobject_cast(v.value())) { m_queue->append(new MKTrack(track, m_queue)); } } + if (currentIndex() < 0) { + setCurrentIndex(0, false); + } + if (shuffleEnabled()) { shuffleTracks(); } } void AudioPlayer::removeTrack(int i) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::removeTrack" << i; -#endif const int current = currentIndex(); m_queue->remove(i); @@ -312,59 +498,252 @@ void AudioPlayer::removeTrack(int i) { if (shuffleEnabled()) { if (m_shuffleIndex < (m_shuffleOrder.size() - 1)) { next(); - } - else { - stop(); + return; } } else if (i < queueCount()) { next(); + return; } - else { - stop(); - } + + stop(); + } + + emit currentIndexChanged(m_index); + emit metaDataChanged(); + } +} + +void AudioPlayer::addLocalFile(const QString &fileName) { + const QFileInfo info(fileName); + + if (info.isDir()) { + Logger::log("AudioPlayer::addLocalFile(). Listing directory " + fileName, Logger::MediumVerbosity); + const QDir dir(fileName); + + foreach (const QString &name, + dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable, + QDir::Name | QDir::IgnoreCase | QDir::DirsLast)) { + addLocalFile(dir.absoluteFilePath(name)); + } + } + else if (info.isReadable()) { + Logger::log("AudioPlayer::addLocalFile(). Checking mimetype for " + fileName, Logger::MediumVerbosity); + + if (Utils::isAudioFile(info)) { + Logger::log("AudioPlayer::addLocalFile(). Adding track " + fileName, Logger::MediumVerbosity); + m_queue->append(trackFromUrl(QUrl::fromLocalFile(fileName))); } else { - emit currentIndexChanged(m_index); + Logger::log("AudioPlayer::addLocalFile(). Mimetype not supported for " + fileName, Logger::MediumVerbosity); } } + else { + Logger::log("AudioPlayer::addLocalFile(). File is not readable: " + fileName); + } } -void AudioPlayer::addUrl(const QUrl &url) { - m_queue->append(new LocalTrack(url, m_queue)); +MKTrack* AudioPlayer::trackFromUrl(const QUrl &url) { + MKTrack *track = new MKTrack(m_queue); + track->setDownloadable(false); + track->setStreamUrl(url); + track->setUrl(url); + + if (Utils::isLocalFile(url)) { + const QFileInfo info(Utils::toLocalFile(url)); + const QDir dir = info.dir(); + track->setArtist(dir.dirName()); + track->setArtistId(dir.dirName()); + track->setDate(info.lastModified().toString("dd MMM yyyy")); + track->setFormat(info.suffix().toUpper()); + track->setGenre(dir.dirName()); + track->setId(info.absoluteFilePath()); + track->setSize(info.size()); + track->setTitle(info.completeBaseName()); + + } + else { + const QString s = url.toString(); + track->setArtist(s.section("/", -2)); + track->setArtistId(track->artist()); + track->setFormat(s.mid(s.lastIndexOf(".") + 1).toUpper()); + track->setGenre(s.section("/", -3)); + track->setId(s); + track->setTitle(s.section("/", -1).section(".", -2)); + } + + return track; +} + +int AudioPlayer::addUrl(const QUrl &url) { + const int count = queueCount(); + + if (Utils::isLocalFile(url)) { + addLocalFile(Utils::toLocalFile(url)); + } + else { + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + + if (!resource.isEmpty()) { + if (resource.value("type") == Resources::TRACK) { + if (resource.value("service") == Resources::SOUNDCLOUD) { + m_queue->append(new SoundCloudTrack(resource.value("id").toString(), m_queue)); + } + else { + m_queue->append(new PluginTrack(resource.value("service").toString(), + resource.value("id").toString(), m_queue)); + } + } + } + else { + m_queue->append(trackFromUrl(url)); + } + } + + if (currentIndex() < 0) { + setCurrentIndex(0, false); + } if (shuffleEnabled()) { shuffleTracks(); } + + return queueCount() - count; } -void AudioPlayer::addUrls(const QList &urls) { - foreach (QUrl url, urls) { - m_queue->append(new LocalTrack(url, m_queue)); +int AudioPlayer::addUrls(const QList &urls) { + const int count = queueCount(); + + foreach (const QUrl &url, urls) { + if (Utils::isLocalFile(url)) { + addLocalFile(Utils::toLocalFile(url)); + } + else { + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + + if (!resource.isEmpty()) { + if (resource.value("type") == Resources::TRACK) { + if (resource.value("service") == Resources::SOUNDCLOUD) { + m_queue->append(new SoundCloudTrack(resource.value("id").toString(), m_queue)); + } + else { + m_queue->append(new PluginTrack(resource.value("service").toString(), + resource.value("id").toString(), m_queue)); + } + } + } + else { + m_queue->append(trackFromUrl(url)); + } + } + } + + if (currentIndex() < 0) { + setCurrentIndex(0, false); } if (shuffleEnabled()) { shuffleTracks(); } + + return queueCount() - count; } void AudioPlayer::clearQueue() { stop(); m_queue->clear(); m_shuffleOrder.clear(); - m_index = 0; + m_index = -1; m_shuffleIndex = 0; + emit currentIndexChanged(-1); + emit metaDataChanged(); +} + +int AudioPlayer::restoreQueue() { + QSettings settings(APP_CONFIG_PATH + "queue.conf", QSettings::IniFormat); + const int size = settings.beginReadArray("queue"); + + if (size > 0) { + QList tracks; + + for (int i = 0; i < size; i++) { + settings.setArrayIndex(i); + MKTrack *track = new MKTrack(m_queue); + track->setArtist(settings.value("artist").toString()); + track->setArtistId(settings.value("artistId").toString()); + track->setDate(settings.value("date").toString()); + track->setDownloadable(settings.value("downloadable", false).toBool()); + track->setDurationString(settings.value("durationString").toString()); + track->setDuration(settings.value("duration").toLongLong()); + track->setFormat(settings.value("format").toString()); + track->setGenre(settings.value("genre").toString()); + track->setId(settings.value("id").toString()); + track->setLargeThumbnailUrl(settings.value("largeThumbnailUrl").toString()); + track->setThumbnailUrl(settings.value("thumbnailUrl").toString()); + track->setPlayCount(settings.value("playCount").toLongLong()); + track->setService(settings.value("service").toString()); + track->setSizeString(settings.value("sizeString").toString()); + track->setSize(settings.value("size").toLongLong()); + track->setStreamUrl(settings.value("streamUrl").toString()); + track->setTitle(settings.value("title").toString()); + track->setUrl(settings.value("url").toString()); + tracks << track; + } + + addTracks(tracks); + } + + settings.endArray(); + setCurrentIndex(qMax(0, settings.value("currentIndex", 0).toInt()), false); + Logger::log(QString("AudioPlayer::restoreQueue(). %1 tracks restored").arg(size), Logger::LowVerbosity); + return size; +} + +void AudioPlayer::saveQueue() { + QSettings settings(APP_CONFIG_PATH + "queue.conf", QSettings::IniFormat); + settings.clear(); + settings.setValue("currentIndex", currentIndex()); + settings.beginWriteArray("queue"); + + for (int i = 0; i < m_queue->rowCount(); i++) { + const QModelIndex index = m_queue->index(i); + settings.setArrayIndex(i); + settings.setValue("artist", index.data(TrackModel::ArtistRole)); + settings.setValue("artistId", index.data(TrackModel::ArtistIdRole)); + settings.setValue("date", index.data(TrackModel::DateRole)); + settings.setValue("downloadable", index.data(TrackModel::DownloadableRole)); + settings.setValue("duration", index.data(TrackModel::DurationRole)); + settings.setValue("durationString", index.data(TrackModel::DurationStringRole)); + settings.setValue("format", index.data(TrackModel::FormatRole)); + settings.setValue("genre", index.data(TrackModel::GenreRole)); + settings.setValue("id", index.data(TrackModel::IdRole)); + settings.setValue("largeThumbnailUrl", index.data(TrackModel::LargeThumbnailUrlRole).toString()); + settings.setValue("thumbnailUrl", index.data(TrackModel::ThumbnailUrlRole).toString()); + settings.setValue("playCount", index.data(TrackModel::PlayCountRole)); + settings.setValue("service", index.data(TrackModel::ServiceRole)); + settings.setValue("size", index.data(TrackModel::SizeRole)); + settings.setValue("sizeString", index.data(TrackModel::SizeStringRole)); + settings.setValue("streamUrl", index.data(TrackModel::StreamUrlRole).toString()); + settings.setValue("title", index.data(TrackModel::TitleRole)); + settings.setValue("url", index.data(TrackModel::UrlRole).toString()); + } + + settings.endArray(); + Logger::log(QString("AudioPlayer::saveQueue(). %1 tracks saved").arg(m_queue->rowCount()), + Logger::LowVerbosity); } -void AudioPlayer::next() { +void AudioPlayer::next(bool autoPlay) { + autoPlay = (autoPlay) || (isPlaying()); + if (shuffleEnabled()) { if (m_shuffleIndex < (m_shuffleOrder.size() - 1)) { m_shuffleIndex++; - setCurrentIndex(m_shuffleOrder.at(m_shuffleIndex)); + setCurrentIndex(m_shuffleOrder.at(m_shuffleIndex), autoPlay); } } else { - setCurrentIndex(currentIndex() + 1); + setCurrentIndex(currentIndex() + 1, autoPlay); } } @@ -373,31 +752,30 @@ void AudioPlayer::pause() { } void AudioPlayer::play() { - switch (status()) { - case Stopped: - case Failed: - setCurrentIndex(currentIndex()); - break; - default: - m_player->play(); - break; + if (currentIndex() < 0) { + setCurrentIndex(0, false); } -} - -bool AudioPlayer::playFolder(const QString &folder) { - QList urls; - QDir dir(folder); - foreach (QString fileName, dir.entryList(SUPPORTED_AUDIO_FORMATS, QDir::Files)) { - urls << QUrl::fromLocalFile(dir.absoluteFilePath(fileName)); + if (isStopped()) { + if (const MKTrack *track = currentTrack()) { + if (!track->streamUrl().isEmpty()) { + m_player->setMedia(track->streamUrl()); + m_player->play(); + } + else if (track->service() == Resources::SOUNDCLOUD) { + initSoundCloudModel(); + m_soundcloudModel->get(track->id()); + } + else { + initPluginModel(); + m_pluginModel->setService(track->service()); + m_pluginModel->list(track->id()); + } + } } - - if (urls.isEmpty()) { - return false; + else { + m_player->play(); } - - playUrls(urls); - return true; } void AudioPlayer::playTrack(MKTrack *track) { @@ -418,49 +796,75 @@ void AudioPlayer::playTracks(const QVariantList &tracks) { play(); } -void AudioPlayer::playUrl(const QUrl &url) { +int AudioPlayer::playUrl(const QUrl &url) { clearQueue(); - addUrl(url); - play(); + const int added = addUrl(url); + + if (added > 0) { + play(); + } + + return added; } -void AudioPlayer::playUrls(const QList &urls) { +int AudioPlayer::playUrls(const QList &urls) { clearQueue(); - addUrls(urls); - play(); + const int added = addUrls(urls); + + if (added > 0) { + play(); + } + + return added; } -void AudioPlayer::previous() { +void AudioPlayer::previous(bool autoPlay) { + autoPlay = (autoPlay) || (isPlaying()); + if (shuffleEnabled()) { if (m_shuffleIndex > 0) { m_shuffleIndex--; - setCurrentIndex(m_shuffleOrder.at(m_shuffleIndex)); + setCurrentIndex(m_shuffleOrder.at(m_shuffleIndex), autoPlay); } } else { - setCurrentIndex(currentIndex() - 1); + setCurrentIndex(currentIndex() - 1, autoPlay); } } void AudioPlayer::stop() { m_player->stop(); m_player->setMedia(QMediaContent()); + emit durationChanged(0); + emit seekableChanged(false); - if (m_soundcloudModel) { + if ((m_soundcloudModel) && (m_soundcloudModel->status() == QSoundCloud::StreamsRequest::Loading)) { m_soundcloudModel->cancel(); } - if (m_pluginModel) { + if ((m_pluginModel) && (m_pluginModel->status() == ResourcesRequest::Loading)) { m_pluginModel->cancel(); } } +void AudioPlayer::shuffleTracks() { + const int oldSize = m_shuffleOrder.size(); + + for (int i = oldSize; i < queueCount(); i++) { + m_shuffleOrder << i; + } + + if (m_shuffleOrder.size() > oldSize) { + std::random_shuffle(m_shuffleOrder.begin() + oldSize, m_shuffleOrder.end()); + } +} + void AudioPlayer::initPluginModel() { if (!m_pluginModel) { m_pluginModel = new PluginStreamModel(this); connect(m_pluginModel, SIGNAL(statusChanged(ResourcesRequest::Status)), this, SLOT(onPluginModelStatusChanged(ResourcesRequest::Status))); - } + } } void AudioPlayer::initSoundCloudModel() { @@ -468,25 +872,7 @@ void AudioPlayer::initSoundCloudModel() { m_soundcloudModel = new SoundCloudStreamModel(this); connect(m_soundcloudModel, SIGNAL(statusChanged(QSoundCloud::StreamsRequest::Status)), this, SLOT(onSoundCloudModelStatusChanged(QSoundCloud::StreamsRequest::Status))); - } -} - -void AudioPlayer::shuffleTracks() { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::shuffleTracks before:" << m_shuffleOrder; -#endif - const int oldSize = m_shuffleOrder.size(); - - for (int i = oldSize; i < queueCount(); i++) { - m_shuffleOrder << i; - } - - if (m_shuffleOrder.size() > oldSize) { - std::random_shuffle(m_shuffleOrder.begin() + oldSize, m_shuffleOrder.end()); - } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "AudioPlayer::shuffleTracks after:" << m_shuffleOrder; -#endif + } } void AudioPlayer::onBufferStatusChanged(int b) { @@ -499,12 +885,20 @@ void AudioPlayer::onBufferStatusChanged(int b) { } void AudioPlayer::onDurationChanged(qint64 d) { + if (d > 0) { + if (MKTrack *track = currentTrack()) { + track->setDuration(d); + } + } + emit durationChanged(d); emit seekableChanged(isSeekable()); + emit metaDataChanged(); } void AudioPlayer::onError(QMediaPlayer::Error e) { - if (e != QMediaPlayer::NoError) { + // Symbian throws an error when setting empty media, so ignore the error in this case + if ((e != QMediaPlayer::NoError) && (!m_player->media().isNull())) { setErrorString(m_player->errorString()); setStatus(Failed); } @@ -532,7 +926,7 @@ void AudioPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus m) { play(); } else { - next(); + next(true); } } @@ -542,6 +936,156 @@ void AudioPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus m) { } } +void AudioPlayer::onMetaDataChanged() { + if (m_metaDataSet) { + return; + } + + MKTrack *track = currentTrack(); + + if (!track) { + emit metaDataChanged(); + return; + } + + const QUrl url = track->url(); + QUrl thumbnailUrl = track->thumbnailUrl(); + + if (Utils::isLocalFile(url)) { + if (thumbnailUrl.isEmpty()) { + thumbnailUrl = Utils::thumbnailUrlForFile(Utils::toLocalFile(url)); + + if (!thumbnailUrl.isEmpty()) { + track->setThumbnailUrl(thumbnailUrl); + track->setLargeThumbnailUrl(thumbnailUrl); + } + } + } + + if (m_player->availableMetaData().isEmpty()) { + Logger::log("AudioPlayer::onMetaDataChanged(). No metadata available", Logger::HighVerbosity); + emit metaDataChanged(); + return; + } + + Logger::log("AudioPlayer::onMetaDataChanged(). Metadata is available", Logger::HighVerbosity); +#if QT_VERSION >= 0x050000 + QVariant artist = m_player->metaData("ContributingArtist"); + + if (artist.isNull()) { + artist = m_player->metaData("AlbumArtist"); + + if (artist.isNull()) { + artist = m_player->metaData("composer"); + } + } + + if (!artist.isNull()) { + track->setArtist(artist.toString()); + } + + const QVariant date = m_player->metaData("Date"); + + if (!date.isNull()) { + track->setDate(date.toDateTime().toString("dd MMM yyyy")); + } + + QVariant genre = m_player->metaData("Genre"); + + if (genre.isNull()) { + genre = m_player->metaData("AlbumTitle"); + } + + if (!genre.isNull()) { + track->setGenre(genre.toString()); + } + + const QVariant size = m_player->metaData("Size"); + + if (!size.isNull()) { + track->setSize(size.toLongLong()); + } + + const QVariant title = m_player->metaData("Title"); + + if (!title.isNull()) { + track->setTitle(title.toString()); + } + + if (thumbnailUrl.isEmpty()) { + QVariant cover = m_player->metaData("coverArtUrlLarge"); + + if (cover.isNull()) { + cover = m_player->metaData("coverArtUrlSmall"); + } + + if (!cover.isNull()) { + thumbnailUrl = cover.toString(); + track->setThumbnailUrl(thumbnailUrl); + track->setLargeThumbnailUrl(thumbnailUrl); + } + } +#else + QVariant artist = m_player->metaData(QtMultimediaKit::ContributingArtist); + + if (artist.isNull()) { + artist = m_player->metaData(QtMultimediaKit::AlbumArtist); + + if (artist.isNull()) { + artist = m_player->metaData(QtMultimediaKit::Composer); + } + } + + if (!artist.isNull()) { + track->setArtist(artist.toString()); + } + + const QVariant date = m_player->metaData(QtMultimediaKit::Date); + + if (!date.isNull()) { + track->setDate(date.toDateTime().toString("dd MMM yyyy")); + } + + QVariant genre = m_player->metaData(QtMultimediaKit::Genre); + + if (genre.isNull()) { + genre = m_player->metaData(QtMultimediaKit::AlbumTitle); + } + + if (!genre.isNull()) { + track->setGenre(genre.toString()); + } + + const QVariant size = m_player->metaData(QtMultimediaKit::Size); + + if (!size.isNull()) { + track->setSize(size.toLongLong()); + } + + const QVariant title = m_player->metaData(QtMultimediaKit::Title); + + if (!title.isNull()) { + track->setTitle(title.toString()); + } + + if (thumbnailUrl.isEmpty()) { + QVariant cover = m_player->metaData(QtMultimediaKit::CoverArtUrlLarge); + + if (cover.isNull()) { + cover = m_player->metaData(QtMultimediaKit::CoverArtUrlSmall); + } + + if (!cover.isNull()) { + thumbnailUrl = cover.toString(); + track->setThumbnailUrl(thumbnailUrl); + track->setLargeThumbnailUrl(thumbnailUrl); + } + } +#endif + m_metaDataSet = true; + emit metaDataChanged(); +} + void AudioPlayer::onPluginModelStatusChanged(ResourcesRequest::Status s) { switch (s) { case ResourcesRequest::Loading: @@ -552,7 +1096,7 @@ void AudioPlayer::onPluginModelStatusChanged(ResourcesRequest::Status s) { if (m_pluginModel->rowCount() > 0) { m_player->setMedia(QUrl(m_pluginModel->data(qMax(0, m_pluginModel->match("name", - Settings::instance()->defaultPlaybackFormat(m_pluginModel->service()))), "value") + Settings::defaultPlaybackFormat(m_pluginModel->service()))), "value") .toMap().value("url").toString())); if (!isPaused()) { @@ -579,6 +1123,15 @@ void AudioPlayer::onSeekableChanged() { emit seekableChanged(isSeekable()); } +void AudioPlayer::onSleepTimerTimeout() { + setSleepTimerRemaining(sleepTimerRemaining() - 1); + + if (sleepTimerRemaining() == 0) { + setSleepTimerEnabled(false); + stop(); + } +} + void AudioPlayer::onSoundCloudModelStatusChanged(QSoundCloud::StreamsRequest::Status s) { switch (s) { case QSoundCloud::StreamsRequest::Loading: @@ -589,7 +1142,7 @@ void AudioPlayer::onSoundCloudModelStatusChanged(QSoundCloud::StreamsRequest::St if (m_soundcloudModel->rowCount() > 0) { m_player->setMedia(QUrl(m_soundcloudModel->data(qMax(0, m_soundcloudModel->match("name", - Settings::instance()->defaultPlaybackFormat(Resources::SOUNDCLOUD))), "value") + Settings::defaultPlaybackFormat(Resources::SOUNDCLOUD))), "value") .toMap().value("url").toString())); if (!isPaused()) { diff --git a/app/src/audioplayer/audioplayer.h b/app/src/audioplayer/audioplayer.h index 58b24d7..99fff26 100644 --- a/app/src/audioplayer/audioplayer.h +++ b/app/src/audioplayer/audioplayer.h @@ -21,11 +21,101 @@ #include "soundcloudstreammodel.h" #include "trackmodel.h" #include +#include + +class AudioPlayerMetaData : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString artist READ artist NOTIFY changed) + Q_PROPERTY(QString artistId READ artistId NOTIFY changed) + Q_PROPERTY(bool available READ isAvailable NOTIFY changed) + Q_PROPERTY(QString date READ date NOTIFY changed) + Q_PROPERTY(QString description READ description NOTIFY changed) + Q_PROPERTY(bool downloadable READ isDownloadable NOTIFY changed) + Q_PROPERTY(qint64 duration READ duration NOTIFY changed) + Q_PROPERTY(QString durationString READ durationString NOTIFY changed) + Q_PROPERTY(QString format READ format NOTIFY changed) + Q_PROPERTY(QString genre READ genre NOTIFY changed) + Q_PROPERTY(QString id READ id NOTIFY changed) + Q_PROPERTY(QUrl largeThumbnailUrl READ largeThumbnailUrl NOTIFY changed) + Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY changed) + Q_PROPERTY(QString service READ service NOTIFY changed) + Q_PROPERTY(qint64 size READ size NOTIFY changed) + Q_PROPERTY(QString sizeString READ sizeString NOTIFY changed) + Q_PROPERTY(QUrl streamUrl READ streamUrl NOTIFY changed) + Q_PROPERTY(QString title READ title NOTIFY changed) + Q_PROPERTY(QUrl url READ url NOTIFY changed) + + Q_ENUMS(MetaData) + +public: + enum MetaData { + Artist = 0, + ArtistId, + Date, + Description, + Downloadable, + Duration, + DurationString, + Format, + Genre, + Id, + LargeThumbnailUrl, + ThumbnailUrl, + Service, + Size, + SizeString, + StreamUrl, + Title, + Url + }; + + explicit AudioPlayerMetaData(QObject *parent = 0); + + static QString artist(); + static QString artistId(); + + static QString date(); + + static QString description(); + + static qint64 duration(); + static QString durationString(); + + static QString format(); + + static QString genre(); + + static QString id(); + + static bool isAvailable(); + + static bool isDownloadable(); + + static QUrl largeThumbnailUrl(); + static QUrl thumbnailUrl(); + + static QString service(); + + static qint64 size(); + static QString sizeString(); + + static QUrl streamUrl(); + + static QString title(); + + static QUrl url(); + +Q_SIGNALS: + void changed(); +}; class AudioPlayer : public QObject { Q_OBJECT + Q_PROPERTY(AudioPlayerMetaData* metaData READ metaData CONSTANT) Q_PROPERTY(int bufferStatus READ bufferStatus NOTIFY bufferStatusChanged) Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) Q_PROPERTY(MKTrack* currentTrack READ currentTrack NOTIFY currentIndexChanged) @@ -42,9 +132,17 @@ class AudioPlayer : public QObject Q_PROPERTY(bool repeat READ repeatEnabled WRITE setRepeatEnabled NOTIFY repeatEnabledChanged) Q_PROPERTY(bool seekable READ isSeekable NOTIFY seekableChanged) Q_PROPERTY(bool shuffle READ shuffleEnabled WRITE setShuffleEnabled NOTIFY shuffleEnabledChanged) + Q_PROPERTY(int sleepTimerDuration READ sleepTimerDuration WRITE setSleepTimerDuration + NOTIFY sleepTimerDurationChanged) + Q_PROPERTY(QString sleepTimerDurationString READ sleepTimerDurationString NOTIFY sleepTimerDurationChanged) + Q_PROPERTY(bool sleepTimerEnabled READ sleepTimerEnabled WRITE setSleepTimerEnabled + NOTIFY sleepTimerEnabledChanged) + Q_PROPERTY(int sleepTimerRemaining READ sleepTimerRemaining NOTIFY sleepTimerRemainingChanged) + Q_PROPERTY(QString sleepTimerRemainingString READ sleepTimerRemainingString NOTIFY sleepTimerRemainingChanged) Q_PROPERTY(bool stopAfterCurrentTrack READ stopAfterCurrentTrack WRITE setStopAfterCurrentTrack NOTIFY stopAfterCurrentTrackChanged) Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) Q_ENUMS(Status) @@ -65,6 +163,9 @@ class AudioPlayer : public QObject static AudioPlayer* instance(); + AudioPlayerMetaData* metaData(); + QVariant metaData(AudioPlayerMetaData::MetaData key) const; + int bufferStatus() const; int currentIndex() const; @@ -84,7 +185,7 @@ class AudioPlayer : public QObject QString positionString() const; TrackModel* queue() const; - + int queueCount() const; bool repeatEnabled() const; @@ -93,15 +194,24 @@ class AudioPlayer : public QObject bool shuffleEnabled() const; + int sleepTimerDuration() const; + QString sleepTimerDurationString() const; + bool sleepTimerEnabled() const; + int sleepTimerRemaining() const; + QString sleepTimerRemainingString() const; + bool stopAfterCurrentTrack() const; Status status() const; + int volume() const; + public Q_SLOTS: - void setCurrentIndex(int i); + void setCurrentIndex(int i, bool autoPlay = false); void setPaused(bool p); void setPlaying(bool p); + void togglePlaying(); void setPosition(qint64 p); @@ -109,39 +219,35 @@ public Q_SLOTS: void setShuffleEnabled(bool s); + void setSleepTimerDuration(int d); + void setSleepTimerEnabled(bool e); + void setStopAfterCurrentTrack(bool s); - bool addFolder(const QString &folder); - + void setVolume(int v); + void addTrack(MKTrack *track); void addTracks(const QList &tracks); void addTracks(const QVariantList &tracks); // For QML void removeTrack(int i); - void addUrl(const QUrl &url); - void addUrls(const QList &urls); + int addUrl(const QUrl &url); + int addUrls(const QList &urls); void clearQueue(); + int restoreQueue(); + void saveQueue(); - void next(); + void next(bool autoPlay = false); void pause(); void play(); - bool playFolder(const QString &folder); void playTrack(MKTrack *track); void playTracks(const QList &tracks); void playTracks(const QVariantList &tracks); // For QML - void playUrl(const QUrl &url); - void playUrls(const QList &urls); - void previous(); - void stop(); - -private: - void setErrorString(const QString &e); - - void setStatus(Status s); - - void initPluginModel(); - void initSoundCloudModel(); + int playUrl(const QUrl &url); + int playUrls(const QList &urls); + void previous(bool autoPlay = false); + void stop(); private Q_SLOTS: void shuffleTracks(); @@ -150,8 +256,10 @@ private Q_SLOTS: void onDurationChanged(qint64 d); void onError(QMediaPlayer::Error e); void onMediaStatusChanged(QMediaPlayer::MediaStatus m); + void onMetaDataChanged(); void onPluginModelStatusChanged(ResourcesRequest::Status s); void onSeekableChanged(); + void onSleepTimerTimeout(); void onSoundCloudModelStatusChanged(QSoundCloud::StreamsRequest::Status s); void onStateChanged(QMediaPlayer::State s); @@ -161,6 +269,10 @@ private Q_SLOTS: void currentIndexChanged(int i); void durationChanged(qint64 d); + + void error(const QString &e); + + void metaDataChanged(); void positionChanged(qint64 p); @@ -172,14 +284,34 @@ private Q_SLOTS: void shuffleEnabledChanged(bool s); + void sleepTimerDurationChanged(int d); + void sleepTimerEnabledChanged(bool e); + void sleepTimerRemainingChanged(int r); + void stopAfterCurrentTrackChanged(bool s); void statusChanged(AudioPlayer::Status s); + + void volumeChanged(int v); private: + void setErrorString(const QString &e); + + void setSleepTimerRemaining(int r); + + void setStatus(Status s); + + void addLocalFile(const QString &fileName); + + MKTrack* trackFromUrl(const QUrl &url); + + void initPluginModel(); + void initSoundCloudModel(); + static AudioPlayer *self; QMediaPlayer *m_player; + AudioPlayerMetaData *m_metaData; TrackModel *m_queue; SoundCloudStreamModel *m_soundcloudModel; @@ -195,9 +327,15 @@ private Q_SLOTS: bool m_shuffle; QList m_shuffleOrder; + QTimer m_sleepTimer; + int m_sleepTimerDuration; + int m_sleepTimerRemaining; + bool m_stopAfterCurrentTrack; Status m_status; -}; + bool m_metaDataSet; +}; + #endif // AUDIOPLAYER_H diff --git a/app/src/base/artist.cpp b/app/src/base/artist.cpp index 0f29edd..c72bed9 100644 --- a/app/src/base/artist.cpp +++ b/app/src/base/artist.cpp @@ -21,14 +21,15 @@ MKArtist::MKArtist(QObject *parent) : { } -MKArtist::MKArtist(MKArtist *artist, QObject *parent) : +MKArtist::MKArtist(const MKArtist *artist, QObject *parent) : QObject(parent), m_description(artist->description()), m_id(artist->id()), m_largeThumbnailUrl(artist->largeThumbnailUrl()), m_thumbnailUrl(artist->thumbnailUrl()), m_name(artist->name()), - m_service(artist->service()) + m_service(artist->service()), + m_url(artist->url()) { } @@ -39,6 +40,7 @@ QString MKArtist::description() const { void MKArtist::setDescription(const QString &d) { if (d != description()) { m_description = d; + emit changed(); emit descriptionChanged(); } } @@ -50,6 +52,7 @@ QString MKArtist::id() const { void MKArtist::setId(const QString &i) { if (i != id()) { m_id = i; + emit changed(); emit idChanged(); } } @@ -61,6 +64,7 @@ QUrl MKArtist::largeThumbnailUrl() const { void MKArtist::setLargeThumbnailUrl(const QUrl &u) { if (u != largeThumbnailUrl()) { m_largeThumbnailUrl = u; + emit changed(); emit largeThumbnailUrlChanged(); } } @@ -72,6 +76,7 @@ QUrl MKArtist::thumbnailUrl() const { void MKArtist::setThumbnailUrl(const QUrl &u) { if (u != thumbnailUrl()) { m_thumbnailUrl = u; + emit changed(); emit thumbnailUrlChanged(); } } @@ -83,6 +88,7 @@ QString MKArtist::name() const { void MKArtist::setName(const QString &u) { if (u != name()) { m_name = u; + emit changed(); emit nameChanged(); } } @@ -94,10 +100,23 @@ QString MKArtist::service() const { void MKArtist::setService(const QString &s) { if (s != service()) { m_service = s; + emit changed(); emit serviceChanged(); } } +QUrl MKArtist::url() const { + return m_url; +} + +void MKArtist::setUrl(const QUrl &u) { + if (u != url()) { + m_url = u; + emit changed(); + emit urlChanged(); + } +} + void MKArtist::loadArtist(MKArtist *artist) { setDescription(artist->description()); setId(artist->id()); @@ -105,4 +124,5 @@ void MKArtist::loadArtist(MKArtist *artist) { setThumbnailUrl(artist->thumbnailUrl()); setName(artist->name()); setService(artist->service()); + setUrl(artist->url()); } diff --git a/app/src/base/artist.h b/app/src/base/artist.h index 7c83e93..1096756 100644 --- a/app/src/base/artist.h +++ b/app/src/base/artist.h @@ -29,38 +29,45 @@ class MKArtist : public QObject Q_PROPERTY(QUrl largeThumbnailUrl READ largeThumbnailUrl NOTIFY largeThumbnailUrlChanged) Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) - Q_PROPERTY(QString service READ service NOTIFY serviceChanged) + Q_PROPERTY(QString service READ service NOTIFY serviceChanged) + Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) public: explicit MKArtist(QObject *parent = 0); - explicit MKArtist(MKArtist *artist, QObject *parent = 0); - + explicit MKArtist(const MKArtist *artist, QObject *parent = 0); + QString description() const; - + QString id() const; QUrl largeThumbnailUrl() const; QUrl thumbnailUrl() const; - + QString name() const; - - QString service() const; - Q_INVOKABLE virtual void loadArtist(MKArtist *artist); + QString service() const; + + QUrl url() const; -protected: + Q_INVOKABLE virtual void loadArtist(MKArtist *artist); + +protected: void setDescription(const QString &d); - + void setId(const QString &i); void setLargeThumbnailUrl(const QUrl &u); void setThumbnailUrl(const QUrl &u); - + void setName(const QString &u); void setService(const QString &s); - + + void setUrl(const QUrl &u); + Q_SIGNALS: + void changed(); + void descriptionChanged(); void idChanged(); @@ -72,7 +79,9 @@ class MKArtist : public QObject void serviceChanged(); -protected: + void urlChanged(); + +private: QString m_description; QString m_id; @@ -83,6 +92,8 @@ class MKArtist : public QObject QString m_name; QString m_service; + + QUrl m_url; }; #endif // MKARTIST_H diff --git a/app/src/base/categorymodel.cpp b/app/src/base/categorymodel.cpp index 5dc5d31..8a2bbe5 100644 --- a/app/src/base/categorymodel.cpp +++ b/app/src/base/categorymodel.cpp @@ -37,12 +37,12 @@ QHash CategoryModel::roleNames() const { } #endif -int CategoryModel::rowCount(const QModelIndex &) const { - return m_list.size(); +int CategoryModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_list.size(); } -int CategoryModel::columnCount(const QModelIndex &) const { - return 2; +int CategoryModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 2; } QVariant CategoryModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -101,11 +101,11 @@ QVariantMap CategoryModel::itemData(int row) const { } void CategoryModel::addCategory(const QString &name, const QString &path) { - Settings::instance()->addCategory(name, path); + Settings::addCategory(name, path); } void CategoryModel::removeCategory(const QString &name) { - Settings::instance()->removeCategory(name); + Settings::removeCategory(name); } void CategoryModel::removeCategory(int row) { @@ -123,7 +123,7 @@ void CategoryModel::clear() { void CategoryModel::reload() { clear(); beginResetModel(); - m_list = Settings::instance()->categories(); + m_list = Settings::categories(); endResetModel(); emit countChanged(rowCount()); diff --git a/app/src/base/categorymodel.h b/app/src/base/categorymodel.h index fcacc9b..6eb8f11 100644 --- a/app/src/base/categorymodel.h +++ b/app/src/base/categorymodel.h @@ -60,7 +60,7 @@ public Q_SLOTS: void reload(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); private: QList m_list; diff --git a/app/src/base/categorynamemodel.h b/app/src/base/categorynamemodel.h index 65d690f..eff5262 100644 --- a/app/src/base/categorynamemodel.h +++ b/app/src/base/categorynamemodel.h @@ -28,7 +28,7 @@ class CategoryNameModel : public SelectionModel explicit CategoryNameModel(QObject *parent = 0) : SelectionModel(parent) { - foreach (QString category, Settings::instance()->categoryNames()) { + foreach (const QString &category, Settings::categoryNames()) { append(category, category); } @@ -39,9 +39,9 @@ public Q_SLOTS: inline void reload() { clear(); - foreach (QString category, Settings::instance()->categoryNames()) { + foreach (const QString &category, Settings::categoryNames()) { append(category, category); } - }; + } }; #endif // CATEGORYMODEL_H diff --git a/app/src/base/clipboard.cpp b/app/src/base/clipboard.cpp index 42d7797..ea0154f 100644 --- a/app/src/base/clipboard.cpp +++ b/app/src/base/clipboard.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -16,91 +16,64 @@ */ #include "clipboard.h" -#include "settings.h" +#include "logger.h" #include #include -#ifdef MEEGO_EDITION_HARMATTAN -#include -#endif -#ifdef CUTETUBE_DEBUG -#include -#endif Clipboard* Clipboard::self = 0; -Clipboard::Clipboard(QObject *parent) : - QObject(parent), - m_monitor(false) -#ifdef MEEGO_EDITION_HARMATTAN - ,m_timer(0) -#endif +Clipboard::Clipboard() : + QObject(), + m_enabled(false) { - if (!self) { - self = this; - } - - connect(Settings::instance(), SIGNAL(clipboardMonitorEnabledChanged()), this, SLOT(onMonitorEnabledChanged())); - onMonitorEnabledChanged(); } Clipboard::~Clipboard() { - if (self == this) { - self = 0; - } + self = 0; } Clipboard* Clipboard::instance() { - return self; + return self ? self : self = new Clipboard; } -QString Clipboard::text() const { +QString Clipboard::text() { return QApplication::clipboard()->text(); } void Clipboard::setText(const QString &text) { - disconnect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onTextChanged())); + if (self) { + self->disconnect(QApplication::clipboard(), SIGNAL(dataChanged()), self, SLOT(onTextChanged())); + } + QApplication::clipboard()->setText(text); - if (m_monitor) { - connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onTextChanged())); + if ((self) && (self->isEnabled())) { + self->connect(QApplication::clipboard(), SIGNAL(dataChanged()), self, SLOT(onTextChanged())); } } -void Clipboard::onMonitorEnabledChanged() { - if (Settings::instance()->clipboardMonitorEnabled()) { - m_monitor = true; - connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onTextChanged())); - } - else { - m_monitor = false; - disconnect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onTextChanged())); +bool Clipboard::isEnabled() const { + return m_enabled; +} + +void Clipboard::setEnabled(bool enabled) { + if (enabled != isEnabled()) { + m_enabled = enabled; + emit enabledChanged(enabled); + + if (enabled) { + connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onTextChanged())); + } + else { + disconnect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onTextChanged())); + } } -#ifdef CUTETUBE_DEBUG - qDebug() << "Clipboard::onMonitorEnabledChanged" << m_monitor; -#endif } void Clipboard::onTextChanged() { -#ifdef MEEGO_EDITION_HARMATTAN - if ((m_timer) && (m_timer->isActive())) { - // QClipboard::dataChanged() signal is emitted twice in Harmattan, - // so ignore the signal if the timer is still active. - return; - } - else { - if (!m_timer) { - m_timer = new QTimer(this); - m_timer->setInterval(3000); - m_timer->setSingleShot(true); - } + const QString text = QApplication::clipboard()->text(); + Logger::log("Clipboard::onTextChanged(). Text: " + text, Logger::HighVerbosity); - m_timer->start(); - } -#endif - QString text = QApplication::clipboard()->text(); -#ifdef CUTETUBE_DEBUG - qDebug() << "Clipboard::onTextChanged" << text; -#endif if (!text.isEmpty()) { emit textChanged(text); } diff --git a/app/src/base/clipboard.h b/app/src/base/clipboard.h index 4f8df65..1f9ab5b 100644 --- a/app/src/base/clipboard.h +++ b/app/src/base/clipboard.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -19,43 +19,41 @@ #define CLIPBOARD_H #include -#include - -#ifdef MEEGO_EDITION_HARMATTAN -class QTimer; -#endif class Clipboard : public QObject { Q_OBJECT - + + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) public: - explicit Clipboard(QObject *parent = 0); ~Clipboard(); static Clipboard* instance(); - QString text() const; + static QString text(); + + bool isEnabled() const; -public Q_SLOTS: - void setText(const QString &text); +public Q_SLOTS: + static void setText(const QString &text); + + void setEnabled(bool enabled); private Q_SLOTS: - void onMonitorEnabledChanged(); void onTextChanged(); Q_SIGNALS: + void enabledChanged(bool enabled); void textChanged(const QString &text); private: - static Clipboard *self; + Clipboard(); - bool m_monitor; -#ifdef MEEGO_EDITION_HARMATTAN - QTimer *m_timer; -#endif + static Clipboard *self; + + bool m_enabled; }; #endif // CLIPBOARD_H diff --git a/app/src/base/comment.cpp b/app/src/base/comment.cpp index 3aa1052..41376e7 100644 --- a/app/src/base/comment.cpp +++ b/app/src/base/comment.cpp @@ -21,7 +21,7 @@ MKComment::MKComment(QObject *parent) : { } -MKComment::MKComment(MKComment *comment, QObject *parent) : +MKComment::MKComment(const MKComment *comment, QObject *parent) : QObject(parent), m_artist(comment->artist()), m_artistId(comment->artistId()), @@ -30,7 +30,8 @@ MKComment::MKComment(MKComment *comment, QObject *parent) : m_id(comment->id()), m_service(comment->service()), m_thumbnailUrl(comment->thumbnailUrl()), - m_trackId(comment->trackId()) + m_trackId(comment->trackId()), + m_url(comment->url()) { } @@ -41,6 +42,7 @@ QString MKComment::artist() const { void MKComment::setArtist(const QString &a) { if (a != artist()) { m_artist = a; + emit changed(); emit artistChanged(); } } @@ -52,6 +54,7 @@ QString MKComment::artistId() const { void MKComment::setArtistId(const QString &i) { if (i != artistId()) { m_artistId = i; + emit changed(); emit artistIdChanged(); } } @@ -63,6 +66,7 @@ QString MKComment::body() const { void MKComment::setBody(const QString &d) { if (d != body()) { m_body = d; + emit changed(); emit bodyChanged(); } } @@ -74,6 +78,7 @@ QString MKComment::date() const { void MKComment::setDate(const QString &d) { if (d != date()) { m_date = d; + emit changed(); emit dateChanged(); } } @@ -85,6 +90,7 @@ QString MKComment::id() const { void MKComment::setId(const QString &i) { if (i != id()) { m_id = i; + emit changed(); emit idChanged(); } } @@ -96,6 +102,7 @@ QString MKComment::service() const { void MKComment::setService(const QString &s) { if (s != service()) { m_service = s; + emit changed(); emit serviceChanged(); } } @@ -107,6 +114,7 @@ QUrl MKComment::thumbnailUrl() const { void MKComment::setThumbnailUrl(const QUrl &u) { if (u != thumbnailUrl()) { m_thumbnailUrl = u; + emit changed(); emit thumbnailUrlChanged(); } } @@ -118,10 +126,23 @@ QString MKComment::trackId() const { void MKComment::setTrackId(const QString &i) { if (i != trackId()) { m_trackId = i; + emit changed(); emit trackIdChanged(); } } +QUrl MKComment::url() const { + return m_url; +} + +void MKComment::setUrl(const QUrl &u) { + if (u != url()) { + m_url = u; + emit changed(); + emit urlChanged(); + } +} + void MKComment::loadComment(MKComment *comment) { setArtist(comment->artist()); setArtistId(comment->artistId()); @@ -131,4 +152,5 @@ void MKComment::loadComment(MKComment *comment) { setService(comment->service()); setThumbnailUrl(comment->thumbnailUrl()); setTrackId(comment->trackId()); + setUrl(comment->url()); } diff --git a/app/src/base/comment.h b/app/src/base/comment.h index 6b36c5d..2e1b202 100644 --- a/app/src/base/comment.h +++ b/app/src/base/comment.h @@ -32,10 +32,11 @@ class MKComment : public QObject Q_PROPERTY(QString service READ service NOTIFY serviceChanged) Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged) Q_PROPERTY(QString trackId READ trackId NOTIFY trackIdChanged) + Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) public: explicit MKComment(QObject *parent = 0); - explicit MKComment(MKComment *comment, QObject *parent = 0); + explicit MKComment(const MKComment *comment, QObject *parent = 0); QString artist() const; QString artistId() const; @@ -43,14 +44,16 @@ class MKComment : public QObject QString body() const; QString date() const; - + QString id() const; - + QString service() const; QUrl thumbnailUrl() const; QString trackId() const; + + QUrl url() const; Q_INVOKABLE virtual void loadComment(MKComment *comment); @@ -69,11 +72,15 @@ class MKComment : public QObject void setThumbnailUrl(const QUrl &u); void setTrackId(const QString &i); + + void setUrl(const QUrl &u); Q_SIGNALS: void artistChanged(); void artistIdChanged(); + void changed(); + void bodyChanged(); void dateChanged(); @@ -86,7 +93,9 @@ class MKComment : public QObject void trackIdChanged(); -protected: + void urlChanged(); + +private: QString m_artist; QString m_artistId; @@ -101,6 +110,8 @@ class MKComment : public QObject QUrl m_thumbnailUrl; QString m_trackId; + + QUrl m_url; }; #endif // MKCOMMENT_H diff --git a/app/src/base/localtrack.cpp b/app/src/base/localtrack.cpp deleted file mode 100644 index 399d78a..0000000 --- a/app/src/base/localtrack.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "localtrack.h" -#ifdef MUSIKLOUD_DEBUG -#include -#endif -#ifdef FETCH_LOCAL_METADATA -#include -#include -#include - -static const QStringList PROPERTY_NAMES = QStringList() << QDocumentGallery::artist << QDocumentGallery::duration - << QDocumentGallery::fileSize << QDocumentGallery::genre - << QDocumentGallery::lastModified << QDocumentGallery::playCount - << QDocumentGallery::title; -#endif - -LocalTrack::LocalTrack(QObject *parent) : - MKTrack(parent) -#ifdef FETCH_LOCAL_METADATA - ,m_request(0) -#endif -{ - setDownloadable(false); -} - -LocalTrack::LocalTrack(const QUrl &url, QObject *parent) : - MKTrack(parent) -#ifdef FETCH_LOCAL_METADATA - ,m_request(0) -#endif -{ - setDownloadable(false); - loadTrack(url); -} - -LocalTrack::LocalTrack(LocalTrack *track, QObject *parent) : - MKTrack(track, parent) -#ifdef FETCH_LOCAL_METADATA - ,m_request(0) -#endif -{ -} - -void LocalTrack::loadTrack(const QUrl &url) { - setFormat(url.path().section('.', -1).toUpper()); - setStreamUrl(url); - setTitle(url.path().section('/', -1).section('.', 0, -2)); - setUrl(url); -#ifdef FETCH_LOCAL_METADATA - initRequest(); - m_request->setItemId("localtagfs::music/songs/" + url.path().replace("/", "%2F")); - m_request->execute(); -#endif -} - -#ifdef FETCH_LOCAL_METADATA -void LocalTrack::initRequest() { - if (!m_request) { - m_request = new QGalleryItemRequest(new QDocumentGallery(this), this); - m_request->setPropertyNames(PROPERTY_NAMES); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); -#ifdef MUSIKLOUD_DEBUG - connect(m_request, SIGNAL(error(int, QString)), this, SLOT(onRequestError(int, QString))); -#endif - } -} - -void LocalTrack::onRequestFinished() { - if (m_request->isValid()) { - setArtist(m_request->metaData(QDocumentGallery::artist).toString()); - setDate(m_request->metaData(QDocumentGallery::lastModified).toDateTime().toString("dd MMM yyyy")); - setDuration(m_request->metaData(QDocumentGallery::duration).toLongLong()); - setFormat(m_request->metaData(QDocumentGallery::fileExtension).toString().toUpper()); - setGenre(m_request->metaData(QDocumentGallery::genre).toString()); - setId(m_request->itemId().toString()); - setPlayCount(m_request->metaData(QDocumentGallery::playCount).toLongLong()); - setSize(m_request->metaData(QDocumentGallery::fileSize).toLongLong()); - setTitle(m_request->metaData(QDocumentGallery::title).toString()); - } -#ifdef MUSIKLOUD_DEBUG - else { - qDebug() << "LocalTrack::onRequestFinished: Item is invalid" << m_request->itemId(); - } -#endif -} - -#ifdef MUSIKLOUD_DEBUG -void LocalTrack::onRequestError(int error, const QString &errorString) { - qDebug() << "LocalTrack::onRequestError" << m_request->itemId() << error << errorString; -} -#endif -#endif diff --git a/app/src/base/localtrack.h b/app/src/base/localtrack.h deleted file mode 100644 index 649f596..0000000 --- a/app/src/base/localtrack.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef LOCALTRACK_H -#define LOCALTRACK_H - -#include "track.h" -#ifdef FETCH_LOCAL_METADATA -#include - -#if QT_VERSION >= 0x050000 -QT_USE_DOCGALLERY_NAMESPACE -class QGalleryItemRequest; -#else -QTM_BEGIN_NAMESPACE -class QGalleryItemRequest; -QTM_END_NAMESPACE -QTM_USE_NAMESPACE -#endif -#endif - -class LocalTrack : public MKTrack -{ - Q_OBJECT - -public: - explicit LocalTrack(QObject *parent = 0); - explicit LocalTrack(const QUrl &url, QObject *parent = 0); - explicit LocalTrack(LocalTrack *track, QObject *parent = 0); - - Q_INVOKABLE virtual void loadTrack(const QUrl &url); - -#ifdef FETCH_LOCAL_METADATA -private Q_SLOTS: - void onRequestFinished(); -#ifdef MUSIKLOUD_DEBUG - void onRequestError(int error, const QString &errorString); -#endif - -private: - void initRequest(); - - QGalleryItemRequest *m_request; -#endif -}; - -#endif // LOCALTRACK_H diff --git a/app/src/plugins/pluginsettingsmodel.h b/app/src/base/loggerverbositymodel.h similarity index 51% rename from app/src/plugins/pluginsettingsmodel.h rename to app/src/base/loggerverbositymodel.h index b71612d..f1a3452 100644 --- a/app/src/plugins/pluginsettingsmodel.h +++ b/app/src/base/loggerverbositymodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,34 +14,27 @@ * along with this program. If not, see . */ -#ifndef PLUGINSETTINGSMODEL_H -#define PLUGINSETTINGSMODEL_H +#ifndef LOGGERVERBOSITYMODEL_H +#define LOGGERVERBOSITYMODEL_H #include "selectionmodel.h" -#include "resourcesplugins.h" +#include "logger.h" -class PluginSettingsModel : public SelectionModel +class LoggerVerbosityModel : public SelectionModel { Q_OBJECT public: - explicit PluginSettingsModel(QObject *parent = 0) : + explicit LoggerVerbosityModel(QObject *parent = 0) : SelectionModel(parent) { - reload(); - } - -public Q_SLOTS: - inline void reload() { - clear(); - QList plugins = ResourcesPlugins::instance()->plugins(); - - for (int i = 0; i < plugins.size(); i++) { - if (!plugins.at(i).settings.isEmpty()) { - append(plugins.at(i).name, plugins.at(i).settings); - } - } + append(tr("No logging"), Logger::NoVerbosity); + append(tr("Lowest"), Logger::LowestVerbosity); + append(tr("Low"), Logger::LowVerbosity); + append(tr("Medium"), Logger::MediumVerbosity); + append(tr("High"), Logger::HighVerbosity); + append(tr("Highest"), Logger::HighestVerbosity); } }; -#endif // PLUGINSETTINGSMODEL_H +#endif // LOGGERVERBOSITYMODEL_H diff --git a/app/src/base/playlist.cpp b/app/src/base/playlist.cpp index 40881b1..1322104 100644 --- a/app/src/base/playlist.cpp +++ b/app/src/base/playlist.cpp @@ -24,7 +24,7 @@ MKPlaylist::MKPlaylist(QObject *parent) : { } -MKPlaylist::MKPlaylist(MKPlaylist *playlist, QObject *parent) : +MKPlaylist::MKPlaylist(const MKPlaylist *playlist, QObject *parent) : QObject(parent), m_artist(playlist->artist()), m_artistId(playlist->artistId()), @@ -38,7 +38,8 @@ MKPlaylist::MKPlaylist(MKPlaylist *playlist, QObject *parent) : m_thumbnailUrl(playlist->thumbnailUrl()), m_service(playlist->service()), m_title(playlist->title()), - m_trackCount(playlist->trackCount()) + m_trackCount(playlist->trackCount()), + m_url(playlist->url()) { } @@ -49,6 +50,7 @@ QString MKPlaylist::artist() const { void MKPlaylist::setArtist(const QString &a) { if (a != artist()) { m_artist = a; + emit changed(); emit artistChanged(); } } @@ -60,6 +62,7 @@ QString MKPlaylist::artistId() const { void MKPlaylist::setArtistId(const QString &i) { if (i != artistId()) { m_artistId = i; + emit changed(); emit artistIdChanged(); } } @@ -71,6 +74,7 @@ QString MKPlaylist::date() const { void MKPlaylist::setDate(const QString &d) { if (d != date()) { m_date = d; + emit changed(); emit dateChanged(); } } @@ -82,6 +86,7 @@ QString MKPlaylist::description() const { void MKPlaylist::setDescription(const QString &d) { if (d != description()) { m_description = d; + emit changed(); emit descriptionChanged(); } } @@ -94,6 +99,7 @@ void MKPlaylist::setDuration(qint64 d) { if (d != duration()) { m_duration = d; m_durationString = Utils::formatMSecs(d); + emit changed(); emit durationChanged(); } } @@ -105,6 +111,7 @@ QString MKPlaylist::durationString() const { void MKPlaylist::setDurationString(const QString &s) { if (s != durationString()) { m_durationString = s; + emit changed(); emit durationChanged(); } } @@ -116,6 +123,7 @@ QString MKPlaylist::genre() const { void MKPlaylist::setGenre(const QString &g) { if (g != genre()) { m_genre = g; + emit changed(); emit genreChanged(); } } @@ -127,6 +135,7 @@ QString MKPlaylist::id() const { void MKPlaylist::setId(const QString &i) { if (i != id()) { m_id = i; + emit changed(); emit idChanged(); } } @@ -138,6 +147,7 @@ QUrl MKPlaylist::largeThumbnailUrl() const { void MKPlaylist::setLargeThumbnailUrl(const QUrl &u) { if (u != largeThumbnailUrl()) { m_largeThumbnailUrl = u; + emit changed(); emit largeThumbnailUrlChanged(); } } @@ -149,6 +159,7 @@ QUrl MKPlaylist::thumbnailUrl() const { void MKPlaylist::setThumbnailUrl(const QUrl &u) { if (u != thumbnailUrl()) { m_thumbnailUrl = u; + emit changed(); emit thumbnailUrlChanged(); } } @@ -160,6 +171,7 @@ QString MKPlaylist::service() const { void MKPlaylist::setService(const QString &s) { if (s != service()) { m_service = s; + emit changed(); emit serviceChanged(); } } @@ -171,6 +183,7 @@ QString MKPlaylist::title() const { void MKPlaylist::setTitle(const QString &t) { if (t != title()) { m_title = t; + emit changed(); emit titleChanged(); } } @@ -182,10 +195,23 @@ int MKPlaylist::trackCount() const { void MKPlaylist::setTrackCount(int c) { if (c != trackCount()) { m_trackCount = c; + emit changed(); emit trackCountChanged(); } } +QUrl MKPlaylist::url() const { + return m_url; +} + +void MKPlaylist::setUrl(const QUrl &u) { + if (u != url()) { + m_url = u; + emit changed(); + emit urlChanged(); + } +} + void MKPlaylist::loadPlaylist(MKPlaylist *playlist) { setArtist(playlist->artist()); setArtistId(playlist->artistId()); @@ -199,6 +225,7 @@ void MKPlaylist::loadPlaylist(MKPlaylist *playlist) { setService(playlist->service()); setTitle(playlist->title()); setTrackCount(playlist->trackCount()); + setUrl(playlist->url()); if (duration() == 0) { setDurationString(playlist->durationString()); diff --git a/app/src/base/playlist.h b/app/src/base/playlist.h index b03545c..803836e 100644 --- a/app/src/base/playlist.h +++ b/app/src/base/playlist.h @@ -37,14 +37,15 @@ class MKPlaylist : public QObject Q_PROPERTY(QString service READ service NOTIFY serviceChanged) Q_PROPERTY(QString title READ title NOTIFY titleChanged) Q_PROPERTY(int trackCount READ trackCount NOTIFY trackCountChanged) + Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) public: explicit MKPlaylist(QObject *parent = 0); - explicit MKPlaylist(MKPlaylist *playlist, QObject *parent = 0); + explicit MKPlaylist(const MKPlaylist *playlist, QObject *parent = 0); QString artist() const; QString artistId() const; - + QString date() const; QString description() const; @@ -64,6 +65,8 @@ class MKPlaylist : public QObject QString title() const; int trackCount() const; + + QUrl url() const; Q_INVOKABLE virtual void loadPlaylist(MKPlaylist *playlist); @@ -91,11 +94,14 @@ class MKPlaylist : public QObject void setTrackCount(int c); + void setUrl(const QUrl &u); Q_SIGNALS: void artistChanged(); void artistIdChanged(); + void changed(); + void dateChanged(); void descriptionChanged(); @@ -115,7 +121,9 @@ class MKPlaylist : public QObject void trackCountChanged(); -protected: + void urlChanged(); + +private: QString m_artist; QString m_artistId; @@ -137,7 +145,9 @@ class MKPlaylist : public QObject QString m_title; - int m_trackCount; + int m_trackCount; + + QUrl m_url; }; #endif // MKPLAYLIST_H diff --git a/app/src/base/resources.cpp b/app/src/base/resources.cpp index 6c30ab5..00e531d 100644 --- a/app/src/base/resources.cpp +++ b/app/src/base/resources.cpp @@ -15,12 +15,10 @@ */ #include "resources.h" -#include "resourcesplugins.h" +#include "logger.h" +#include "pluginmanager.h" #include "soundcloud.h" #include "utils.h" -#ifdef MUSIKLOUD_DEBUG -#include -#endif const QString Resources::SOUNDCLOUD("soundcloud"); @@ -31,24 +29,116 @@ const QString Resources::PLAYLIST("playlist"); const QString Resources::STREAM("stream"); const QString Resources::TRACK("track"); -static const QRegExp SOUNDCLOUD_REGEXP("http(s|)://(api\\.|)soundcloud\\.com/[\\w-_/]+"); +GetResource::GetResource(const QVariantMap &map) : + QVariantMap() +{ + insert("type", map.value("type")); + insert("regExp", map.value("regExp")); +} -ListResource::ListResource(const QString &name, const QString &type, const QString &id) : +GetResource::GetResource(const QString &type, const QRegExp ®Exp) : QVariantMap() { - insert("name", name); + setType(type); + setRegExp(regExp); +} + +QRegExp GetResource::regExp() const { + return QRegExp(value("regExp").toString()); +} + +void GetResource::setRegExp(const QRegExp ®Exp) { + insert("regExp", regExp); +} + +QString GetResource::type() const { + return value("type").toString(); +} + +void GetResource::setType(const QString &type) { insert("type", type); - insert("id", id); } -SearchResource::SearchResource(const QString &name, const QString &type, const QString &order) : +ListResource::ListResource(const QVariantMap &map) : + QVariantMap() +{ + insert("label", map.value("label")); + insert("type", map.value("type")); + insert("id", map.value("id")); +} + +ListResource::ListResource(const QString &label, const QString &type, const QString &id) : QVariantMap() { - insert("name", name); + setId(id); + setLabel(label); + setType(type); +} + +QString ListResource::id() const { + return value("id").toString(); +} + +void ListResource::setId(const QString &id) { + insert("id", id); +} + +QString ListResource::label() const { + return value("label").toString(); +} + +void ListResource::setLabel(const QString &label) { + insert("label", label); +} + +QString ListResource::type() const { + return value("type").toString(); +} + +void ListResource::setType(const QString &type) { insert("type", type); +} + +SearchResource::SearchResource(const QVariantMap &map) : + QVariantMap() +{ + insert("label", map.value("label")); + insert("type", map.value("type")); + insert("order", map.value("order")); +} + +SearchResource::SearchResource(const QString &label, const QString &type, const QString &order) : + QVariantMap() +{ + setLabel(label); + setType(type); + setOrder(order); +} + +QString SearchResource::label() const { + return value("label").toString(); +} + +void SearchResource::setLabel(const QString &label) { + insert("label", label); +} + +QString SearchResource::order() const { + return value("order").toString(); +} + +void SearchResource::setOrder(const QString &order) { insert("order", order); } +QString SearchResource::type() const { + return value("type").toString(); +} + +void SearchResource::setType(const QString &type) { + insert("type", type); +} + Resources::Resources(QObject *parent) : QObject(parent) { @@ -83,13 +173,11 @@ QString Resources::trackConstant() { } QVariantMap Resources::getResourceFromUrl(QString url) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Resources::getResourceFromUrl" << url; -#endif + Logger::log("Resources::getResourceFromUrl. URL: " + url, Logger::MediumVerbosity); url = Utils::unescape(url); QVariantMap result; - if (SOUNDCLOUD_REGEXP.indexIn(url) == 0) { + if (SoundCloud::URL_REGEXP.indexIn(url) == 0) { result.insert("service", SOUNDCLOUD); result.insert("id", url); @@ -104,30 +192,22 @@ QVariantMap Resources::getResourceFromUrl(QString url) { } } else { - QList plugins = ResourcesPlugins::instance()->plugins(); - - for (int i = 0; i < plugins.size(); i++) { - if (!plugins.at(i).regExps.isEmpty()) { - QMapIterator iterator(plugins.at(i).regExps); + const QList plugins = PluginManager::instance()->plugins(); + + foreach (const ServicePluginPair &pair, plugins) { + if (const ServicePluginConfig *config = pair.config) { - while (iterator.hasNext()) { - iterator.next(); - - if (iterator.value().indexIn(url) == 0) { - result.insert("service", plugins.at(i).name); - result.insert("type", iterator.key()); + foreach (const GetResource &resource, config->getResources()) { + if (resource.regExp().indexIn(url) == 0) { + result.insert("service", config->id()); + result.insert("type", resource.type()); result.insert("id", url); -#ifdef MUSIKLOUD_DEBUG - qDebug() << result; -#endif return result; } } } } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << result; -#endif + return result; } diff --git a/app/src/base/resources.h b/app/src/base/resources.h index 183db65..5180312 100644 --- a/app/src/base/resources.h +++ b/app/src/base/resources.h @@ -18,20 +18,55 @@ #define RESOURCES_H #include +#include #include +class GetResource : public QVariantMap +{ + +public: + explicit GetResource(const QVariantMap &map); + explicit GetResource(const QString &type, const QRegExp ®Exp); + + QRegExp regExp() const; + void setRegExp(const QRegExp ®Exp); + + QString type() const; + void setType(const QString &type); +}; + class ListResource : public QVariantMap { public: - ListResource(const QString &name, const QString &type, const QString &id); + explicit ListResource(const QVariantMap &map); + explicit ListResource(const QString &label, const QString &type, const QString &id); + + QString id() const; + void setId(const QString &id); + + QString label() const; + void setLabel(const QString &label); + + QString type() const; + void setType(const QString &type); }; class SearchResource : public QVariantMap { public: - SearchResource(const QString &name, const QString &type, const QString &order); + explicit SearchResource(const QVariantMap &map); + explicit SearchResource(const QString &label, const QString &type, const QString &order); + + QString label() const; + void setLabel(const QString &label); + + QString order() const; + void setOrder(const QString &order); + + QString type() const; + void setType(const QString &type); }; class Resources : public QObject @@ -46,7 +81,7 @@ class Resources : public QObject Q_PROPERTY(QString PLAYLIST READ playlistConstant CONSTANT) Q_PROPERTY(QString STREAM READ streamConstant CONSTANT) Q_PROPERTY(QString TRACK READ trackConstant CONSTANT) - + public: explicit Resources(QObject *parent = 0); @@ -68,7 +103,7 @@ class Resources : public QObject static QString streamConstant(); static QString trackConstant(); - Q_INVOKABLE static QVariantMap getResourceFromUrl(QString url); + Q_INVOKABLE static QVariantMap getResourceFromUrl(QString url); }; #endif // RESOURCES_H diff --git a/app/src/base/searchhistorymodel.cpp b/app/src/base/searchhistorymodel.cpp index 8c3997c..38dd74e 100644 --- a/app/src/base/searchhistorymodel.cpp +++ b/app/src/base/searchhistorymodel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -20,7 +20,7 @@ SearchHistoryModel::SearchHistoryModel(QObject *parent) : QSortFilterProxyModel(parent), - m_model(new QStringListModel(Settings::instance()->searchHistory(), this)), + m_model(new QStringListModel(Settings::searchHistory(), this)), m_alignment(Qt::AlignCenter) { setSourceModel(m_model); @@ -59,22 +59,22 @@ QVariant SearchHistoryModel::data(int row, const QByteArray &role) const { } void SearchHistoryModel::addSearch(const QString &query) { - Settings::instance()->addSearch(query); + Settings::addSearch(query); } void SearchHistoryModel::removeSearch(const QString &query) { - Settings::instance()->removeSearch(query); + Settings::removeSearch(query); } void SearchHistoryModel::removeSearch(int row) { - Settings::instance()->removeSearch(data(index(row, 0)).toString()); + Settings::removeSearch(data(index(row, 0)).toString()); } void SearchHistoryModel::clear() { - Settings::instance()->setSearchHistory(QStringList()); + Settings::setSearchHistory(QStringList()); } void SearchHistoryModel::reload() { - m_model->setStringList(Settings::instance()->searchHistory()); + m_model->setStringList(Settings::searchHistory()); emit countChanged(rowCount()); } diff --git a/app/src/base/searchhistorymodel.h b/app/src/base/searchhistorymodel.h index 654ec0b..bb69e30 100644 --- a/app/src/base/searchhistorymodel.h +++ b/app/src/base/searchhistorymodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -46,7 +46,7 @@ public Q_SLOTS: void reload(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void textAlignmentChanged(); private: diff --git a/app/src/base/selectionmodel.cpp b/app/src/base/selectionmodel.cpp index f3250f7..9f0ab3e 100644 --- a/app/src/base/selectionmodel.cpp +++ b/app/src/base/selectionmodel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -33,8 +33,8 @@ QHash SelectionModel::roleNames() const { } #endif -int SelectionModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int SelectionModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); } Qt::Alignment SelectionModel::textAlignment() const { diff --git a/app/src/base/selectionmodel.h b/app/src/base/selectionmodel.h index ca27a37..d31bdb9 100644 --- a/app/src/base/selectionmodel.h +++ b/app/src/base/selectionmodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -63,7 +63,7 @@ public Q_SLOTS: void clear(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void textAlignmentChanged(); protected: diff --git a/app/src/base/servicemodel.h b/app/src/base/servicemodel.h index fd78bb2..bb442af 100644 --- a/app/src/base/servicemodel.h +++ b/app/src/base/servicemodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -18,8 +18,8 @@ #define SERVICEMODEL_H #include "selectionmodel.h" +#include "pluginmanager.h" #include "resources.h" -#include "resourcesplugins.h" class ServiceModel : public SelectionModel { @@ -30,6 +30,7 @@ class ServiceModel : public SelectionModel SelectionModel(parent) { reload(); + connect(PluginManager::instance(), SIGNAL(loaded(int)), this, SLOT(reload())); } public Q_SLOTS: @@ -37,9 +38,9 @@ public Q_SLOTS: clear(); append("SoundCloud", Resources::SOUNDCLOUD); - - foreach (QString name, ResourcesPlugins::instance()->pluginNames()) { - append(name, name); + + foreach (const ServicePluginPair &pair, PluginManager::instance()->plugins()) { + append(pair.config->displayName(), pair.config->id()); } } }; diff --git a/app/src/base/settings.h b/app/src/base/settings.h deleted file mode 100644 index b6b6baf..0000000 --- a/app/src/base/settings.h +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef SETTINGS_H -#define SETTINGS_H - -#include -#include -#include -#include - -struct Category { - QString name; - QString path; -}; - -class Settings : public QObject -{ - Q_OBJECT - -#if (defined MEEGO_EDITION_HARMATTAN) || (SYMBIAN_OS) - Q_PROPERTY(QString activeColor READ activeColor WRITE setActiveColor NOTIFY activeColorChanged) - Q_PROPERTY(QString activeColorString READ activeColorString WRITE setActiveColorString - NOTIFY activeColorStringChanged) -#endif - Q_PROPERTY(QStringList categoryNames READ categoryNames NOTIFY categoriesChanged) - Q_PROPERTY(QString defaultCategory READ defaultCategory WRITE setDefaultCategory NOTIFY defaultCategoryChanged) - Q_PROPERTY(bool clipboardMonitorEnabled READ clipboardMonitorEnabled WRITE setClipboardMonitorEnabled - NOTIFY clipboardMonitorEnabledChanged) - Q_PROPERTY(QString currentService READ currentService WRITE setCurrentService NOTIFY currentServiceChanged) - Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) - Q_PROPERTY(int maximumConcurrentTransfers READ maximumConcurrentTransfers WRITE setMaximumConcurrentTransfers - NOTIFY maximumConcurrentTransfersChanged) - Q_PROPERTY(bool networkProxyEnabled READ networkProxyEnabled WRITE setNetworkProxyEnabled - NOTIFY networkProxyChanged) - Q_PROPERTY(QString networkProxyHost READ networkProxyHost WRITE setNetworkProxyHost NOTIFY networkProxyChanged) - Q_PROPERTY(QString networkProxyPassword READ networkProxyPassword WRITE setNetworkProxyPassword - NOTIFY networkProxyChanged) - Q_PROPERTY(int networkProxyPort READ networkProxyPort WRITE setNetworkProxyPort NOTIFY networkProxyChanged) - Q_PROPERTY(int networkProxyType READ networkProxyType WRITE setNetworkProxyType NOTIFY networkProxyChanged) - Q_PROPERTY(QString networkProxyUsername READ networkProxyUsername WRITE setNetworkProxyUsername - NOTIFY networkProxyChanged) - Q_PROPERTY(int screenOrientation READ screenOrientation WRITE setScreenOrientation NOTIFY screenOrientationChanged) - Q_PROPERTY(QStringList searchHistory READ searchHistory WRITE setSearchHistory NOTIFY searchHistoryChanged) - Q_PROPERTY(bool startTransfersAutomatically READ startTransfersAutomatically WRITE setStartTransfersAutomatically - NOTIFY startTransfersAutomaticallyChanged) - -public: - explicit Settings(QObject *parent = 0); - ~Settings(); - - static Settings* instance(); - -#if (defined MEEGO_EDITION_HARMATTAN) || (SYMBIAN_OS) - QString activeColor() const; - QString activeColorString() const; -#endif - - QStringList categoryNames() const; - QList categories() const; - void setCategories(const QList &c); - - bool clipboardMonitorEnabled() const; - - QString currentService() const; - - QString defaultCategory() const; - - Q_INVOKABLE QString defaultDownloadFormat(const QString &service) const; - Q_INVOKABLE QString defaultPlaybackFormat(const QString &service) const; - - Q_INVOKABLE QString defaultSearchType(const QString &service) const; - - QString downloadPath() const; - Q_INVOKABLE QString downloadPath(const QString &category) const; - - int maximumConcurrentTransfers() const; - - bool networkProxyEnabled() const; - QString networkProxyHost() const; - QString networkProxyPassword() const; - int networkProxyPort() const; - int networkProxyType() const; - QString networkProxyUsername() const; - - int screenOrientation() const; - - QStringList searchHistory() const; - void setSearchHistory(const QStringList &searches); - - bool startTransfersAutomatically() const; - - Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; - -public Q_SLOTS: -#if (defined MEEGO_EDITION_HARMATTAN) || (SYMBIAN_OS) - void setActiveColor(const QString &color); - void setActiveColorString(const QString &s); -#endif - void addCategory(const QString &name, const QString &path); - void setDefaultCategory(const QString &category); - void removeCategory(const QString &name); - - void setClipboardMonitorEnabled(bool enabled); - - void setCurrentService(const QString &service); - - void setDefaultDownloadFormat(const QString &service, const QString &format); - void setDefaultPlaybackFormat(const QString &service, const QString &format); - - void setDefaultSearchType(const QString &service, const QString &type); - - void setDownloadPath(const QString &path); - - void setMaximumConcurrentTransfers(int maximum); - - void setNetworkProxy(); - void setNetworkProxyEnabled(bool enabled); - void setNetworkProxyHost(const QString &host); - void setNetworkProxyPassword(const QString &password); - void setNetworkProxyPort(int port); - void setNetworkProxyType(int type); - void setNetworkProxyUsername(const QString &username); - - void setScreenOrientation(int orientation); - - void addSearch(const QString &query); - void removeSearch(const QString &query); - - void setStartTransfersAutomatically(bool enabled); - - void setValue(const QString &key, const QVariant &value); - -Q_SIGNALS: -#if (defined MEEGO_EDITION_HARMATTAN) || (SYMBIAN_OS) - void activeColorChanged(); - void activeColorStringChanged(); -#endif - void categoriesChanged(); - void defaultCategoryChanged(); - void clipboardMonitorEnabledChanged(); - void currentServiceChanged(); - void defaultSearchOrderChanged(); - void defaultSearchTypeChanged(); - void downloadFormatsChanged(); - void downloadPathChanged(); - void maximumConcurrentTransfersChanged(); - void networkProxyChanged(); - void playbackFormatsChanged(); - void screenOrientationChanged(); - void searchHistoryChanged(); - void startTransfersAutomaticallyChanged(); - -private: - static Settings *self; -}; - -#endif // SETTINGS_H diff --git a/app/src/base/track.cpp b/app/src/base/track.cpp index 3eda078..eef6bf4 100644 --- a/app/src/base/track.cpp +++ b/app/src/base/track.cpp @@ -19,21 +19,15 @@ MKTrack::MKTrack(QObject *parent) : QObject(parent), - m_artist(tr("Unknown artist")), - m_date(tr("Unknown date")), - m_description(tr("No description")), m_downloadable(true), m_duration(0), m_durationString("--:--"), - m_format(tr("Unknown format")), - m_genre(tr("Unknown genre")), m_playCount(0), - m_size(0), - m_title(tr("Unknown title")) + m_size(0) { } -MKTrack::MKTrack(MKTrack *track, QObject *parent) : +MKTrack::MKTrack(const MKTrack *track, QObject *parent) : QObject(parent), m_artist(track->artist()), m_artistId(track->artistId()), @@ -64,6 +58,7 @@ QString MKTrack::artist() const { void MKTrack::setArtist(const QString &a) { if (a != artist()) { m_artist = a; + emit changed(); emit artistChanged(); } } @@ -75,6 +70,7 @@ QString MKTrack::artistId() const { void MKTrack::setArtistId(const QString &i) { if (i != artistId()) { m_artistId = i; + emit changed(); emit artistIdChanged(); } } @@ -86,6 +82,7 @@ QString MKTrack::date() const { void MKTrack::setDate(const QString &d) { if (d != date()) { m_date = d; + emit changed(); emit dateChanged(); } } @@ -97,6 +94,7 @@ QString MKTrack::description() const { void MKTrack::setDescription(const QString &d) { if (d != description()) { m_description = d; + emit changed(); emit descriptionChanged(); } } @@ -108,6 +106,7 @@ bool MKTrack::isDownloadable() const { void MKTrack::setDownloadable(bool d) { if (d != isDownloadable()) { m_downloadable = d; + emit changed(); emit downloadableChanged(); } } @@ -120,6 +119,7 @@ void MKTrack::setDuration(qint64 d) { if (d != duration()) { m_duration = d; m_durationString = Utils::formatMSecs(d); + emit changed(); emit durationChanged(); } } @@ -131,6 +131,7 @@ QString MKTrack::durationString() const { void MKTrack::setDurationString(const QString &s) { if (s != durationString()) { m_durationString = s; + emit changed(); emit durationChanged(); } } @@ -142,6 +143,7 @@ QString MKTrack::format() const { void MKTrack::setFormat(const QString &f) { if (f != format()) { m_format = f; + emit changed(); emit formatChanged(); } } @@ -153,6 +155,7 @@ QString MKTrack::genre() const { void MKTrack::setGenre(const QString &g) { if (g != genre()) { m_genre = g; + emit changed(); emit genreChanged(); } } @@ -164,6 +167,7 @@ QString MKTrack::id() const { void MKTrack::setId(const QString &i) { if (i != id()) { m_id = i; + emit changed(); emit idChanged(); } } @@ -175,6 +179,7 @@ QUrl MKTrack::largeThumbnailUrl() const { void MKTrack::setLargeThumbnailUrl(const QUrl &u) { if (u != largeThumbnailUrl()) { m_largeThumbnailUrl = u; + emit changed(); emit largeThumbnailUrlChanged(); } } @@ -197,6 +202,7 @@ qint64 MKTrack::playCount() const { void MKTrack::setPlayCount(qint64 c) { if (c != playCount()) { m_playCount = c; + emit changed(); emit playCountChanged(); } } @@ -208,6 +214,7 @@ QString MKTrack::service() const { void MKTrack::setService(const QString &s) { if (s != service()) { m_service = s; + emit changed(); emit serviceChanged(); } } @@ -220,6 +227,7 @@ void MKTrack::setSize(qint64 s) { if (s != size()) { m_size = s; m_sizeString = Utils::formatBytes(s); + emit changed(); emit sizeChanged(); } } @@ -231,6 +239,7 @@ QString MKTrack::sizeString() const { void MKTrack::setSizeString(const QString &s) { if (s != sizeString()) { m_sizeString = s; + emit changed(); emit sizeChanged(); } } @@ -242,6 +251,7 @@ QUrl MKTrack::streamUrl() const { void MKTrack::setStreamUrl(const QUrl &u) { if (u != streamUrl()) { m_streamUrl = u; + emit changed(); emit streamUrlChanged(); } } @@ -253,6 +263,7 @@ QString MKTrack::title() const { void MKTrack::setTitle(const QString &t) { if (t != title()) { m_title = t; + emit changed(); emit titleChanged(); } } @@ -264,6 +275,7 @@ QUrl MKTrack::url() const { void MKTrack::setUrl(const QUrl &u) { if (u != url()) { m_url = u; + emit changed(); emit urlChanged(); } } diff --git a/app/src/base/track.h b/app/src/base/track.h index 05aa0f0..7aa2937 100644 --- a/app/src/base/track.h +++ b/app/src/base/track.h @@ -46,7 +46,7 @@ class MKTrack : public QObject public: explicit MKTrack(QObject *parent = 0); - explicit MKTrack(MKTrack *track, QObject *parent = 0); + explicit MKTrack(const MKTrack *track, QObject *parent = 0); QString artist() const; QString artistId() const; @@ -126,6 +126,8 @@ public Q_SLOTS: void artistChanged(); void artistIdChanged(); + void changed(); + void dateChanged(); void descriptionChanged(); @@ -155,7 +157,7 @@ public Q_SLOTS: void urlChanged(); -protected: +private: QString m_artist; QString m_artistId; @@ -189,6 +191,9 @@ public Q_SLOTS: QString m_title; QUrl m_url; + + friend class AudioPlayer; + friend class AudioPlayerMetaData; }; #endif // MKTRACK_H diff --git a/app/src/audioplayer/trackmodel.cpp b/app/src/base/trackmodel.cpp similarity index 62% rename from app/src/audioplayer/trackmodel.cpp rename to app/src/base/trackmodel.cpp index bf7bc8b..831fe61 100644 --- a/app/src/audioplayer/trackmodel.cpp +++ b/app/src/base/trackmodel.cpp @@ -15,9 +15,8 @@ */ #include "trackmodel.h" -#ifdef MUSIKLOUD_DEBUG -#include -#endif +#include "audioplayer.h" +#include TrackModel::TrackModel(QObject *parent) : QAbstractListModel(parent) @@ -26,6 +25,7 @@ TrackModel::TrackModel(QObject *parent) : m_roles[ArtistIdRole] = "artistId"; m_roles[DateRole] = "date"; m_roles[DescriptionRole] = "description"; + m_roles[DownloadableRole] = "downloadable"; m_roles[DurationRole] = "duration"; m_roles[DurationStringRole] = "durationString"; m_roles[FormatRole] = "format"; @@ -51,12 +51,59 @@ QHash TrackModel::roleNames() const { } #endif -int TrackModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int TrackModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int TrackModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 4; +} + +QVariant TrackModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } + + switch (section) { + case 0: + return tr("Title"); + case 1: + return tr("Artist"); + case 2: + return tr("Genre"); + case 3: + return tr("Duration"); + default: + return QVariant(); + } } QVariant TrackModel::data(const QModelIndex &index, int role) const { - if (MKTrack *track = get(index.row())) { + if (const MKTrack *track = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return track->title(); + case 1: + return track->artist(); + case 2: + return track->genre(); + case 3: + return track->durationString(); + default: + return QVariant(); + } + } + else if (role == Qt::FontRole) { + if (index.row() == AudioPlayer::instance()->currentIndex()) { + QFont font; + font.setBold(true); + return font; + } + + return QVariant(); + } + return track->property(m_roles[role]); } @@ -66,7 +113,7 @@ QVariant TrackModel::data(const QModelIndex &index, int role) const { QMap TrackModel::itemData(const QModelIndex &index) const { QMap map; - if (MKTrack *track = get(index.row())) { + if (const MKTrack *track = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -79,7 +126,7 @@ QMap TrackModel::itemData(const QModelIndex &index) const { } QVariant TrackModel::data(int row, const QByteArray &role) const { - if (MKTrack *track = get(row)) { + if (const MKTrack *track = get(row)) { return track->property(role); } @@ -89,8 +136,8 @@ QVariant TrackModel::data(int row, const QByteArray &role) const { QVariantMap TrackModel::itemData(int row) const { QVariantMap map; - if (MKTrack *track = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const MKTrack *track = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = track->property(role); } } @@ -119,6 +166,7 @@ void TrackModel::clear() { void TrackModel::append(MKTrack *track) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); track->setParent(this); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << track; endInsertRows(); emit countChanged(rowCount()); @@ -128,6 +176,7 @@ void TrackModel::insert(int row, MKTrack *track) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); track->setParent(this); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, track); endInsertRows(); emit countChanged(rowCount()); @@ -145,3 +194,15 @@ void TrackModel::remove(int row) { emit countChanged(rowCount()); } } + +void TrackModel::onCurrentIndexChanged() { + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void TrackModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} diff --git a/app/src/audioplayer/trackmodel.h b/app/src/base/trackmodel.h similarity index 87% rename from app/src/audioplayer/trackmodel.h rename to app/src/base/trackmodel.h index 39edb54..5b493bc 100644 --- a/app/src/audioplayer/trackmodel.h +++ b/app/src/base/trackmodel.h @@ -32,6 +32,7 @@ class TrackModel : public QAbstractListModel ArtistIdRole, DateRole, DescriptionRole, + DownloadableRole, DurationRole, DurationStringRole, FormatRole, @@ -54,6 +55,9 @@ class TrackModel : public QAbstractListModel QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -71,8 +75,11 @@ class TrackModel : public QAbstractListModel private Q_SLOTS: void clear(); + void onCurrentIndexChanged(); + void onItemChanged(); + Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); private: QList m_items; diff --git a/app/src/base/transfermodel.cpp b/app/src/base/transfermodel.cpp index d0cf6cf..4188769 100644 --- a/app/src/base/transfermodel.cpp +++ b/app/src/base/transfermodel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -16,15 +16,14 @@ #include "transfermodel.h" #include "transfers.h" -#include "utils.h" TransferModel::TransferModel(QObject *parent) : QAbstractListModel(parent) { m_roles[BytesTransferredRole] = "bytesTransferred"; - m_roles[CanConvertToAudioRole] = "canConvertToAudio"; - m_roles[ConvertToAudioRole] = "convertToAudio"; m_roles[CategoryRole] = "category"; + m_roles[CustomCommandRole] = "customCommand"; + m_roles[CustomCommandOverrideEnabledRole] = "customCommandOverrideEnabled"; m_roles[DownloadPathRole] = "downloadPath"; m_roles[ErrorStringRole] = "errorString"; m_roles[FileNameRole] = "fileName"; @@ -32,13 +31,14 @@ TransferModel::TransferModel(QObject *parent) : m_roles[PriorityRole] = "priority"; m_roles[PriorityStringRole] = "priorityString"; m_roles[ProgressRole] = "progress"; - m_roles[ResourceIdRole] = "resourceId"; + m_roles[ProgressStringRole] = "progressString"; m_roles[ServiceRole] = "service"; m_roles[SizeRole] = "size"; m_roles[StatusRole] = "status"; m_roles[StatusStringRole] = "statusString"; m_roles[StreamIdRole] = "streamId"; m_roles[TitleRole] = "title"; + m_roles[TrackIdRole] = "trackId"; m_roles[TransferTypeRole] = "transferType"; m_roles[UrlRole] = "url"; #if QT_VERSION < 0x050000 @@ -61,12 +61,12 @@ QHash TransferModel::roleNames() const { } #endif -int TransferModel::rowCount(const QModelIndex &) const { - return Transfers::instance()->count(); +int TransferModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : Transfers::instance()->count(); } -int TransferModel::columnCount(const QModelIndex &) const { - return 5; +int TransferModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 5; } QVariant TransferModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -91,7 +91,7 @@ QVariant TransferModel::headerData(int section, Qt::Orientation orientation, int } QVariant TransferModel::data(const QModelIndex &index, int role) const { - if (Transfer *transfer = Transfers::instance()->get(index.row())) { + if (const Transfer *transfer = Transfers::instance()->get(index.row())) { if (role == Qt::DisplayRole) { switch (index.column()) { case 0: @@ -101,18 +101,15 @@ QVariant TransferModel::data(const QModelIndex &index, int role) const { case 2: return transfer->priorityString(); case 3: - return QString("%1 of %2 (%3%)").arg(Utils::formatBytes(transfer->bytesTransferred())) - .arg(Utils::formatBytes(transfer->size())) - .arg(transfer->progress()); + return transfer->progressString(); case 4: return transfer->statusString(); default: return transfer->title(); } } - else { - return transfer->property(m_roles[role]); - } + + return transfer->property(m_roles[role]); } return QVariant(); @@ -121,7 +118,7 @@ QVariant TransferModel::data(const QModelIndex &index, int role) const { QMap TransferModel::itemData(const QModelIndex &index) const { QMap map; - if (Transfer *transfer = Transfers::instance()->get(index.row())) { + if (const Transfer *transfer = Transfers::instance()->get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -143,22 +140,20 @@ bool TransferModel::setData(const QModelIndex &index, const QVariant &value, int bool TransferModel::setItemData(const QModelIndex &index, const QMap &roles) { QMapIterator iterator(roles); - bool ok = false; while (iterator.hasNext()) { iterator.next(); - ok = setData(index, iterator.value(), iterator.key()); - if (!ok) { + if (!setData(index, iterator.value(), iterator.key())) { return false; } } - return ok; + return true; } QVariant TransferModel::data(int row, const QByteArray &role) const { - if (Transfer *transfer = Transfers::instance()->get(row)) { + if (const Transfer *transfer = Transfers::instance()->get(row)) { return transfer->property(role); } @@ -168,8 +163,8 @@ QVariant TransferModel::data(int row, const QByteArray &role) const { QVariantMap TransferModel::itemData(int row) const { QVariantMap map; - if (Transfer *transfer = Transfers::instance()->get(row)) { - foreach (QByteArray value, m_roles.values()) { + if (const Transfer *transfer = Transfers::instance()->get(row)) { + foreach (const QByteArray &value, m_roles.values()) { map[value] = transfer->property(value); } } @@ -187,18 +182,16 @@ bool TransferModel::setData(int row, const QVariant &value, const QByteArray &ro bool TransferModel::setItemData(int row, const QVariantMap &roles) { QMapIterator iterator(roles); - bool ok = false; while (iterator.hasNext()) { iterator.next(); - ok = setData(row, iterator.value(), iterator.key().toUtf8()); - - if (!ok) { + + if (!setData(row, iterator.value(), iterator.key().toUtf8())) { return false; } } - return ok; + return true; } int TransferModel::match(const QByteArray &role, const QVariant &value) const { @@ -231,6 +224,7 @@ void TransferModel::onTransferAdded(Transfer *transfer) { connect(transfer, SIGNAL(titleChanged()), this, SLOT(onTransferTitleChanged())); connect(transfer, SIGNAL(categoryChanged()), this, SLOT(onTransferCategoryChanged())); connect(transfer, SIGNAL(priorityChanged()), this, SLOT(onTransferPriorityChanged())); + connect(transfer, SIGNAL(bytesTransferredChanged()), this, SLOT(onTransferProgressChanged())); connect(transfer, SIGNAL(progressChanged()), this, SLOT(onTransferProgressChanged())); connect(transfer, SIGNAL(sizeChanged()), this, SLOT(onTransferSizeChanged())); connect(transfer, SIGNAL(statusChanged()), this, SLOT(onTransferStatusChanged())); diff --git a/app/src/base/transfermodel.h b/app/src/base/transfermodel.h index 452c535..9dc0695 100644 --- a/app/src/base/transfermodel.h +++ b/app/src/base/transfermodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -30,9 +30,9 @@ class TransferModel : public QAbstractListModel public: enum Roles { BytesTransferredRole = Qt::UserRole + 1, - CanConvertToAudioRole, - ConvertToAudioRole, CategoryRole, + CustomCommandRole, + CustomCommandOverrideEnabledRole, DownloadPathRole, ErrorStringRole, FileNameRole, @@ -40,13 +40,14 @@ class TransferModel : public QAbstractListModel PriorityRole, PriorityStringRole, ProgressRole, - ResourceIdRole, + ProgressStringRole, ServiceRole, SizeRole, StatusRole, StatusStringRole, StreamIdRole, TitleRole, + TrackIdRole, TransferTypeRole, UrlRole }; @@ -88,7 +89,7 @@ private Q_SLOTS: void onTransferStatusChanged(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); private: QHash m_roles; diff --git a/app/src/base/transfers.cpp b/app/src/base/transfers.cpp index b1014bf..0ec3e7d 100644 --- a/app/src/base/transfers.cpp +++ b/app/src/base/transfers.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -16,14 +16,14 @@ #include "transfers.h" #include "definitions.h" +#include "logger.h" #include "plugintransfer.h" #include "resources.h" #include "settings.h" #include "soundcloudtransfer.h" -#include +#include "utils.h" #include #include -#include Transfers* Transfers::self = 0; @@ -31,36 +31,29 @@ inline static Transfer* createTransfer(const QString &service, QObject *parent = if (service == Resources::SOUNDCLOUD) { return new SoundCloudTransfer(parent); } - else { - return new PluginTransfer(service, parent); - } + + return new PluginTransfer(service, parent); } -Transfers::Transfers(QObject *parent) : - QObject(parent), +Transfers::Transfers() : + QObject(), m_nam(new QNetworkAccessManager(this)) { - if (!self) { - self = this; - } - m_queueTimer.setSingleShot(true); m_queueTimer.setInterval(1000); connect(&m_queueTimer, SIGNAL(timeout()), this, SLOT(startNextTransfers())); - connect(Settings::instance(), SIGNAL(maximumConcurrentTransfersChanged()), - this, SLOT(onMaximumConcurrentTransfersChanged())); - connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(storeTransfers())); + connect(Settings::instance(), SIGNAL(maximumConcurrentTransfersChanged(int)), + this, SLOT(onMaximumConcurrentTransfersChanged(int))); } Transfers::~Transfers() { - if (self == this) { - self = 0; - } + save(); + self = 0; } Transfers* Transfers::instance() { - return self; + return self ? self : self = new Transfers; } int Transfers::active() const { @@ -71,19 +64,23 @@ int Transfers::count() const { return m_transfers.size(); } -void Transfers::addDownloadTransfer(const QString &service, const QString &resourceId, const QString &streamId, - const QUrl &streamUrl, const QString &title, const QString &category) { +void Transfers::addDownloadTransfer(const QString &service, const QString &trackId, const QString &streamId, + const QUrl &streamUrl, const QString &title, const QString &category, + const QString &customCommand, bool customCommandOverrideEnabled) { + Logger::log(QString("Transfers::addDownloadTransfer(). Service: %1, Track ID: %2, Stream ID: %3, Stream URL: %4, Title: %5, Category: %6, Command: %7").arg(service).arg(trackId).arg(streamId).arg(streamUrl.toString()) + .arg(title).arg(category).arg(customCommand)); Transfer *transfer = createTransfer(service, this); transfer->setNetworkAccessManager(m_nam); - transfer->setId(QByteArray(QByteArray::number(QDateTime::currentMSecsSinceEpoch()) + "#" - + resourceId.toUtf8()).toBase64()); - transfer->setDownloadPath(Settings::instance()->downloadPath() + ".incomplete/" + transfer->id()); + transfer->setId(Utils::createId()); + transfer->setDownloadPath(Settings::downloadPath() + ".incomplete/" + transfer->id()); transfer->setFileName(title); transfer->setCategory(category); - transfer->setResourceId(resourceId); + transfer->setTrackId(trackId); transfer->setStreamId(streamId); transfer->setStreamUrl(streamUrl); transfer->setTitle(title); + transfer->setCustomCommand(customCommand); + transfer->setCustomCommandOverrideEnabled(customCommandOverrideEnabled); connect(transfer, SIGNAL(statusChanged()), this, SLOT(onTransferStatusChanged())); @@ -91,7 +88,7 @@ void Transfers::addDownloadTransfer(const QString &service, const QString &resou emit countChanged(count()); emit transferAdded(transfer); - if (Settings::instance()->startTransfersAutomatically()) { + if (Settings::startTransfersAutomatically()) { transfer->queue(); } } @@ -123,6 +120,8 @@ bool Transfers::start() { } bool Transfers::pause() { + Logger::log("Transfers::pause()", Logger::HighVerbosity); + foreach (Transfer *transfer, m_transfers) { transfer->pause(); } @@ -131,6 +130,8 @@ bool Transfers::pause() { } bool Transfers::start(const QString &id) { + Logger::log("Transfers::start(). ID: " + id, Logger::HighVerbosity); + if (Transfer *transfer = get(id)) { transfer->queue(); return true; @@ -140,6 +141,8 @@ bool Transfers::start(const QString &id) { } bool Transfers::pause(const QString &id) { + Logger::log("Transfers::pause(). ID: " + id, Logger::HighVerbosity); + if (Transfer *transfer = get(id)) { transfer->pause(); return true; @@ -149,6 +152,8 @@ bool Transfers::pause(const QString &id) { } bool Transfers::cancel(const QString &id) { + Logger::log("Transfers::cancel(). ID: " + id, Logger::HighVerbosity); + if (Transfer *transfer = get(id)) { transfer->cancel(); return true; @@ -157,58 +162,69 @@ bool Transfers::cancel(const QString &id) { return false; } -void Transfers::storeTransfers() { - QSettings settings(STORAGE_PATH + "transfers.conf", QSettings::NativeFormat); +void Transfers::save() { + QSettings settings(APP_CONFIG_PATH + "transfers.conf", QSettings::IniFormat); settings.clear(); + settings.beginWriteArray("transfers"); - foreach (Transfer *transfer, m_transfers) { - settings.beginGroup(transfer->id()); + for (int i = 0; i < m_transfers.size(); i++) { + const Transfer *transfer = m_transfers.at(i); + settings.setArrayIndex(i); + settings.setValue("id", transfer->id()); settings.setValue("downloadPath", transfer->downloadPath()); settings.setValue("fileName", transfer->fileName()); settings.setValue("category", transfer->category()); settings.setValue("priority", Transfer::Priority(transfer->priority())); settings.setValue("size", transfer->size()); settings.setValue("service", transfer->service()); - settings.setValue("resourceId", transfer->resourceId()); + settings.setValue("trackId", transfer->trackId()); settings.setValue("streamId", transfer->streamId()); settings.setValue("streamUrl", transfer->streamUrl()); settings.setValue("title", transfer->title()); - settings.endGroup(); + settings.setValue("customCommand", transfer->customCommand()); + settings.setValue("customCommandOverrideEnabled", transfer->customCommandOverrideEnabled()); } + + settings.endArray(); + Logger::log(QString("Transfers::save(). %1 transfers saved").arg(m_transfers.size()), Logger::LowVerbosity); } -void Transfers::restoreTransfers() { - QSettings settings(STORAGE_PATH + "transfers.conf", QSettings::NativeFormat); +void Transfers::restore() { + QSettings settings(APP_CONFIG_PATH + "transfers.conf", QSettings::IniFormat); + const int size = settings.beginReadArray("transfers"); + Logger::log(QString("Transfers::restore(). %1 transfers restored").arg(size), Logger::LowVerbosity); - foreach (QString group, settings.childGroups()) { - settings.beginGroup(group); - Transfer *transfer = createTransfer(settings.value("service").toString(), this); - transfer->setId(group); + for (int i = 0; i < size; i++) { + settings.setArrayIndex(i); + Transfer *transfer = createTransfer(settings.value("service", Resources::SOUNDCLOUD).toString(), this); + transfer->setId(settings.value("id").toString()); transfer->setDownloadPath(settings.value("downloadPath").toString()); transfer->setFileName(settings.value("fileName").toString()); transfer->setCategory(settings.value("category").toString()); - transfer->setPriority(Transfer::Priority(settings.value("priority").toInt())); + transfer->setPriority(Transfer::Priority(settings.value("priority", 1).toInt())); transfer->setSize(settings.value("size").toLongLong()); - transfer->setResourceId(settings.value("resourceId").toString()); + transfer->setTrackId(settings.value("trackId").toString()); transfer->setStreamId(settings.value("streamId").toString()); transfer->setStreamUrl(settings.value("streamUrl").toString()); transfer->setTitle(settings.value("title").toString()); - settings.endGroup(); - + transfer->setCustomCommand(settings.value("customCommand").toString()); + transfer->setCustomCommandOverrideEnabled(settings.value("customCommandOverrideEnabled", false).toBool()); connect(transfer, SIGNAL(statusChanged()), this, SLOT(onTransferStatusChanged())); m_transfers << transfer; emit countChanged(count()); emit transferAdded(transfer); - if (Settings::instance()->startTransfersAutomatically()) { + if (Settings::startTransfersAutomatically()) { transfer->queue(); } } + + settings.endArray(); } void Transfers::getNextTransfers() { - const int max = Settings::instance()->maximumConcurrentTransfers(); + const int max = Settings::maximumConcurrentTransfers(); for (int priority = Transfer::HighPriority; priority <= Transfer::LowPriority; priority++) { foreach (Transfer *transfer, m_transfers) { @@ -217,6 +233,8 @@ void Transfers::getNextTransfers() { addActiveTransfer(transfer); } else { + Logger::log("Transfers::getNextTransfers(). Maximum concurrent transfers reached", + Logger::MediumVerbosity); return; } } @@ -240,11 +258,13 @@ void Transfers::removeTransfer(Transfer *transfer) { } void Transfers::addActiveTransfer(Transfer *transfer) { + Logger::log("Transfers::addActiveTransfer(). ID: " + transfer->id(), Logger::MediumVerbosity); m_active << transfer; emit activeChanged(active()); } void Transfers::removeActiveTransfer(Transfer *transfer) { + Logger::log("Transfers::removeActiveTransfer(). ID: " + transfer->id(), Logger::MediumVerbosity); m_active.removeOne(transfer); emit activeChanged(active()); } @@ -259,7 +279,7 @@ void Transfers::onTransferStatusChanged() { case Transfer::Canceled: case Transfer::Completed: removeTransfer(transfer); - storeTransfers(); + save(); break; case Transfer::Queued: break; @@ -267,27 +287,26 @@ void Transfers::onTransferStatusChanged() { return; } - if (active() < Settings::instance()->maximumConcurrentTransfers()) { + if (active() < Settings::maximumConcurrentTransfers()) { m_queueTimer.start(); } } } -void Transfers::onMaximumConcurrentTransfersChanged() { - const int max = Settings::instance()->maximumConcurrentTransfers(); +void Transfers::onMaximumConcurrentTransfersChanged(int maximum) { int act = active(); - if (act < max) { + if (act < maximum) { startNextTransfers(); } - else if (act > max) { + else if (act > maximum) { for (int priority = Transfer::LowPriority; priority >= Transfer::HighPriority; priority--) { for (int i = m_active.size() - 1; i >= 0; i--) { if (m_active.at(i)->priority() == priority) { m_active.at(i)->pause(); act--; - if (act == max) { + if (act == maximum) { return; } } diff --git a/app/src/base/transfers.h b/app/src/base/transfers.h index d9d63d2..98404c8 100644 --- a/app/src/base/transfers.h +++ b/app/src/base/transfers.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -30,7 +30,6 @@ class Transfers : public QObject Q_PROPERTY(int count READ count NOTIFY countChanged) public: - explicit Transfers(QObject *parent = 0); ~Transfers(); static Transfers* instance(); @@ -38,8 +37,10 @@ class Transfers : public QObject int active() const; int count() const; - Q_INVOKABLE void addDownloadTransfer(const QString &service, const QString &resourceId, const QString &streamId, - const QUrl &streamUrl, const QString &title, const QString &category); + Q_INVOKABLE void addDownloadTransfer(const QString &service, const QString &trackId, const QString &streamId, + const QUrl &streamUrl, const QString &title, const QString &category, + const QString &customCommand = QString(), + bool customCommandOverrideEnabled = false); Q_INVOKABLE Transfer* get(int i) const; Q_INVOKABLE Transfer* get(const QString &id) const; @@ -51,8 +52,8 @@ public Q_SLOTS: bool pause(const QString &id); bool cancel(const QString &id); - void storeTransfers(); - void restoreTransfers(); + void save(); + void restore(); private: void getNextTransfers(); @@ -66,14 +67,16 @@ private Q_SLOTS: void startNextTransfers(); void onTransferStatusChanged(); - void onMaximumConcurrentTransfersChanged(); + void onMaximumConcurrentTransfersChanged(int maximum); Q_SIGNALS: - void activeChanged(int a); - void countChanged(int c); + void activeChanged(int active); + void countChanged(int count); void transferAdded(Transfer *transfer); private: + Transfers(); + static Transfers *self; QNetworkAccessManager *m_nam; diff --git a/app/src/base/utils.cpp b/app/src/base/utils.cpp index 7f579c5..d946231 100644 --- a/app/src/base/utils.cpp +++ b/app/src/base/utils.cpp @@ -17,26 +17,25 @@ #include "utils.h" #include #include +#include +#if QT_VERSION >= 0x050000 +#include +#else +const QStringList Utils::AUDIO_FILE_TYPES = QStringList() << "3gp" << "aa" << "aac" << "aax" << "aiff" << "ape" + << "flac" << "m4a" << "m4p" << "mogg" << "mp3" << "oga" + << "ogg" << "ra" << "rm" << "wav" << "wma"; +#endif +const QStringList Utils::THUMBNAIL_NAMES = QStringList() << "cover" << "thumb" << "folder" << "front"; +const QStringList Utils::THUMBNAIL_TYPES = QStringList() << "*.jpg" << "*.jpeg" << "*.png"; Utils::Utils(QObject *parent) : QObject(parent) { } -QUrl Utils::findThumbnailUrl(const QUrl &url) { - if (!isLocalFile(url)) { - return QUrl(); - } - - QDir dir(url.path().left(url.path().lastIndexOf('/'))); - - foreach (QString fileName, QStringList() << "cover.jpg" << "folder.jpg" << "front.jpg") { - if (dir.exists(fileName)) { - return QUrl::fromLocalFile(dir.absoluteFilePath(fileName)); - } - } - - return QUrl(); +QString Utils::createId() { + const QString uuid = QUuid::createUuid().toString(); + return uuid.mid(1, uuid.size() - 2); } QString Utils::formatBytes(qint64 bytes) { @@ -44,9 +43,9 @@ QString Utils::formatBytes(qint64 bytes) { return QString("0B"); } - double kb = 1024; - double mb = kb * 1024; - double gb = mb * 1024; + const double kb = 1024; + const double mb = kb * 1024; + const double gb = mb * 1024; QString size; @@ -71,8 +70,8 @@ QString Utils::formatLargeNumber(qint64 num) { return QString::number(num); } - double k = 1000; - double m = k * 1000; + const double k = 1000; + const double m = k * 1000; QString result; @@ -86,22 +85,64 @@ QString Utils::formatLargeNumber(qint64 num) { return result; } -QString Utils::formatMSecs(qint64 ms) { +QString Utils::formatMSecs(qint64 ms) { return ms > 0 ? formatSecs(ms / 1000) : QString("--:--"); } -QString Utils::formatSecs(qint64 s) { +QString Utils::formatSecs(qint64 s) { return s > 0 ? QString("%1:%2").arg(s / 60, 2, 10, QChar('0')).arg(s % 60, 2, 10, QChar('0')) : QString("--:--"); } +bool Utils::isAudioFile(const QString &fileName) { + return isAudioFile(QFileInfo(fileName)); +} + +bool Utils::isAudioFile(const QFileInfo &info) { +#if QT_VERSION >= 0x050000 + const QMimeDatabase db; + return db.mimeTypeForFile(info).name().startsWith("audio"); +#else + return AUDIO_FILE_TYPES.contains(info.suffix()); +#endif +} + bool Utils::isLocalFile(const QUrl &url) { - return (url.scheme() == "file") || (url.toString().startsWith("/")); + const QString scheme = url.scheme(); + return (scheme == "file") || (scheme.size() <= 1); +} + +QString Utils::toLocalFile(const QUrl &url) { + const QString scheme = url.scheme(); + + if (scheme == "file") { + return url.toLocalFile(); + } + + return url.toString(); +} + +QUrl Utils::thumbnailUrlForFile(const QString &fileName) { + return thumbnailUrlForFile(QFileInfo(fileName)); +} + +QUrl Utils::thumbnailUrlForFile(const QFileInfo &info) { + const QFileInfoList images = info.dir().entryInfoList(THUMBNAIL_TYPES, QDir::Files); + + foreach (const QString &name, THUMBNAIL_NAMES) { + foreach (const QFileInfo &image, images) { + if (image.completeBaseName().endsWith(name, Qt::CaseInsensitive)) { + return QUrl::fromLocalFile(image.absoluteFilePath()); + } + } + } + + return QUrl(); } QString Utils::toRichText(QString s) { - s.replace("&", "&").replace("<", "<").replace(QRegExp("[\n\r]"), "
"); + s.replace("&", "&").replace("< ", "< ").replace(QRegExp("[\n\r]"), "
"); - QRegExp re("((http(s|)://|[\\w-_\\.]+@)[^\\s<:\"']+)"); + QRegExp re("(http(s|)://[^\\s<:\"']+|[\\w-_\\.]+@[\\w-_]+\\.[a-zA-Z]+)"); int pos = 0; while ((pos = re.indexIn(s, pos)) != -1) { @@ -109,7 +150,7 @@ QString Utils::toRichText(QString s) { s.replace(pos, link.size(), QString("%2").arg(link.contains('@') ? "mailto:" + link : link).arg(link)); pos += re.matchedLength() * 2 + 15; } - + return s; } @@ -119,7 +160,7 @@ QString Utils::unescape(const QString &s) { while ((us.contains('%')) && (unescapes < 10)) { us = QByteArray::fromPercentEncoding(us); - unescapes++; + ++unescapes; } return QString::fromUtf8(us); diff --git a/app/src/base/utils.h b/app/src/base/utils.h index ef7c2f2..bf365eb 100644 --- a/app/src/base/utils.h +++ b/app/src/base/utils.h @@ -18,6 +18,7 @@ #define UTILS_H #include +#include #include class Utils : public QObject @@ -27,20 +28,34 @@ class Utils : public QObject public: explicit Utils(QObject *parent = 0); - Q_INVOKABLE static QUrl findThumbnailUrl(const QUrl &url); - + Q_INVOKABLE static QString createId(); + Q_INVOKABLE static QString formatBytes(qint64 bytes); Q_INVOKABLE static QString formatLargeNumber(qint64 num); Q_INVOKABLE static QString formatMSecs(qint64 ms); - Q_INVOKABLE static QString formatSecs(qint64 s); + Q_INVOKABLE static QString formatSecs(qint64 s); + + Q_INVOKABLE static bool isAudioFile(const QString &fileName); + static bool isAudioFile(const QFileInfo &info); Q_INVOKABLE static bool isLocalFile(const QUrl &url); + Q_INVOKABLE static QString toLocalFile(const QUrl &url); + + Q_INVOKABLE static QUrl thumbnailUrlForFile(const QString &fileName); + static QUrl thumbnailUrlForFile(const QFileInfo &info); Q_INVOKABLE static QString toRichText(QString s); Q_INVOKABLE static QString unescape(const QString &s); + +private: +#if QT_VERSION < 0x050000 + static const QStringList AUDIO_FILE_TYPES; +#endif + static const QStringList THUMBNAIL_NAMES; + static const QStringList THUMBNAIL_TYPES; }; #endif // UTILS_H diff --git a/app/src/dbus/dbusservice.cpp b/app/src/dbus/dbusservice.cpp index dc5a5c7..2218e00 100644 --- a/app/src/dbus/dbusservice.cpp +++ b/app/src/dbus/dbusservice.cpp @@ -15,12 +15,10 @@ */ #include "dbusservice.h" +#include "logger.h" #include "resources.h" #include #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif DBusService::DBusService(QObject *parent) : QObject(parent) @@ -35,10 +33,9 @@ QVariantMap DBusService::requestedResource() const { } bool DBusService::showResource(const QString &url) { - QVariantMap resource = Resources::getResourceFromUrl(url); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "DBusService::showResource" << url << resource; -#endif + Logger::log("DBusService::showResource(). URL: " + url, Logger::MediumVerbosity); + const QVariantMap resource = Resources::getResourceFromUrl(url); + if (resource.isEmpty()) { return false; } diff --git a/app/src/desktop-qml/definitions.h b/app/src/desktop-qml/definitions.h deleted file mode 100644 index a3d7bd5..0000000 --- a/app/src/desktop-qml/definitions.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef DEFINITIONS_H -#define DEFINITIONS_H - -#include -#include -#if QT_VERSION >= 0x050000 -#include -#else -#include -#endif - -static const int MAX_CONCURRENT_TRANSFERS = 4; -static const int MAX_REDIRECTS = 8; - -static const int MAX_RESULTS = 20; - -static const int LARGE_THUMBNAIL_SIZE = 300; -static const int THUMBNAIL_SIZE = 64; - -static const QRegExp ILLEGAL_FILENAME_CHARS_RE("[\"@&~=\\/:?#!|<>*^]"); - -static const QString VERSION_NUMBER("0.0.3"); - -static const QStringList SUPPORTED_AUDIO_FORMATS = QStringList() << "*.aiff" << "*.ape" << "*.flac" << "*.m4a" << "*.mp3" - << "*.ogg" << "*.wav" << "*.wma"; - -#if QT_VERSION >= 0x050000 -static const QString DATABASE_PATH(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/MusiKloud2/"); -static const QString DOWNLOAD_PATH(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/MusiKloud2/"); -static const QString STORAGE_PATH(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/MusiKloud2/"); -static const QStringList PLUGIN_PATHS = QStringList() << "/opt/musikloud2/plugins/" - << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) - + "/MusiKloud2/plugins/"; -#else -static const QString DATABASE_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); -static const QString DOWNLOAD_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/MusiKloud2/"); -static const QString STORAGE_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); -static const QStringList PLUGIN_PATHS = QStringList() << "/opt/musikloud2/plugins/" - << QDesktopServices::storageLocation(QDesktopServices::HomeLocation) - + "/.config/MusiKloud2/plugins/"; -#endif - -#endif // DEFINITIONS_H diff --git a/app/src/desktop-qml/main.cpp b/app/src/desktop-qml/main.cpp deleted file mode 100644 index cdc41db..0000000 --- a/app/src/desktop-qml/main.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "audioplayer.h" -#include "categorymodel.h" -#include "categorynamemodel.h" -#include "clipboard.h" -#include "concurrenttransfersmodel.h" -#include "database.h" -#include "dbusservice.h" -#include "definitions.h" -#include "networkproxytypemodel.h" -#include "pluginartistmodel.h" -#include "plugincategorymodel.h" -#include "plugincommentmodel.h" -#include "pluginnavmodel.h" -#include "pluginplaylistmodel.h" -#include "pluginsearchtypemodel.h" -#include "pluginsettingsmodel.h" -#include "pluginstreammodel.h" -#include "plugintrackmodel.h" -#include "resources.h" -#include "resourcesplugins.h" -#include "resourcesrequest.h" -#include "searchhistorymodel.h" -#include "servicemodel.h" -#include "settings.h" -#include "soundcloud.h" -#include "soundcloudaccountmodel.h" -#include "soundcloudartistmodel.h" -#include "soundcloudcommentmodel.h" -#include "soundcloudconnectionmodel.h" -#include "soundcloudnavmodel.h" -#include "soundcloudplaylistmodel.h" -#include "soundcloudsearchtypemodel.h" -#include "soundcloudstreammodel.h" -#include "soundcloudtrackmodel.h" -#include "transfermodel.h" -#include "transfers.h" -#include "utils.h" -#include -#include -#include -#include -#include - -inline void registerTypes() { - qmlRegisterType("MusiKloud", 2, 0, "AudioPlayer"); - qmlRegisterType("MusiKloud", 2, 0, "CategoryModel"); - qmlRegisterType("MusiKloud", 2, 0, "CategoryNameModel"); - qmlRegisterType("MusiKloud", 2, 0, "ConcurrentTransfersModel"); - qmlRegisterType("MusiKloud", 2, 0, "Track"); - qmlRegisterType("MusiKloud", 2, 0, "NetworkProxyTypeModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginArtist"); - qmlRegisterType("MusiKloud", 2, 0, "PluginArtistModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginCategoryModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginComment"); - qmlRegisterType("MusiKloud", 2, 0, "PluginCommentModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginNavModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginPlaylist"); - qmlRegisterType("MusiKloud", 2, 0, "PluginPlaylistModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginSearchTypeModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginSettingsModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginStreamModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginTrack"); - qmlRegisterType("MusiKloud", 2, 0, "PluginTrackModel"); - qmlRegisterType("MusiKloud", 2, 0, "ResourcesRequest"); - qmlRegisterType("MusiKloud", 2, 0, "SearchHistoryModel"); - qmlRegisterType("MusiKloud", 2, 0, "SelectionModel"); - qmlRegisterType("MusiKloud", 2, 0, "ServiceModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudAccountModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudArtist"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudArtistModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudComment"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudCommentModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudConnectionModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudNavModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudPlaylist"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudPlaylistModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudSearchTypeModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudStreamModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudTrack"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudTrackModel"); - qmlRegisterType("MusiKloud", 2, 0, "TrackModel"); - qmlRegisterType("MusiKloud", 2, 0, "TransferModel"); - - qmlRegisterUncreatableType("MusiKloud", 2, 0, "Transfer", ""); -} - -Q_DECL_EXPORT int main(int argc, char *argv[]) { - QApplication app(argc, argv); - app.setOrganizationName("MusiKloud2"); - app.setApplicationName("MusiKloud2"); - app.setApplicationVersion(VERSION_NUMBER); - app.setWindowIcon(QIcon::fromTheme("musikloud2")); - - Settings settings; - Clipboard clipboard; - DBusService dbus; - Resources resources; - ResourcesPlugins plugins; - SoundCloud soundcloud; - Transfers transfers; - Utils utils; - - initDatabase(); - registerTypes(); - plugins.load(); - settings.setNetworkProxy(); - - QQmlApplicationEngine engine; - QQmlContext *context = engine.rootContext(); - - context->setContextProperty("Clipboard", &clipboard); - context->setContextProperty("DBus", &dbus); - context->setContextProperty("Plugins", &plugins); - context->setContextProperty("Resources", &resources); - context->setContextProperty("Settings", &settings); - context->setContextProperty("SoundCloud", &soundcloud); - context->setContextProperty("Transfers", &transfers); - context->setContextProperty("Utils", &utils); - context->setContextProperty("MAX_RESULTS", MAX_RESULTS); - context->setContextProperty("VERSION_NUMBER", VERSION_NUMBER); - - engine.load("/opt/musikloud2/qml/main.qml"); - - return app.exec(); -} diff --git a/app/src/desktop-qml/qml/AboutDialog.qml b/app/src/desktop-qml/qml/AboutDialog.qml deleted file mode 100644 index b1f42a2..0000000 --- a/app/src/desktop-qml/qml/AboutDialog.qml +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 - -MyDialog { - id: root - - minimumWidth: 400 - minimumHeight: row.height + 60 - title: qsTr("About") - content: RowLayout { - id: row - - width: parent.width - - Image { - Layout.alignment: Qt.AlignTop - source: "/usr/share/icons/hicolor/48x48/apps/musikloud2.png" - } - - Label { - id: label - - Layout.fillWidth: true - wrapMode: Text.WordWrap - text: "MusiKloud2\n\n" + qsTr("A SoundCloud client and music player that can be expanded via plugins.") - + "\n\nCopyright Stuart Howarth 2015" - } - } - buttons: Button { - text: qsTr("&Ok") - iconName: "dialog-ok" - isDefault: true - onClicked: root.accept() - } -} diff --git a/app/src/desktop-qml/qml/ArtistDelegate.qml b/app/src/desktop-qml/qml/ArtistDelegate.qml deleted file mode 100644 index 423436c..0000000 --- a/app/src/desktop-qml/qml/ArtistDelegate.qml +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import ".." - -Item { - id: root - - signal pressed - signal clicked - signal doubleClicked - signal rightClicked - - Label { - anchors { - left: loader.item ? loader.right : parent.left - right: parent.right - margins: 2 - } - verticalAlignment: Text.AlignVCenter - color: styleData.textColor - elide: styleData.elideMode - text: styleData.value - } - - Loader { - id: loader - - width: height - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - margins: 2 - } - sourceComponent: styleData.column == 0 ? thumbnail : undefined - } - - Component { - id: thumbnail - - Image { - anchors.fill: parent - source: view.model.data(styleData.row, "thumbnailUrl") - smooth: true - } - } - - MouseArea { - id: mouseArea - - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onPressed: root.pressed() - onClicked: mouse.button == Qt.RightButton ? root.rightClicked() : root.clicked() - onDoubleClicked: if (mouse.button == Qt.LeftButton) root.doubleClicked(); - } -} diff --git a/app/src/desktop-qml/qml/ArtistPopup.qml b/app/src/desktop-qml/qml/ArtistPopup.qml deleted file mode 100644 index 75ab749..0000000 --- a/app/src/desktop-qml/qml/ArtistPopup.qml +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Rectangle { - id: root - - property string artistId - property alias thumbnail: thumbnail.source - property alias name: nameLabel.text - property alias description: descriptionLabel.text - - z: 1000 - width: 400 - height: Math.max(thumbnail.height, grid.height) + 20 - color: palette.window - - MouseArea { - anchors.fill: parent - } - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - } - - GridLayout { - id: grid - - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Name") - } - - Label { - id: nameLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - Label { - id: descriptionLabel - - Layout.fillWidth: true - wrapMode: Text.Wrap - maximumLineCount: 10 - elide: Text.ElideRight - } - } -} diff --git a/app/src/desktop-qml/qml/CommentDelegate.qml b/app/src/desktop-qml/qml/CommentDelegate.qml deleted file mode 100644 index 2980f84..0000000 --- a/app/src/desktop-qml/qml/CommentDelegate.qml +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 - -Item { - id: root - - signal thumbnailClicked - - width: parent ? parent.width : undefined - height: userLabel.height + commentLabel.height + 30 - - Avatar { - id: avatar - - z: 100 - width: 30 - height: 30 - anchors { - top: parent.top - left: parent.left - margins: 10 - } - source: thumbnailUrl - onClicked: root.thumbnailClicked() - } - - Label { - id: userLabel - - anchors { - top: avatar.top - left: avatar.right - leftMargin: 10 - right: parent.right - rightMargin: 10 - } - wrapMode: Text.Wrap - color: palette.mid - text: qsTr("by") + " " + artist + " " + qsTr("on") + " " + date - } - - Label { - id: commentLabel - - anchors { - top: userLabel.bottom - left: userLabel.left - right: parent.right - rightMargin: 10 - } - wrapMode: Text.Wrap - text: "\"" + body + "\"" - } -} diff --git a/app/src/desktop-qml/qml/MyDialog.qml b/app/src/desktop-qml/qml/MyDialog.qml deleted file mode 100644 index 172c30c..0000000 --- a/app/src/desktop-qml/qml/MyDialog.qml +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Window 2.0 -import QtQuick.Layouts 1.1 - -Window { - id: root - - property alias content: contentItem.data - property alias buttons: buttonRow.data - - signal opened - signal closed - signal accepted - signal rejected - - function open() { - x = Math.floor(window.x + ((window.width - width) / 2)); - y = Math.floor(window.y + ((window.height - height) / 2)); - show(); - opened(); - } - - function accept() { - hide(); - closed(); - accepted(); - } - - function reject() { - hide(); - closed(); - rejected(); - } - - width: minimumWidth - height: minimumHeight - color: palette.window - - Item { - id: contentItem - - anchors { - left: parent.left - top: parent.top - right: parent.right - bottom: buttonRow.visible ? buttonRow.top : parent.bottom - margins: 10 - } - } - - RowLayout { - id: buttonRow - - anchors { - right: parent.right - bottom: parent.bottom - margins: 10 - } - - visible: children.length > 0 - } -} diff --git a/app/src/desktop-qml/qml/Page.qml b/app/src/desktop-qml/qml/Page.qml deleted file mode 100644 index 56a4082..0000000 --- a/app/src/desktop-qml/qml/Page.qml +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 - -FocusScope { - id: root - - property string title - property alias tools: toolBar.children - default property alias content: contentItem.data - - width: parent ? parent.width : undefined - height: parent ? parent.height : undefined - focus: true - - ToolBar { - id: toolBar - - visible: children.length > 2 - height: 30 - anchors { - left: parent.left - right: parent.right - top: parent.top - } - } - - Item { - id: contentItem - - anchors { - left: parent.left - right: parent.right - top: toolBar.visible ? toolBar.bottom : parent.top - bottom: parent.bottom - } - clip: true - } - - onVisibleChanged: if (visible) forceActiveFocus(); - - Component.onCompleted: forceActiveFocus() -} diff --git a/app/src/desktop-qml/qml/PlaybackQueuePage.qml b/app/src/desktop-qml/qml/PlaybackQueuePage.qml deleted file mode 100644 index fd73419..0000000 --- a/app/src/desktop-qml/qml/PlaybackQueuePage.qml +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQml 2.2 -import QtQuick.Controls 1.1 - -Page { - id: root - - property alias model: view.model - property alias view: view - - tools: ToolBarLayout { - - Label { - text: qsTr("Playback queue") + " (" + player.queueCount + ")" - } - } - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: player.queue - itemDelegate: LabelDelegate { - font.bold: styleData.row == player.currentIndex - text: styleData.column == 0 ? styleData.row + 1 : styleData.value - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: player.currentIndex = styleData.row - //onRightClicked: contextMenu.popup() Disabled as 'styleData.row' is not updated when an item is removed - // from the model. - } - - TableViewColumn { - title: qsTr("#") - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "artist" - title: qsTr("Artist") - } - - TableViewColumn { - role: "genre" - title: qsTr("Genre") - } - - TableViewColumn { - role: "durationString" - title: qsTr("Length") - } - - Keys.onReturnPressed: player.currentIndex = currentRow - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - onMouseYChanged: row = view.rowAt(10, mouseY) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - - if (row >= 0) { - popupTimer.restart(); - } - } - onPressed: mouse.accepted = false - } - - Menu { - id: contextMenu - - MenuItem { - text: qsTr("Remove") - iconName: "edit-delete" - onTriggered: player.removeTrack(view.currentRow) - } - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - TrackPopup { - z: 1000 - trackId: player.queue.data(Math.max(0, popupMouseArea.row), "id") - thumbnail: player.queue.data(Math.max(0, popupMouseArea.row), "thumbnailUrl") - title: player.queue.data(Math.max(0, popupMouseArea.row), "title") - artist: player.queue.data(Math.max(0, popupMouseArea.row), "artist") - genre: player.queue.data(Math.max(0, popupMouseArea.row), "genre") - date: player.queue.data(Math.max(0, popupMouseArea.row), "date") - format: player.queue.data(Math.max(0, popupMouseArea.row), "format") - duration: player.queue.data(Math.max(0, popupMouseArea.row), "durationString") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } - - Connections { - target: player - onCurrentIndexChanged: { - view.selection.clear(); - view.selection.select(i); - view.currentRow = i; - } - } -} diff --git a/app/src/desktop-qml/qml/PlaylistDelegate.qml b/app/src/desktop-qml/qml/PlaylistDelegate.qml deleted file mode 100644 index 3152676..0000000 --- a/app/src/desktop-qml/qml/PlaylistDelegate.qml +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 - -ItemDelegate { - id: root - - Label { - anchors { - left: loader.item ? loader.right : parent.left - right: parent.right - margins: 2 - } - verticalAlignment: Text.AlignVCenter - color: styleData.textColor - elide: styleData.elideMode - text: styleData.value - } - - Loader { - id: loader - - width: height - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - margins: 2 - } - sourceComponent: styleData.column == 0 ? thumbnail : undefined - } - - Component { - id: thumbnail - - Image { - anchors.fill: parent - source: view.model.data(styleData.row, "thumbnailUrl") - smooth: true - } - } -} diff --git a/app/src/desktop-qml/qml/PlaylistPopup.qml b/app/src/desktop-qml/qml/PlaylistPopup.qml deleted file mode 100644 index 30d6efe..0000000 --- a/app/src/desktop-qml/qml/PlaylistPopup.qml +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Rectangle { - id: root - - property string playlistId - property alias thumbnail: thumbnail.source - property alias title: titleLabel.text - property alias artist: artistLabel.text - property alias genre: genreLabel.text - property alias date: dateLabel.text - property alias trackCount: trackCountLabel.text - property alias duration: durationLabel.text - - - z: 1000 - width: 400 - height: Math.max(thumbnail.height, grid.height) + 20 - color: palette.window - - MouseArea { - anchors.fill: parent - } - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - } - - GridLayout { - id: grid - - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - id: titleLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Artist") - } - - Label { - id: artistLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Genre") - } - - Label { - id: genreLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Date") - } - - Label { - id: dateLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Tracks") - } - - Label { - id: trackCountLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Length") - } - - Label { - id: durationLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - } -} diff --git a/app/src/desktop-qml/qml/SettingsDialog.qml b/app/src/desktop-qml/qml/SettingsDialog.qml deleted file mode 100644 index 6becbe3..0000000 --- a/app/src/desktop-qml/qml/SettingsDialog.qml +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.2 -import MusiKloud 2.0 - -MyDialog { - id: root - - minimumWidth: 600 - minimumHeight: 400 - title: qsTr("Preferences") - content: TabView { - id: tabs - - anchors.fill: parent - - Tab { - id: generalTab - - title: qsTr("General") - - GridLayout { - anchors { - left: parent.left - right: parent.right - top: parent.top - margins: 10 - } - columns: 3 - columnSpacing: 10 - rowSpacing: 10 - - Label { - text: qsTr("Default download path") + ":" - } - - TextField { - id: downloadPathField - - Layout.fillWidth: true - text: Settings.downloadPath - } - - Button { - id: browseButton - - text: qsTr("Browse") - iconName: "document-open" - onClicked: { - loader.sourceComponent = fileDialog; - loader.item.open(); - } - } - - CheckBox { - id: transfersCheckBox - - Layout.columnSpan: 3 - text: qsTr("Start transfers automatically") - checked: Settings.startTransfersAutomatically - onCheckedChanged: Settings.startTransfersAutomatically = checked - } - - Item { - Layout.minimumWidth: browseButton.width - } - - CheckBox { - id: safeSearchCheckBox - - Layout.columnSpan: 3 - text: qsTr("Enable safe search") - checked: Settings.safeSearchEnabled - onCheckedChanged: Settings.safeSearchEnabled = checked - } - - CheckBox { - id: clipboardCheckBox - - Layout.columnSpan: 3 - text: qsTr("Monitor clipboard for URLs") - checked: Settings.clipboardMonitorEnabled - onCheckedChanged: Settings.clipboardMonitorEnabled = checked - } - - Component { - id: fileDialog - - FileDialog { - folder: Settings.downloadPath - selectFolder: true - title: qsTr("Default download path") - onAccepted: downloadPathField.text = folder.toString().substring(7) - } - } - } - } - - Tab { - id: categoriesTab - - title: qsTr("Categories") - - GridLayout { - anchors { - fill: parent - margins: 10 - } - columns: 3 - columnSpacing: 10 - rowSpacing: 10 - - TableView { - id: catgoriesView - - Layout.columnSpan: 3 - Layout.fillWidth: true - alternatingRowColors: false - model: CategoryModel { - id: categoryModel - } - - TableViewColumn { - role: "name" - title: qsTr("Name") - width: 200 - } - - TableViewColumn { - role: "path" - title: qsTr("Download path") - } - - onActivated: { - nameField.text = categoryModel.data(row, "name"); - pathField.text = categoryModel.data(row, "path"); - } - onClicked: { - nameField.text = categoryModel.data(row, "name"); - pathField.text = categoryModel.data(row, "path"); - } - } - - Label { - Layout.columnSpan: 3 - text: qsTr("Add/edit category") - } - - Label { - text: qsTr("Name") + ":" - } - - TextField { - id: nameField - - Layout.fillWidth: true - } - - Item { - Layout.minimumWidth: browseButton.width - } - - Label { - text: qsTr("Download path") + ":" - } - - TextField { - id: pathField - - Layout.fillWidth: true - } - - Button { - id: browseButton - - text: qsTr("Browse") - iconName: "document-open" - enabled: nameField.text != "" - onClicked: { - loader.sourceComponent = fileDialog; - loader.item.folder = pathField.text; - loader.item.open(); - } - } - - Button { - id: saveButton - - text: qsTr("Save") - iconName: "document-save" - enabled: (nameField.text) && (pathField.text) - onClicked: { - categoryModel.addCategory(nameField.text, pathField.text); - nameField.text = ""; - pathField.text = ""; - } - } - - Component { - id: fileDialog - - FileDialog { - selectFolder: true - title: qsTr("Download path") - onAccepted: pathField.text = folder.toString().substring(7) - } - } - } - } - - Tab { - id: networkTab - - title: qsTr("Network") - - GroupBox { - anchors { - fill: parent - margins: 10 - } - title: qsTr("Enable network proxy") - checkable: true - checked: Settings.networkProxyEnabled - onCheckedChanged: Settings.networkProxyEnabled = checked - - GridLayout { - anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: 10 - } - columns: 4 - columnSpacing: 10 - rowSpacing: 10 - - Label { - text: qsTr("Host") + ":" - } - - TextField { - id: proxyHostField - - Layout.fillWidth: true - text: Settings.networkProxyHost - onTextChanged: Settings.networkProxyHost = text - } - - Label { - text: qsTr("Port") + ":" - } - - TextField { - id: proxyPortField - - Layout.fillWidth: true - validator: IntValidator { - bottom: 0 - top: 100000 - } - text: Settings.networkProxyPort - onTextChanged: Settings.networkProxyPort = parseInt(text) - } - - Label { - text: qsTr("Username") + ":" - } - - TextField { - id: proxyUsernameField - - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true - text: Settings.networkProxyUsername - onTextChanged: Settings.networkProxyUsername = text - } - - Label { - text: qsTr("Password") + ":" - } - - TextField { - id: proxyPasswordField - - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true - echoMode: TextInput.Password - text: Settings.networkProxyPassword - onTextChanged: Settings.networkProxyPassword = text - } - } - } - } - - Tab { - id: pluginsTab - - title: qsTr("Plugins") - - Item { - anchors { - fill: parent - margins: 10 - } - - function loadSettings(pluginName, fileName) { - clearLoaders(); - - var request = new XMLHttpRequest(); - request.onreadystatechange = function() { - if (request.readyState === XMLHttpRequest.DONE) { - var doc = request.responseXML.documentElement; - - for (var i = 0; i < doc.childNodes.length; i++) { - var node = doc.childNodes[i]; - - if (node.nodeName === "group") { - addGroup(node.attributes[0].value); - - for (var ii = 0; ii < node.childNodes.length; ii++) { - var groupNode = node.childNodes[ii]; - - if (groupNode.nodeName === "list") { - var key = pluginName + "/" + findAttributeValue(groupNode, "key"); - var defaultValue = findAttributeValue(groupNode, "default"); - var title = findAttributeValue(groupNode, "title"); - var list = []; - - for (var iii = 0; iii < groupNode.childNodes.length; iii++) { - var listNode = groupNode.childNodes[iii]; - - if (listNode.nodeName === "element") { - var name = findAttributeValue(listNode, "name"); - var value = findAttributeValue(listNode, "value"); - var element = { "name": name, "value": value }; - list.push(element); - } - } - - addComboBox(key, defaultValue, title, list); - } - else if (groupNode.nodeName === "boolean") { - var key = pluginName + "/" + findAttributeValue(groupNode, "key"); - var defaultValue = findAttributeValue(groupNode, "default"); - var title = findAttributeValue(groupNode, "title"); - addCheckBox(key, defaultValue, title); - } - else if (groupNode.nodeName === "integer") { - var key = pluginName + "/" + findAttributeValue(groupNode, "key"); - var defaultValue = findAttributeValue(groupNode, "default"); - var title = findAttributeValue(groupNode, "title"); - var min = parseInt(findAttributeValue(groupNode, "min")); - var max = parseInt(findAttributeValue(groupNode, "max")); - var step = parseInt(findAttributeValue(groupNode, "step")); - addSlider(key, defaultValue, title, min, max, step); - } - else if (groupNode.nodeName === "text") { - var key = pluginName + "/" + findAttributeValue(groupNode, "key"); - var defaultValue = findAttributeValue(groupNode, "default"); - var title = findAttributeValue(groupNode, "title"); - addTextField(key, defaultValue, title); - } - } - } - - if (node.nodeName === "list") { - var key = pluginName + "/" + findAttributeValue(node, "key"); - var defaultValue = findAttributeValue(node, "default"); - var title = findAttributeValue(node, "title"); - var list = []; - - for (var iii = 0; iii < node.childNodes.length; iii++) { - var listNode = node.childNodes[iii]; - - if (listNode.nodeName === "element") { - var name = findAttributeValue(listNode, "name"); - var value = findAttributeValue(listNode, "value"); - var element = { "name": name, "value": value }; - list.push(element); - } - } - - addComboBox(key, defaultValue, title, list); - } - else if (node.nodeName === "boolean") { - var key = pluginName + "/" + findAttributeValue(node, "key"); - var defaultValue = findAttributeValue(node, "default"); - var title = findAttributeValue(node, "title"); - addCheckBox(key, defaultValue, title); - } - else if (node.nodeName === "integer") { - var key = pluginName + "/" + findAttributeValue(node, "key"); - var defaultValue = findAttributeValue(node, "default"); - var title = findAttributeValue(node, "title"); - var min = parseInt(findAttributeValue(node, "min")); - var max = parseInt(findAttributeValue(node, "max")); - var step = parseInt(findAttributeValue(node, "step")); - addSlider(key, defaultValue, title, min, max, step); - } - else if (node.nodeName === "text") { - var key = pluginName + "/" + findAttributeValue(node, "key"); - var defaultValue = findAttributeValue(node, "default"); - var title = findAttributeValue(node, "title"); - addTextField(key, defaultValue, title); - } - } - } - } - - request.open("GET", fileName); - request.send(); - } - - function findAttributeValue(node, name) { - for (var i = 0; i < node.attributes.length; i++) { - var att = node.attributes[i]; - - if (att.name === name) { - return att.value; - } - } - - return ""; - } - - function findEmptyLoader() { - for (var i = 1; i < column.children.length; i++) { - var child = column.children[i]; - - if ((child.hasOwnProperty("item")) && (!child.item)) { - return child; - } - } - } - - function clearLoaders() { - for (var i = 1; i < column.children.length; i++) { - var child = column.children[i]; - - if (child.hasOwnProperty("sourceComponent")) { - child.sourceComponent = undefined; - } - } - } - - function addGroup(title) { - var loader = findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("plugins/PluginSettingsGroupLabel.qml"); - loader.item.text = title; - } - } - - function addComboBox(key, defaultValue, title, list) { - var loader = findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("plugins/PluginSettingsComboBox.qml"); - loader.item.title = title; - loader.item.setList(key, defaultValue, list); - } - } - - function addCheckBox(key, defaultValue, title) { - var loader = findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("plugins/PluginSettingsCheckBox.qml"); - loader.item.text = title; - loader.item.setKey(key, defaultValue); - } - } - - function addSlider(key, defaultValue, title, min, max, step) { - var loader = findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("plugins/PluginSettingsSlider.qml"); - loader.item.title = title; - loader.item.minimumValue = min; - loader.item.maximumValue = max; - loader.item.stepSize = step; - loader.item.setKey(key, defaultValue); - } - } - - function addTextField(key, defaultValue, title) { - var loader = findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("plugins/PluginSettingsTextField.qml"); - loader.item.title = title; - loader.item.setKey(key, defaultValue); - } - } - - TableView { - id: view - - width: 150 - alternatingRowColors: false - headerVisible: false - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - model: PluginSettingsModel { - id: pluginModel - } - onCurrentRowChanged: loadSettings(pluginModel.data(currentRow, "name"), - pluginModel.data(currentRow, "value")) - - TableViewColumn { - role: "name" - title: qsTr("Plugin") - } - } - - ScrollView { - anchors { - left: view.right - leftMargin: 10 - right: parent.right - top: parent.top - bottom: parent.bottom - } - - Flickable { - anchors.fill: parent - contentHeight: column.height - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: 10 - } - spacing: 10 - - Repeater { - model: 20 - - Loader { - width: column.width - } - } - } - } - } - - Component.onCompleted: { - if (pluginModel.count > 0) { - view.selection.select(0); - view.currentRow = 0; - } - } - } - } - } - - buttons: Button { - text: qsTr("&Ok") - iconName: "dialog-ok" - isDefault: true - onClicked: root.accept(); - } - - Loader { - id: loader - } - - onVisibleChanged: tabs.currentIndex = 0 -} diff --git a/app/src/desktop-qml/qml/TrackDelegate.qml b/app/src/desktop-qml/qml/TrackDelegate.qml deleted file mode 100644 index 3152676..0000000 --- a/app/src/desktop-qml/qml/TrackDelegate.qml +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 - -ItemDelegate { - id: root - - Label { - anchors { - left: loader.item ? loader.right : parent.left - right: parent.right - margins: 2 - } - verticalAlignment: Text.AlignVCenter - color: styleData.textColor - elide: styleData.elideMode - text: styleData.value - } - - Loader { - id: loader - - width: height - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - margins: 2 - } - sourceComponent: styleData.column == 0 ? thumbnail : undefined - } - - Component { - id: thumbnail - - Image { - anchors.fill: parent - source: view.model.data(styleData.row, "thumbnailUrl") - smooth: true - } - } -} diff --git a/app/src/desktop-qml/qml/TrackPopup.qml b/app/src/desktop-qml/qml/TrackPopup.qml deleted file mode 100644 index bc3fb3f..0000000 --- a/app/src/desktop-qml/qml/TrackPopup.qml +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Rectangle { - id: root - - property string trackId - property alias thumbnail: thumbnail.source - property alias title: titleLabel.text - property alias artist: artistLabel.text - property alias genre: genreLabel.text - property alias date: dateLabel.text - property alias format: formatLabel.text - property alias duration: durationLabel.text - - z: 1000 - width: 400 - height: Math.max(thumbnail.height, grid.height) + 20 - color: palette.window - - MouseArea { - anchors.fill: parent - } - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - } - - GridLayout { - id: grid - - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - id: titleLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Artist") - } - - Label { - id: artistLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Genre") - } - - Label { - id: genreLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Date") - } - - Label { - id: dateLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Format") - } - - Label { - id: formatLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Length") - } - - Label { - id: durationLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Loader { - id: loader - - Layout.columnSpan: 2 - Layout.fillWidth: true - sourceComponent: (player.currentTrack) && (player.currentTrack.id == root.trackId) ? progressBar - : undefined - } - } - - Component { - id: progressBar - - ProgressBar { - Layout.columnSpan: 2 - Layout.fillWidth: true - maximumValue: player.duration - value: player.position - - Label { - anchors.centerIn: parent - text: player.positionString - } - } - } -} diff --git a/app/src/desktop-qml/qml/TransfersPage.qml b/app/src/desktop-qml/qml/TransfersPage.qml deleted file mode 100644 index cd52d83..0000000 --- a/app/src/desktop-qml/qml/TransfersPage.qml +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQml 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 - -Page { - id: root - - title: qsTr("Transfers") + " (" + Transfers.count + ")" - tools: ToolBarLayout { - - Label { - id: label - - Layout.fillWidth: true - text: root.title - } - - Label { - text: qsTr("Maximum concurrent transfers") + ":" - } - - ComboBox { - id: concurrentSelector - - model: ConcurrentTransfersModel { - id: concurrentModel - } - textRole: "name" - currentIndex: concurrentModel.match("value", Settings.maximumConcurrentTransfers) - onActivated: Settings.maximumConcurrentTransfers = concurrentModel.data(index, "value") - } - - ToolButton { - id: startButton - - text: qsTr("Start") - tooltip: qsTr("Start all transfers") - iconName: "media-playback-start" - enabled: Transfers.count > 0 - onClicked: Transfers.start() - } - - ToolButton { - id: pauseButton - - text: qsTr("Pause") - tooltip: qsTr("Pause all transfers") - iconName: "media-playback-pause" - enabled: Transfers.count > 0 - onClicked: Transfers.pause() - } - } - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: TransferModel { - id: transferModel - } - itemDelegate: LabelDelegate { - text: styleData.column == 3 ? Utils.formatBytes(transferModel.data(styleData.row, "bytesTransferred")) - + " " + qsTr("of") + " " - + Utils.formatBytes(transferModel.data(styleData.row, "size")) - + " (" + styleData.value + "%)" - : styleData.value - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onRightClicked: contextMenu.popup() - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "category" - title: qsTr("Category") - } - - TableViewColumn { - role: "priorityString" - title: qsTr("Priority") - } - - TableViewColumn { - role: "progress" - title: qsTr("Progress") - } - - TableViewColumn { - role: "statusString" - title: qsTr("Status") - } - } - - Menu { - id: contextMenu - - MenuItem { - text: qsTr("Start") - iconName: "media-playback-start" - enabled: transferModel.data(view.currentRow, "status") <= Transfer.Queued - onTriggered: Transfers.get(view.currentRow).queue() - } - - MenuItem { - text: qsTr("Pause") - iconName: "media-playback-pause" - enabled: transferModel.data(view.currentRow, "status") >= Transfer.Downloading - onTriggered: Transfers.get(view.currentRow).pause() - } - - Menu { - id: categoryMenu - - title: qsTr("Category") - visible: categoryModel.count > 0 - - ExclusiveGroup { - id: categoryGroup - } - - Instantiator { - model: CategoryNameModel { - id: categoryModel - } - delegate: MenuItem { - text: name - checkable: true - exclusiveGroup: categoryGroup - checked: transferModel.data(view.currentRow, "category") == name - onTriggered: transferModel.setData(view.currentRow, name, "category") - } - onObjectAdded: categoryMenu.insertItem(index, object) - onObjectRemoved: categoryMenu.removeItem(object) - } - } - - Menu { - title: qsTr("Priority") - - ExclusiveGroup { - id: priorityGroup - } - - MenuItem { - text: qsTr("High") - checkable: true - exclusiveGroup: priorityGroup - checked: transferModel.data(view.currentRow, "priority") == Transfer.HighPriority - onTriggered: transferModel.setData(view.currentRow, Transfer.HighPriority, "priority") - } - - MenuItem { - text: qsTr("Normal") - checkable: true - exclusiveGroup: priorityGroup - checked: transferModel.data(view.currentRow, "priority") == Transfer.NormalPriority - onTriggered: transferModel.setData(view.currentRow, Transfer.NormalPriority, "priority") - } - - MenuItem { - text: qsTr("Low") - checkable: true - exclusiveGroup: priorityGroup - checked: transferModel.data(view.currentRow, "priority") == Transfer.LowPriority - onTriggered: transferModel.setData(view.currentRow, Transfer.LowPriority, "priority") - } - } - - MenuItem { - iconName: "edit-delete" - text: qsTr("Remove") - onTriggered: Transfers.get(view.currentRow).cancel() - } - } -} diff --git a/app/src/desktop-qml/qml/UrlDialog.qml b/app/src/desktop-qml/qml/UrlDialog.qml deleted file mode 100644 index 767ce14..0000000 --- a/app/src/desktop-qml/qml/UrlDialog.qml +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 - -MyDialog { - id: root - - property alias url: urlField.text - - minimumWidth: 400 - minimumHeight: urlField.height + 60 - title: qsTr("Open URL") - content: TextField { - id: urlField - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - placeholderText: qsTr("URL") - validator: RegExpValidator { - regExp: /^.+/ - } - onAccepted: root.accept() - } - buttons: [ - Button { - text: qsTr("&Cancel") - iconName: "dialog-cancel" - onClicked: root.reject() - }, - - Button { - text: qsTr("&Ok") - iconName: "dialog-ok" - isDefault: true - enabled: urlField.acceptableInput - onClicked: root.accept() - } - ] - - onOpened: { - urlField.text = ""; - urlField.forceActiveFocus(); - } -} diff --git a/app/src/desktop-qml/qml/main.qml b/app/src/desktop-qml/qml/main.qml deleted file mode 100644 index efa015f..0000000 --- a/app/src/desktop-qml/qml/main.qml +++ /dev/null @@ -1,597 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQml 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 - -ApplicationWindow { - id: window - - property StackView pageStack: null - - function search(service, query, type, order) { - raise(); - setService(service); - return pageStack.currentItem.search(query, type, order); - } - - function setService(service) { - if (!service) { - return; - } - - tabView.currentIndex = 2; - - if ((service != Settings.currentService) || (pageStack.depth == 0)) { - Settings.currentService = service; - pageStack.clear(); - - if (service == Resources.SOUNDCLOUD) { - pageStack.push({item: Qt.resolvedUrl("soundcloud/SoundCloudPage.qml"), immediate: true}); - } - else { - pageStack.push({item: Qt.resolvedUrl("plugins/PluginPage.qml"), properties: {service: service}, immediate: true}); - } - } - } - - function showResource(resource) { - if (!resource.service) { - return false; - } - - raise(); - setService(resource.service); - return pageStack.currentItem.showResource(resource); - } - - function showResourceFromUrl(url) { - return showResource(Resources.getResourceFromUrl(url)); - } - - minimumWidth: 800 - minimumHeight: 500 - visible: true - title: (player.stopped ? "" : (player.currentIndex + 1) + ". " + player.currentTrack.artist + " - " - + player.currentTrack.title + " (" + player.durationString + ") - ") + "MusiKloud2" - menuBar: MenuBar { - - Menu { - id: fileMenu - - title: qsTr("&File") - - MenuItem { - action: Action { - id: urlAction - - text: qsTr("Open &URL") - iconName: "applications-internet" - shortcut: "Ctrl+U" - onTriggered: { - loader.sourceComponent = urlDialog; - loader.item.open(); - } - } - } - - MenuItem { - action: Action { - id: openFilesAction - - text: qsTr("&Open Files") - iconName: "document-open" - shortcut: "Ctrl+O" - onTriggered: { - loader.sourceComponent = fileDialog; - loader.item.mode = "play"; - loader.item.open(); - } - } - } - - MenuItem { - action: Action { - id: addFilesAction - - text: qsTr("&Add Files") - iconName: "list-add" - shortcut: "Ctrl+A" - onTriggered: { - loader.sourceComponent = fileDialog; - loader.item.mode = "add"; - loader.item.open(); - } - } - } - - MenuItem { - action: Action { - id: quitAction - - text: qsTr("Quit") - iconName: "application-exit" - shortcut: "Ctrl+Q" - onTriggered: Qt.quit() - } - } - } - - Menu { - id: playbackMenu - - title: qsTr("&Playback") - - MenuItem { - action: Action { - id: playAction - - text: qsTr("Play") - iconName: "media-playback-start" - shortcut: "Ctrl+Return" - enabled: player.queueCount > 0 - onTriggered: player.play() - } - } - - MenuItem { - action: Action { - id: pauseAction - - text: qsTr("Pause") - iconName: "media-playback-pause" - shortcut: "Ctrl+," - enabled: player.queueCount > 0 - onTriggered: player.pause() - } - } - - MenuItem { - action: Action { - id: stopAction - - text: qsTr("&Stop") - iconName: "media-playback-stop" - shortcut: "Ctrl+." - enabled: player.queueCount > 0 - onTriggered: player.stop() - } - } - - MenuItem { - action: Action { - id: previousAction - - text: qsTr("&Previous") - iconName: "media-skip-backward" - shortcut: "Alt+Up" - enabled: player.queueCount > 0 - onTriggered: player.previous() - } - } - - MenuItem { - action: Action { - id: nextAction - - text: qsTr("&Next") - iconName: "media-skip-forward" - shortcut: "Alt+Down" - enabled: player.queueCount > 0 - onTriggered: player.next() - } - } - - MenuSeparator {} - - MenuItem { - action: Action { - id: repeatAction - - text: qsTr("&Repeat") - shortcut: "Ctrl+R" - checkable: true - onTriggered: player.repeat = checked - } - } - - MenuItem { - action: Action { - id: shuffleAction - - text: qsTr("&Shuffle") - shortcut: "Ctrl+S" - checkable: true - onTriggered: player.shuffle = checked - } - } - - MenuItem { - action: Action { - id: stopAfterCurrentAction - - text: qsTr("Stop after current track") - shortcut: "Ctrl+M" - checkable: true - onTriggered: player.stopAfterCurrentTrack = checked - } - } - - MenuSeparator {} - - MenuItem { - action: Action { - id: clearAction - - text: qsTr("&Clear playback queue") - iconName: "edit-clear" - shortcut: "Ctrl+Backspace" - onTriggered: player.clearQueue() - } - } - } - - Menu { - id: viewMenu - - title: qsTr("&View") - - ExclusiveGroup { - id: viewGroup - } - - MenuItem { - action: Action { - id: queueAction - - text: qsTr("Playback queue") - shortcut: "Ctrl+1" - checkable: true - checked: true - exclusiveGroup: viewGroup - onTriggered: tabView.currentIndex = 0 - } - } - - MenuItem { - action: Action { - id: transfersAction - - text: qsTr("Transfers") - shortcut: "Ctrl+2" - checkable: true - exclusiveGroup: viewGroup - onTriggered: tabView.currentIndex = 1 - } - } - - MenuSeparator {} - - Instantiator { - model: ServiceModel {} - delegate: MenuItem { - action: Action { - text: name - shortcut: "Ctrl+" + (index + 3) - checkable: true - exclusiveGroup: viewGroup - onTriggered: window.setService(value) - } - } - onObjectAdded: viewMenu.insertItem(index, object) - onObjectRemoved: viewMenu.removeItem(object) - } - } - - Menu { - id: editMenu - - title: qsTr("&Edit") - - MenuItem { - text: qsTr("&Preferences") - iconName: "preferences-desktop" - shortcut: "Ctrl+P" - onTriggered: { - loader.sourceComponent = settingsDialog; - loader.item.open(); - } - } - } - - Menu { - id: helpMenu - - title: qsTr("&Help") - - MenuItem { - text: qsTr("&About") - iconName: "help-about" - onTriggered: { - loader.sourceComponent = aboutDialog; - loader.item.open(); - } - } - } - } - - toolBar: ToolBar { - - RowLayout { - anchors.fill: parent - - ToolButton { - action: openFilesAction - } - - ToolButton { - action: addFilesAction - } - - ToolButton { - action: player.playing ? pauseAction : playAction - } - - ToolButton { - action: stopAction - } - - ToolButton { - action: previousAction - } - - ToolButton { - action: nextAction - } - - Slider { - Layout.fillWidth: true - maximumValue: player.duration - value: player.position - updateValueWhileDragging: false - enabled: player.seekable - opacity: player.queueCount > 0 ? 1 : 0 - onPressedChanged: if (!pressed) player.position = value; - } - - Label { - font.bold: true - text: player.positionString + " / " + player.durationString - opacity: player.queueCount > 0 ? 1 : 0 - } - - ToolButton { - action: repeatAction - iconName: "media-playlist-repeat" - } - - ToolButton { - action: shuffleAction - iconName: "media-playlist-shuffle" - } - } - } - - statusBar: StatusBar { - id: statusBar - - function showMessage(message) { - statusLabel.text = message; - statusTimer.restart(); - } - - Timer { - id: statusTimer - - interval: 3000 - onTriggered: statusLabel.text = "" - } - - RowLayout { - - Label { - id: statusLabel - - Layout.fillWidth: true - } - } - } - - TabView { - id: tabView - - anchors { - left: parent.left - right: parent.right - top: parent.top - bottom: nowPlaying.top - } - frameVisible: false - tabsVisible: false - - Tab { - PlaybackQueuePage { - id: queuePage - } - } - - Tab { - TransfersPage { - id: transfersPage - } - } - - Tab { - StackView { - id: stack - - Component.onCompleted: window.pageStack = stack - } - } - } - - Rectangle { - id: nowPlaying - - height: 80 - anchors { - left: parent.left - leftMargin: 5 - right: parent.right - rightMargin: 5 - bottom: parent.bottom - } - color: "#0b0b0b" - - Image { - id: thumbnail - - width: height - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - margins: 5 - } - source: player.currentTrack ? player.currentTrack.thumbnailUrl : "" - } - - ColumnLayout { - id: column - - anchors { - left: thumbnail.right - right: parent.right - top: parent.top - bottom: parent.bottom - margins: 5 - } - - Label { - id: titleLabel - - Layout.fillWidth: true - font.pixelSize: 24 - color: palette.light - elide: Text.ElideRight - text: player.currentTrack ? player.currentTrack.title : "" - } - - Label { - id: artistLabel - - Layout.fillWidth: true - color: palette.light - elide: Text.ElideRight - text: player.currentTrack ? player.currentTrack.artist : "" - } - - Label { - id: genreLabel - - Layout.fillWidth: true - color: palette.light - elide: Text.ElideRight - text: player.currentTrack ? player.currentTrack.genre : "" - } - } - } - - MessageBox { - id: messageBox - } - - SystemPalette { - id: palette - } - - AudioPlayer { - id: player - - onStatusChanged: if (status == AudioPlayer.Failed) messageBox.showError(errorString); - } - - Loader { - id: loader - } - - Component { - id: urlDialog - - UrlDialog { - onAccepted: showResourceFromUrl(url) - } - } - - Component { - id: fileDialog - - FileDialog { - property string mode: "play" - - title: mode == "add" ? qsTr("Add audio files") : qsTr("Play audio files") - selectExisting: true - selectMultiple: true - onAccepted: mode == "add" ? player.addUrls(fileUrls) : player.playUrls(fileUrls) - } - } - - Component { - id: settingsDialog - - SettingsDialog {} - } - - Component { - id: aboutDialog - - AboutDialog {} - } - - Connections { - id: clipboardConnections - - target: Clipboard - onTextChanged: showResourceFromUrl(text) - } - - Connections { - id: dbusConnections - - target: DBus - onResourceRequested: showResource(resource) - } - - Connections { - id: transferConnections - - target: null - onTransferAdded: statusBar.showMessage("'" + transfer.title + "' " + qsTr("added to transfers")) - } - - Component.onCompleted: { - Transfers.restoreTransfers(); - transferConnections.target = Transfers; - - if (DBus.requestedResource.service) { - showResource(DBus.requestedResource); - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginArtistPage.qml b/app/src/desktop-qml/qml/plugins/PluginArtistPage.qml deleted file mode 100644 index 9dda1ad..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginArtistPage.qml +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - function loadArtist(artistOrId) { - if (artistOrId.hasOwnProperty("id")) { - artist.loadArtist(artistOrId); - } - else { - artist.loadArtist(Settings.currentService, artistOrId); - } - } - - title: artist.name ? artist.name : qsTr("Artist") - - PluginArtist { - id: artist - - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - - TabView { - id: tabView - - anchors.fill: parent - frameVisible: false - enabled: artist.id != "" - - Tab { - id: profileTab - - title: qsTr("Profile") - - Page { - id: profilePage - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - source: artist.thumbnailUrl - } - - GridLayout { - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Name") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.name - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Tracks") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.trackCount - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Sets") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.playlistCount - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Followers") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.followersCount - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - ScrollView { - Layout.fillWidth: true - - Flickable { - anchors.fill: parent - contentHeight: descriptionLabel.height - - Label { - id: descriptionLabel - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - wrapMode: Text.Wrap - text: Utils.toRichText(artist.description) - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != artist.id) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), immediate: true}) - .loadArtist(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("PluginPlaylistPage.qml"), immediate: true}) - .loadPlaylist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("PluginTrackPage.qml"), immediate: true}) - .loadTrack(resource.id); - } - } - } - } - } - } - } - } - - Tab { - id: tracksTab - - title: qsTr("Tracks") - - PluginTracksPage { - id: tracksPage - - Component.onCompleted: model.list(artist.id) - } - } - - Tab { - id: playlistsTab - - title: qsTr("Sets") - enabled: Plugins.resourceTypeIsSupported(artist.service, Resources.PLAYLIST) - - PluginPlaylistsPage { - id: playlistsPage - - Component.onCompleted: model.list(artist.id) - } - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginArtistsPage.qml b/app/src/desktop-qml/qml/plugins/PluginArtistsPage.qml deleted file mode 100644 index 372bcda..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginArtistsPage.qml +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - property alias model: artistModel - property alias view: view - - title: qsTr("Artists") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: PluginArtistModel { - id: artistModel - - service: Settings.currentService - } - itemDelegate: ArtistDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), immediate: true}) - .loadArtist(artistModel.get(styleData.row)) - } - - TableViewColumn { - role: "name" - title: qsTr("Name") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), immediate: true}) - .loadArtist(artistModel.get(currentRow)); - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - ArtistPopup { - z: 1000 - artistId: artistModel.data(popupMouseArea.row, "id") - thumbnail: artistModel.data(popupMouseArea.row, "thumbnailUrl") - name: artistModel.data(popupMouseArea.row, "name") - description: artistModel.data(popupMouseArea.row, "description") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginCategoriesPage.qml b/app/src/desktop-qml/qml/plugins/PluginCategoriesPage.qml deleted file mode 100644 index 02410ed..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginCategoriesPage.qml +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - property alias model: categoryModel - property alias view: view - - title: qsTr("Categories") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: PluginCategoryModel { - id: categoryModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: LabelDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: pageStack.push({item: Qt.resolvedUrl("PluginTracksPage.qml"), - properties: {title: categoryModel.data(styleData.row, "name")}, immediate: true}) - .model.list(categoryModel.data(styleData.row, "value")) - } - - TableViewColumn { - role: "name" - title: qsTr("Name") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("PluginTracksPage.qml"), - properties: {title: categoryModel.data(currentRow, "name")}, immediate: true}) - .model.list(categoryModel.data(currentRow, "value")); - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginDownloadDialog.qml b/app/src/desktop-qml/qml/plugins/PluginDownloadDialog.qml deleted file mode 100644 index 96acdab..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginDownloadDialog.qml +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -MyDialog { - id: root - - property string resourceId - property string resourceTitle - - minimumWidth: grid.width + 20 - minimumHeight: grid.height + 60 - title: qsTr("Download track") - content: GridLayout { - id: grid - - columns: 2 - - Label { - text: qsTr("Audio format") + ":" - } - - ComboBox { - id: streamSelector - - Layout.minimumWidth: 200 - model: PluginStreamModel { - id: streamModel - - service: Settings.currentService - onStatusChanged: { - switch (status) { - case ResourcesRequest.Ready: - if (count > 0) { - streamSelector.currentIndex = Math.max(0, match("name", - Settings.defaultDownloadFormat(service))); - } - else { - messageBox.showError(qsTr("No streams found")); - } - - break; - case ResourcesRequest.Failed: { - messageBox.showError(errorString); - break; - } - default: - break; - } - } - } - textRole: "name" - onActivated: Settings.setDefaultDownloadFormat(streamModel.service, streamModel.data(index, "name")) - } - - Label { - text: qsTr("Category") + ":" - } - - ComboBox { - id: categorySelector - - Layout.minimumWidth: 200 - model: CategoryNameModel { - id: categoryModel - } - textRole: "name" - currentIndex: categoryModel.match("value", Settings.defaultCategory) - onActivated: Settings.defaultCategory = categoryModel.data(index, "value") - } - } - buttons: [ - Button { - text: qsTr("&Cancel") - iconName: "dialog-cancel" - onClicked: root.reject() - }, - - Button { - text: qsTr("&Ok") - iconName: "dialog-ok" - isDefault: true - enabled: (streamModel.status == ResourcesRequest.Ready) && (streamModel.count > 0) - onClicked: root.accept() - } - ] - - onOpened: streamModel.list(resourceId) - onRejected: streamModel.cancel() - onAccepted: Transfers.addDownloadTransfer(streamModel.service, resourceId, - streamModel.data(streamSelector.currentIndex, "value").id, "", - resourceTitle, categoryModel.data(categorySelector.currentIndex, "value")) -} diff --git a/app/src/desktop-qml/qml/plugins/PluginPage.qml b/app/src/desktop-qml/qml/plugins/PluginPage.qml deleted file mode 100644 index fcf4d1e..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginPage.qml +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - function search(query, type, order) { - var url; - - if (type == Resources.PLAYLIST) { - url = Qt.resolvedUrl("PluginPlaylistsPage.qml"); - } - else if (type == Resources.ARTIST) { - url = Qt.resolvedUrl("PluginArtistsPage.qml"); - } - else { - url = Qt.resolvedUrl("PluginTracksPage.qml"); - } - - view.selection.clear(); - view.selection.select(0); - view.currentRow = 0; - - if (pageStack.depth > 0) { - pageStack.clear(); - } - - pageStack.push({item: url, properties: {title: qsTr("Search") + " ('" + query + "')"}, immediate: true}) - .model.search(query, order); - return true; - } - - function showResource(resource) { - if (pageStack.depth > 0) { - pageStack.clear(); - } - - if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("PluginPlaylistPage.qml"), immediate: true}).loadPlaylist(resource.id); - } - else if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), immediate: true}).loadArtist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("PluginTrackPage.qml"), immediate: true}).loadTrack(resource.id); - } - - return true; - } - - tools: ToolBarLayout { - - ToolButton { - id: backButton - - action: Action { - id: backAction - - text: qsTr("Go back") - iconName: "go-previous" - enabled: pageStack.depth > 1 - onTriggered: pageStack.pop({immediate: true}) - } - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: (pageStack.currentItem) && (pageStack.currentItem.title) ? pageStack.currentItem.title - : Settings.currentService - } - - Label { - text: qsTr("Search for") - enabled: searchTypeModel.count > 0 - } - - ComboBox { - id: searchTypeSelector - - Layout.minimumWidth: 200 - model: PluginSearchTypeModel { - id: searchTypeModel - - service: Settings.currentService - } - textRole: "name" - currentIndex: searchTypeModel.match("name", Settings.defaultSearchType(Resources.SOUNDCLOUD)) - enabled: searchTypeModel.count > 0 - onActivated: Settings.setDefaultSearchType(Resources.SOUNDCLOUD, searchTypeModel.data(index, "name")) - } - - TextField { - id: searchField - - Layout.minimumWidth: 300 - placeholderText: qsTr("Search") - validator: RegExpValidator { - regExp: /^.+/ - } - enabled: searchTypeModel.count > 0 - onAccepted: { - root.search(text, searchTypeModel.data(searchTypeSelector.currentIndex, "value").type, - searchTypeModel.data(searchTypeSelector.currentIndex, "value").order); - text = ""; - } - } - } - - TableView { - id: view - - width: 150 - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - alternatingRowColors: false - headerVisible: false - model: PluginNavModel { - id: navModel - - service: Settings.currentService - } - onCurrentRowChanged: { - if (pageStack.depth > 0) { - pageStack.clear(); - } - - switch (currentRow) { - case 0: - if (searchTypeModel.count > 0) { - return; - } - - break; - default: - break; - } - - var value = navModel.data(currentRow, "value"); - - if (value.type == Resources.CATEGORY) { - pageStack.push({item: Qt.resolvedUrl("PluginCategoriesPage.qml"), properties: {title: value.name}, - immediate: true}).model.list(value.id); - } - else if (value.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("PluginPlaylistsPage.qml"), properties: {title: value.name}, - immediate: true}).model.list(value.id); - } - else if (value.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("PluginArtistsPage.qml"), properties: {title: value.name}, - immediate: true}).model.list(value.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("PluginTracksPage.qml"), properties: {title: value.name}, - immediate: true}).model.list(value.id); - } - } - - TableViewColumn { - role: "name" - title: qsTr("View") - } - } - - StackView { - id: pageStack - - anchors { - left: view.right - right: parent.right - top: parent.top - bottom: parent.bottom - } - } - - Component.onCompleted: { - if (navModel.count > 0) { - view.selection.select(0); - view.currentRow = 0; - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginPlaylistPage.qml b/app/src/desktop-qml/qml/plugins/PluginPlaylistPage.qml deleted file mode 100644 index 2372c62..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginPlaylistPage.qml +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - function loadPlaylist(playlistOrId) { - if (playlistOrId.hasOwnProperty("id")) { - playlist.loadPlaylist(playlistOrId); - } - else { - playlist.loadPlaylist(Settings.currentService, playlistOrId); - } - } - - title: playlist.title ? playlist.title : qsTr("Playlist") - - PluginPlaylist { - id: playlist - - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - - TabView { - id: tabView - - anchors.fill: parent - frameVisible: false - enabled: playlist.id != "" - - Tab { - id: infoTab - - title: qsTr("Info") - - Page { - id: infoPage - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - source: playlist.thumbnailUrl - } - - ColumnLayout { - anchors { - left: thumbnail.left - right: thumbnail.right - top: thumbnail.bottom - topMargin: 10 - } - - Button { - Layout.fillWidth: true - text: qsTr("Queue") - enabled: (tracksTab.item) && (tracksTab.item.model.count > 0) - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.item.model.count; i++) { - tracks.push(tracksTab.item.model.get(i)); - } - - player.addTracks(tracks); - } - } - - Button { - Layout.fillWidth: true - text: qsTr("Play") - enabled: (tracksTab.item) && (tracksTab.item.model.count > 0) - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.item.model.count; i++) { - tracks.push(tracksTab.item.model.get(i)); - } - - player.playTracks(tracks); - } - } - } - - GridLayout { - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.title - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Artist") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.artist - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Genre") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.genre - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Date") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.date - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Tracks") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.trackCount - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Length") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.durationString - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - ScrollView { - Layout.fillWidth: true - - Flickable { - anchors.fill: parent - contentHeight: descriptionLabel.height - - Label { - id: descriptionLabel - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - wrapMode: Text.Wrap - text: Utils.toRichText(playlist.description) - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != playlist.service) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), immediate: true}) - .loadArtist(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("PluginPlaylistPage.qml"), immediate: true}) - .loadPlaylist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("PluginTrackPage.qml"), immediate: true}) - .loadTrack(resource.id); - } - } - } - } - } - } - } - } - - Tab { - id: tracksTab - - title: qsTr("Tracks") - - PluginTracksPage { - id: tracksPage - - Component.onCompleted: model.list(playlist.id) - } - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginPlaylistsPage.qml b/app/src/desktop-qml/qml/plugins/PluginPlaylistsPage.qml deleted file mode 100644 index 42f888c..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginPlaylistsPage.qml +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - property alias model: playlistModel - property alias view: view - - title: qsTr("Playlists") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: PluginPlaylistModel { - id: playlistModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: PlaylistDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: pageStack.push({item: Qt.resolvedUrl("PluginPlaylistPage.qml"), immediate: true}) - .loadPlaylist(playlistModel.get(styleData.row)) - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "artist" - title: qsTr("Artist") - } - - TableViewColumn { - role: "date" - title: qsTr("Date") - } - - TableViewColumn { - role: "trackCount" - title: qsTr("Tracks") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("PluginPlaylistPage.qml"), immediate: true}) - .loadPlaylist(playlistModel.get(currentRow)); - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - PlaylistPopup { - z: 1000 - playlistId: playlistModel.data(popupMouseArea.row, "id") - thumbnail: playlistModel.data(popupMouseArea.row, "thumbnailUrl") - title: playlistModel.data(popupMouseArea.row, "title") - artist: playlistModel.data(popupMouseArea.row, "artist") - genre: playlistModel.data(popupMouseArea.row, "genre") - date: playlistModel.data(popupMouseArea.row, "date") - trackCount: playlistModel.data(popupMouseArea.row, "trackCount") - duration: playlistModel.data(popupMouseArea.row, "durationString") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginSettingsComboBox.qml b/app/src/desktop-qml/qml/plugins/PluginSettingsComboBox.qml deleted file mode 100644 index 8003c46..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginSettingsComboBox.qml +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import cuteTube 2.0 - -RowLayout { - id: root - - property alias title: titleLabel.text - property string key - - function setList(key, defaultValue, list) { - root.key = key; - - var value = Settings.value(key); - - if (value === undefined) { - value = defaultValue; - Settings.setValue(key, value); - } - - for (var i = 0; i < list.length; i++) { - selectionModel.append(list[i].name, list[i].value); - - if (list[i].value === value) { - comboBox.currentIndex = i; - } - } - } - - Label { - id: titleLabel - } - - ComboBox { - id: comboBox - - Layout.fillWidth: true - model: SelectionModel { - id: selectionModel - } - textRole: "name" - onActivated: Settings.setValue(root.key, selectionModel.data(index, "value")) - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginSettingsSlider.qml b/app/src/desktop-qml/qml/plugins/PluginSettingsSlider.qml deleted file mode 100644 index 4eb67dc..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginSettingsSlider.qml +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 - -RowLayout { - id: root - - property alias title: titleLabel.text - property alias minimumValue: slider.minimumValue - property alias maximumValue: slider.maximumValue - property alias stepSize: slider.stepSize - property string key - - function setKey(key, defaultValue) { - root.key = key; - var value = Settings.value(key); - - if (value === undefined) { - slider.value = parseFloat(defaultValue); - } - else { - slider.value = parseFloat(value); - } - } - - Label { - id: titleLabel - } - - Slider { - id: slider - - Layout.fillWidth: true - orientation: Qt.Horizontal - valueIndicatorVisible: true - onValueChanged: Settings.setValue(root.key, slider.value) - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginTrackPage.qml b/app/src/desktop-qml/qml/plugins/PluginTrackPage.qml deleted file mode 100644 index defba1c..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginTrackPage.qml +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - function loadTrack(trackOrId) { - if (trackOrId.hasOwnProperty("id")) { - track.loadTrack(trackOrId); - } - else { - track.loadTrack(Settings.currentService, trackOrId); - } - } - - title: track.title ? track.title : qsTr("Track") - - PluginTrack { - id: track - - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - - TabView { - id: tabView - - anchors.fill: parent - frameVisible: false - enabled: track.id != "" - - Tab { - id: infoTab - - title: qsTr("Info") - - Page { - id: infoPage - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - source: track.thumbnailUrl - } - - ColumnLayout { - anchors { - left: thumbnail.left - right: thumbnail.right - top: thumbnail.bottom - topMargin: 10 - } - - Button { - Layout.fillWidth: true - text: qsTr("Queue") - iconName: "list-add" - onClicked: player.addTrack(track) - } - - Button { - Layout.fillWidth: true - text: qsTr("Play") - iconName: "media-playback-start" - onClicked: player.playTrack(track) - } - - Button { - Layout.fillWidth: true - text: qsTr("Download") - iconName: "folder-download" - enabled: track.downloadable - onClicked: { - loader.sourceComponent = downloadDialog; - loader.item.open(); - } - } - } - - GridLayout { - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.title - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Artist") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.artist - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Genre") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.genre - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Date") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.date - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Format") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.format - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Length") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.durationString - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - ScrollView { - Layout.fillWidth: true - - Flickable { - anchors.fill: parent - contentHeight: descriptionLabel.height - - Label { - id: descriptionLabel - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - wrapMode: Text.Wrap - text: Utils.toRichText(track.description) - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != track.service) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), immediate: true}) - .loadArtist(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("PluginPlaylistPage.qml"), immediate: true}) - .loadPlaylist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("PluginTrackPage.qml"), immediate: true}) - .loadTrack(resource.id); - } - } - } - } - } - } - } - } - - Tab { - id: commentsTab - - title: qsTr("Comments") - enabled: Plugins.resourceTypeIsSupported(track.service, Resources.COMMENT) - - ScrollView { - anchors.fill: parent - - ListView { - id: commentView - - anchors.fill: parent - model: PluginCommentModel { - id: commentModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - delegate: CommentDelegate { - onThumbnailClicked: pageStack.push({item: Qt.resolvedUrl("PluginArtistPage.qml"), - immediate: true}).loadArtist(commentModel.get(index)) - } - } - - Component.onCompleted: commentModel.list(track.id) - } - } - } - - Loader { - id: loader - } - - Component { - id: downloadDialog - - PluginDownloadDialog { - resourceId: track.id - resourceTitle: track.title - } - } -} diff --git a/app/src/desktop-qml/qml/plugins/PluginTracksPage.qml b/app/src/desktop-qml/qml/plugins/PluginTracksPage.qml deleted file mode 100644 index 09a36b4..0000000 --- a/app/src/desktop-qml/qml/plugins/PluginTracksPage.qml +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - property alias model: trackModel - property alias view: view - - title: qsTr("Tracks") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: PluginTrackModel { - id: trackModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: TrackDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: player.playTrack(trackModel.get(styleData.row)) - onRightClicked: contextMenu.popup() - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "artist" - title: qsTr("Artist") - } - - TableViewColumn { - role: "date" - title: qsTr("Date") - } - - TableViewColumn { - role: "durationString" - title: qsTr("Length") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("PluginTrackPage.qml"), immediate: true}) - .loadTrack(trackModel.get(currentRow)); - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Menu { - id: contextMenu - - MenuItem { - text: qsTr("View info") - iconName: "help-info" - onTriggered: pageStack.push({item: Qt.resolvedUrl("PluginTrackPage.qml"), immediate: true}) - .loadTrack(trackModel.get(view.currentRow)) - } - - MenuItem { - text: qsTr("Queue") - iconName: "list-add" - onTriggered: player.addTrack(trackModel.get(view.currentRow)) - } - - MenuItem { - text: qsTr("Download") - iconName: "folder-download" - enabled: (view.currentRow >= 0) && (trackmodel.data(view.currentRow, "downloadable")) - onTriggered: { - loader.sourceComponent = downloadDialog; - loader.item.open(); - } - } - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - TrackPopup { - z: 1000 - trackId: trackModel.data(popupMouseArea.row, "id") - thumbnail: trackModel.data(popupMouseArea.row, "thumbnailUrl") - title: trackModel.data(popupMouseArea.row, "title") - artist: trackModel.data(popupMouseArea.row, "artist") - genre: trackModel.data(popupMouseArea.row, "genre") - date: trackModel.data(popupMouseArea.row, "date") - format: trackModel.data(popupMouseArea.row, "format") - duration: trackModel.data(popupMouseArea.row, "durationString") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } - - Component { - id: downloadDialog - - PluginDownloadDialog { - resourceId: trackModel.data(view.currentRow, "id") - resourceTitle: trackModel.data(view.currentRow, "title") - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudAccountsPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudAccountsPage.qml deleted file mode 100644 index 9ae7863..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudAccountsPage.qml +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import QtWebKit.experimental 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - title: qsTr("Accounts") - - Loader { - id: viewLoader - - width: 150 - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - sourceComponent: accountModel.count > 0 ? accountsView : undefined - } - - Component { - id: accountsView - - Item { - width: 150 - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - - TableView { - id: view - - anchors.fill: parent - alternatingRowColors: false - headerVisible: false - model: accountModel - itemDelegate: LabelDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: { - if (accountModel.selectAccount(styleData.row)) { - statusBar.showMessage(qsTr("You have selected account") + " '" - + accountModel.data(styleData.row, "username") + "'"); - } - else { - messageBox.showError(accountModel.errorString); - } - } - onRightClicked: contextMenu.popup() - } - - TableViewColumn { - role: "username" - title: qsTr("Accounts") - } - } - - Menu { - id: contextMenu - - MenuItem { - text: qsTr("Remove") - iconName: "edit-delete" - onTriggered: { - var username = accountModel.data(view.currentRow, "username"); - - if (accountModel.removeAccount(view.currentRow)) { - messageBox.showMessage(qsTr("Account") + " '" + username + "' " + qsTr("removed")); - } - else { - messageBox.showError(accountModel.errorString); - } - } - } - } - } - } - - ColumnLayout { - id: column - - anchors { - left: parent.left - leftMargin: viewLoader.item ? viewLoader.item.width : 0 - right: parent.right - top: parent.top - bottom: parent.bottom - } - - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - text: qsTr("Sign in to your SoundCloud account") - } - - Button { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Sign in") - enabled: authLoader.sourceComponent == undefined - onClicked: authLoader.sourceComponent = authView - } - - Loader { - id: authLoader - - Layout.fillWidth: true - Layout.fillHeight: true - } - } - - Component { - id: authView - - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - - WebView { - id: webView - - anchors.fill: parent - onUrlChanged: { - var s = url.toString(); - - if (/code=/i.test(s)) { - authRequest.exchangeCodeForAccessToken(s.split("code=")[1].split("#")[0]); - authLoader.sourceComponent = undefined; - } - } - - Component.onCompleted: url = SoundCloud.authUrl() - } - } - } - - SoundCloudAccountModel { - id: accountModel - } - - QSoundCloud.AuthenticationRequest { - id: authRequest - - clientId: SoundCloud.clientId - clientSecret: SoundCloud.clientSecret - redirectUri: SoundCloud.redirectUri - scopes: SoundCloud.scopes - onFinished: { - if (status == QSoundCloud.AuthenticationRequest.Ready) { - if (result.access_token) { - userRequest.accessToken = result.access_token; - userRequest.refreshToken = (result.refresh_token ? result.refresh_token : ""); - userRequest.get("/me"); - return; - } - } - - messageBox.showError(SoundCloud.getErrorString(result)); - } - } - - QSoundCloud.ResourcesRequest { - id: userRequest - - clientId: SoundCloud.clientId - clientSecret: SoundCloud.clientSecret - onFinished: { - if (status == QSoundCloud.ResourcesRequest.Ready) { - if (accountModel.addAccount(result.id, result.username, accessToken, refreshToken, - SoundCloud.scopes.join(" "))) { - statusBar.showMessage(qsTr("You are signed in to account") + " '" + result.username + "'"); - } - else { - messageBox.showError(accountModel.errorString); - } - - return; - } - - messageBox.showError(SoundCloud.getErrorString(result)); - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudActivitiesPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudActivitiesPage.qml deleted file mode 100644 index dd0325b..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudActivitiesPage.qml +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - property alias model: activityModel - property alias view: view - - title: qsTr("Stream") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: SoundCloudActivityModel { - id: activityModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: SoundCloudActivityDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: { - if (activityModel.data(styleData.row, "activityType") == "playlist") { - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(activityModel.data(styleData.row, "originId")); - } - else { - pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(activityModel.data(styleData.row, "originId")); - } - } - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "activityTypeString" - title: qsTr("Type") - } - - Keys.onReturnPressed: { - if (currentRow >= 0) { - if (activityModel.data(currentRow, "activityType") == "playlist") { - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(activityModel.data(currentRow, "originId")); - } - else { - pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(activityModel.data(currentRow, "originId")); - } - } - } - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - SoundCloudActivityPopup { - z: 1000 - activityId: activityModel.data(popupMouseArea.row, "id") - thumbnail: activityModel.data(popupMouseArea.row, "originThumbnailUrl") - title: activityModel.data(popupMouseArea.row, "title") - type: activityModel.data(popupMouseArea.row, "activityTypeString"); - description: activityModel.data(popupMouseArea.row, "description") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudActivityDelegate.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudActivityDelegate.qml deleted file mode 100644 index 86260bb..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudActivityDelegate.qml +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import ".." - -ItemDelegate { - id: root - - Label { - anchors { - left: loader.item ? loader.right : parent.left - right: parent.right - margins: 2 - } - verticalAlignment: Text.AlignVCenter - color: styleData.textColor - elide: styleData.elideMode - text: styleData.value - } - - Loader { - id: loader - - width: height - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - margins: 2 - } - sourceComponent: styleData.column == 0 ? thumbnail : undefined - } - - Component { - id: thumbnail - - Image { - anchors.fill: parent - source: view.model.data(styleData.row, "originThumbnailUrl") - smooth: true - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudActivityPopup.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudActivityPopup.qml deleted file mode 100644 index 39b185b..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudActivityPopup.qml +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Rectangle { - id: root - - property string activityId - property alias thumbnail: thumbnail.source - property alias title: titleLabel.text - property alias type: typeLabel.text - property alias description: descriptionLabel.text - - z: 1000 - width: 400 - height: Math.max(thumbnail.height, grid.height) + 20 - color: palette.window - - MouseArea { - anchors.fill: parent - } - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - } - - GridLayout { - id: grid - - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - id: titleLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Type") - } - - Label { - id: typeLabel - - Layout.fillWidth: true - elide: Text.ElideRight - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - Label { - id: descriptionLabel - - Layout.fillWidth: true - wrapMode: Text.Wrap - maximumLineCount: 10 - elide: Text.ElideRight - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudArtistPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudArtistPage.qml deleted file mode 100644 index 9ae936b..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudArtistPage.qml +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - function loadArtist(artistOrId) { - artist.loadArtist(artistOrId); - - if ((artist.id) && (!artist.followed) && (SoundCloud.userId)) { - artist.checkIfFollowed(); - } - } - - title: artist.name ? artist.name : qsTr("User") - - SoundCloudArtist { - id: artist - - onStatusChanged: { - switch (status) { - case QSoundCloud.ResourcesRequest.Ready: - if ((!followed) && (SoundCloud.userId)) { - checkIfFollowed(); - } - - break; - case QSoundCloud.ResourcesRequest.Failed: - messageBox.showError(errorString); - break; - default: - break; - } - } - } - - TabView { - id: tabView - - anchors.fill: parent - frameVisible: false - enabled: artist.id != "" - - Tab { - id: profileTab - - title: qsTr("Profile") - - Page { - id: profilePage - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - source: artist.thumbnailUrl - } - - Button { - id: followButton - - anchors { - left: thumbnail.left - right: thumbnail.right - top: thumbnail.bottom - topMargin: 10 - } - - text: artist.followed ? qsTr("Followed") : qsTr("Follow") - iconName: artist.followed ? "dialog-yes" : "" - tooltip: (artist.followed ? qsTr("Unfollow") : qsTr("Follow")) + " " + artist.name - enabled: (SoundCloud.userId) && (artist.id) && (artist.id != SoundCloud.userId) - onClicked: artist.followed ? artist.unfollow() : artist.follow() - } - - GridLayout { - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Name") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.name - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Tracks") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.trackCount - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Sets") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.playlistCount - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Followers") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: artist.followersCount - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - ScrollView { - Layout.fillWidth: true - - Flickable { - anchors.fill: parent - contentHeight: descriptionLabel.height - - Label { - id: descriptionLabel - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - wrapMode: Text.Wrap - text: Utils.toRichText(artist.description) - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Resources.SOUNDCLOUD) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), immediate: true}) - .loadArtist(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(resource.id); - } - } - } - } - } - } - } - } - - Tab { - id: tracksTab - - title: qsTr("Tracks") - - SoundCloudTracksPage { - id: tracksPage - - Component.onCompleted: model.get("/users/" + artist.id + "/tracks", {limit: MAX_RESULTS}) - } - } - - Tab { - id: favouritesTab - - title: qsTr("Favourites") - - SoundCloudTracksPage { - id: favouritesPage - - Component.onCompleted: model.get("/users/" + artist.id + "/favorites", {limit: MAX_RESULTS}) - } - } - - Tab { - id: playlistsTab - - title: qsTr("Sets") - - SoundCloudPlaylistsPage { - id: playlistsPage - - Component.onCompleted: model.get("/users/" + artist.id + "/playlists", {limit: MAX_RESULTS}) - } - } - - Tab { - id: followingsTab - - title: qsTr("Followings") - - SoundCloudArtistsPage { - id: followingsPage - - Component.onCompleted: model.get("/users/" + artist.id + "/followings", {limit: MAX_RESULTS}) - } - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudArtistsPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudArtistsPage.qml deleted file mode 100644 index 635d06d..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudArtistsPage.qml +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - property alias model: artistModel - property alias view: view - - title: qsTr("Users") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: SoundCloudArtistModel { - id: artistModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: ArtistDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), immediate: true}) - .loadArtist(artistModel.get(styleData.row)) - } - - TableViewColumn { - role: "name" - title: qsTr("Name") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), immediate: true}) - .loadArtist(artistModel.get(currentRow)); - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - ArtistPopup { - z: 1000 - artistId: artistModel.data(popupMouseArea.row, "id") - thumbnail: artistModel.data(popupMouseArea.row, "thumbnailUrl") - name: artistModel.data(popupMouseArea.row, "name") - description: artistModel.data(popupMouseArea.row, "description") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudDownloadDialog.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudDownloadDialog.qml deleted file mode 100644 index 9f72bf6..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudDownloadDialog.qml +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -MyDialog { - id: root - - property string resourceId - property string resourceTitle - - minimumWidth: grid.width + 20 - minimumHeight: grid.height + 60 - title: qsTr("Download track") - content: GridLayout { - id: grid - - columns: 2 - - Label { - text: qsTr("Audio format") + ":" - } - - ComboBox { - id: streamSelector - - Layout.minimumWidth: 200 - model: SoundCloudStreamModel { - id: streamModel - - onStatusChanged: { - switch (status) { - case QSoundCloud.StreamsRequest.Ready: - if (count > 0) { - streamSelector.currentIndex = Math.max(0, match("name", - Settings.defaultDownloadFormat(Resources.SOUNDCLOUD))); - } - else { - messageBox.showError(qsTr("No streams found")); - } - - break; - case QSoundCloud.StreamsRequest.Failed: { - messageBox.showError(errorString); - break; - } - default: - break; - } - } - } - textRole: "name" - onActivated: Settings.setDefaultDownloadFormat(Resources.SOUNDCLOUD, streamModel.data(index, "name")) - } - - Label { - text: qsTr("Category") + ":" - } - - ComboBox { - id: categorySelector - - Layout.minimumWidth: 200 - model: CategoryNameModel { - id: categoryModel - } - textRole: "name" - currentIndex: categoryModel.match("value", Settings.defaultCategory) - onActivated: Settings.defaultCategory = categoryModel.data(index, "value") - } - } - buttons: [ - Button { - text: qsTr("&Cancel") - iconName: "dialog-cancel" - onClicked: root.reject() - }, - - Button { - text: qsTr("&Ok") - iconName: "dialog-ok" - isDefault: true - enabled: (streamModel.status == QSoundCloud.ResourcesRequest.Ready) && (streamModel.count > 0) - onClicked: root.accept() - } - ] - - onOpened: streamModel.get(resourceId) - onRejected: streamModel.cancel() - onAccepted: Transfers.addDownloadTransfer(Resources.SOUNDCLOUD, resourceId, - streamModel.data(streamSelector.currentIndex, "value").id, "", - resourceTitle, categoryModel.data(categorySelector.currentIndex, "value")) -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudPage.qml deleted file mode 100644 index a2e6e79..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudPage.qml +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import ".." - -Page { - id: root - - function search(query, type, order) { - var url; - var path; - var filters = {}; - filters["q"] = query; - filters["limit"] = MAX_RESULTS; - - if (type == Resources.PLAYLIST) { - url = Qt.resolvedUrl("SoundCloudPlaylistsPage.qml"); - path = "/playlists"; - } - else if (type == Resources.ARTIST) { - url = Qt.resolvedUrl("SoundCloudArtistsPage.qml"); - path = "/users"; - } - else { - url = Qt.resolvedUrl("SoundCloudTracksPage.qml"); - path = "/tracks"; - } - - view.currentRow = 1; - - if (pageStack.depth > 0) { - pageStack.clear(); - } - - pageStack.push({item: url, properties: {title: qsTr("Search") + " ('" + query + "')"}, immediate: true}) - .model.get(path, filters); - return true; - } - - function showResource(resource) { - if (pageStack.depth > 0) { - pageStack.clear(); - } - - if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}).loadPlaylist(resource.id); - } - else if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), immediate: true}).loadArtist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}).loadTrack(resource.id); - } - - return true; - } - - tools: ToolBarLayout { - - ToolButton { - id: backButton - - action: Action { - id: backAction - - text: qsTr("Go back") - iconName: "go-previous" - shortcut: "Backspace" - enabled: pageStack.depth > 1 - onTriggered: pageStack.pop({immediate: true}) - } - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: (pageStack.currentItem) && (pageStack.currentItem.title) ? pageStack.currentItem.title : "SoundCloud" - } - - Label { - text: qsTr("Search for") - } - - ComboBox { - id: searchTypeSelector - - Layout.minimumWidth: 200 - model: SoundCloudSearchTypeModel { - id: searchTypeModel - } - textRole: "name" - currentIndex: searchTypeModel.match("name", Settings.defaultSearchType(Resources.SOUNDCLOUD)) - onActivated: Settings.setDefaultSearchType(Resources.SOUNDCLOUD, searchTypeModel.data(index, "name")) - } - - TextField { - id: searchField - - Layout.minimumWidth: 300 - placeholderText: qsTr("Search") - validator: RegExpValidator { - regExp: /^.+/ - } - onAccepted: { - root.search(text, searchTypeModel.data(searchTypeSelector.currentIndex, "value").type, - searchTypeModel.data(searchTypeSelector.currentIndex, "value").order); - text = ""; - } - } - } - - TableView { - id: view - - width: 150 - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - alternatingRowColors: false - headerVisible: false - model: SoundCloudNavModel { - id: navModel - } - onCurrentRowChanged: { - if (pageStack.depth > 0) { - pageStack.clear(); - } - - switch (currentRow) { - case 0: - pageStack.push({item: Qt.resolvedUrl("SoundCloudAccountsPage.qml"), immediate: true}); - break; - case 1: - pageStack.push({item: Qt.resolvedUrl("SoundCloudSearchLabel.qml"), immediate: true}); - break; - case 2: - pageStack.push({item: Qt.resolvedUrl("SoundCloudTracksPage.qml"), properties: {title: qsTr("Tracks")}, - immediate: true}).model.get("/me/tracks", {limit: MAX_RESULTS}); - break; - case 3: - pageStack.push({item: Qt.resolvedUrl("SoundCloudTracksPage.qml"), properties: {title: qsTr("Favourites")}, - immediate: true}).model.get("/me/favorites", {limit: MAX_RESULTS}); - break; - case 4: - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistsPage.qml"), properties: {title: qsTr("Sets")}, - immediate: true}).model.get("/me/playlists", {limit: MAX_RESULTS}); - break; - case 5: - pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistsPage.qml"), properties: {title: qsTr("Followings")}, - immediate: true}).model.get("/me/followings", {limit: MAX_RESULTS}); - break; - default: - break; - } - } - - TableViewColumn { - role: "display" - title: qsTr("View") - } - } - - StackView { - id: pageStack - - anchors { - left: view.right - right: parent.right - top: parent.top - bottom: parent.bottom - } - } - - Component.onCompleted: { - view.selection.select(1); - view.currentRow = 1; - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudPlaylistPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudPlaylistPage.qml deleted file mode 100644 index a785a4d..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudPlaylistPage.qml +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - function loadPlaylist(playlistOrId) { - playlist.loadPlaylist(playlistOrId); - } - - title: playlist.title ? playlist.title : qsTr("Set") - - SoundCloudPlaylist { - id: playlist - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - - TabView { - id: tabView - - anchors.fill: parent - frameVisible: false - enabled: playlist.id != "" - - Tab { - id: infoTab - - title: qsTr("Info") - - Page { - id: infoPage - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - source: playlist.thumbnailUrl - } - - ColumnLayout { - anchors { - left: thumbnail.left - right: thumbnail.right - top: thumbnail.bottom - topMargin: 10 - } - - Button { - Layout.fillWidth: true - text: qsTr("Queue") - iconName: "list-add" - enabled: (tracksTab.item) && (tracksTab.item.model.count > 0) - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.item.model.count; i++) { - tracks.push(tracksTab.item.model.get(i)); - } - - player.addTracks(tracks); - } - } - - Button { - Layout.fillWidth: true - text: qsTr("Play") - iconName: "media-playback-start" - enabled: (tracksTab.item) && (tracksTab.item.model.count > 0) - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.item.model.count; i++) { - tracks.push(tracksTab.item.model.get(i)); - } - - player.playTracks(tracks); - } - } - } - - GridLayout { - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.title - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Artist") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.artist - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Genre") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.genre - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Date") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.date - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Tracks") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.trackCount - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Length") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: playlist.durationString - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - ScrollView { - Layout.fillWidth: true - - Flickable { - anchors.fill: parent - contentHeight: descriptionLabel.height - - Label { - id: descriptionLabel - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - wrapMode: Text.Wrap - text: Utils.toRichText(playlist.description) - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Resources.SOUNDCLOUD) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), immediate: true}) - .loadArtist(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(resource.id); - } - } - } - } - } - } - } - } - - Tab { - id: tracksTab - - title: qsTr("Tracks") - - SoundCloudTracksPage { - id: tracksPage - - Component.onCompleted: model.get("/playlists/" + playlist.id) - } - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudPlaylistsPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudPlaylistsPage.qml deleted file mode 100644 index d5fe566..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudPlaylistsPage.qml +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - property alias model: playlistModel - property alias view: view - - title: qsTr("Sets") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: SoundCloudPlaylistModel { - id: playlistModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: PlaylistDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(playlistModel.get(styleData.row)) - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "artist" - title: qsTr("Artist") - } - - TableViewColumn { - role: "date" - title: qsTr("Date") - } - - TableViewColumn { - role: "trackCount" - title: qsTr("Tracks") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(playlistModel.get(currentRow)); - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - PlaylistPopup { - z: 1000 - playlistId: playlistModel.data(popupMouseArea.row, "id") - thumbnail: playlistModel.data(popupMouseArea.row, "thumbnailUrl") - title: playlistModel.data(popupMouseArea.row, "title") - artist: playlistModel.data(popupMouseArea.row, "artist") - genre: playlistModel.data(popupMouseArea.row, "genre") - date: playlistModel.data(popupMouseArea.row, "date") - trackCount: playlistModel.data(popupMouseArea.row, "trackCount") - duration: playlistModel.data(popupMouseArea.row, "durationString") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudTrackPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudTrackPage.qml deleted file mode 100644 index 363dc6f..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudTrackPage.qml +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - function loadTrack(trackOrId) { - track.loadTrack(trackOrId); - } - - title: track.title ? track.title : qsTr("Track") - - SoundCloudTrack { - id: track - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - - TabView { - id: tabView - - anchors.fill: parent - frameVisible: false - enabled: track.id != "" - - Tab { - id: infoTab - - title: qsTr("Info") - - Page { - id: infoPage - - Image { - id: thumbnail - - width: height - height: 100 - anchors { - left: parent.left - top: parent.top - margins: 10 - } - smooth: true - source: track.thumbnailUrl - } - - ColumnLayout { - anchors { - left: thumbnail.left - right: thumbnail.right - top: thumbnail.bottom - topMargin: 10 - } - - Button { - Layout.fillWidth: true - text: qsTr("Queue") - iconName: "list-add" - onClicked: player.addTrack(track) - } - - Button { - Layout.fillWidth: true - text: qsTr("Play") - iconName: "media-playback-start" - onClicked: player.playTrack(track) - } - - Button { - Layout.fillWidth: true - text: qsTr("Download") - iconName: "folder-download" - onClicked: { - loader.sourceComponent = downloadDialog; - loader.item.open(); - } - } - - Button { - Layout.fillWidth: true - text: track.favourited ? qsTr("Unfavourite") : qsTr("Favourite") - iconName: track.favourited ? "dialog-no" : "dialog-yes" - enabled: SoundCloud.userId != "" - onClicked: track.favourited ? track.unfavourite() : track.favourite() - } - } - - GridLayout { - width: parent.width - thumbnail.width - 30 - anchors { - right: parent.right - top: parent.top - margins: 10 - } - columns: 2 - - Label { - color: palette.mid - font.bold: true - text: qsTr("Title") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.title - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Artist") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.artist - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Genre") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.genre - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Date") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.date - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Format") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.format - } - - Label { - color: palette.mid - font.bold: true - text: qsTr("Length") - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: track.durationString - } - - Label { - Layout.alignment: Qt.AlignTop - color: palette.mid - font.bold: true - text: qsTr("Description") - } - - ScrollView { - Layout.fillWidth: true - - Flickable { - anchors.fill: parent - contentHeight: descriptionLabel.height - - Label { - id: descriptionLabel - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - wrapMode: Text.Wrap - text: Utils.toRichText(track.description) - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Resources.SOUNDCLOUD) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), immediate: true}) - .loadArtist(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - pageStack.push({item: Qt.resolvedUrl("SoundCloudPlaylistPage.qml"), immediate: true}) - .loadPlaylist(resource.id); - } - else { - pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(resource.id); - } - } - } - } - } - } - } - } - - Tab { - id: commentsTab - - title: qsTr("Comments") - - ScrollView { - anchors.fill: parent - - ListView { - id: commentView - - anchors.fill: parent - model: SoundCloudCommentModel { - id: commentModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - delegate: CommentDelegate { - onThumbnailClicked: pageStack.push({item: Qt.resolvedUrl("SoundCloudArtistPage.qml"), - immediate: true}).loadArtist(commentModel.get(index)) - } - } - - Component.onCompleted: commentModel.get("/tracks/" + track.id + "/comments", {limit: MAX_RESULTS}) - } - } - } - - Loader { - id: loader - } - - Component { - id: downloadDialog - - SoundCloudDownloadDialog { - resourceId: track.id - resourceTitle: track.title - } - } -} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudTracksPage.qml b/app/src/desktop-qml/qml/soundcloud/SoundCloudTracksPage.qml deleted file mode 100644 index 43a4ef2..0000000 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudTracksPage.qml +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." - -Page { - id: root - - property alias model: trackModel - property alias view: view - - title: qsTr("Tracks") - - TableView { - id: view - - anchors.fill: parent - focus: true - alternatingRowColors: false - model: SoundCloudTrackModel { - id: trackModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) messageBox.showError(errorString); - } - itemDelegate: TrackDelegate { - onPressed: { - view.forceActiveFocus(); - view.selection.clear(); - view.selection.select(styleData.row); - view.currentRow = styleData.row; - } - onDoubleClicked: player.playTrack(trackModel.get(styleData.row)) - onRightClicked: contextMenu.popup() - } - - TableViewColumn { - role: "title" - title: qsTr("Title") - width: Math.floor(root.width / 3) - } - - TableViewColumn { - role: "artist" - title: qsTr("Artist") - } - - TableViewColumn { - role: "date" - title: qsTr("Date") - } - - TableViewColumn { - role: "durationString" - title: qsTr("Length") - } - - Keys.onReturnPressed: if (currentRow >= 0) pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(trackModel.get(currentRow)); - } - - MouseArea { - id: popupMouseArea - - property int row: -1 - - anchors.fill: view - hoverEnabled: true - enabled: view.rowCount > 0 - onMouseYChanged: row = Math.max(0, view.rowAt(10, mouseY)) - onExited: { - loader.sourceComponent = undefined; - popupTimer.stop(); - } - onRowChanged: { - loader.sourceComponent = undefined; - popupTimer.restart(); - } - onPressed: mouse.accepted = false - } - - Menu { - id: contextMenu - - MenuItem { - text: qsTr("View info") - iconName: "help-info" - onTriggered: pageStack.push({item: Qt.resolvedUrl("SoundCloudTrackPage.qml"), immediate: true}) - .loadTrack(trackModel.get(view.currentRow)) - } - - MenuItem { - text: qsTr("Queue") - iconName: "list-add" - onTriggered: player.addTrack(trackModel.get(view.currentRow)) - } - - MenuItem { - text: qsTr("Download") - iconName: "folder-download" - onTriggered: { - loader.sourceComponent = downloadDialog; - loader.item.open(); - } - } - - MenuItem { - text: (view.currentRow >= 0) && (trackModel.data(view.currentRow, "favourited")) ? qsTr("Unfavorite") - : qsTr("Favourite") - - iconName: (view.currentRow >= 0) && (trackModel.data(view.currentRow, "favourited")) ? "dialog-no" - : "dialog-yes" - enabled: SoundCloud.userId != "" - onTriggered: { - var track = trackModel.get(view.currentRow); - - if (track.favourited) { - track.unfavourite(); - } - else { - track.favourite(); - } - } - } - } - - Loader { - id: loader - } - - Timer { - id: popupTimer - - interval: 500 - onTriggered: loader.sourceComponent = popup - } - - Component { - id: popup - - TrackPopup { - z: 1000 - trackId: trackModel.data(popupMouseArea.row, "id") - thumbnail: trackModel.data(popupMouseArea.row, "thumbnailUrl") - title: trackModel.data(popupMouseArea.row, "title") - artist: trackModel.data(popupMouseArea.row, "artist") - genre: trackModel.data(popupMouseArea.row, "genre") - date: trackModel.data(popupMouseArea.row, "date") - format: trackModel.data(popupMouseArea.row, "format") - duration: trackModel.data(popupMouseArea.row, "durationString") - - Component.onCompleted: { - x = Math.min(popupMouseArea.mouseX + 2, popupMouseArea.width - width) - y = Math.min(popupMouseArea.mouseY + 2, popupMouseArea.height - height); - } - } - } - - Component { - id: downloadDialog - - SoundCloudDownloadDialog { - resourceId: trackModel.data(view.currentRow, "id") - resourceTitle: trackModel.data(view.currentRow, "title") - } - } -} diff --git a/app/src/desktop/aboutdialog.cpp b/app/src/desktop/aboutdialog.cpp new file mode 100644 index 0000000..8dd26f9 --- /dev/null +++ b/app/src/desktop/aboutdialog.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "aboutdialog.h" +#include "definitions.h" +#include "pluginconfigmodel.h" +#include +#include +#include +#include +#include + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), + m_model(new PluginConfigModel(this)), + m_iconLabel(new QLabel(this)), + m_textLabel(new QLabel(this)), + m_view(new QTreeView(this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, this)), + m_layout(new QVBoxLayout(this)) +{ + setWindowTitle(tr("About")); + + m_iconLabel->setAlignment(Qt::AlignCenter); + m_iconLabel->setPixmap(QIcon::fromTheme("musikloud2").pixmap(64, 64)); + + m_textLabel->setWordWrap(true); + m_textLabel->setTextFormat(Qt::RichText); + m_textLabel->setOpenExternalLinks(true); + m_textLabel->setText(QString("

MusiKloud2

Version: %1

A plugin-extensible SoundCloud client and music player.

© Stuart Howarth 2016

marxoft.co.uk

Installed plugins:

").arg(VERSION_NUMBER)); + + m_view->setModel(m_model); + m_view->setSelectionMode(QTreeView::NoSelection); + m_view->setEditTriggers(QTreeView::NoEditTriggers); + m_view->setItemsExpandable(false); + m_view->setUniformRowHeights(true); + m_view->setAllColumnsShowFocus(true); + m_view->setRootIsDecorated(false); + m_view->header()->setStretchLastSection(true); + + m_layout->addWidget(m_iconLabel); + m_layout->addWidget(m_textLabel); + m_layout->addWidget(m_view); + m_layout->addWidget(m_buttonBox); + + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} diff --git a/app/src/desktop-qml/qml/ItemDelegate.qml b/app/src/desktop/aboutdialog.h similarity index 52% rename from app/src/desktop-qml/qml/ItemDelegate.qml rename to app/src/desktop/aboutdialog.h index 343c8a9..7925f32 100644 --- a/app/src/desktop-qml/qml/ItemDelegate.qml +++ b/app/src/desktop/aboutdialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,24 +14,35 @@ * along with this program. If not, see . */ -import QtQuick 2.0 +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H -Item { - id: root +#include + +class PluginConfigModel; +class QDialogButtonBox; +class QLabel; +class QTreeView; +class QVBoxLayout; + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = 0); + +private: + PluginConfigModel *m_model; - signal pressed - signal clicked - signal doubleClicked - signal rightClicked + QLabel *m_iconLabel; + QLabel *m_textLabel; + + QTreeView *m_view; - MouseArea { - id: mouseArea - - z: 1000 - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onPressed: root.pressed() - onClicked: mouse.button == Qt.RightButton ? root.rightClicked() : root.clicked() - onDoubleClicked: if (mouse.button == Qt.LeftButton) root.doubleClicked(); - } -} + QDialogButtonBox *m_buttonBox; + + QVBoxLayout *m_layout; +}; + +#endif // ABOUTDIALOG_H diff --git a/app/src/desktop/categorysettingspage.cpp b/app/src/desktop/categorysettingspage.cpp new file mode 100644 index 0000000..3114f4b --- /dev/null +++ b/app/src/desktop/categorysettingspage.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "categorysettingspage.h" +#include "categorymodel.h" +#include +#include +#include +#include +#include +#include +#include + +CategorySettingsPage::CategorySettingsPage(QWidget *parent) : + SettingsPage(parent), + m_model(new CategoryModel(this)), + m_view(new QTreeView(this)), + m_nameEdit(new QLineEdit(this)), + m_pathEdit(new QLineEdit(this)), + m_pathButton(new QPushButton(QIcon::fromTheme("document-open"), tr("&Browse"), this)), + m_saveButton(new QPushButton(QIcon::fromTheme("document-save"), tr("&Save"), this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Categories")); + + m_view->setModel(m_model); + m_view->setAlternatingRowColors(true); + m_view->setSelectionBehavior(QTreeView::SelectRows); + m_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_view->setEditTriggers(QTreeView::NoEditTriggers); + m_view->setItemsExpandable(false); + m_view->setUniformRowHeights(true); + m_view->setAllColumnsShowFocus(true); + m_view->setRootIsDecorated(false); + m_view->header()->setStretchLastSection(true); + + m_saveButton->setEnabled(false); + + m_layout->addRow(m_view); + m_layout->addRow(tr("&Name:"), m_nameEdit); + m_layout->addRow(tr("&Path:"), m_pathEdit); + m_layout->addWidget(m_pathButton); + m_layout->addWidget(m_saveButton); + + connect(m_view, SIGNAL(clicked(QModelIndex)), this, SLOT(setCurrentCategory(QModelIndex))); + connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + connect(m_nameEdit, SIGNAL(textChanged(QString)), this, SLOT(onNameChanged(QString))); + connect(m_nameEdit, SIGNAL(returnPressed()), m_saveButton, SLOT(animateClick())); + connect(m_pathEdit, SIGNAL(textChanged(QString)), this, SLOT(onPathChanged(QString))); + connect(m_pathEdit, SIGNAL(returnPressed()), m_saveButton, SLOT(animateClick())); + connect(m_pathButton, SIGNAL(clicked()), this, SLOT(showFileDialog())); + connect(m_saveButton, SIGNAL(clicked()), this, SLOT(addCategory())); +} + +void CategorySettingsPage::addCategory() { + m_model->addCategory(m_nameEdit->text(), m_pathEdit->text()); + m_nameEdit->clear(); + m_pathEdit->clear(); +} + +void CategorySettingsPage::setCurrentCategory(const QModelIndex &index) { + m_nameEdit->setText(index.data(CategoryModel::NameRole).toString()); + m_pathEdit->setText(index.data(CategoryModel::PathRole).toString()); +} + +void CategorySettingsPage::showContextMenu(const QPoint &pos) { + if (!m_view->currentIndex().isValid()) { + return; + } + + QMenu menu(this); + menu.addAction(QIcon::fromTheme("edit-delete"), tr("&Remove")); + + if (menu.exec(m_view->mapToGlobal(pos))) { + m_model->removeCategory(m_view->currentIndex().row()); + } +} + +void CategorySettingsPage::showFileDialog() { + const QString path = QFileDialog::getExistingDirectory(this, tr("Path"), m_pathEdit->text()); + + if (!path.isEmpty()) { + m_pathEdit->setText(path); + } +} + +void CategorySettingsPage::onNameChanged(const QString &name) { + m_saveButton->setEnabled((!name.isEmpty()) && (!m_pathEdit->text().isEmpty())); +} + +void CategorySettingsPage::onPathChanged(const QString &path) { + m_saveButton->setEnabled((!path.isEmpty()) && (!m_nameEdit->text().isEmpty())); +} diff --git a/app/src/desktop/categorysettingspage.h b/app/src/desktop/categorysettingspage.h new file mode 100644 index 0000000..711bad6 --- /dev/null +++ b/app/src/desktop/categorysettingspage.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CATEGORYSETTINGSPAGE_H +#define CATEGORYSETTINGSPAGE_H + +#include "settingspage.h" + +class CategoryModel; +class QFormLayout; +class QLineEdit; +class QPushButton; +class QTreeView; + +class CategorySettingsPage : public SettingsPage +{ + Q_OBJECT + +public: + explicit CategorySettingsPage(QWidget *parent = 0); + +private Q_SLOTS: + void addCategory(); + void setCurrentCategory(const QModelIndex &index); + + void showContextMenu(const QPoint &pos); + void showFileDialog(); + + void onNameChanged(const QString &name); + void onPathChanged(const QString &path); + +private: + CategoryModel *m_model; + + QTreeView *m_view; + + QLineEdit *m_nameEdit; + QLineEdit *m_pathEdit; + + QPushButton *m_pathButton; + QPushButton *m_saveButton; + + QFormLayout *m_layout; +}; + +#endif // CATEGORYSETTINGSPAGE_H diff --git a/app/src/desktop/customcommanddialog.cpp b/app/src/desktop/customcommanddialog.cpp new file mode 100644 index 0000000..fb758b3 --- /dev/null +++ b/app/src/desktop/customcommanddialog.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "customcommanddialog.h" +#include +#include +#include +#include + +CustomCommandDialog::CustomCommandDialog(QWidget *parent) : + QDialog(parent), + m_commandEdit(new QLineEdit(this)), + m_overrideCheckBox(new QCheckBox(tr("&Override global command"), this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Set custom command")); + + m_layout->addRow(tr("&Command:"), m_commandEdit); + m_layout->addRow(m_overrideCheckBox); + m_layout->addRow(m_buttonBox); + + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +QString CustomCommandDialog::command() const { + return m_commandEdit->text(); +} + +void CustomCommandDialog::setCommand(const QString &command) { + m_commandEdit->setText(command); +} + +bool CustomCommandDialog::overrideEnabled() const { + return m_overrideCheckBox->isChecked(); +} + +void CustomCommandDialog::setOverrideEnabled(bool enabled) { + m_overrideCheckBox->setChecked(enabled); +} diff --git a/app/src/desktop/customcommanddialog.h b/app/src/desktop/customcommanddialog.h new file mode 100644 index 0000000..499ed0a --- /dev/null +++ b/app/src/desktop/customcommanddialog.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CUSTOMCOMMANDDIALOG_H +#define CUSTOMCOMMANDDIALOG_H + +#include + +class QCheckBox; +class QDialogButtonBox; +class QFormLayout; +class QLineEdit; + +class CustomCommandDialog : public QDialog +{ + Q_OBJECT + + Q_PROPERTY(QString command READ command WRITE setCommand) + Q_PROPERTY(bool overrideEnabled READ overrideEnabled WRITE setOverrideEnabled) + +public: + explicit CustomCommandDialog(QWidget *parent = 0); + + QString command() const; + + bool overrideEnabled() const; + +public Q_SLOTS: + void setCommand(const QString &command); + + void setOverrideEnabled(bool enabled); + +private: + QLineEdit *m_commandEdit; + + QCheckBox *m_overrideCheckBox; + + QDialogButtonBox *m_buttonBox; + + QFormLayout *m_layout; +}; + +#endif // CUSTOMCOMMANDDIALOG_H diff --git a/app/src/base/database.h b/app/src/desktop/database.h similarity index 83% rename from app/src/base/database.h rename to app/src/desktop/database.h index 008f1e9..2c9273a 100644 --- a/app/src/base/database.h +++ b/app/src/desktop/database.h @@ -18,20 +18,17 @@ #define DATABASE_H #include "definitions.h" +#include "logger.h" #include #include #include #include -#include inline void initDatabase() { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); -#ifdef SYMBIAN_OS - db.setDatabaseName("musikloud2.db"); -#else - QDir().mkpath(DATABASE_PATH); - db.setDatabaseName(DATABASE_PATH + "musikloud2.db"); -#endif + QDir().mkpath(APP_CONFIG_PATH); + db.setDatabaseName(APP_CONFIG_PATH + "musikloud2.db"); + if (!db.isOpen()) { db.open(); } @@ -40,7 +37,7 @@ inline void initDatabase() { accessToken TEXT, refreshToken TEXT, scopes TEXT)"); if (query.lastError().isValid()) { - qDebug() << "initDatabase: database error:" << query.lastError().text(); + Logger::log("initDatabase: database error: " + query.lastError().text()); } } diff --git a/app/src/desktop/definitions.h b/app/src/desktop/definitions.h new file mode 100644 index 0000000..601f71b --- /dev/null +++ b/app/src/desktop/definitions.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +#include +#include +#if QT_VERSION >= 0x050000 +#include +#else +#include +#endif + +// Home +#if QT_VERSION >= 0x050000 +static const QString HOME_PATH(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); +#else +static const QString HOME_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); +#endif + +// Plugins +static const QStringList PLUGIN_PATHS = QStringList() << QString("/usr/share/musikloud2/plugins/") + << QString(HOME_PATH + "/musikloud2/plugins/"); + +static const QString LIB_PREFIX("lib"); +static const QString LIB_SUFFIX(".so"); + +// Config +static const QString APP_CONFIG_PATH(HOME_PATH + "/.config/MusiKloud2/"); +static const QString PLUGIN_CONFIG_PATH(APP_CONFIG_PATH + "plugins/"); + +// Downloads +static const QString DOWNLOAD_PATH(HOME_PATH + "/Downloads/musikloud2/"); +static const QRegExp ILLEGAL_FILENAME_CHARS_RE("[\"@&~=\\/:?#!|<>*^]"); + +// Content +static const int LARGE_THUMBNAIL_SIZE = 120; +static const int THUMBNAIL_SIZE = 40; + +// Network +static const int DOWNLOAD_BUFFER_SIZE = 64000; +static const int MAX_CONCURRENT_TRANSFERS = 4; +static const int MAX_REDIRECTS = 8; +static const int MAX_RESULTS = 20; +static const QByteArray USER_AGENT("Wget/1.13.4 (linux-gnu)"); + +// Version +static const QString VERSION_NUMBER("0.2.0"); + +#endif // DEFINITIONS_H diff --git a/app/src/desktop/drawing.h b/app/src/desktop/drawing.h new file mode 100644 index 0000000..b80e609 --- /dev/null +++ b/app/src/desktop/drawing.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DRAWING_H +#define DRAWING_H + +#include + +inline void drawCenteredImage(QPainter *painter, const QRect &rect, const QImage &image) { + painter->save(); + painter->setClipRect(rect); + + const int wDiff = rect.width() - image.width(); + const int hDiff = rect.height() - image.height(); + const int x = rect.x() + (wDiff == 0 ? 0 : wDiff / 2); + const int y = rect.y() + (hDiff == 0 ? 0 : hDiff / 2); + + painter->drawImage(x, y, image); + painter->restore(); +} + +inline void drawCenteredImage(QPainter *painter, const QRect &rect, const QPixmap &pixmap) { + painter->save(); + painter->setClipRect(rect); + + const int wDiff = rect.width() - pixmap.width(); + const int hDiff = rect.height() - pixmap.height(); + const int x = rect.x() + (wDiff == 0 ? 0 : wDiff / 2); + const int y = rect.y() + (hDiff == 0 ? 0 : hDiff / 2); + + painter->drawPixmap(x, y, pixmap); + painter->restore(); +} + +#endif // DRAWING_H diff --git a/app/src/desktop/generalsettingspage.cpp b/app/src/desktop/generalsettingspage.cpp new file mode 100644 index 0000000..0ff6947 --- /dev/null +++ b/app/src/desktop/generalsettingspage.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "generalsettingspage.h" +#include "definitions.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include + +GeneralSettingsPage::GeneralSettingsPage(QWidget *parent) : + SettingsPage(parent), + m_pathEdit(new QLineEdit(this)), + m_commandEdit(new QLineEdit(this)), + m_pathButton(new QPushButton(QIcon::fromTheme("document-open"), tr("&Browse"), this)), + m_concurrentSpinBox(new QSpinBox(this)), + m_commandCheckBox(new QCheckBox(tr("&Enable custom transfer command"), this)), + m_automaticCheckBox(new QCheckBox(tr("Start transfers &automatically"), this)), + m_restoreQueueCheckBox(new QCheckBox(tr("&Restore playback queue on startup"), this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Transfers")); + + m_concurrentSpinBox->setRange(1, MAX_CONCURRENT_TRANSFERS); + + m_layout->addRow(tr("Download &path:"), m_pathEdit); + m_layout->addWidget(m_pathButton); + m_layout->addRow(tr("&Maximum concurrent transfers:"), m_concurrentSpinBox); + m_layout->addRow(tr("&Custom transfer command (%f for filename):"), m_commandEdit); + m_layout->addRow(m_commandCheckBox); + m_layout->addRow(m_automaticCheckBox); + m_layout->addRow(m_restoreQueueCheckBox); + + connect(m_pathButton, SIGNAL(clicked()), this, SLOT(showFileDialog())); + + restore(); +} + +void GeneralSettingsPage::restore() { + m_pathEdit->setText(Settings::downloadPath()); + m_concurrentSpinBox->setValue(Settings::maximumConcurrentTransfers()); + m_commandEdit->setText(Settings::customTransferCommand()); + m_commandCheckBox->setChecked(Settings::customTransferCommandEnabled()); + m_automaticCheckBox->setChecked(Settings::startTransfersAutomatically()); + m_restoreQueueCheckBox->setChecked(Settings::restorePlaybackQueueOnStartup()); +} + +void GeneralSettingsPage::save() { + Settings::setDownloadPath(m_pathEdit->text()); + Settings::setMaximumConcurrentTransfers(m_concurrentSpinBox->value()); + Settings::setCustomTransferCommand(m_commandEdit->text()); + Settings::setCustomTransferCommandEnabled(m_commandCheckBox->isChecked()); + Settings::setStartTransfersAutomatically(m_automaticCheckBox->isChecked()); + Settings::setRestorePlaybackQueueOnStartup(m_restoreQueueCheckBox->isChecked()); +} + +void GeneralSettingsPage::showFileDialog() { + const QString path = QFileDialog::getExistingDirectory(this, tr("Download path"), Settings::downloadPath()); + + if (!path.isEmpty()) { + m_pathEdit->setText(path); + } +} diff --git a/app/src/desktop/generalsettingspage.h b/app/src/desktop/generalsettingspage.h new file mode 100644 index 0000000..549d50a --- /dev/null +++ b/app/src/desktop/generalsettingspage.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GENERALSETTINGSPAGE_H +#define GENERALSETTINGSPAGE_H + +#include "settingspage.h" + +class QCheckBox; +class QFormLayout; +class QLineEdit; +class QPushButton; +class QSpinBox; + +class GeneralSettingsPage : public SettingsPage +{ + Q_OBJECT + +public: + explicit GeneralSettingsPage(QWidget *parent = 0); + +public Q_SLOTS: + virtual void restore(); + virtual void save(); + +private Q_SLOTS: + void showFileDialog(); + +private: + QLineEdit *m_pathEdit; + QLineEdit *m_commandEdit; + + QPushButton *m_pathButton; + + QSpinBox *m_concurrentSpinBox; + + QCheckBox *m_commandCheckBox; + QCheckBox *m_automaticCheckBox; + QCheckBox *m_restoreQueueCheckBox; + + QFormLayout *m_layout; +}; + +#endif // GENERALSETTINGSPAGE_H diff --git a/app/src/desktop/image.cpp b/app/src/desktop/image.cpp new file mode 100644 index 0000000..4cd4604 --- /dev/null +++ b/app/src/desktop/image.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "image.h" +#include "drawing.h" +#include "imagecache.h" +#include +#include + +Image::Image(QWidget *parent) : + QWidget(parent), + m_cache(new ImageCache), + m_aspectRatioMode(Qt::KeepAspectRatio), + m_transformationMode(Qt::SmoothTransformation) +{ + connect(m_cache, SIGNAL(imageReady()), this, SLOT(update())); +} + +Image::~Image() { + delete m_cache; + m_cache = 0; +} + +QUrl Image::source() const { + return m_source; +} + +void Image::setSource(const QUrl &url) { + if (url != source()) { + m_source = url; + update(); + } +} + +Qt::AspectRatioMode Image::aspectRatioMode() const { + return m_aspectRatioMode; +} + +void Image::setAspectRatioMode(Qt::AspectRatioMode mode) { + if (mode != aspectRatioMode()) { + m_aspectRatioMode = mode; + update(); + } +} + +Qt::TransformationMode Image::transformationMode() const { + return m_transformationMode; +} + +void Image::setTransformationMode(Qt::TransformationMode mode) { + if (mode != transformationMode()) { + m_transformationMode = mode; + update(); + } +} + +void Image::paintEvent(QPaintEvent *) { + QPainter painter(this); + + if (source().isValid()) { + QImage image = m_cache->image(source(), size(), aspectRatioMode(), transformationMode()); + + if (!image.isNull()) { + drawCenteredImage(&painter, rect(), image); + return; + } + } + + painter.fillRect(rect(), Qt::black); +} + +void Image::mousePressEvent(QMouseEvent *e) { + m_pressed = true; + e->accept(); +} + +void Image::mouseReleaseEvent(QMouseEvent *e) { + if ((m_pressed) && (rect().contains(e->pos()))) { + emit clicked(); + e->accept(); + } + + m_pressed = false; +} diff --git a/app/src/desktop/image.h b/app/src/desktop/image.h new file mode 100644 index 0000000..d7ddb20 --- /dev/null +++ b/app/src/desktop/image.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGE_H +#define IMAGE_H + +#include +#include + +class ImageCache; + +class Image : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QUrl source READ source WRITE setSource) + Q_PROPERTY(Qt::AspectRatioMode aspectRatioMode READ aspectRatioMode WRITE setAspectRatioMode) + Q_PROPERTY(Qt::TransformationMode transformationMode READ transformationMode WRITE setTransformationMode) + +public: + explicit Image(QWidget *parent = 0); + ~Image(); + + QUrl source() const; + void setSource(const QUrl &url); + + Qt::AspectRatioMode aspectRatioMode() const; + void setAspectRatioMode(Qt::AspectRatioMode mode); + + Qt::TransformationMode transformationMode() const; + void setTransformationMode(Qt::TransformationMode mode); + +Q_SIGNALS: + void clicked(); + +protected: + void paintEvent(QPaintEvent *); + + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + + ImageCache *m_cache; + + QUrl m_source; + + Qt::AspectRatioMode m_aspectRatioMode; + Qt::TransformationMode m_transformationMode; + + bool m_pressed; +}; + +#endif // IMAGE_H diff --git a/app/src/desktop/imagecache.cpp b/app/src/desktop/imagecache.cpp new file mode 100644 index 0000000..46fb628 --- /dev/null +++ b/app/src/desktop/imagecache.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "imagecache.h" +#include "definitions.h" +#include +#include +#include + +class CachedImage +{ + +public: + explicit CachedImage(const QUrl &u) : + url(u) + { + } + + const QUrl url; + QImage image; +}; + +QThread* ImageCache::thread = 0; +QQueue ImageCache::queue; +QCache ImageCache::cache; + +int ImageCache::requestCount = 0; +int ImageCache::refCount = 0; + +const int ImageCache::MAX_REQUESTS = 8; + +ImageCache::ImageCache() : + QObject(), + m_manager(new QNetworkAccessManager) +{ + refCount++; + + if (!thread) { + thread = new QThread; + thread->start(); + } + + moveToThread(thread); +} + +ImageCache::~ImageCache() { + delete m_manager; + m_manager = 0; + refCount--; + + if (refCount == 0) { + thread->quit(); + thread->deleteLater(); + thread = 0; + } +} + +QImage ImageCache::image(const QUrl &url, const QSize &size, Qt::AspectRatioMode aspectRatioMode, + Qt::TransformationMode transformationMode) { + if (CachedImage *ci = cache.object(url)) { + return (!size.isEmpty()) + && (!ci->image.isNull()) ? ci->image.scaled(size, aspectRatioMode, transformationMode) + : ci->image; + } + + cache.insert(url, new CachedImage(url)); + + if (requestCount < MAX_REQUESTS) { + getImage(url); + } + else { + queue.enqueue(url); + } + + return QImage(); +} + +void ImageCache::getImage(const QUrl &url) { + requestCount++; + ImageRequest *request = new ImageRequest(m_manager, url); + connect(request, SIGNAL(finished(ImageRequest*)), this, SLOT(onRequestFinished(ImageRequest*))); +} + +void ImageCache::onRequestFinished(ImageRequest *request) { + if (CachedImage *ci = cache.object(request->url)) { + QImage image; + image.loadFromData(request->reply->readAll()); + ci->image = image; + emit imageReady(); + } + + request->deleteLater(); + requestCount--; + + if ((!queue.isEmpty()) && (requestCount < MAX_REQUESTS)) { + getImage(queue.dequeue()); + } +} + +ImageRequest::ImageRequest(QNetworkAccessManager *manager, const QUrl &u) : + QObject() +{ + url = u; + QNetworkRequest request(url); + request.setRawHeader("User-Agent", USER_AGENT); + reply = manager->get(request); + connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +ImageRequest::~ImageRequest() { + reply->deleteLater(); + reply = 0; +} + +void ImageRequest::onReplyFinished() { + emit finished(this); +} diff --git a/app/src/desktop/imagecache.h b/app/src/desktop/imagecache.h new file mode 100644 index 0000000..a6f48a8 --- /dev/null +++ b/app/src/desktop/imagecache.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGECACHE_H +#define IMAGECACHE_H + +#include +#include +#include +#include + +class CachedImage; +class ImageRequest; +class QThread; +class QNetworkAccessManager; +class QNetworkReply; + +class ImageCache : public QObject +{ + Q_OBJECT + +public: + explicit ImageCache(); + ~ImageCache(); + + QImage image(const QUrl &url, const QSize &size = QSize(), Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio, + Qt::TransformationMode transformatioMode = Qt::SmoothTransformation); + +private Q_SLOTS: + void onRequestFinished(ImageRequest *request); + +Q_SIGNALS: + void imageReady(); + +private: + void getImage(const QUrl &url); + + static QThread *thread; + + static QQueue queue; + static QCache cache; + + static int requestCount; + static int refCount; + + static const int MAX_REQUESTS; + + QNetworkAccessManager *m_manager; +}; + +class ImageRequest : public QObject +{ + Q_OBJECT + +private: + ImageRequest(QNetworkAccessManager *manager, const QUrl &u); + ~ImageRequest(); + + QNetworkReply *reply; + QUrl url; + + friend class ImageCache; + +private Q_SLOTS: + void onReplyFinished(); + +Q_SIGNALS: + void finished(ImageRequest *request); +}; + +#endif // IMAGECACHE_H diff --git a/app/src/desktop/itemmetadata.cpp b/app/src/desktop/itemmetadata.cpp new file mode 100644 index 0000000..ca2f76d --- /dev/null +++ b/app/src/desktop/itemmetadata.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "itemmetadata.h" +#include "image.h" +#include +#include + +ItemMetaDataValue::ItemMetaDataValue(const QString &name, const QVariant &value) +{ + this->name = name; + this->value = value; +} + +ItemMetaData::ItemMetaData(QWidget *parent) : + QWidget(parent), + m_image(new Image(this)), + m_label(new QLabel(this)), + m_layout(new QHBoxLayout(this)) +{ + m_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); + + m_layout->addWidget(m_image, Qt::AlignLeft | Qt::AlignTop); + m_layout->addWidget(m_label, Qt::AlignLeft | Qt::AlignTop); +} + +QList ItemMetaData::metaData() const { + return m_metaData; +} + +void ItemMetaData::setMetaData(const QList &metaData) { + m_metaData = metaData; + QString text; + + for (int i = 0; i < metaData.size(); i++) { + text.append(metaData.at(i).name + ": " + metaData.at(i).value.toString() + "\n"); + } + + m_label->setText(text); +} + +QSize ItemMetaData::thumbnailSize() const { + return m_image->size(); +} + +void ItemMetaData::setThumbnailSize(const QSize &size) { + m_image->setFixedSize(size); +} + +QUrl ItemMetaData::thumbnailSource() const { + return m_image->source(); +} + +void ItemMetaData::setThumbnailSource(const QUrl &url) { + m_image->setSource(url); + m_image->setVisible(!url.isEmpty()); +} + +void ItemMetaData::clear() { + m_image->setSource(QUrl()); + m_image->hide(); + m_label->clear(); +} diff --git a/app/src/desktop/itemmetadata.h b/app/src/desktop/itemmetadata.h new file mode 100644 index 0000000..7e91aad --- /dev/null +++ b/app/src/desktop/itemmetadata.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEMMETADATA_H +#define ITEMMETADATA_H + +#include +#include +#include +#include + +class Image; +class QHBoxLayout; +class QLabel; + +class ItemMetaDataValue +{ + +public: + explicit ItemMetaDataValue(const QString &name, const QVariant &value); + + QString name; + QVariant value; +}; + +class ItemMetaData : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QSize thumbnailSize READ thumbnailSize WRITE setThumbnailSize) + Q_PROPERTY(QUrl thumbnailSource READ thumbnailSource WRITE setThumbnailSource) + +public: + explicit ItemMetaData(QWidget *parent = 0); + + QList metaData() const; + void setMetaData(const QList &metaData); + + QSize thumbnailSize() const; + void setThumbnailSize(const QSize &size); + + QUrl thumbnailSource() const; + void setThumbnailSource(const QUrl &url); + +public Q_SLOTS: + void clear(); + +private: + Image *m_image; + + QLabel *m_label; + + QHBoxLayout *m_layout; + + QList m_metaData; +}; + +#endif // ITEMMETADATA_H diff --git a/app/src/desktop/logger.cpp b/app/src/desktop/logger.cpp new file mode 100644 index 0000000..a070d61 --- /dev/null +++ b/app/src/desktop/logger.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "logger.h" +#include +#include +#include +#include + +QString Logger::fn; +int Logger::vb = 0; + +Logger::Logger(QObject *parent) : + QObject(parent) +{ +} + +QString Logger::fileName() { + return fn; +} + +void Logger::setFileName(const QString &f) { + fn = f; +} + +QString Logger::text() { + QString output; + + if (!fn.isEmpty()) { + QFile file(fn); + + if (file.open(QFile::ReadOnly | QFile::Text)) { + QTextStream stream(&file); + + while (!stream.atEnd()) { + output.append(stream.readLine()); + output.append("\n"); + } + + file.close(); + } + } + + return output; +} + +int Logger::verbosity() { + return vb; +} + +void Logger::setVerbosity(int v) { + vb = v; +} + +void Logger::clear() { + if (!fn.isEmpty()) { + QFile::remove(fn); + } +} + +void Logger::log(const QString &message, int minimumVerbosity) { + if (minimumVerbosity <= vb) { + const QString date = QDateTime::currentDateTime().toString(Qt::ISODate); + QString output = QString("%1: %2\n").arg(date).arg(message); + + if (!fn.isEmpty()) { + QFile file(fn); + + if (file.open(QFile::Append | QFile::Text)) { + QTextStream stream(&file); + stream << output; + file.close(); + return; + } + + output = tr("%1: Cannot write to log file '%2'. Error: %3").arg(date).arg(fn).arg(file.errorString()); + } + + std::cout << output.toUtf8().constData(); + } +} diff --git a/app/src/desktop/logger.h b/app/src/desktop/logger.h new file mode 100644 index 0000000..eb93509 --- /dev/null +++ b/app/src/desktop/logger.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include + +class Logger : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(int verbosity READ verbosity WRITE setVerbosity) + + Q_ENUMS(Verbosity) + +public: + enum Verbosity { + NoVerbosity = 0, + LowestVerbosity, + LowVerbosity, + MediumVerbosity, + HighVerbosity, + HighestVerbosity + }; + + explicit Logger(QObject *parent = 0); + + static QString fileName(); + + static QString text(); + + static int verbosity(); + +public Q_SLOTS: + static void setFileName(const QString &f); + + static void setVerbosity(int v); + + static void clear(); + + static void log(const QString &message, int minimumVerbosity = LowestVerbosity); + +private: + static QString fn; + static int vb; +}; + +#endif // LOGGER_H diff --git a/app/src/desktop/main.cpp b/app/src/desktop/main.cpp new file mode 100644 index 0000000..2379a41 --- /dev/null +++ b/app/src/desktop/main.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "audioplayer.h" +#include "database.h" +#include "definitions.h" +#include "mainwindow.h" +#include "pluginmanager.h" +#include "settings.h" +#include "soundcloud.h" +#include "transfers.h" +#include +#include + +Q_DECL_EXPORT int main(int argc, char *argv[]) { + QApplication app(argc, argv); + app.setOrganizationName("MusiKloud2"); + app.setApplicationName("MusiKloud2"); + app.setApplicationVersion(VERSION_NUMBER); + app.setWindowIcon(QIcon::fromTheme("musikloud2")); + + const QStringList args = app.arguments(); + const int verbosity = args.indexOf("-v") + 1; + + if ((verbosity > 1) && (verbosity < args.size())) { + Logger::setVerbosity(qMax(1, args.at(verbosity).toInt())); + } + else { + Logger::setFileName(Settings::loggerFileName()); + Logger::setVerbosity(Settings::loggerVerbosity()); + } + + QScopedPointer player(AudioPlayer::instance()); + QScopedPointer settings(Settings::instance()); + QScopedPointer plugins(PluginManager::instance()); + QScopedPointer soundcloud(SoundCloud::instance()); + QScopedPointer transfers(Transfers::instance()); + + initDatabase(); + Settings::setNetworkProxy(); + SoundCloud::init(); + + plugins.data()->load(); + transfers.data()->restore(); + + if (Settings::restorePlaybackQueueOnStartup()) { + player.data()->restoreQueue(); + } + + MainWindow window; + window.show(); + + return app.exec(); +} diff --git a/app/src/desktop/mainwindow.cpp b/app/src/desktop/mainwindow.cpp new file mode 100644 index 0000000..2a6cd52 --- /dev/null +++ b/app/src/desktop/mainwindow.cpp @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mainwindow.h" +#include "aboutdialog.h" +#include "definitions.h" +#include "itemmetadata.h" +#include "playbackqueuepage.h" +#include "pluginmanager.h" +#include "plugintrackspage.h" +#include "searchdialog.h" +#include "settings.h" +#include "settingsdialog.h" +#include "soundcloudtrackspage.h" +#include "transfers.h" +#include "transferspage.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + m_fileMenu(new QMenu(tr("&File"), this)), + m_viewMenu(new QMenu(tr("&View"), this)), + m_playbackMenu(new QMenu(tr("&Playback"), this)), + m_transfersMenu(new QMenu(tr("&Downloads"), this)), + m_editMenu(new QMenu(tr("&Edit"), this)), + m_helpMenu(new QMenu(tr("&About"), this)), + m_openFilesAction(new QAction(QIcon::fromTheme("document-open"), tr("&Open files"), this)), + m_openUrlAction(new QAction(QIcon::fromTheme("folder-remote"), tr("Open &URL"), this)), + m_addFilesAction(new QAction(QIcon::fromTheme("list-add"), tr("&Add files"), this)), + m_addUrlAction(new QAction(QIcon::fromTheme("folder-remote"), tr("Add &URL"), this)), + m_searchAction(new QAction(QIcon::fromTheme("edit-find"), tr("&Search"), this)), + m_pluginsAction(new QAction(QIcon::fromTheme("view-refresh"), tr("Load &plugins"), this)), + m_quitAction(new QAction(QIcon::fromTheme("application-exit"), tr("&Quit"), this)), + m_showPlaybackQueueAction(new QAction(QIcon::fromTheme("music-library"), tr("Show &playback queue"), this)), + m_showTransfersAction(new QAction(QIcon::fromTheme("folder-publicshare"), tr("Show &transfers"), this)), + m_reloadAction(new QAction(QIcon::fromTheme("view-refresh"), tr("&Reload current tab"), this)), + m_closeAction(new QAction(QIcon::fromTheme("list-remove"), tr("&Close current tab"), this)), + m_playPauseAction(new QAction(QIcon::fromTheme("media-playback-start"), tr("&Play"), this)), + m_stopAction(new QAction(QIcon::fromTheme("media-playback-stop"), tr("&Stop"), this)), + m_previousAction(new QAction(QIcon::fromTheme("media-skip-backward"), tr("&Previous"), this)), + m_nextAction(new QAction(QIcon::fromTheme("media-skip-forward"), tr("&Next"), this)), + m_repeatAction(new QAction(QIcon::fromTheme("media-playlist-repeat"), tr("&Repeat"), this)), + m_shuffleAction(new QAction(QIcon::fromTheme("media-playlist-shuffle"), tr("&Shuffle"), this)), + m_stopAfterCurrentAction(new QAction(tr("Stop &after current track"), this)), + m_clearAction(new QAction(QIcon::fromTheme("list-remove"), tr("&Clear playback queue"), this)), + m_startTransfersAction(new QAction(QIcon::fromTheme("media-playback-start"), tr("&Start transfers"), this)), + m_pauseTransfersAction(new QAction(QIcon::fromTheme("media-playback-pause"), tr("&Pause transfers"), this)), + m_settingsAction(new QAction(QIcon::fromTheme("preferences-desktop"), tr("&Preferences"), this)), + m_aboutAction(new QAction(QIcon::fromTheme("help-about"), tr("&About"), this)), + m_positionSpacer(new QWidget(this)), + m_positionSpacerAction(0), + m_positionSlider(new QSlider(Qt::Horizontal, this)), + m_positionSliderAction(0), + m_positionLabel(new QLabel(this)), + m_positionLabelAction(0), + m_toolBar(new QToolBar(this)), + m_widget(new QWidget(this)), + m_tabWidget(new QTabWidget(m_widget)), + m_nowPlayingMetaData(new ItemMetaData(m_widget)), + m_layout(new QVBoxLayout(m_widget)) +{ + setWindowTitle("MusiKloud2"); + + // Initialise status bar + statusBar(); + + // Add menus + menuBar()->addMenu(m_fileMenu); + menuBar()->addMenu(m_viewMenu); + menuBar()->addMenu(m_playbackMenu); + menuBar()->addMenu(m_transfersMenu); + menuBar()->addMenu(m_editMenu); + menuBar()->addMenu(m_helpMenu); + + // Setup file menu + m_fileMenu->addAction(m_openFilesAction); + m_fileMenu->addAction(m_openUrlAction); + m_fileMenu->addAction(m_addFilesAction); + m_fileMenu->addAction(m_addUrlAction); + m_fileMenu->addAction(m_searchAction); + m_fileMenu->addSeparator(); + m_fileMenu->addAction(m_pluginsAction); + m_fileMenu->addAction(m_quitAction); + + // Setup file menu actions + m_openFilesAction->setShortcut(tr("Ctrl+O")); + m_openUrlAction->setShortcut(tr("Ctrl+U")); + m_addFilesAction->setShortcut(tr("Ctrl+Shift+O")); + m_addUrlAction->setShortcut(tr("Ctrl+Shift+U")); + m_searchAction->setShortcut(tr("Ctrl+Y")); + m_pluginsAction->setShortcut(tr("Ctrl+L")); + m_quitAction->setShortcut(tr("Ctrl+Q")); + + // Setup view menu + m_viewMenu->addAction(m_showPlaybackQueueAction); + m_viewMenu->addAction(m_showTransfersAction); + m_viewMenu->addSeparator(); + m_viewMenu->addAction(m_reloadAction); + m_viewMenu->addAction(m_closeAction); + + // Setup view menu actions + m_reloadAction->setEnabled(false); + m_closeAction->setShortcut(tr("Ctrl+W")); + m_closeAction->setEnabled(false); + + // Setup playback menu + m_playbackMenu->addAction(m_playPauseAction); + m_playbackMenu->addAction(m_stopAction); + m_playbackMenu->addAction(m_previousAction); + m_playbackMenu->addAction(m_nextAction); + m_playbackMenu->addSeparator(); + m_playbackMenu->addAction(m_repeatAction); + m_playbackMenu->addAction(m_shuffleAction); + m_playbackMenu->addAction(m_stopAfterCurrentAction); + m_playbackMenu->addSeparator(); + m_playbackMenu->addAction(m_clearAction); + + // Setup playback menu actions + m_playPauseAction->setShortcut(tr("Ctrl+Return")); + m_stopAction->setShortcut(tr("Ctrl+.")); + m_previousAction->setShortcut(tr("Alt+Up")); + m_nextAction->setShortcut(tr("Alt+Down")); + m_repeatAction->setCheckable(true); + m_repeatAction->setShortcut(tr("Ctrl+R")); + m_repeatAction->setToolTip(tr("Toggle repeat playback mode")); + m_shuffleAction->setCheckable(true); + m_shuffleAction->setShortcut(tr("Ctrl+S")); + m_shuffleAction->setToolTip(tr("Toggle shuffle playback mode")); + m_stopAfterCurrentAction->setCheckable(true); + m_clearAction->setShortcut(tr("Ctrl+Shift+X")); + + // Setup transfers menu + m_transfersMenu->addAction(m_startTransfersAction); + m_transfersMenu->addAction(m_pauseTransfersAction); + + // Setup edit menu + m_editMenu->addAction(m_settingsAction); + + // Setup edit menu actions + m_settingsAction->setShortcut(tr("Ctrl+P")); + + // Setup help menu + m_helpMenu->addAction(m_aboutAction); + + // Setup toolbar + m_toolBar->setObjectName("mainToolBar"); + m_toolBar->setWindowTitle(tr("Main toolbar")); + m_toolBar->setMovable(false); + m_toolBar->setAllowedAreas(Qt::TopToolBarArea); + m_toolBar->addAction(m_openFilesAction); + m_toolBar->addAction(m_addFilesAction); + m_toolBar->addAction(m_searchAction); + m_toolBar->addAction(m_previousAction); + m_toolBar->addAction(m_nextAction); + m_toolBar->addAction(m_playPauseAction); + m_toolBar->addAction(m_stopAction); + m_positionSpacerAction = m_toolBar->addWidget(m_positionSpacer); + m_positionSliderAction = m_toolBar->addWidget(m_positionSlider); + m_positionLabelAction = m_toolBar->addWidget(m_positionLabel); + m_toolBar->addAction(m_repeatAction); + m_toolBar->addAction(m_shuffleAction); + addToolBar(Qt::TopToolBarArea, m_toolBar); + + // Setup position spacer, slider and label + m_positionSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_positionSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_positionSliderAction->setVisible(false); + m_positionLabel->setFixedWidth(m_positionLabel->fontMetrics().width("000:00 / 000:00")); + m_positionLabel->setAlignment(Qt::AlignCenter); + m_positionLabelAction->setVisible(false); + + // Setup tab widget + m_tabWidget->setTabsClosable(true); + m_tabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectLeftTab); + m_tabWidget->tabBar()->setExpanding(false); + + // Setup now playing metadata + m_nowPlayingMetaData->setStyleSheet("font-weight: bold"); + m_nowPlayingMetaData->setThumbnailSize(QSize(96, 96)); + m_nowPlayingMetaData->hide(); + + // Setup layout + m_layout->addWidget(m_tabWidget); + m_layout->addWidget(m_nowPlayingMetaData); + m_layout->setContentsMargins(0, 0, 0, 0); + setCentralWidget(m_widget); + + // Restore window geometry/state + restoreGeometry(Settings::mainWindowGeometry()); + restoreState(Settings::mainWindowState()); + + // Connect signals and slots + connect(AudioPlayer::instance(), SIGNAL(durationChanged(qint64)), this, SLOT(onPlayerDurationChanged(qint64))); + connect(AudioPlayer::instance(), SIGNAL(metaDataChanged()), this, SLOT(updateNowPlayingMetaData())); + connect(AudioPlayer::instance(), SIGNAL(positionChanged(qint64)), this, SLOT(onPlayerPositionChanged(qint64))); + connect(AudioPlayer::instance(), SIGNAL(seekableChanged(bool)), this, SLOT(onPlayerSeekableChanged(bool))); + connect(AudioPlayer::instance(), SIGNAL(statusChanged(AudioPlayer::Status)), + this, SLOT(onPlayerStatusChanged(AudioPlayer::Status))); + connect(m_openFilesAction, SIGNAL(triggered()), this, SLOT(openFiles())); + connect(m_openUrlAction, SIGNAL(triggered()), this, SLOT(openUrl())); + connect(m_addFilesAction, SIGNAL(triggered()), this, SLOT(addFiles())); + connect(m_addUrlAction, SIGNAL(triggered()), this, SLOT(addUrl())); + connect(m_searchAction, SIGNAL(triggered()), this, SLOT(search())); + connect(m_pluginsAction, SIGNAL(triggered()), this, SLOT(loadPlugins())); + connect(m_quitAction, SIGNAL(triggered()), this, SLOT(close())); + connect(m_showPlaybackQueueAction, SIGNAL(triggered()), this, SLOT(showPlaybackQueue())); + connect(m_showTransfersAction, SIGNAL(triggered()), this, SLOT(showTransfers())); + connect(m_reloadAction, SIGNAL(triggered()), this, SLOT(reloadCurrentTab())); + connect(m_closeAction, SIGNAL(triggered()), this, SLOT(closeCurrentTab())); + connect(m_playPauseAction, SIGNAL(triggered()), AudioPlayer::instance(), SLOT(togglePlaying())); + connect(m_stopAction, SIGNAL(triggered()), AudioPlayer::instance(), SLOT(stop())); + connect(m_previousAction, SIGNAL(triggered()), AudioPlayer::instance(), SLOT(previous())); + connect(m_nextAction, SIGNAL(triggered()), AudioPlayer::instance(), SLOT(next())); + connect(m_repeatAction, SIGNAL(triggered(bool)), AudioPlayer::instance(), SLOT(setRepeatEnabled(bool))); + connect(m_shuffleAction, SIGNAL(triggered(bool)), AudioPlayer::instance(), SLOT(setShuffleEnabled(bool))); + connect(m_stopAfterCurrentAction, SIGNAL(triggered(bool)), + AudioPlayer::instance(), SLOT(setStopAfterCurrentTrack(bool))); + connect(m_clearAction, SIGNAL(triggered()), AudioPlayer::instance(), SLOT(clearQueue())); + connect(m_startTransfersAction, SIGNAL(triggered()), Transfers::instance(), SLOT(start())); + connect(m_pauseTransfersAction, SIGNAL(triggered()), Transfers::instance(), SLOT(pause())); + connect(m_settingsAction, SIGNAL(triggered()), this, SLOT(showSettingsDialog())); + connect(m_aboutAction, SIGNAL(triggered()), this, SLOT(showAboutDialog())); + connect(m_positionSlider, SIGNAL(sliderReleased()), this, SLOT(updatePlayerPosition())); + connect(m_positionSlider, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int))); + connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(onTabStatusChanged())); + connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int))); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + Settings::setMainWindowGeometry(saveGeometry()); + Settings::setMainWindowState(saveState()); + QMainWindow::closeEvent(event); +} + +void MainWindow::addFiles() { + QFileDialog dialog(this, tr("Add files")); + dialog.setOptions(QFileDialog::ReadOnly); + dialog.setFileMode(QFileDialog::ExistingFiles); + + if (dialog.exec() == QDialog::Accepted) { + AudioPlayer::instance()->addUrls(dialog.selectedUrls()); + } +} + +void MainWindow::addUrl() { + const QString url = QInputDialog::getText(this, tr("Add URL"), tr("URL")); + + if (!url.isEmpty()) { + AudioPlayer::instance()->addUrl(url); + } +} + +void MainWindow::openFiles() { + QFileDialog dialog(this, tr("Open files")); + dialog.setOptions(QFileDialog::ReadOnly); + dialog.setFileMode(QFileDialog::ExistingFiles); + + if (dialog.exec() == QDialog::Accepted) { + AudioPlayer::instance()->playUrls(dialog.selectedUrls()); + } +} + +void MainWindow::openUrl() { + const QString url = QInputDialog::getText(this, tr("Open URL"), tr("URL")); + + if (!url.isEmpty()) { + AudioPlayer::instance()->playUrl(url); + } +} + +void MainWindow::search() { + SearchDialog dialog(this); + + if (dialog.exec() == QDialog::Accepted) { + if (dialog.service() == Resources::SOUNDCLOUD) { + SoundCloudTracksPage *page = new SoundCloudTracksPage(m_tabWidget); + m_tabWidget->addTab(page, tr("Search '%1'").arg(dialog.query())); + m_tabWidget->setCurrentWidget(page); + connect(page, SIGNAL(statusChanged(Page::Status)), this, SLOT(onTabStatusChanged())); + connect(page, SIGNAL(statusTextChanged(QString)), statusBar(), SLOT(showMessage(QString))); + connect(page, SIGNAL(windowTitleChanged(QString)), this, SLOT(updateTabText(QString))); + + QVariantMap filters; + filters["q"] = dialog.query(); + filters["limit"] = MAX_RESULTS; + page->get("/tracks", filters); + } + else { + PluginTracksPage *page = new PluginTracksPage(dialog.service(), m_tabWidget); + m_tabWidget->addTab(page, tr("Search '%1'").arg(dialog.query())); + m_tabWidget->setCurrentWidget(page); + connect(page, SIGNAL(statusChanged(Page::Status)), this, SLOT(onTabStatusChanged())); + connect(page, SIGNAL(statusTextChanged(QString)), statusBar(), SLOT(showMessage(QString))); + connect(page, SIGNAL(windowTitleChanged(QString)), this, SLOT(updateTabText(QString))); + page->search(dialog.query(), dialog.order()); + } + + m_reloadAction->setEnabled(true); + m_closeAction->setEnabled(true); + } +} + +void MainWindow::reloadTab(int index) { + if (Page *page = qobject_cast(m_tabWidget->widget(index))) { + page->reload(); + } +} + +void MainWindow::reloadCurrentTab() { + reloadTab(m_tabWidget->currentIndex()); +} + +void MainWindow::closeTab(int index) { + if (QWidget *page = m_tabWidget->widget(index)) { + m_tabWidget->removeTab(index); + page->close(); + page->deleteLater(); + + if (m_tabWidget->count() == 0) { + m_reloadAction->setEnabled(false); + m_closeAction->setEnabled(false); + } + } +} + +void MainWindow::closeCurrentTab() { + closeTab(m_tabWidget->currentIndex()); +} + +void MainWindow::updateTabText(const QString &text) { + if (QWidget *widget = qobject_cast(sender())) { + m_tabWidget->setTabText(m_tabWidget->indexOf(widget), text); + } +} + +void MainWindow::onTabStatusChanged() { + if (Page *page = qobject_cast(m_tabWidget->currentWidget())) { + m_reloadAction->setEnabled(page->status() != Page::Loading); + } +} + +void MainWindow::loadPlugins() { + const int count = PluginManager::instance()->load(); + + if (count > 0) { + QMessageBox::information(this, tr("Load plugins"), tr("%1 new plugin(s) found")); + } + else { + QMessageBox::information(this, tr("Load plugins"), tr("No new plugins found")); + } +} + +void MainWindow::showAboutDialog() { + AboutDialog(this).exec(); +} + +void MainWindow::showPlaybackQueue() { + for (int i = 0; i < m_tabWidget->count(); i++) { + if (qobject_cast(m_tabWidget->widget(i))) { + m_tabWidget->setCurrentIndex(i); + m_reloadAction->setEnabled(true); + m_closeAction->setEnabled(true); + return; + } + } + + PlaybackQueuePage *page = new PlaybackQueuePage(m_tabWidget); + m_tabWidget->addTab(page, page->windowTitle()); + m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); + connect(page, SIGNAL(statusChanged(Page::Status)), this, SLOT(onTabStatusChanged())); + connect(page, SIGNAL(statusTextChanged(QString)), statusBar(), SLOT(showMessage(QString))); + connect(page, SIGNAL(windowTitleChanged(QString)), this, SLOT(updateTabText(QString))); + m_reloadAction->setEnabled(true); + m_closeAction->setEnabled(true); +} + +void MainWindow::showSettingsDialog() { + SettingsDialog(this).exec(); +} + +void MainWindow::showTransfers() { + for (int i = 0; i < m_tabWidget->count(); i++) { + if (qobject_cast(m_tabWidget->widget(i))) { + m_tabWidget->setCurrentIndex(i); + m_reloadAction->setEnabled(true); + m_closeAction->setEnabled(true); + return; + } + } + + TransfersPage *page = new TransfersPage(m_tabWidget); + m_tabWidget->addTab(page, page->windowTitle()); + m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); + connect(page, SIGNAL(statusChanged(Page::Status)), this, SLOT(onTabStatusChanged())); + connect(page, SIGNAL(statusTextChanged(QString)), statusBar(), SLOT(showMessage(QString))); + connect(page, SIGNAL(windowTitleChanged(QString)), this, SLOT(updateTabText(QString))); + m_reloadAction->setEnabled(true); + m_closeAction->setEnabled(true); +} + +void MainWindow::updateNowPlayingMetaData() { + if (AudioPlayerMetaData::isAvailable()) { + QList metaData; + metaData << ItemMetaDataValue(tr("Title"), AudioPlayerMetaData::title()); + metaData << ItemMetaDataValue(tr("Artist"), AudioPlayerMetaData::artist()); + metaData << ItemMetaDataValue(tr("Genre"), AudioPlayerMetaData::genre()); + metaData << ItemMetaDataValue(tr("Duration"), AudioPlayerMetaData::durationString()); + m_nowPlayingMetaData->setThumbnailSource(AudioPlayerMetaData::largeThumbnailUrl()); + m_nowPlayingMetaData->setMetaData(metaData); + m_nowPlayingMetaData->show(); + setWindowTitle(QString("MusiKloud2 - %1 - %2").arg(AudioPlayerMetaData::artist()) + .arg(AudioPlayerMetaData::title())); + } + else { + m_nowPlayingMetaData->hide(); + m_nowPlayingMetaData->clear(); + setWindowTitle("MusiKloud2"); + } +} + +void MainWindow::updatePlayerPosition() { + AudioPlayer::instance()->setPosition(qint64(m_positionSlider->value())); +} + +void MainWindow::onPlayerDurationChanged(qint64 duration) { + m_positionSlider->setMaximum(int(duration)); + m_positionLabel->setText(QString("%1 / %2").arg(AudioPlayer::instance()->positionString()) + .arg(AudioPlayer::instance()->durationString())); +} + +void MainWindow::onPlayerPositionChanged(qint64 position) { + if (m_positionSlider->isEnabled()) { + if (!m_positionSlider->isSliderDown()) { + m_positionSlider->setValue(int(position)); + } + } + else { + m_positionLabel->setText(QString("%1 / %2").arg(AudioPlayer::instance()->positionString()) + .arg(AudioPlayer::instance()->durationString())); + } +} + +void MainWindow::onPlayerSeekableChanged(bool seekable) { + m_positionSpacerAction->setVisible(!seekable); + m_positionSliderAction->setVisible(seekable); +} + +void MainWindow::onPlayerStatusChanged(AudioPlayer::Status status) { + switch (status) { + case AudioPlayer::Stopped: + m_playPauseAction->setIcon(QIcon::fromTheme("media-playback-start")); + m_playPauseAction->setText(tr("Play")); + m_positionSpacerAction->setVisible(true); + m_positionSliderAction->setVisible(false); + m_positionLabelAction->setVisible(false); + break; + case AudioPlayer::Paused: + m_playPauseAction->setIcon(QIcon::fromTheme("media-playback-start")); + m_playPauseAction->setText(tr("Play")); + break; + case AudioPlayer::Playing: + m_playPauseAction->setIcon(QIcon::fromTheme("media-playback-pause")); + m_playPauseAction->setText(tr("Pause")); + m_positionLabelAction->setVisible(true); + break; + case AudioPlayer::Failed: + m_playPauseAction->setIcon(QIcon::fromTheme("media-playback-start")); + m_playPauseAction->setText(tr("Play")); + m_positionSpacerAction->setVisible(true); + m_positionSliderAction->setVisible(false); + m_positionLabelAction->setVisible(false); + QMessageBox::critical(this, tr("Playback error"), AudioPlayer::instance()->errorString()); + break; + default: + m_playPauseAction->setIcon(QIcon::fromTheme("media-playback-start")); + m_playPauseAction->setText(tr("Play")); + break; + } +} + +void MainWindow::onSliderValueChanged(int value) { + m_positionLabel->setText(QString("%1 / %2").arg(Utils::formatMSecs(qint64(value))) + .arg(AudioPlayer::instance()->durationString())); +} diff --git a/app/src/desktop/mainwindow.h b/app/src/desktop/mainwindow.h new file mode 100644 index 0000000..dfe77ff --- /dev/null +++ b/app/src/desktop/mainwindow.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "audioplayer.h" +#include + +class ItemMetaData; +class QLabel; +class QSlider; +class QTabWidget; +class QToolButton; +class QVBoxLayout; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + +private Q_SLOTS: + void addFiles(); + void addUrl(); + void openFiles(); + void openUrl(); + void search(); + + void reloadTab(int index); + void reloadCurrentTab(); + void closeTab(int index); + void closeCurrentTab(); + void updateTabText(const QString &text); + void onTabStatusChanged(); + + void loadPlugins(); + + void showAboutDialog(); + void showPlaybackQueue(); + void showSettingsDialog(); + void showTransfers(); + + void updateNowPlayingMetaData(); + + void updatePlayerPosition(); + void onPlayerDurationChanged(qint64 duration); + void onPlayerPositionChanged(qint64 position); + void onPlayerSeekableChanged(bool seekable); + void onPlayerStatusChanged(AudioPlayer::Status status); + + void onSliderValueChanged(int value); + +private: + virtual void closeEvent(QCloseEvent *event); + + QMenu *m_fileMenu; + QMenu *m_viewMenu; + QMenu *m_playbackMenu; + QMenu *m_transfersMenu; + QMenu *m_editMenu; + QMenu *m_helpMenu; + + QAction *m_openFilesAction; + QAction *m_openUrlAction; + QAction *m_addFilesAction; + QAction *m_addUrlAction; + QAction *m_searchAction; + QAction *m_pluginsAction; + QAction *m_quitAction; + + QAction *m_showPlaybackQueueAction; + QAction *m_showTransfersAction; + QAction *m_reloadAction; + QAction *m_closeAction; + + QAction *m_playPauseAction; + QAction *m_stopAction; + QAction *m_previousAction; + QAction *m_nextAction; + QAction *m_repeatAction; + QAction *m_shuffleAction; + QAction *m_stopAfterCurrentAction; + QAction *m_clearAction; + + QAction *m_startTransfersAction; + QAction *m_pauseTransfersAction; + + QAction *m_settingsAction; + + QAction *m_aboutAction; + + QWidget *m_positionSpacer; + QAction *m_positionSpacerAction; + + QSlider *m_positionSlider; + QAction *m_positionSliderAction; + + QLabel *m_positionLabel; + QAction *m_positionLabelAction; + + QToolBar *m_toolBar; + + QWidget *m_widget; + + QTabWidget *m_tabWidget; + + ItemMetaData *m_nowPlayingMetaData; + + QVBoxLayout *m_layout; +}; + +#endif // MAINWINDOW_H diff --git a/app/src/desktop/networksettingspage.cpp b/app/src/desktop/networksettingspage.cpp new file mode 100644 index 0000000..97c2be7 --- /dev/null +++ b/app/src/desktop/networksettingspage.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "networksettingspage.h" +#include "networkproxytypemodel.h" +#include "settings.h" +#include +#include +#include +#include +#include + +NetworkSettingsPage::NetworkSettingsPage(QWidget *parent) : + SettingsPage(parent), + m_proxyTypeModel(new NetworkProxyTypeModel(this)), + m_proxyCheckBox(new QCheckBox(tr("&Enable network proxy"), this)), + m_authCheckBox(new QCheckBox(tr("Enable &authentication"), this)), + m_proxyTypeSelector(new QComboBox(this)), + m_hostEdit(new QLineEdit(this)), + m_usernameEdit(new QLineEdit(this)), + m_passwordEdit(new QLineEdit(this)), + m_portSpinBox(new QSpinBox(this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Network")); + + m_proxyTypeSelector->setModel(m_proxyTypeModel); + + m_passwordEdit->setEchoMode(QLineEdit::Password); + + m_portSpinBox->setMaximum(100000); + + m_layout->addRow(m_proxyCheckBox); + m_layout->addRow(m_proxyTypeSelector); + m_layout->addRow(tr("&Host:"), m_hostEdit); + m_layout->addRow(tr("&Port:"), m_portSpinBox); + m_layout->addRow(m_authCheckBox); + m_layout->addRow(tr("&Username:"), m_usernameEdit); + m_layout->addRow(tr("&Password:"), m_passwordEdit); + + restore(); +} + +void NetworkSettingsPage::restore() { + m_proxyCheckBox->setChecked(Settings::networkProxyEnabled()); + m_authCheckBox->setChecked(Settings::networkProxyAuthenticationEnabled()); + m_proxyTypeSelector->setCurrentIndex(qMax(0, m_proxyTypeSelector->findData(Settings::networkProxyType()))); + m_hostEdit->setText(Settings::networkProxyHost()); + m_usernameEdit->setText(Settings::networkProxyUsername()); + m_passwordEdit->setText(Settings::networkProxyPassword()); + m_portSpinBox->setValue(Settings::networkProxyPort()); +} + +void NetworkSettingsPage::save() { + const bool enabled = m_proxyCheckBox->isChecked(); + const bool authEnabled = m_authCheckBox->isChecked(); + const int type = m_proxyTypeSelector->itemData(m_proxyTypeSelector->currentIndex()).toInt(); + const QString host = m_hostEdit->text(); + const QString username = m_usernameEdit->text(); + const QString password = m_passwordEdit->text(); + const int port = m_portSpinBox->value(); + + Settings::setNetworkProxyEnabled(enabled); + Settings::setNetworkProxyAuthenticationEnabled(authEnabled); + Settings::setNetworkProxyType(type); + Settings::setNetworkProxyHost(host); + Settings::setNetworkProxyUsername(username); + Settings::setNetworkProxyPassword(password); + Settings::setNetworkProxyPort(port); + Settings::setNetworkProxy(); +} diff --git a/app/src/desktop/networksettingspage.h b/app/src/desktop/networksettingspage.h new file mode 100644 index 0000000..24fb71a --- /dev/null +++ b/app/src/desktop/networksettingspage.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NETWORKSETTINGSPAGE_H +#define NETWORKSETTINGSPAGE_H + +#include "settingspage.h" + +class NetworkProxyTypeModel; +class QCheckBox; +class QComboBox; +class QFormLayout; +class QLineEdit; +class QSpinBox; + +class NetworkSettingsPage : public SettingsPage +{ + Q_OBJECT + +public: + explicit NetworkSettingsPage(QWidget *parent = 0); + +public Q_SLOTS: + virtual void restore(); + virtual void save(); + +private: + NetworkProxyTypeModel *m_proxyTypeModel; + + QCheckBox *m_proxyCheckBox; + QCheckBox *m_authCheckBox; + + QComboBox *m_proxyTypeSelector; + + QLineEdit *m_hostEdit; + QLineEdit *m_usernameEdit; + QLineEdit *m_passwordEdit; + + QSpinBox *m_portSpinBox; + + QFormLayout *m_layout; +}; + +#endif // NETWORKSETTINGSPAGE_H diff --git a/app/src/desktop-qml/qml/plugins/PluginSettingsTextField.qml b/app/src/desktop/page.cpp similarity index 50% rename from app/src/desktop-qml/qml/plugins/PluginSettingsTextField.qml rename to app/src/desktop/page.cpp index 87c7d01..bcb353e 100644 --- a/app/src/desktop-qml/qml/plugins/PluginSettingsTextField.qml +++ b/app/src/desktop/page.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,35 +14,39 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 +#include "page.h" +#include -RowLayout { - id: root +Page::Page(QWidget *parent) : + QWidget(parent) +{ +} - property alias title: titleLabel.text - property string key +Page::Status Page::status() const { + return Null; +} - function setKey(key, defaultValue) { - root.key = key; - var value = Settings.value(key); +QString Page::statusText() const { + return QString(); +} - if (value === undefined) { - value = defaultValue; - } +void Page::cancel() {} - textField.text = value; - } +void Page::reload() {} - Label { - id: titleLabel +void Page::toggleLoading() { + if (status() == Loading) { + cancel(); } + else { + reload(); + } +} - TextField { - id: textField - - Layout.fillWidth: true - onTextChanged: Settings.setValue(root.key, textField.text) +bool Page::event(QEvent *e) { + if (e->type() == QEvent::WindowTitleChange) { + emit windowTitleChanged(windowTitle()); } + + return QWidget::event(e); } diff --git a/app/src/desktop/page.h b/app/src/desktop/page.h new file mode 100644 index 0000000..e9f3849 --- /dev/null +++ b/app/src/desktop/page.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PAGE_H +#define PAGE_H + +#include + +class Page : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged) + + Q_ENUMS(Status) + +public: + enum Status { + Null = 0, + Loading, + Ready + }; + + explicit Page(QWidget *parent = 0); + + virtual Status status() const; + virtual QString statusText() const; + +public Q_SLOTS: + virtual void cancel(); + virtual void reload(); + virtual void toggleLoading(); + +protected: + virtual bool event(QEvent *e); + +Q_SIGNALS: + void statusChanged(Page::Status status); + void statusTextChanged(const QString &text); + void windowTitleChanged(const QString &text); +}; + +#endif // PAGE_H diff --git a/app/src/desktop/playbackqueuepage.cpp b/app/src/desktop/playbackqueuepage.cpp new file mode 100644 index 0000000..58c44d7 --- /dev/null +++ b/app/src/desktop/playbackqueuepage.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "playbackqueuepage.h" +#include "audioplayer.h" +#include "plugindownloaddialog.h" +#include "resources.h" +#include "settings.h" +#include "soundclouddownloaddialog.h" +#include "transfers.h" +#include "treeview.h" +#include +#include +#include +#include +#include + +PlaybackQueuePage::PlaybackQueuePage(QWidget *parent) : + Page(parent), + m_view(new MKTreeView(this)), + m_layout(new QVBoxLayout(this)) +{ + setAcceptDrops(true); + updateWindowTitle(); + + m_view->setModel(AudioPlayer::instance()->queue()); + m_view->setAlternatingRowColors(true); + m_view->setSelectionBehavior(QTreeView::SelectRows); + m_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_view->setEditTriggers(QTreeView::NoEditTriggers); + m_view->setItemsExpandable(false); + m_view->setUniformRowHeights(true); + m_view->setAllColumnsShowFocus(true); + m_view->setRootIsDecorated(false); + m_view->header()->restoreState(Settings::tracksHeaderViewState()); + m_view->setItemMetaDataEnabled(true); + m_view->setItemMetaDataThumbnailRole(TrackModel::LargeThumbnailUrlRole); + + QList roles; + roles << ItemMetaDataRole(tr("Title"), TrackModel::TitleRole); + roles << ItemMetaDataRole(tr("Artist"), TrackModel::ArtistRole); + roles << ItemMetaDataRole(tr("Genre"), TrackModel::GenreRole); + roles << ItemMetaDataRole(tr("Duration"), TrackModel::DurationStringRole); + m_view->setItemMetaDataTextRoles(roles); + + m_layout->addWidget(m_view); + m_layout->setContentsMargins(0, 0, 0, 0); + + connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(playTrack(QModelIndex))); + connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + connect(AudioPlayer::instance(), SIGNAL(queueCountChanged(int)), this, SLOT(updateWindowTitle())); + connect(AudioPlayer::instance(), SIGNAL(currentIndexChanged(int)), this, SLOT(updateWindowTitle())); +} + +void PlaybackQueuePage::closeEvent(QCloseEvent *e) { + Settings::setTracksHeaderViewState(m_view->header()->saveState()); + Page::closeEvent(e); +} + +void PlaybackQueuePage::dragEnterEvent(QDragEnterEvent *e) { + if (e->mimeData()->hasUrls()) { + e->acceptProposedAction(); + } +} + +void PlaybackQueuePage::dropEvent(QDropEvent *e) { + if (e->mimeData()->hasUrls()) { + AudioPlayer::instance()->addUrls(e->mimeData()->urls()); + } +} + +void PlaybackQueuePage::downloadTrack(const QModelIndex &index) { + const QString trackId = index.data(TrackModel::IdRole).toString(); + const QString title = index.data(TrackModel::TitleRole).toString(); + const QString service = index.data(TrackModel::ServiceRole).toString(); + + if (service == Resources::SOUNDCLOUD) { + SoundCloudDownloadDialog dialog(this); + dialog.get(trackId); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, trackId, dialog.streamId(), + QUrl(), title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } + } + else { + const QUrl streamUrl = index.data(TrackModel::StreamUrlRole).toString(); + PluginDownloadDialog dialog(service, this); + dialog.list(trackId, streamUrl.isEmpty()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(service, trackId, dialog.streamId(), streamUrl, + title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } + } +} + +void PlaybackQueuePage::playTrack(const QModelIndex &index) { + AudioPlayer::instance()->setCurrentIndex(index.row(), true); +} + +void PlaybackQueuePage::removeTrack(const QModelIndex &index) { + AudioPlayer::instance()->removeTrack(index.row()); +} + +void PlaybackQueuePage::showContextMenu(const QPoint &pos) { + if (!m_view->currentIndex().isValid()) { + return; + } + + if (m_view->currentIndex().data(TrackModel::DownloadableRole).toBool()) { + QMenu menu(this); + QAction *playAction = menu.addAction(QIcon::fromTheme("media-playback-start"), tr("&Play")); + QAction *downloadAction = menu.addAction(QIcon::fromTheme("document-save"), tr("&Download")); + QAction *removeAction = menu.addAction(QIcon::fromTheme("list-remove"), tr("&Remove")); + QAction *action = menu.exec(m_view->mapToGlobal(pos)); + + if (action == playAction) { + playTrack(m_view->currentIndex()); + } + else if (action == downloadAction) { + downloadTrack(m_view->currentIndex()); + } + else if (action == removeAction) { + removeTrack(m_view->currentIndex()); + } + } + else { + QMenu menu(this); + QAction *playAction = menu.addAction(QIcon::fromTheme("media-playback-start"), tr("&Play")); + QAction *removeAction = menu.addAction(QIcon::fromTheme("list-remove"), tr("&Remove")); + QAction *action = menu.exec(m_view->mapToGlobal(pos)); + + if (action == playAction) { + playTrack(m_view->currentIndex()); + } + else if (action == removeAction) { + removeTrack(m_view->currentIndex()); + } + } +} + +void PlaybackQueuePage::updateWindowTitle() { + if (AudioPlayer::instance()->queueCount() > 0) { + if (AudioPlayer::instance()->currentIndex() >= 0) { + setWindowTitle(tr("Playback queue (%1/%2)").arg(AudioPlayer::instance()->currentIndex() + 1) + .arg(AudioPlayer::instance()->queueCount())); + } + else { + setWindowTitle(tr("Playback queue (%1)").arg(AudioPlayer::instance()->queueCount())); + } + } + else { + setWindowTitle(tr("Playback queue")); + } +} diff --git a/app/src/desktop/playbackqueuepage.h b/app/src/desktop/playbackqueuepage.h new file mode 100644 index 0000000..2a9331a --- /dev/null +++ b/app/src/desktop/playbackqueuepage.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLAYBACKQUEUEPAGE_H +#define PLAYBACKQUEUEPAGE_H + +#include "page.h" + +class MKTreeView; +class QVBoxLayout; + +class PlaybackQueuePage : public Page +{ + Q_OBJECT + +public: + explicit PlaybackQueuePage(QWidget *parent = 0); + +private Q_SLOTS: + void downloadTrack(const QModelIndex &index); + void playTrack(const QModelIndex &index); + void removeTrack(const QModelIndex &index); + + void showContextMenu(const QPoint &pos); + + void updateWindowTitle(); + +private: + virtual void closeEvent(QCloseEvent *e); + virtual void dragEnterEvent(QDragEnterEvent *e); + virtual void dropEvent(QDropEvent *e); + + MKTreeView *m_view; + + QVBoxLayout *m_layout; +}; + +#endif // PLAYBACKQUEUEPAGE_H diff --git a/app/src/desktop/plugins/plugindownloaddialog.cpp b/app/src/desktop/plugins/plugindownloaddialog.cpp new file mode 100644 index 0000000..01df70e --- /dev/null +++ b/app/src/desktop/plugins/plugindownloaddialog.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "plugindownloaddialog.h" +#include "categorynamemodel.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include + +PluginDownloadDialog::PluginDownloadDialog(const QString &service, QWidget *parent) : + QDialog(parent), + m_streamModel(new PluginStreamModel(this)), + m_categoryModel(new CategoryNameModel(this)), + m_streamSelector(new QComboBox(this)), + m_categorySelector(new QComboBox(this)), + m_commandCheckBox(new QCheckBox(tr("&Override global custom command"), this)), + m_commandEdit(new QLineEdit(this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Download track")); + + m_streamModel->setService(service); + + m_streamSelector->setModel(m_streamModel); + + m_categorySelector->setModel(m_categoryModel); + + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + m_layout->addRow(tr("Audio &format:"), m_streamSelector); + m_layout->addRow(tr("&Category:"), m_categorySelector); + m_layout->addRow(tr("&Custom command:"), m_commandEdit); + m_layout->addRow(m_commandCheckBox); + m_layout->addRow(m_buttonBox); + + connect(m_streamModel, SIGNAL(statusChanged(ResourcesRequest::Status)), + this, SLOT(onStreamModelStatusChanged(ResourcesRequest::Status))); + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +QString PluginDownloadDialog::trackId() const { + return m_trackId; +} + +QString PluginDownloadDialog::streamId() const { + return m_streamSelector->itemData(m_streamSelector->currentIndex()).toMap().value("id").toString(); +} + +QString PluginDownloadDialog::category() const { + return m_categorySelector->currentText(); +} + +QString PluginDownloadDialog::customCommand() const { + return m_commandEdit->text(); +} + +bool PluginDownloadDialog::customCommandOverrideEnabled() const { + return m_commandCheckBox->isChecked(); +} + +void PluginDownloadDialog::accept() { + Settings::setDefaultDownloadFormat(m_streamModel->service(), m_streamSelector->currentText()); + Settings::setDefaultCategory(category()); + QDialog::accept(); +} + +void PluginDownloadDialog::list(const QString &trackId, bool listStreams) { + m_trackId = trackId; + + if (listStreams) { + m_streamModel->list(trackId); + } + else { + m_streamModel->clear(); + m_streamModel->append(tr("Default format"), QVariant()); + } +} + +void PluginDownloadDialog::onStreamModelStatusChanged(ResourcesRequest::Status status) { + switch (status) { + case ResourcesRequest::Loading: + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + break; + case ResourcesRequest::Ready: + m_streamSelector->setCurrentIndex(qMax(0, m_streamModel->match("name", + Settings::defaultDownloadFormat(m_streamModel->service())))); + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + break; + case ResourcesRequest::Failed: + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + QMessageBox::critical(this, tr("Error"), m_streamModel->errorString()); + break; + default: + break; + } +} diff --git a/app/src/desktop/plugins/plugindownloaddialog.h b/app/src/desktop/plugins/plugindownloaddialog.h new file mode 100644 index 0000000..1bdafbb --- /dev/null +++ b/app/src/desktop/plugins/plugindownloaddialog.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINDOWNLOADDIALOG_H +#define PLUGINDOWNLOADDIALOG_H + +#include "pluginstreammodel.h" +#include + +class CategoryNameModel; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QFormLayout; +class QLineEdit; + +class PluginDownloadDialog : public QDialog +{ + Q_OBJECT + + Q_PROPERTY(QString trackId READ trackId) + Q_PROPERTY(QString streamId READ streamId) + Q_PROPERTY(QString category READ category) + Q_PROPERTY(QString customCommand READ customCommand) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled) + +public: + explicit PluginDownloadDialog(const QString &service, QWidget *parent = 0); + + QString trackId() const; + + QString streamId() const; + + QString category() const; + + QString customCommand() const; + bool customCommandOverrideEnabled() const; + +public Q_SLOTS: + virtual void accept(); + + void list(const QString &trackId, bool listStreams = true); + +private Q_SLOTS: + void onStreamModelStatusChanged(ResourcesRequest::Status status); + +private: + PluginStreamModel *m_streamModel; + CategoryNameModel *m_categoryModel; + + QComboBox *m_streamSelector; + QComboBox *m_categorySelector; + + QCheckBox *m_commandCheckBox; + + QLineEdit *m_commandEdit; + + QDialogButtonBox *m_buttonBox; + + QFormLayout *m_layout; + + QString m_trackId; +}; + +#endif // PLUGINDOWNLOADDIALOG_H diff --git a/app/src/desktop/plugins/plugintrackspage.cpp b/app/src/desktop/plugins/plugintrackspage.cpp new file mode 100644 index 0000000..cd53c10 --- /dev/null +++ b/app/src/desktop/plugins/plugintrackspage.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "plugintrackspage.h" +#include "audioplayer.h" +#include "plugindownloaddialog.h" +#include "resources.h" +#include "settings.h" +#include "transfers.h" +#include "treeview.h" +#include +#include +#include +#include + +PluginTracksPage::PluginTracksPage(const QString &service, QWidget *parent) : + Page(parent), + m_model(new PluginTrackModel(this)), + m_view(new MKTreeView(this)), + m_layout(new QVBoxLayout(this)) +{ + m_model->setService(service); + + m_view->setModel(m_model); + m_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_view->setAlternatingRowColors(true); + m_view->setSelectionBehavior(QTreeView::SelectRows); + m_view->setEditTriggers(QTreeView::NoEditTriggers); + m_view->setItemsExpandable(false); + m_view->setUniformRowHeights(true); + m_view->setAllColumnsShowFocus(true); + m_view->setRootIsDecorated(false); + m_view->header()->restoreState(Settings::tracksHeaderViewState()); + m_view->setItemMetaDataEnabled(true); + m_view->setItemMetaDataThumbnailRole(PluginTrackModel::LargeThumbnailUrlRole); + + QList roles; + roles << ItemMetaDataRole(tr("Title"), PluginTrackModel::TitleRole); + roles << ItemMetaDataRole(tr("Artist"), PluginTrackModel::ArtistRole); + roles << ItemMetaDataRole(tr("Genre"), PluginTrackModel::GenreRole); + roles << ItemMetaDataRole(tr("Duration"), PluginTrackModel::DurationStringRole); + m_view->setItemMetaDataTextRoles(roles); + + m_layout->addWidget(m_view); + m_layout->setContentsMargins(0, 0, 0, 0); + + connect(m_model, SIGNAL(statusChanged(ResourcesRequest::Status)), + this, SLOT(onModelStatusChanged(ResourcesRequest::Status))); + connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(playTrack(QModelIndex))); + connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); +} + +void PluginTracksPage::closeEvent(QCloseEvent *e) { + Settings::setTracksHeaderViewState(m_view->header()->saveState()); + Page::closeEvent(e); +} + +PluginTracksPage::Status PluginTracksPage::status() const { + return m_status; +} + +void PluginTracksPage::setStatus(PluginTracksPage::Status s) { + if (s != status()) { + m_status = s; + emit statusChanged(s); + } +} + +QString PluginTracksPage::statusText() const { + return m_statusText; +} + +void PluginTracksPage::setStatusText(const QString &text) { + if (text != statusText()) { + m_statusText = text; + emit statusTextChanged(text); + } +} + +void PluginTracksPage::cancel() { + m_model->cancel(); +} + +void PluginTracksPage::reload() { + m_model->reload(); +} + +void PluginTracksPage::list(const QString &resourceId) { + m_model->list(resourceId); +} + +void PluginTracksPage::search(const QString &query, const QString &order) { + m_model->search(query, order); +} + +void PluginTracksPage::downloadTrack(const QModelIndex &index) { + const QString trackId = index.data(PluginTrackModel::IdRole).toString(); + const QString title = index.data(PluginTrackModel::TitleRole).toString(); + const QUrl streamUrl = index.data(PluginTrackModel::StreamUrlRole).toString(); + + PluginDownloadDialog dialog(m_model->service(), this); + dialog.list(trackId, streamUrl.isEmpty()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(m_model->service(), trackId, dialog.streamId(), streamUrl, + title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } +} + +void PluginTracksPage::playTrack(const QModelIndex &index) { + if (PluginTrack *track = m_model->get(index.row())) { + AudioPlayer::instance()->playTrack(track); + } +} + +void PluginTracksPage::queueTrack(const QModelIndex &index) { + if (PluginTrack *track = m_model->get(index.row())) { + AudioPlayer::instance()->addTrack(track); + } +} + +void PluginTracksPage::showContextMenu(const QPoint &pos) { + const QModelIndex index = m_view->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (index.data(PluginTrackModel::DownloadableRole).toBool()) { + QMenu menu(this); + QAction *playAction = menu.addAction(QIcon::fromTheme("media-playback-start"), tr("&Play")); + QAction *queueAction = menu.addAction(QIcon::fromTheme("list-add"), tr("&Queue")); + QAction *downloadAction = menu.addAction(QIcon::fromTheme("document-save"), tr("&Download")); + + foreach (const QVariant &v, index.data(PluginTrackModel::ActionsRole).toList()) { + const QVariantMap a = v.toMap(); + QAction *pa = menu.addAction(a.value("label").toString()); + pa->setData(a); + } + + QAction *action = menu.exec(m_view->mapToGlobal(pos)); + + if (!action) { + return; + } + + if (action == playAction) { + playTrack(m_view->currentIndex()); + } + else if (action == queueAction) { + queueTrack(m_view->currentIndex()); + } + else if (action == downloadAction) { + downloadTrack(m_view->currentIndex()); + } + else if (PluginTrack *track = m_model->get(index.row())) { + const QVariantMap data = action->data().toMap(); + const QString method = data.value("method").toString(); + + if (method == "del") { + track->del(data.value("type").toString(), data.value("id").toString()); + } + else if (method == "insert") { + track->insert(data.value("type").toString(), data.value("id").toString()); + } + } + } + else { + QMenu menu(this); + QAction *playAction = menu.addAction(QIcon::fromTheme("media-playback-start"), tr("&Play")); + QAction *queueAction = menu.addAction(QIcon::fromTheme("list-add"), tr("&Queue")); + + foreach (const QVariant &v, index.data(PluginTrackModel::ActionsRole).toList()) { + const QVariantMap a = v.toMap(); + QAction *pa = menu.addAction(a.value("label").toString()); + pa->setData(a); + } + + QAction *action = menu.exec(m_view->mapToGlobal(pos)); + + if (!action) { + return; + } + + if (action == playAction) { + playTrack(m_view->currentIndex()); + } + else if (action == queueAction) { + queueTrack(m_view->currentIndex()); + } + else if (PluginTrack *track = m_model->get(index.row())) { + const QVariantMap data = action->data().toMap(); + const QString method = data.value("method").toString(); + + if (method == "del") { + track->del(data.value("type").toString(), data.value("id").toString()); + } + else if (method == "insert") { + track->insert(data.value("type").toString(), data.value("id").toString()); + } + } + } +} + +void PluginTracksPage::onModelStatusChanged(ResourcesRequest::Status status) { + switch (status) { + case ResourcesRequest::Loading: + setStatus(Loading); + setStatusText(tr("Loading tracks")); + break; + case ResourcesRequest::Failed: + setStatus(Ready); + setStatusText(m_model->errorString()); + QMessageBox::critical(this, tr("Error"), m_model->errorString()); + break; + default: + setStatus(Ready); + setStatusText(tr("Finished")); + break; + } +} diff --git a/app/src/desktop/plugins/plugintrackspage.h b/app/src/desktop/plugins/plugintrackspage.h new file mode 100644 index 0000000..3bdf8c4 --- /dev/null +++ b/app/src/desktop/plugins/plugintrackspage.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINTRACKSPAGE_H +#define PLUGINTRACKSPAGE_H + +#include "page.h" +#include "plugintrackmodel.h" + +class MKTreeView; +class QVBoxLayout; + +class PluginTracksPage : public Page +{ + Q_OBJECT + +public: + explicit PluginTracksPage(const QString &service, QWidget *parent = 0); + + virtual Status status() const; + virtual QString statusText() const; + +public Q_SLOTS: + virtual void cancel(); + virtual void reload(); + + void list(const QString &resourceId); + void search(const QString &query, const QString &order); + +private Q_SLOTS: + void downloadTrack(const QModelIndex &index); + void playTrack(const QModelIndex &index); + void queueTrack(const QModelIndex &index); + + void showContextMenu(const QPoint &pos); + + void onModelStatusChanged(ResourcesRequest::Status status); + +private: + virtual void closeEvent(QCloseEvent *e); + + void setStatus(Status s); + void setStatusText(const QString &text); + + PluginTrackModel *m_model; + + MKTreeView *m_view; + + QVBoxLayout *m_layout; + + Status m_status; + QString m_statusText; +}; + +#endif // PLUGINTRACKSPAGE_H diff --git a/app/src/desktop/pluginsettingspage.cpp b/app/src/desktop/pluginsettingspage.cpp new file mode 100644 index 0000000..16e7875 --- /dev/null +++ b/app/src/desktop/pluginsettingspage.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pluginsettingspage.h" +#include "pluginsettings.h" +#include "selectionmodel.h" +#include +#include +#include +#include +#include +#include +#include + +PluginSettingsPage::PluginSettingsPage(const QString &pluginId, const QVariantList &settings, QWidget *parent) : + SettingsPage(parent), + m_plugin(new PluginSettings(pluginId, this)), + m_layout(new QFormLayout(this)) +{ + foreach (const QVariant &setting, settings) { + addWidget(m_layout, setting.toMap()); + } +} + +void PluginSettingsPage::save() { + if (m_settings.isEmpty()) { + return; + } + + QMapIterator iterator(m_settings); + + while (iterator.hasNext()) { + iterator.next(); + m_plugin->setValue(iterator.key(), iterator.value()); + } +} + +void PluginSettingsPage::addCheckBox(QFormLayout *layout, const QString &label, const QString &key, bool value) { + QCheckBox *checkbox = new QCheckBox("&" + label, this); + checkbox->setProperty("key", key); + checkbox->setChecked(value); + layout->addRow(checkbox); + connect(checkbox, SIGNAL(toggled(bool)), this, SLOT(setBooleanValue(bool))); +} + +void PluginSettingsPage::addComboBox(QFormLayout *layout, const QString &label, const QString &key, + const QVariantList &options, const QVariant &value) { + QComboBox *combobox = new QComboBox(this); + SelectionModel *model = new SelectionModel(combobox); + combobox->setProperty("key", key); + combobox->setModel(model); + + foreach (const QVariant &var, options) { + const QVariantMap option = var.toMap(); + model->append(option.value("label").toString(), option.value("value")); + } + + combobox->setCurrentIndex(qMax(0, combobox->findData(value))); + layout->addRow("&" + label + ":", combobox); + connect(combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(setListValue(int))); +} + +void PluginSettingsPage::addGroupBox(QFormLayout *layout, const QString &label, const QString &key, + const QVariantList &settings) { + QGroupBox *groupbox = new QGroupBox("&" + label, this); + QFormLayout *form = new QFormLayout(groupbox); + + foreach (const QVariant &setting, settings) { + addWidget(form, setting.toMap(), key); + } + + layout->addRow(groupbox); +} + +void PluginSettingsPage::addLineEdit(QFormLayout *layout, const QString &label, const QString &key, + const QString &value, bool isPassword) { + QLineEdit *edit = new QLineEdit(value, this); + edit->setProperty("key", key); + + if (isPassword) { + edit->setEchoMode(QLineEdit::Password); + } + + layout->addRow("&" + label + ":", edit); + connect(edit, SIGNAL(textChanged(QString)), this, SLOT(setTextValue(QString))); +} + +void PluginSettingsPage::addSpinBox(QFormLayout *layout, const QString &label, const QString &key, int minimum, + int maximum, int step, int value) { + QSpinBox *spinbox = new QSpinBox(this); + spinbox->setProperty("key", key); + spinbox->setMinimum(minimum); + spinbox->setMaximum(maximum); + spinbox->setSingleStep(step); + spinbox->setValue(value); + layout->addRow("&" + label + ":", spinbox); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(setIntegerValue(int))); +} + +void PluginSettingsPage::addWidget(QFormLayout *layout, const QVariantMap &setting, const QString &group) { + QString key = setting.value("key").toString(); + + if (key.isEmpty()) { + return; + } + + if (!group.isEmpty()) { + key.prepend("/"); + key.prepend(group); + } + + const QString type = setting.value("type").toString(); + const QVariant value = m_plugin->value(key, setting.value("value")); + + if (type == "boolean") { + addCheckBox(layout, setting.value("label").toString(), key, value.toBool()); + } + else if (type == "group") { + addGroupBox(layout, setting.value("label").toString(), key, setting.value("settings").toList()); + } + else if (type == "integer") { + addSpinBox(layout, setting.value("label").toString(), key, setting.value("minimum", 0).toInt(), + setting.value("maximum", 100).toInt(), setting.value("step", 1).toInt(), value.toInt()); + } + else if (type == "list") { + addComboBox(layout, setting.value("label").toString(), key, setting.value("options").toList(), value); + } + else if (type == "password") { + addLineEdit(layout, setting.value("label").toString(), key, value.toString(), true); + } + else if (type == "text") { + addLineEdit(layout, setting.value("label").toString(), key, value.toString()); + } +} + +void PluginSettingsPage::setBooleanValue(bool value) { + if (const QObject *obj = sender()) { + m_settings[obj->property("key").toString()] = value; + } +} + +void PluginSettingsPage::setIntegerValue(int value) { + if (const QObject *obj = sender()) { + m_settings[obj->property("key").toString()] = value; + } +} + +void PluginSettingsPage::setListValue(int value) { + if (const QComboBox *combobox = qobject_cast(sender())) { + m_settings[combobox->property("key").toString()] = combobox->itemData(value); + } +} + +void PluginSettingsPage::setTextValue(const QString &value) { + if (const QObject *obj = sender()) { + m_settings[obj->property("key").toString()] = value; + } +} diff --git a/app/src/desktop/pluginsettingspage.h b/app/src/desktop/pluginsettingspage.h new file mode 100644 index 0000000..9f5b4c7 --- /dev/null +++ b/app/src/desktop/pluginsettingspage.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINSETTINGSPAGE_H +#define PLUGINSETTINGSPAGE_H + +#include "settingspage.h" +#include + +class PluginSettings; +class QFormLayout; + +class PluginSettingsPage : public SettingsPage +{ + Q_OBJECT + +public: + explicit PluginSettingsPage(const QString &pluginId, const QVariantList &settings, QWidget *parent = 0); + +public Q_SLOTS: + virtual void save(); + +private Q_SLOTS: + void setBooleanValue(bool value); + void setIntegerValue(int value); + void setListValue(int value); + void setTextValue(const QString &value); + +private: + void addCheckBox(QFormLayout *layout, const QString &label, const QString &key, bool value); + void addComboBox(QFormLayout *layout, const QString &label, const QString &key, const QVariantList &options, + const QVariant &value); + void addGroupBox(QFormLayout *layout, const QString &label, const QString &key, const QVariantList &settings); + void addLineEdit(QFormLayout *layout, const QString &label, const QString &key, const QString &value, + bool isPassword = false); + void addSpinBox(QFormLayout *layout, const QString &label, const QString &key, int minimum, int maximum, + int step, int value); + void addWidget(QFormLayout *layout, const QVariantMap &setting, const QString &group = QString()); + + PluginSettings *m_plugin; + + QFormLayout *m_layout; + + QVariantMap m_settings; +}; + +#endif // PLUGINSETTINGSPAGE_H diff --git a/app/src/desktop/pluginssettingspage.cpp b/app/src/desktop/pluginssettingspage.cpp new file mode 100644 index 0000000..1837ad8 --- /dev/null +++ b/app/src/desktop/pluginssettingspage.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pluginssettingspage.h" +#include "pluginconfigmodel.h" +#include "pluginsettingspage.h" +#include +#include +#include +#include +#include + +PluginsSettingsPage::PluginsSettingsPage(QWidget *parent) : + SettingsPage(parent), + m_model(new PluginConfigModel(this)), + m_view(new QListView(this)), + m_scrollArea(new QScrollArea(this)), + m_splitter(new QSplitter(Qt::Horizontal, this)), + m_layout(new QVBoxLayout(this)) +{ + setWindowTitle(tr("Plugins")); + + m_view->setModel(m_model); + m_view->setUniformItemSizes(true); + + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setWidget(new QLabel(tr("No plugin selected"), m_scrollArea)); + + m_splitter->addWidget(m_view); + m_splitter->addWidget(m_scrollArea); + m_splitter->setStretchFactor(1, 1); + + m_layout->addWidget(m_splitter); + + connect(m_view, SIGNAL(clicked(QModelIndex)), this, SLOT(setCurrentPlugin(QModelIndex))); +} + +void PluginsSettingsPage::save() { + if (PluginSettingsPage *page = qobject_cast(m_scrollArea->widget())) { + page->save(); + } +} + +void PluginsSettingsPage::setCurrentPlugin(const QModelIndex &index) { + save(); + + if (!index.isValid()) { + m_scrollArea->setWidget(new QLabel(tr("No plugin selected"), m_scrollArea)); + return; + } + + const QString id = index.data(PluginConfigModel::IdRole).toString(); + const QVariantList settings = index.data(PluginConfigModel::SettingsRole).toList(); + + if ((id.isEmpty()) || (settings.isEmpty())) { + m_scrollArea->setWidget(new QLabel(tr("No settings for this plugin"), m_scrollArea)); + return; + } + + m_scrollArea->setWidget(new PluginSettingsPage(id, settings, m_scrollArea)); +} diff --git a/plugins/podcasts/src/rss.h b/app/src/desktop/pluginssettingspage.h similarity index 50% rename from plugins/podcasts/src/rss.h rename to app/src/desktop/pluginssettingspage.h index 2f6b62b..b0aeb9a 100644 --- a/plugins/podcasts/src/rss.h +++ b/app/src/desktop/pluginssettingspage.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,40 +14,40 @@ * along with this program. If not, see . */ -#ifndef RSS_H -#define RSS_H +#ifndef PLUGINSSETTINGSPAGE_H +#define PLUGINSSETTINGSPAGE_H -#include -#include -#include +#include "settingspage.h" -class QNetworkAccessManager; -class QNetworkReply; -class QUrl; +class PluginConfigModel; +class QListView; +class QScrollArea; +class QSplitter; +class QVBoxLayout; -class Rss : public QObject +class PluginsSettingsPage : public SettingsPage { Q_OBJECT public: - explicit Rss(QObject *parent = 0); - - void listTracks(const QStringList &urls); - void listTracks(const QString &url); - -private: - void followRedirect(const QUrl &url); - + explicit PluginsSettingsPage(QWidget *parent = 0); + +public Q_SLOTS: + virtual void save(); + private Q_SLOTS: - void parseTracks(QNetworkReply *reply); - void printResult(); - + void setCurrentPlugin(const QModelIndex &index); + private: - QNetworkAccessManager *m_nam; - - QStringList m_urls; - QVariantList m_results; - int m_redirects; + PluginConfigModel *m_model; + + QListView *m_view; + + QScrollArea *m_scrollArea; + + QSplitter *m_splitter; + + QVBoxLayout *m_layout; }; - -#endif // RSS_H + +#endif // PLUGINSSETTINGSPAGE_H diff --git a/app/src/desktop/searchdialog.cpp b/app/src/desktop/searchdialog.cpp new file mode 100644 index 0000000..53f1f3d --- /dev/null +++ b/app/src/desktop/searchdialog.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "searchdialog.h" +#include "pluginmanager.h" +#include "resources.h" +#include "selectionmodel.h" +#include +#include +#include +#include +#include + +SearchDialog::SearchDialog(QWidget *parent) : + QDialog(parent), + m_model(new SelectionModel(this)), + m_queryEdit(new QLineEdit(this)), + m_serviceSelector(new QComboBox(this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Search")); + + QVariantMap resource; + resource["service"] = Resources::SOUNDCLOUD; + resource["type"] = Resources::TRACK; + m_model->append(tr("SoundCloud: Tracks"), resource); + + const ServicePluginList plugins = PluginManager::instance()->plugins(); + + for (int i = 0; i < plugins.size(); i++) { + const ServicePluginConfig *config = plugins.at(i).config; + foreach (SearchResource resource, config->searchResources()) { + if (resource.type() == Resources::TRACK) { + resource["service"] = config->id(); + m_model->append(QString("%1: %2").arg(config->displayName()).arg(resource.label()), resource); + } + } + } + + m_serviceSelector->setModel(m_model); + + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + m_layout->addRow(tr("&Query"), m_queryEdit); + m_layout->addRow(tr("&Service"), m_serviceSelector); + m_layout->addWidget(m_buttonBox); + + connect(m_queryEdit, SIGNAL(textChanged(QString)), this, SLOT(onQueryChanged(QString))); + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +QString SearchDialog::service() const { + return m_serviceSelector->itemData(m_serviceSelector->currentIndex()).toMap().value("service").toString(); +} + +QString SearchDialog::query() const { + return m_queryEdit->text(); +} + +void SearchDialog::setQuery(const QString &query) { + m_queryEdit->setText(query); +} + +QString SearchDialog::order() const { + return m_serviceSelector->itemData(m_serviceSelector->currentIndex()).toMap().value("order").toString(); +} + +void SearchDialog::onQueryChanged(const QString &query) { + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!query.isEmpty()); +} diff --git a/app/src/desktop/searchdialog.h b/app/src/desktop/searchdialog.h new file mode 100644 index 0000000..ad58b53 --- /dev/null +++ b/app/src/desktop/searchdialog.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SEARCHDIALOG_H +#define SEARCHDIALOG_H + +#include + +class SelectionModel; +class QComboBox; +class QDialogButtonBox; +class QFormLayout; +class QLineEdit; + +class SearchDialog : public QDialog +{ + Q_OBJECT + + Q_PROPERTY(QString service READ service) + Q_PROPERTY(QString query READ query WRITE setQuery) + Q_PROPERTY(QString order READ order) + +public: + explicit SearchDialog(QWidget *parent = 0); + + QString service() const; + + QString query() const; + void setQuery(const QString &query); + + QString order() const; + +private Q_SLOTS: + void onQueryChanged(const QString &query); + +private: + SelectionModel *m_model; + + QLineEdit *m_queryEdit; + + QComboBox *m_serviceSelector; + + QDialogButtonBox *m_buttonBox; + + QFormLayout *m_layout; +}; + +#endif // SEARCHDIALOG_H diff --git a/app/src/desktop/settings.cpp b/app/src/desktop/settings.cpp new file mode 100644 index 0000000..76f6b36 --- /dev/null +++ b/app/src/desktop/settings.cpp @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "settings.h" +#include "definitions.h" +#include +#include + +Settings* Settings::self = 0; + +Settings::Settings() : + QObject() +{ +} + +Settings::~Settings() { + self = 0; +} + +Settings* Settings::instance() { + return self ? self : self = new Settings; +} + +QStringList Settings::categoryNames() { + QSettings settings; + settings.beginGroup("Categories"); + QStringList names = settings.childKeys(); + names.prepend(tr("Default")); + settings.endGroup(); + + return names; +} + +QList Settings::categories() { + QList list; + QSettings settings; + settings.beginGroup("Categories"); + + foreach (QString key, settings.childKeys()) { + Category category; + category.name = key; + category.path = settings.value(key).toString(); + list << category; + } + + settings.endGroup(); + + return list; +} + +void Settings::setCategories(const QList &c) { + QSettings settings; + settings.remove("Categories"); + settings.beginGroup("Categories"); + + foreach (Category category, c) { + settings.setValue(category.name, category.path); + } + + settings.endGroup(); + + if (self) { + emit self->categoriesChanged(); + } +} + +void Settings::addCategory(const QString &name, const QString &path) { + if (path != downloadPath(name)) { + setValue("Categories/" + name, path); + + if (self) { + emit self->categoriesChanged(); + } + } +} + +void Settings::removeCategory(const QString &name) { + QSettings settings; + settings.beginGroup("Categories"); + + if (settings.contains(name)) { + settings.remove(name); + + if (self) { + emit self->categoriesChanged(); + } + } + + settings.endGroup(); +} + +QString Settings::defaultCategory() { + return value("Transfers/defaultCategory", tr("Default")).toString(); +} + +void Settings::setDefaultCategory(const QString &category) { + if (category != defaultCategory()) { + setValue("Transfers/defaultCategory", category); + + if (self) { + emit self->defaultCategoryChanged(category); + } + } +} + +QString Settings::customTransferCommand() { + return value("Transfers/customCommand").toString(); +} + +void Settings::setCustomTransferCommand(const QString &command) { + if (command != customTransferCommand()) { + setValue("Transfers/customCommand", command); + + if (self) { + emit self->customTransferCommandChanged(command); + } + } +} + +bool Settings::customTransferCommandEnabled() { + return value("Transfers/customCommandEnabled", false).toBool(); +} + +void Settings::setCustomTransferCommandEnabled(bool enabled) { + if (enabled != customTransferCommandEnabled()) { + setValue("Transfers/customCommandEnabled", enabled); + + if (self) { + emit self->customTransferCommandEnabledChanged(enabled); + } + } +} + +QString Settings::defaultDownloadFormat(const QString &service) { + return value("DownloadFormats/" + service).toString(); +} + +void Settings::setDefaultDownloadFormat(const QString &service, const QString &format) { + if (format != defaultDownloadFormat(service)) { + setValue("DownloadFormats/" + service, format); + + if (self) { + emit self->downloadFormatsChanged(); + } + } +} + +QString Settings::defaultPlaybackFormat(const QString &service) { + return value("PlaybackFormats/" + service).toString(); +} + +void Settings::setDefaultPlaybackFormat(const QString &service, const QString &format) { + if (format != defaultPlaybackFormat(service)) { + setValue("PlaybackFormats/" + service, format); + + if (self) { + emit self->playbackFormatsChanged(); + } + } +} + +QString Settings::downloadPath() { + QString path = value("Transfers/downloadPath", DOWNLOAD_PATH).toString(); + + if (!path.endsWith("/")) { + path.append("/"); + } + + return path; +} + +QString Settings::downloadPath(const QString &category) { + return value("Categories/" + category, downloadPath()).toString(); +} + +void Settings::setDownloadPath(const QString &path) { + if (path != downloadPath()) { + setValue("Transfers/downloadPath", path); + + if (self) { + emit self->downloadPathChanged(path); + } + } +} + +QString Settings::loggerFileName() { + return value("Logger/fileName", APP_CONFIG_PATH + "log").toString(); +} + +void Settings::setLoggerFileName(const QString &fileName) { + if (fileName != loggerFileName()) { + setValue("Logger/fileName", fileName); + + if (self) { + emit self->loggerFileNameChanged(fileName); + } + } +} + +int Settings::loggerVerbosity() { + return value("Logger/verbosity", 0).toInt(); +} + +void Settings::setLoggerVerbosity(int verbosity) { + if (verbosity != loggerVerbosity()) { + setValue("Logger/verbosity", verbosity); + + if (self) { + emit self->loggerVerbosityChanged(verbosity); + } + } +} + +QByteArray Settings::mainWindowGeometry() { + return value("UI/mainWindowGeometry").toByteArray(); +} + +void Settings::setMainWindowGeometry(const QByteArray &geometry) { + setValue("UI/mainWindowGeometry", geometry); +} + +QByteArray Settings::mainWindowState() { + return value("UI/mainWindowState").toByteArray(); +} + +void Settings::setMainWindowState(const QByteArray &state) { + setValue("UI/mainWindowState", state); +} + +int Settings::maximumConcurrentTransfers() { + return qBound(1, value("Transfers/maximumConcurrentTransfers", 1).toInt(), MAX_CONCURRENT_TRANSFERS); +} + +void Settings::setMaximumConcurrentTransfers(int maximum) { + if (maximum != maximumConcurrentTransfers()) { + maximum = qBound(1, maximum, MAX_CONCURRENT_TRANSFERS); + setValue("Transfers/maximumConcurrentTransfers", maximum); + + if (self) { + emit self->maximumConcurrentTransfersChanged(maximum); + } + } +} + +void Settings::setNetworkProxy() { + if (!networkProxyEnabled()) { + QNetworkProxy::setApplicationProxy(QNetworkProxy()); + return; + } + + QNetworkProxy proxy(QNetworkProxy::ProxyType(networkProxyType()), networkProxyHost(), networkProxyPort()); + + if (networkProxyAuthenticationEnabled()) { + proxy.setUser(networkProxyUsername()); + proxy.setPassword(networkProxyPassword()); + } + + QNetworkProxy::setApplicationProxy(proxy); +} + +bool Settings::networkProxyAuthenticationEnabled() { + return value("Network/networkProxyAuthenticationEnabled", false).toBool(); +} + +void Settings::setNetworkProxyAuthenticationEnabled(bool enabled) { + if (enabled != networkProxyAuthenticationEnabled()) { + setValue("Network/networkProxyAuthenticationEnabled", enabled); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +bool Settings::networkProxyEnabled() { + return value("Network/networkProxyEnabled", false).toBool(); +} + +void Settings::setNetworkProxyEnabled(bool enabled) { + if (enabled != networkProxyEnabled()) { + setValue("Network/networkProxyEnabled", enabled); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +QString Settings::networkProxyHost() { + return value("Network/networkProxyHost").toString(); +} + +void Settings::setNetworkProxyHost(const QString &host) { + if (host != networkProxyHost()) { + setValue("Network/networkProxyHost", host); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +QString Settings::networkProxyPassword() { + return QByteArray::fromBase64(value("Network/networkProxyPassword").toByteArray()); +} + +void Settings::setNetworkProxyPassword(const QString &password) { + QByteArray pass = password.toUtf8().toBase64(); + + if (pass != networkProxyPassword()) { + setValue("Network/networkProxyPassword", pass); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +int Settings::networkProxyPort() { + return value("Network/networkProxyPort", 80).toInt(); +} + +void Settings::setNetworkProxyPort(int port) { + if (port != networkProxyPort()) { + setValue("Network/networkProxyPort", port); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +int Settings::networkProxyType() { + return value("Network/networkProxyType", QNetworkProxy::ProxyType(QNetworkProxy::HttpProxy)).toInt(); +} + +void Settings::setNetworkProxyType(int type) { + if (type != networkProxyType()) { + setValue("Network/networkProxyType", type); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +QString Settings::networkProxyUsername() { + return value("Network/networkProxyUsername").toString(); +} + +void Settings::setNetworkProxyUsername(const QString &username) { + if (username != networkProxyUsername()) { + setValue("Network/networkProxyUsername", username); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +bool Settings::restorePlaybackQueueOnStartup() { + return value("Playback/restorePlaybackQueueOnStartup", true).toBool(); +} + +void Settings::setRestorePlaybackQueueOnStartup(bool enabled) { + if (enabled != restorePlaybackQueueOnStartup()) { + setValue("Playback/restorePlaybackQueueOnStartup", enabled); + + if (self) { + emit self->restorePlaybackQueueOnStartupChanged(enabled); + } + } +} + +QStringList Settings::searchHistory() { + return value("Search/searchHistory").toStringList(); +} + +void Settings::setSearchHistory(const QStringList &searches) { + setValue("Search/searchHistory", searches); + + if (self) { + emit self->searchHistoryChanged(); + } +} + +void Settings::addSearch(const QString &query) { + QStringList searches = searchHistory(); + searches.removeOne(query); + searches.prepend(query); + setSearchHistory(searches); +} + +void Settings::removeSearch(const QString &query) { + QStringList searches = searchHistory(); + searches.removeOne(query); + setSearchHistory(searches); +} + +bool Settings::startTransfersAutomatically() { + return value("Transfers/startTransfersAutomatically", true).toBool(); +} + +void Settings::setStartTransfersAutomatically(bool enabled) { + if (enabled != startTransfersAutomatically()) { + setValue("Transfers/startTransfersAutomatically", enabled); + + if (self) { + emit self->startTransfersAutomaticallyChanged(enabled); + } + } +} + +QByteArray Settings::tracksHeaderViewState() { + return value("UI/tracksHeaderViewState").toByteArray(); +} + +void Settings::setTracksHeaderViewState(const QByteArray &state) { + setValue("UI/tracksHeaderViewState", state); +} + +QByteArray Settings::transfersHeaderViewState() { + return value("UI/transfersHeaderViewState").toByteArray(); +} + +void Settings::setTransfersHeaderViewState(const QByteArray &state) { + setValue("UI/transfersHeaderViewState", state); +} + +QVariant Settings::value(const QString &key, const QVariant &defaultValue) { + return QSettings().value(key, defaultValue); +} + +void Settings::setValue(const QString &key, const QVariant &value) { + QSettings().setValue(key, value); +} diff --git a/app/src/desktop/settings.h b/app/src/desktop/settings.h new file mode 100644 index 0000000..c45cb11 --- /dev/null +++ b/app/src/desktop/settings.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include + +struct Category { + QString name; + QString path; +}; + +class Settings : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QStringList categoryNames READ categoryNames NOTIFY categoriesChanged) + Q_PROPERTY(QString defaultCategory READ defaultCategory WRITE setDefaultCategory NOTIFY defaultCategoryChanged) + Q_PROPERTY(QString customTransferCommand READ customTransferCommand WRITE setCustomTransferCommand + NOTIFY customTransferCommandChanged) + Q_PROPERTY(bool customTransferCommandEnabled READ customTransferCommandEnabled WRITE setCustomTransferCommandEnabled + NOTIFY customTransferCommandEnabledChanged) + Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) + Q_PROPERTY(QString loggerFileName READ loggerFileName WRITE setLoggerFileName NOTIFY loggerFileNameChanged) + Q_PROPERTY(int loggerVerbosity READ loggerVerbosity WRITE setLoggerVerbosity NOTIFY loggerVerbosityChanged) + Q_PROPERTY(QByteArray mainWindowGeometry READ mainWindowGeometry WRITE setMainWindowGeometry) + Q_PROPERTY(QByteArray mainWindowState READ mainWindowState WRITE setMainWindowState) + Q_PROPERTY(int maximumConcurrentTransfers READ maximumConcurrentTransfers WRITE setMaximumConcurrentTransfers + NOTIFY maximumConcurrentTransfersChanged) + Q_PROPERTY(bool networkProxyAuthenticationEnabled READ networkProxyAuthenticationEnabled + WRITE setNetworkProxyAuthenticationEnabled NOTIFY networkProxyChanged) + Q_PROPERTY(bool networkProxyEnabled READ networkProxyEnabled WRITE setNetworkProxyEnabled + NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyHost READ networkProxyHost WRITE setNetworkProxyHost NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyPassword READ networkProxyPassword WRITE setNetworkProxyPassword + NOTIFY networkProxyChanged) + Q_PROPERTY(int networkProxyPort READ networkProxyPort WRITE setNetworkProxyPort NOTIFY networkProxyChanged) + Q_PROPERTY(int networkProxyType READ networkProxyType WRITE setNetworkProxyType NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyUsername READ networkProxyUsername WRITE setNetworkProxyUsername + NOTIFY networkProxyChanged) + Q_PROPERTY(bool restorePlaybackQueueOnStartup READ restorePlaybackQueueOnStartup + WRITE setRestorePlaybackQueueOnStartup NOTIFY restorePlaybackQueueOnStartupChanged) + Q_PROPERTY(QStringList searchHistory READ searchHistory WRITE setSearchHistory NOTIFY searchHistoryChanged) + Q_PROPERTY(bool startTransfersAutomatically READ startTransfersAutomatically WRITE setStartTransfersAutomatically + NOTIFY startTransfersAutomaticallyChanged) + Q_PROPERTY(QByteArray tracksHeaderViewState READ tracksHeaderViewState WRITE setTracksHeaderViewState) + Q_PROPERTY(QByteArray transfersHeaderViewState READ transfersHeaderViewState WRITE setTransfersHeaderViewState) + +public: + ~Settings(); + + static Settings* instance(); + + static QStringList categoryNames(); + static QList categories(); + static void setCategories(const QList &c); + + static QString customTransferCommand(); + static bool customTransferCommandEnabled(); + + static QString defaultCategory(); + + static QString defaultDownloadFormat(const QString &service); + static QString defaultPlaybackFormat(const QString &service); + + static QString downloadPath(); + static QString downloadPath(const QString &category); + + static QString loggerFileName(); + static int loggerVerbosity(); + + static QByteArray mainWindowGeometry(); + static QByteArray mainWindowState(); + + static int maximumConcurrentTransfers(); + + static bool networkProxyAuthenticationEnabled(); + static bool networkProxyEnabled(); + static QString networkProxyHost(); + static QString networkProxyPassword(); + static int networkProxyPort(); + static int networkProxyType(); + static QString networkProxyUsername(); + + static bool restorePlaybackQueueOnStartup(); + + static QStringList searchHistory(); + + static bool startTransfersAutomatically(); + + static QByteArray tracksHeaderViewState(); + static QByteArray transfersHeaderViewState(); + + static QVariant value(const QString &key, const QVariant &defaultValue = QVariant()); + +public Q_SLOTS: + static void addCategory(const QString &name, const QString &path); + static void setDefaultCategory(const QString &category); + static void removeCategory(const QString &name); + + static void setCustomTransferCommand(const QString &command); + static void setCustomTransferCommandEnabled(bool enabled); + + static void setDefaultDownloadFormat(const QString &service, const QString &format); + static void setDefaultPlaybackFormat(const QString &service, const QString &format); + + static void setDownloadPath(const QString &path); + + static void setLoggerFileName(const QString &fileName); + static void setLoggerVerbosity(int verbosity); + + static void setMainWindowGeometry(const QByteArray &geometry); + static void setMainWindowState(const QByteArray &state); + + static void setMaximumConcurrentTransfers(int maximum); + + static void setNetworkProxy(); + static void setNetworkProxyAuthenticationEnabled(bool enabled); + static void setNetworkProxyEnabled(bool enabled); + static void setNetworkProxyHost(const QString &host); + static void setNetworkProxyPassword(const QString &password); + static void setNetworkProxyPort(int port); + static void setNetworkProxyType(int type); + static void setNetworkProxyUsername(const QString &username); + + static void setRestorePlaybackQueueOnStartup(bool enabled); + + static void setSearchHistory(const QStringList &searches); + static void addSearch(const QString &query); + static void removeSearch(const QString &query); + + static void setStartTransfersAutomatically(bool enabled); + + static void setTracksHeaderViewState(const QByteArray &state); + static void setTransfersHeaderViewState(const QByteArray &state); + + static void setValue(const QString &key, const QVariant &value); + +Q_SIGNALS: + void categoriesChanged(); + void defaultCategoryChanged(const QString &category); + void customTransferCommandChanged(const QString &command); + void customTransferCommandEnabledChanged(bool enabled); + void downloadFormatsChanged(); + void downloadPathChanged(const QString &path); + void loggerFileNameChanged(const QString &fileName); + void loggerVerbosityChanged(int verbosity); + void maximumConcurrentTransfersChanged(int maximum); + void networkProxyChanged(); + void playbackFormatsChanged(); + void restorePlaybackQueueOnStartupChanged(bool enabled); + void safeSearchEnabledChanged(bool enabled); + void searchHistoryChanged(); + void startTransfersAutomaticallyChanged(bool enabled); + +private: + Settings(); + + static Settings *self; +}; + +#endif // SETTINGS_H diff --git a/app/src/desktop/settingsdialog.cpp b/app/src/desktop/settingsdialog.cpp new file mode 100644 index 0000000..0141312 --- /dev/null +++ b/app/src/desktop/settingsdialog.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "settingsdialog.h" +#include "categorysettingspage.h" +#include "generalsettingspage.h" +#include "networksettingspage.h" +#include "pluginssettingspage.h" +#include +#include +#include +#include +#include + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + m_generalTab(0), + m_networkTab(0), + m_categoryTab(0), + m_pluginsTab(0), + m_tabBar(new QTabBar(this)), + m_stack(new QStackedWidget(this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save, Qt::Horizontal, this)), + m_layout(new QVBoxLayout(this)) +{ + setWindowTitle(tr("Preferences")); + + m_tabBar->addTab(tr("General")); + m_tabBar->addTab(tr("Network")); + m_tabBar->addTab(tr("Categories")); + m_tabBar->addTab(tr("Plugins")); + + m_buttonBox->button(QDialogButtonBox::Cancel)->setDefault(false); + + m_layout->addWidget(m_tabBar); + m_layout->addWidget(m_stack); + m_layout->addWidget(m_buttonBox); + + connect(m_tabBar, SIGNAL(currentChanged(int)), this, SLOT(setCurrentTab(int))); + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + showGeneralTab(); +} + +void SettingsDialog::accept() { + for (int i = 0; i < m_stack->count(); i++) { + if (SettingsPage *tab = qobject_cast(m_stack->widget(i))) { + tab->save(); + } + } + + QDialog::accept(); +} + +void SettingsDialog::setCurrentTab(int index) { + switch (index) { + case 0: + showGeneralTab(); + break; + case 1: + showNetworkTab(); + break; + case 2: + showCategoryTab(); + break; + case 3: + showPluginsTab(); + break; + default: + break; + } +} + +void SettingsDialog::showGeneralTab() { + if (!m_generalTab) { + m_generalTab = new GeneralSettingsPage(m_stack); + m_stack->addWidget(m_generalTab); + } + + m_stack->setCurrentWidget(m_generalTab); +} + +void SettingsDialog::showNetworkTab() { + if (!m_networkTab) { + m_networkTab = new NetworkSettingsPage(m_stack); + m_stack->addWidget(m_networkTab); + } + + m_stack->setCurrentWidget(m_networkTab); +} + +void SettingsDialog::showCategoryTab() { + if (!m_categoryTab) { + m_categoryTab = new CategorySettingsPage(m_stack); + m_stack->addWidget(m_categoryTab); + } + + m_stack->setCurrentWidget(m_categoryTab); +} + +void SettingsDialog::showPluginsTab() { + if (!m_pluginsTab) { + m_pluginsTab = new PluginsSettingsPage(m_stack); + m_stack->addWidget(m_pluginsTab); + } + + m_stack->setCurrentWidget(m_pluginsTab); +} diff --git a/app/src/desktop/settingsdialog.h b/app/src/desktop/settingsdialog.h new file mode 100644 index 0000000..a8850b5 --- /dev/null +++ b/app/src/desktop/settingsdialog.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include + +class CategorySettingsPage; +class GeneralSettingsPage; +class NetworkSettingsPage; +class PluginsSettingsPage; +class QDialogButtonBox; +class QStackedWidget; +class QTabBar; +class QVBoxLayout; + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(QWidget *parent = 0); + +public Q_SLOTS: + virtual void accept(); + +private Q_SLOTS: + void setCurrentTab(int index); + + void showGeneralTab(); + void showNetworkTab(); + void showCategoryTab(); + void showPluginsTab(); + +private: + GeneralSettingsPage *m_generalTab; + NetworkSettingsPage *m_networkTab; + CategorySettingsPage *m_categoryTab; + PluginsSettingsPage *m_pluginsTab; + + QTabBar *m_tabBar; + + QStackedWidget *m_stack; + + QDialogButtonBox *m_buttonBox; + + QVBoxLayout *m_layout; +}; + +#endif // SETTINGSDIALOG_H diff --git a/app/src/desktop-qml/qml/plugins/PluginSettingsGroupLabel.qml b/app/src/desktop/settingspage.cpp similarity index 72% rename from app/src/desktop-qml/qml/plugins/PluginSettingsGroupLabel.qml rename to app/src/desktop/settingspage.cpp index 5bac4ed..7757d42 100644 --- a/app/src/desktop-qml/qml/plugins/PluginSettingsGroupLabel.qml +++ b/app/src/desktop/settingspage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,7 +14,13 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 +#include "settingspage.h" -Label {} +SettingsPage::SettingsPage(QWidget *parent) : + QWidget(parent) +{ +} + +void SettingsPage::restore() {} + +void SettingsPage::save() {} diff --git a/app/src/desktop/settingspage.h b/app/src/desktop/settingspage.h new file mode 100644 index 0000000..04a6e19 --- /dev/null +++ b/app/src/desktop/settingspage.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETTINGSPAGE_H +#define SETTINGSPAGE_H + +#include + +class SettingsPage : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsPage(QWidget *parent = 0); + +public Q_SLOTS: + virtual void restore(); + virtual void save(); +}; + +#endif // SETTINGSPAGE_H diff --git a/app/src/desktop/soundcloud/soundclouddownloaddialog.cpp b/app/src/desktop/soundcloud/soundclouddownloaddialog.cpp new file mode 100644 index 0000000..9e8c094 --- /dev/null +++ b/app/src/desktop/soundcloud/soundclouddownloaddialog.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "soundclouddownloaddialog.h" +#include "categorynamemodel.h" +#include "resources.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include + +SoundCloudDownloadDialog::SoundCloudDownloadDialog(QWidget *parent) : + QDialog(parent), + m_streamModel(new SoundCloudStreamModel(this)), + m_categoryModel(new CategoryNameModel(this)), + m_streamSelector(new QComboBox(this)), + m_categorySelector(new QComboBox(this)), + m_commandCheckBox(new QCheckBox(tr("&Override global custom command"), this)), + m_commandEdit(new QLineEdit(this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this)), + m_layout(new QFormLayout(this)) +{ + setWindowTitle(tr("Download track")); + + m_streamSelector->setModel(m_streamModel); + + m_categorySelector->setModel(m_categoryModel); + + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + m_layout->addRow(tr("Audio &format:"), m_streamSelector); + m_layout->addRow(tr("&Category:"), m_categorySelector); + m_layout->addRow(tr("&Custom command:"), m_commandEdit); + m_layout->addRow(m_commandCheckBox); + m_layout->addRow(m_buttonBox); + + connect(m_streamModel, SIGNAL(statusChanged(QSoundCloud::StreamsRequest::Status)), + this, SLOT(onStreamModelStatusChanged(QSoundCloud::StreamsRequest::Status))); + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +QString SoundCloudDownloadDialog::trackId() const { + return m_trackId; +} + +QString SoundCloudDownloadDialog::streamId() const { + return m_streamSelector->itemData(m_streamSelector->currentIndex()).toMap().value("id").toString(); +} + +QString SoundCloudDownloadDialog::category() const { + return m_categorySelector->currentText(); +} + +QString SoundCloudDownloadDialog::customCommand() const { + return m_commandEdit->text(); +} + +bool SoundCloudDownloadDialog::customCommandOverrideEnabled() const { + return m_commandCheckBox->isChecked(); +} + +void SoundCloudDownloadDialog::accept() { + Settings::setDefaultDownloadFormat(Resources::SOUNDCLOUD, m_streamSelector->currentText()); + Settings::setDefaultCategory(category()); + QDialog::accept(); +} + +void SoundCloudDownloadDialog::get(const QString &trackId) { + m_trackId = trackId; + m_streamModel->get(trackId); +} + +void SoundCloudDownloadDialog::onStreamModelStatusChanged(QSoundCloud::StreamsRequest::Status status) { + switch (status) { + case QSoundCloud::StreamsRequest::Loading: + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + break; + case QSoundCloud::StreamsRequest::Ready: + m_streamSelector->setCurrentIndex(qMax(0, m_streamModel->match("name", + Settings::defaultDownloadFormat(Resources::SOUNDCLOUD)))); + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + break; + case QSoundCloud::StreamsRequest::Failed: + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + QMessageBox::critical(this, tr("Error"), m_streamModel->errorString()); + break; + default: + break; + } +} diff --git a/app/src/desktop/soundcloud/soundclouddownloaddialog.h b/app/src/desktop/soundcloud/soundclouddownloaddialog.h new file mode 100644 index 0000000..91ce97d --- /dev/null +++ b/app/src/desktop/soundcloud/soundclouddownloaddialog.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SOUNDCLOUDDOWNLOADDIALOG_H +#define SOUNDCLOUDDOWNLOADDIALOG_H + +#include "soundcloudstreammodel.h" +#include + +class CategoryNameModel; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QFormLayout; +class QLineEdit; + +class SoundCloudDownloadDialog : public QDialog +{ + Q_OBJECT + + Q_PROPERTY(QString trackId READ trackId) + Q_PROPERTY(QString streamId READ streamId) + Q_PROPERTY(QString category READ category) + Q_PROPERTY(QString customCommand READ customCommand) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled) + +public: + explicit SoundCloudDownloadDialog(QWidget *parent = 0); + + QString trackId() const; + + QString streamId() const; + + QString category() const; + + QString customCommand() const; + bool customCommandOverrideEnabled() const; + +public Q_SLOTS: + virtual void accept(); + + void get(const QString &trackId); + +private Q_SLOTS: + void onStreamModelStatusChanged(QSoundCloud::StreamsRequest::Status status); + +private: + SoundCloudStreamModel *m_streamModel; + CategoryNameModel *m_categoryModel; + + QComboBox *m_streamSelector; + QComboBox *m_categorySelector; + + QCheckBox *m_commandCheckBox; + + QLineEdit *m_commandEdit; + + QDialogButtonBox *m_buttonBox; + + QFormLayout *m_layout; + + QString m_trackId; +}; + +#endif // SOUNDCLOUDDOWNLOADDIALOG_H diff --git a/app/src/desktop/soundcloud/soundcloudtrackspage.cpp b/app/src/desktop/soundcloud/soundcloudtrackspage.cpp new file mode 100644 index 0000000..027049f --- /dev/null +++ b/app/src/desktop/soundcloud/soundcloudtrackspage.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "soundcloudtrackspage.h" +#include "audioplayer.h" +#include "soundclouddownloaddialog.h" +#include "resources.h" +#include "settings.h" +#include "transfers.h" +#include "treeview.h" +#include +#include +#include +#include + +SoundCloudTracksPage::SoundCloudTracksPage(QWidget *parent) : + Page(parent), + m_model(new SoundCloudTrackModel(this)), + m_view(new MKTreeView(this)), + m_layout(new QVBoxLayout(this)), + m_status(Null) +{ + m_view->setModel(m_model); + m_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_view->setAlternatingRowColors(true); + m_view->setSelectionBehavior(QTreeView::SelectRows); + m_view->setEditTriggers(QTreeView::NoEditTriggers); + m_view->setItemsExpandable(false); + m_view->setUniformRowHeights(true); + m_view->setAllColumnsShowFocus(true); + m_view->setRootIsDecorated(false); + m_view->header()->restoreState(Settings::tracksHeaderViewState()); + m_view->setItemMetaDataEnabled(true); + m_view->setItemMetaDataThumbnailRole(SoundCloudTrackModel::LargeThumbnailUrlRole); + + QList roles; + roles << ItemMetaDataRole(tr("Title"), SoundCloudTrackModel::TitleRole); + roles << ItemMetaDataRole(tr("Artist"), SoundCloudTrackModel::ArtistRole); + roles << ItemMetaDataRole(tr("Genre"), SoundCloudTrackModel::GenreRole); + roles << ItemMetaDataRole(tr("Duration"), SoundCloudTrackModel::DurationStringRole); + m_view->setItemMetaDataTextRoles(roles); + + m_layout->addWidget(m_view); + m_layout->setContentsMargins(0, 0, 0, 0); + + connect(m_model, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), + this, SLOT(onModelStatusChanged(QSoundCloud::ResourcesRequest::Status))); + connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(playTrack(QModelIndex))); + connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); +} + +void SoundCloudTracksPage::closeEvent(QCloseEvent *e) { + Settings::setTracksHeaderViewState(m_view->header()->saveState()); + Page::closeEvent(e); +} + +SoundCloudTracksPage::Status SoundCloudTracksPage::status() const { + return m_status; +} + +void SoundCloudTracksPage::setStatus(SoundCloudTracksPage::Status s) { + if (s != status()) { + m_status = s; + emit statusChanged(s); + } +} + +QString SoundCloudTracksPage::statusText() const { + return m_statusText; +} + +void SoundCloudTracksPage::setStatusText(const QString &text) { + if (text != statusText()) { + m_statusText = text; + emit statusTextChanged(text); + } +} + +void SoundCloudTracksPage::cancel() { + m_model->cancel(); +} + +void SoundCloudTracksPage::reload() { + m_model->reload(); +} + +void SoundCloudTracksPage::get(const QString &resourcePath, const QVariantMap &filters) { + m_model->get(resourcePath, filters); +} + +void SoundCloudTracksPage::downloadTrack(const QModelIndex &index) { + const QString trackId = index.data(SoundCloudTrackModel::IdRole).toString(); + const QString title = index.data(SoundCloudTrackModel::TitleRole).toString(); + SoundCloudDownloadDialog dialog(this); + dialog.get(trackId); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, trackId, dialog.streamId(), + QUrl(), title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } +} + +void SoundCloudTracksPage::playTrack(const QModelIndex &index) { + if (SoundCloudTrack *track = m_model->get(index.row())) { + AudioPlayer::instance()->playTrack(track); + } +} + +void SoundCloudTracksPage::queueTrack(const QModelIndex &index) { + if (SoundCloudTrack *track = m_model->get(index.row())) { + AudioPlayer::instance()->addTrack(track); + } +} + +void SoundCloudTracksPage::showContextMenu(const QPoint &pos) { + if (!m_view->currentIndex().isValid()) { + return; + } + + QMenu menu(this); + QAction *playAction = menu.addAction(QIcon::fromTheme("media-playback-start"), tr("&Play")); + QAction *queueAction = menu.addAction(QIcon::fromTheme("list-add"), tr("&Queue")); + QAction *downloadAction = menu.addAction(QIcon::fromTheme("document-save"), tr("&Download")); + QAction *action = menu.exec(m_view->mapToGlobal(pos)); + + if (action == playAction) { + playTrack(m_view->currentIndex()); + } + else if (action == queueAction) { + queueTrack(m_view->currentIndex()); + } + else if (action == downloadAction) { + downloadTrack(m_view->currentIndex()); + } +} + +void SoundCloudTracksPage::onModelStatusChanged(QSoundCloud::ResourcesRequest::Status status) { + switch (status) { + case QSoundCloud::ResourcesRequest::Loading: + setStatus(Loading); + setStatusText(tr("Loading tracks")); + break; + case QSoundCloud::ResourcesRequest::Failed: + setStatus(Ready); + setStatusText(m_model->errorString()); + QMessageBox::critical(this, tr("Error"), m_model->errorString()); + break; + default: + setStatus(Ready); + setStatusText(tr("Finished")); + break; + } +} diff --git a/app/src/desktop/soundcloud/soundcloudtrackspage.h b/app/src/desktop/soundcloud/soundcloudtrackspage.h new file mode 100644 index 0000000..1f475f0 --- /dev/null +++ b/app/src/desktop/soundcloud/soundcloudtrackspage.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SOUNDCLOUDTRACKSPAGE_H +#define SOUNDCLOUDTRACKSPAGE_H + +#include "page.h" +#include "soundcloudtrackmodel.h" + +class MKTreeView; +class QVBoxLayout; + +class SoundCloudTracksPage : public Page +{ + Q_OBJECT + +public: + explicit SoundCloudTracksPage(QWidget *parent = 0); + + virtual Status status() const; + virtual QString statusText() const; + +public Q_SLOTS: + virtual void cancel(); + virtual void reload(); + + void get(const QString &resourcePath, const QVariantMap &filters = QVariantMap()); + +private Q_SLOTS: + void downloadTrack(const QModelIndex &index); + void playTrack(const QModelIndex &index); + void queueTrack(const QModelIndex &index); + + void showContextMenu(const QPoint &pos); + + void onModelStatusChanged(QSoundCloud::ResourcesRequest::Status status); + +private: + virtual void closeEvent(QCloseEvent *e); + + void setStatus(Status s); + void setStatusText(const QString &text); + + SoundCloudTrackModel *m_model; + + MKTreeView *m_view; + + QVBoxLayout *m_layout; + + Status m_status; + QString m_statusText; +}; + +#endif // SOUNDCLOUDTRACKSPAGE_H diff --git a/app/src/desktop/trackdelegate.cpp b/app/src/desktop/trackdelegate.cpp new file mode 100644 index 0000000..7a7010c --- /dev/null +++ b/app/src/desktop/trackdelegate.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "playlistdelegate.h" +#include "drawing.h" +#include "imagecache.h" +#include +#include + +PlaylistDelegate::PlaylistDelegate(ImageCache *cache, int dateRole, int thumbnailRole, int titleRole, int usernameRole, + int videoCountRole, QObject *parent) : + QStyledItemDelegate(parent), + m_cache(cache), + m_dateRole(dateRole), + m_thumbnailRole(thumbnailRole), + m_titleRole(titleRole), + m_usernameRole(usernameRole), + m_videoCountRole(videoCountRole) +{ +} + +void PlaylistDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + QStyledItemDelegate::paint(painter, option, index); + const QImage image = m_cache->image(index.data(m_thumbnailRole).toString(), QSize(80, 60)); + + QRect imageRect = option.rect; + imageRect.setLeft(imageRect.left() + 8); + imageRect.setTop(imageRect.top() + 8); + imageRect.setWidth(80); + imageRect.setHeight(60); + + painter->fillRect(imageRect, Qt::black); + drawCenteredImage(painter, imageRect, image); + + const int count = index.data(m_videoCountRole).toInt(); + + if (count != -1) { + QFont font; + QRect durationRect = imageRect; + const int durationHeight = durationRect.height() / 4; + font.setPixelSize(durationHeight); + durationRect.setTop(durationRect.bottom() - (durationHeight)); + + painter->save(); + painter->setOpacity(0.8); + painter->fillRect(durationRect, Qt::black); + painter->setFont(font); + painter->setOpacity(1); + painter->setPen(Qt::white); + painter->drawText(durationRect, Qt::AlignCenter, count > 0 ? tr("%1 videos").arg(count) : tr("No videos")); + painter->restore(); + } + + painter->save(); + painter->setPen(QApplication::palette().color(QPalette::Mid)); + painter->drawRect(imageRect); + painter->setPen(QApplication::palette().color(QPalette::Text)); + + QRect textRect = option.rect; + textRect.setLeft(imageRect.right() + 8); + textRect.setRight(textRect.right() - 8); + textRect.setTop(textRect.top() + 8); + textRect.setBottom(textRect.bottom() - 8); + + QString subTitle; + const QString username = index.data(m_usernameRole).toString(); + const QString date = index.data(m_dateRole).toString(); + + if (!username.isEmpty()) { + subTitle.append(QString("%1 %2").arg(tr("by")).arg(username)); + } + + if (!date.isEmpty()) { + if (!username.isEmpty()) { + subTitle.append(QString(" %1 ").arg(tr("on"))); + } + + subTitle.append(date); + } + + if (!subTitle.isEmpty()) { + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, + painter->fontMetrics().elidedText(index.data(m_titleRole).toString(), + Qt::ElideRight, textRect.width())); + painter->setPen(QApplication::palette().color(QPalette::Mid)); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignBottom, + painter->fontMetrics().elidedText(subTitle, Qt::ElideRight, textRect.width())); + } + else { + painter->drawText(textRect, Qt::AlignVCenter | Qt::TextWordWrap, index.data(m_titleRole).toString()); + } + + painter->restore(); +} + +QSize PlaylistDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const { + return QSize(option.rect.width(), 76); +} diff --git a/app/src/desktop/trackdelegate.h b/app/src/desktop/trackdelegate.h new file mode 100644 index 0000000..4ad96df --- /dev/null +++ b/app/src/desktop/trackdelegate.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRACKDELEGATE_H +#define TRACKDELEGATE_H + +#include + +class TrackDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit TrackDelegate(int artistRole, int durationRole, int titleRole, QObject *parent = 0); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const; + +private: + int m_artistRole; + int m_durationRole; + int m_titleRole; +}; + +#endif // TRACKDELEGATE_H diff --git a/app/src/base/transfer.cpp b/app/src/desktop/transfer.cpp similarity index 60% rename from app/src/base/transfer.cpp rename to app/src/desktop/transfer.cpp index b85f2c5..c323090 100644 --- a/app/src/base/transfer.cpp +++ b/app/src/desktop/transfer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -16,73 +16,33 @@ #include "transfer.h" #include "definitions.h" +#include "logger.h" #include "settings.h" +#include "utils.h" +#include +#include #include #include -#include -#include -#ifdef MEEGO_EDITION_HARMATTAN -#include -#include -#endif -#ifdef MUSIKLOUD_DEBUG -#include -#endif - -#ifdef MEEGO_EDITION_HARMATTAN -TransferUI::Client* Transfer::tuiClient = 0; -#endif -#ifdef SYMBIAN_OS -static const qint64 BUFFER_SIZE = 1024 * 512; -#endif +#include Transfer::Transfer(QObject *parent) : QObject(parent), m_nam(0), m_reply(0), + m_process(0), m_ownNetworkAccessManager(false), m_canceled(false), m_category(tr("Default")), - m_fileExtension(".mp3"), + m_customCommandOverrideEnabled(false), m_priority(NormalPriority), m_progress(0), m_size(0), m_bytesTransferred(0), m_redirects(0), m_status(Paused), - m_transferType(Download) + m_transferType(Download), + m_metadataSet(false) { -#ifdef MEEGO_EDITION_HARMATTAN - if (!tuiClient) { - tuiClient = new TransferUI::Client; - tuiClient->init(); - } - - m_tuiTransfer = tuiClient->registerTransfer(QString(), TransferUI::Client::TRANSFER_TYPES_DOWNLOAD); - m_tuiTransfer->waitForCommit(); - m_tuiTransfer->setIcon("icon-m-content-audio"); - m_tuiTransfer->setCanPause(true); - m_tuiTransfer->markPaused(); - m_tuiTransfer->commit(); - - connect(m_tuiTransfer, SIGNAL(start()), this, SLOT(queue())); - connect(m_tuiTransfer, SIGNAL(pause()), this, SLOT(pause())); - connect(m_tuiTransfer, SIGNAL(cancel()), this, SLOT(cancel())); - connect(m_tuiTransfer, SIGNAL(repairError()), this, SLOT(queue())); -#endif -} - -Transfer::~Transfer() { -#ifdef MEEGO_EDITION_HARMATTAN - if (m_tuiTransfer) { - if (tuiClient) { - tuiClient->removeTransfer(m_tuiTransfer->transferId()); - } - - delete m_tuiTransfer; - m_tuiTransfer = 0; - } -#endif } void Transfer::setNetworkAccessManager(QNetworkAccessManager *manager) { @@ -107,9 +67,28 @@ void Transfer::setCategory(const QString &c) { m_category = c; emit categoryChanged(); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setCategory" << c; -#endif +} + +QString Transfer::customCommand() const { + return m_customCommand; +} + +void Transfer::setCustomCommand(const QString &c) { + if (c != customCommand()) { + m_customCommand = c; + emit customCommandChanged(); + } +} + +bool Transfer::customCommandOverrideEnabled() const { + return m_customCommandOverrideEnabled; +} + +void Transfer::setCustomCommandOverrideEnabled(bool enabled) { + if (enabled != customCommandOverrideEnabled()) { + m_customCommandOverrideEnabled = enabled; + emit customCommandOverrideEnabledChanged(); + } } QString Transfer::downloadPath() const { @@ -130,9 +109,6 @@ void Transfer::setDownloadPath(const QString &path) { } } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setDownloadPath" << path; -#endif } QString Transfer::errorString() const { @@ -141,20 +117,6 @@ QString Transfer::errorString() const { void Transfer::setErrorString(const QString &es) { m_errorString = es; -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setErrorString" << es; -#endif -} - -QString Transfer::fileExtension() const { - return m_fileExtension; -} - -void Transfer::setFileExtension(const QString &ext) { - m_fileExtension = ext.startsWith('.') ? ext.toLower() : "." + ext.toLower(); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setFileExtension" << m_fileExtension; -#endif } QString Transfer::fileName() const { @@ -184,9 +146,6 @@ void Transfer::setFileName(const QString &name) { } } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setFileName" << name; -#endif } QString Transfer::id() const { @@ -198,9 +157,6 @@ void Transfer::setId(const QString &i) { m_id = i; emit idChanged(); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setId" << i; -#endif } Transfer::Priority Transfer::priority() const { @@ -212,9 +168,6 @@ void Transfer::setPriority(Priority p) { m_priority = p; emit priorityChanged(); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setPriority" << p; -#endif } QString Transfer::priorityString() const { @@ -238,28 +191,12 @@ void Transfer::setProgress(int p) { if (p != progress()) { m_progress = p; emit progressChanged(); -#ifdef MEEGO_EDITION_HARMATTAN - if ((tuiClient) && (tuiClient->isTUIVisible())) { - if (m_tuiTransfer) { - m_tuiTransfer->setProgress(float(p) / 100); - } - } -#endif } } -QString Transfer::resourceId() const { - return m_resourceId; -} - -void Transfer::setResourceId(const QString &ri) { - if (ri != resourceId()) { - m_resourceId = ri; - emit resourceIdChanged(); - } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setResourceId" << ri; -#endif +QString Transfer::progressString() const { + return tr("%1 of %2 (%3%)").arg(Utils::formatBytes(bytesTransferred())).arg(Utils::formatBytes(size())) + .arg(progress()); } QString Transfer::service() const { @@ -271,9 +208,6 @@ void Transfer::setService(const QString &s) { m_service = s; emit serviceChanged(); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setService" << s; -#endif } qint64 Transfer::size() const { @@ -284,18 +218,11 @@ void Transfer::setSize(qint64 s) { if (s != size()) { m_size = s; emit sizeChanged(); -#ifdef MEEGO_EDITION_HARMATTAN - if (m_tuiTransfer) { - m_tuiTransfer->setSize(s); - } -#endif + if ((m_size > 0) && (m_bytesTransferred > 0)) { setProgress(m_bytesTransferred * 100 / m_size); } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setSize" << s; -#endif } Transfer::Status Transfer::status() const { @@ -305,35 +232,10 @@ Transfer::Status Transfer::status() const { void Transfer::setStatus(Status s) { if (s != status()) { m_status = s; + Logger::log(QString("Transfer::setStatus(). ID: %1, Status: %2").arg(id()).arg(statusString()), + Logger::LowVerbosity); emit statusChanged(); -#ifdef MEEGO_EDITION_HARMATTAN - switch (s) { - case Transfer::Queued: - if (m_tuiTransfer) m_tuiTransfer->setPending(statusString()); - break; - case Transfer::Connecting: - if (m_tuiTransfer) m_tuiTransfer->markResumed(); - break; - case Transfer::Completed: - if (m_tuiTransfer) m_tuiTransfer->markCompleted(); - break; - case Transfer::Canceled: - if (m_tuiTransfer) m_tuiTransfer->markCancelled(); - break; - case Transfer::Paused: - if (m_tuiTransfer) m_tuiTransfer->markPaused(); - break; - case Transfer::Failed: - if (m_tuiTransfer) m_tuiTransfer->markRepairableFailure(statusString(), errorString(), tr("Retry")); - break; - default: - break; - } -#endif } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setStatus" << s; -#endif } QString Transfer::statusString() const { @@ -343,7 +245,7 @@ QString Transfer::statusString() const { case Canceled: return tr("Canceled"); case Failed: - return tr("Failed"); + return tr("Failed: %1").arg(errorString()); case Completed: return tr("Completed"); case Queued: @@ -354,6 +256,8 @@ QString Transfer::statusString() const { return tr("Downloading"); case Uploading: return tr("Uploading"); + case ExecutingCustomCommand: + return tr("Executing custom command"); default: return QString(); } @@ -368,9 +272,6 @@ void Transfer::setStreamId(const QString &si) { m_streamId = si; emit streamIdChanged(); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setStreamId" << si; -#endif } QUrl Transfer::streamUrl() const { @@ -382,9 +283,6 @@ void Transfer::setStreamUrl(const QUrl &url) { m_streamUrl = url; emit streamUrlChanged(); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setStreamUrl" << url; -#endif } QString Transfer::title() const { @@ -395,15 +293,18 @@ void Transfer::setTitle(const QString &t) { if (t != title()) { m_title = t; emit titleChanged(); -#ifdef MEEGO_EDITION_HARMATTAN - if (m_tuiTransfer) { - m_tuiTransfer->setName(t); - } -#endif } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setTitle" << t; -#endif +} + +QString Transfer::trackId() const { + return m_trackId; +} + +void Transfer::setTrackId(const QString &i) { + if (i != trackId()) { + m_trackId = i; + emit trackIdChanged(); + } } Transfer::TransferType Transfer::transferType() const { @@ -414,45 +315,11 @@ void Transfer::setTransferType(TransferType type) { if (type != transferType()) { m_transferType = type; emit transferTypeChanged(); -#ifdef MEEGO_EDITION_HARMATTAN - if (m_tuiTransfer) { - switch (type) { - case Transfer::Upload: - m_tuiTransfer->setTransferType(TransferUI::Client::TRANSFER_TYPES_UPLOAD); - return; - default: - m_tuiTransfer->waitForCommit(); - m_tuiTransfer->setTransferType(TransferUI::Client::TRANSFER_TYPES_DOWNLOAD); - m_tuiTransfer->setCanPause(true); - m_tuiTransfer->commit(); - return; - } - } -#endif } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setTransferType" << type; -#endif } QUrl Transfer::url() const { - return m_url; -} - -void Transfer::setUrl(const QUrl &u) { - if (u != url()) { - m_url = u; - emit urlChanged(); - - QString path = u.path(); - - if (path.contains(QRegExp("\\.\\w{3,4}$"))) { - setFileExtension(path.mid(path.lastIndexOf('.'))); - } - } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::setUrl" << u; -#endif + return m_reply ? m_reply->url() : QUrl(); } void Transfer::queue() { @@ -463,6 +330,7 @@ void Transfer::queue() { case Connecting: case Downloading: case Uploading: + case ExecutingCustomCommand: return; default: break; @@ -478,6 +346,7 @@ void Transfer::start() { case Connecting: case Downloading: case Uploading: + case ExecutingCustomCommand: return; default: break; @@ -506,6 +375,7 @@ void Transfer::pause() { case Canceled: case Completed: case Connecting: + case ExecutingCustomCommand: return; default: break; @@ -524,6 +394,7 @@ void Transfer::cancel() { switch (status()) { case Canceled: case Completed: + case ExecutingCustomCommand: return; default: break; @@ -541,7 +412,7 @@ void Transfer::cancel() { } void Transfer::startDownload(const QUrl &u) { - setUrl(u); + Logger::log("Transfer::startDownload(). URL: " + u.toString(), Logger::LowVerbosity); QDir().mkpath(downloadPath()); if (!m_file.open(m_file.exists() ? QFile::Append : QFile::WriteOnly)) { @@ -556,16 +427,12 @@ void Transfer::startDownload(const QUrl &u) { } QNetworkRequest request(u); + request.setRawHeader("User-Agent", USER_AGENT); if (m_bytesTransferred > 0) { request.setRawHeader("Range", "bytes=" + QByteArray::number(m_bytesTransferred) + "-"); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::startDownload: Resuming download from" << m_bytesTransferred; -#endif } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::startDownload: Downloading" << u; -#endif + setStatus(Downloading); m_redirects = 0; @@ -576,7 +443,7 @@ void Transfer::startDownload(const QUrl &u) { } void Transfer::followRedirect(const QUrl &u) { - setUrl(u); + Logger::log("Transfer::followRedirect(). URL: " + u.toString(), Logger::LowVerbosity); QDir().mkpath(downloadPath()); if (!m_file.open(m_file.exists() ? QFile::Append : QFile::WriteOnly)) { @@ -593,24 +460,71 @@ void Transfer::followRedirect(const QUrl &u) { } QNetworkRequest request(u); + request.setRawHeader("User-Agent", USER_AGENT); if (m_bytesTransferred > 0) { request.setRawHeader("Range", "bytes=" + QByteArray::number(m_bytesTransferred) + "-"); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::followRedirect: Resuming download from" << m_bytesTransferred; -#endif } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::followRedirect: Downloading" << u; -#endif + m_reply = m_nam->get(request); connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); } +bool Transfer::executeCustomCommands() { + Logger::log("Transfer::executeCustomCommands()", Logger::LowVerbosity); + m_commands.clear(); + QString command = customCommand(); + const QString defaultCommand = Settings::customTransferCommand(); + const bool defaultEnabled = (!defaultCommand.isEmpty()) && (Settings::customTransferCommandEnabled()); + + if (!command.isEmpty()) { + const QString workingDirectory = downloadPath(); + command.replace("%f", fileName()); + m_commands << Command(workingDirectory, command); + Logger::log(QString("Transfer::executeCustomCommands(): Adding custom command: Working directory: %1, Command: %2") + .arg(workingDirectory).arg(command), Logger::LowVerbosity); + } + + if ((defaultEnabled) && ((command.isEmpty()) || (!customCommandOverrideEnabled()))) { + const QString workingDirectory = downloadPath(); + command = defaultCommand; + command.replace("%f", fileName()); + m_commands << Command(workingDirectory, command); + Logger::log(QString("Transfer::executeCustomCommands(): Adding custom command: Working directory: %1, Command: %2") + .arg(workingDirectory).arg(command), Logger::LowVerbosity); + } + + if (!m_commands.isEmpty()) { + setStatus(ExecutingCustomCommand); + executeCustomCommand(m_commands.takeFirst()); + return true; + } + + return false; +} + +void Transfer::executeCustomCommand(const Command &command) { + if (!m_process) { + m_process = new QProcess(this); + connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onCustomCommandFinished(int))); + connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onCustomCommandError())); + } + + Logger::log(QString("Transfer::executeCustomCommand(): Working directory: %1, Command: %2") + .arg(command.workingDirectory).arg(command.command), Logger::LowVerbosity); + + if (QDir(command.workingDirectory).exists()) { + m_process->setWorkingDirectory(command.workingDirectory); + } + + m_process->start(command.command); +} + void Transfer::moveDownloadedFiles() { - QDir destDir(Settings::instance()->downloadPath(category())); + Logger::log("Transfer::moveDownloadedFiles()", Logger::LowVerbosity); + QDir destDir(Settings::downloadPath(category())); if (!destDir.mkpath(destDir.path())) { setErrorString(tr("Cannot make download path %1").arg(destDir.path())); @@ -620,10 +534,23 @@ void Transfer::moveDownloadedFiles() { QDir downDir(downloadPath()); - foreach (QString oldFileName, downDir.entryList(QDir::Files)) { + foreach (const QFileInfo &info, downDir.entryInfoList(QDir::Files)) { int i = 0; - QString newFileName = QString("%1/%2%3").arg(destDir.path()).arg(oldFileName).arg(fileExtension()); - + QString suffix = info.suffix(); + + if (suffix.isEmpty()) { + const QMimeDatabase db; + QFile file(info.absoluteFilePath()); + suffix = db.mimeTypeForData(&file).preferredSuffix(); + } + + if (suffix.isEmpty()) { + Logger::log("Transfer::moveDownloadedFiles(). Using default suffix .mp3", Logger::MediumVerbosity); + suffix = "mp3"; + } + + QString newFileName = QString("%1/%2.%3").arg(destDir.path()).arg(info.completeBaseName()).arg(suffix); + while ((destDir.exists(newFileName)) && (i < 100)) { i++; newFileName = (i == 1 ? QString("%1(%2)%3").arg(newFileName.left(newFileName.lastIndexOf('.'))) @@ -631,10 +558,8 @@ void Transfer::moveDownloadedFiles() { : QString("%1(%2)%3").arg(newFileName.left(newFileName.lastIndexOf('('))) .arg(i).arg(newFileName.mid(newFileName.lastIndexOf('.')))); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Transfer::moveDownloadedFiles: Renaming downloaded file to" << newFileName; -#endif - if (!destDir.rename(downDir.absoluteFilePath(oldFileName), newFileName)) { + + if (!destDir.rename(info.absoluteFilePath(), newFileName)) { setErrorString(tr("Cannot rename downloaded file to %1").arg(newFileName)); setStatus(Failed); return; @@ -647,71 +572,60 @@ void Transfer::moveDownloadedFiles() { } void Transfer::onReplyMetaDataChanged() { - if (size() > 0) { + if ((m_metadataSet) || (m_reply->error() != QNetworkReply::NoError) || (!m_reply->rawHeader("Location").isEmpty())) { return; } + + qint64 bytes = m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); - qint64 s = m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); - - if (s <= 0) { - s = m_reply->rawHeader("Content-Length").toLongLong(); + if (bytes <= 0) { + bytes = m_reply->rawHeader("Content-Length").toLongLong(); } - - if (s <= 0) { - QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - - if (!redirect.isNull()) { - return; - } - - redirect = m_reply->header(QNetworkRequest::LocationHeader); - - if (!redirect.isNull()) { - return; - } + + if (bytes > 0) { + setSize(bytes + bytesTransferred()); } - setSize(s); + m_metadataSet = true; } void Transfer::onReplyReadyRead() { - m_bytesTransferred += m_reply->bytesAvailable(); -#ifdef SYMBIAN_OS - m_buffer += m_reply->readAll(); + if (!m_metadataSet) { + return; + } + + const qint64 bytes = m_reply->bytesAvailable(); - if (m_buffer.size() >= BUFFER_SIZE) { - m_file.write(m_buffer); - m_buffer.clear(); + if (bytes < DOWNLOAD_BUFFER_SIZE) { + return; + } + + if (m_file.write(m_reply->read(bytes)) == -1) { + m_reply->deleteLater(); + m_reply = 0; + setErrorString(tr("Cannot write to file - %1").arg(m_file.errorString())); + setStatus(Failed); + return; } -#else - m_file.write(m_reply->readAll()); -#endif + + m_bytesTransferred += bytes; + emit bytesTransferredChanged(); + if (m_size > 0) { setProgress(m_bytesTransferred * 100 / m_size); } } void Transfer::onReplyFinished() { - QNetworkReply::NetworkError error = m_reply->error(); - QString errorString = m_reply->errorString(); - QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + const QString redirect = QString::fromUtf8(m_reply->rawHeader("Location")); - if (redirect.isNull()) { - redirect = m_reply->header(QNetworkRequest::LocationHeader); - } -#ifdef SYMBIAN_OS - if (!m_buffer.isEmpty()) { - m_file.write(m_buffer); - m_buffer.clear(); - } -#endif - m_file.close(); - m_reply->deleteLater(); - m_reply = 0; - - if (!redirect.isNull()) { + if (!redirect.isEmpty()) { + m_file.close(); + m_reply->deleteLater(); + m_reply = 0; + if (m_redirects < MAX_REDIRECTS) { - followRedirect(redirect.toString()); + followRedirect(redirect); } else { setErrorString(tr("Maximum redirects reached")); @@ -720,6 +634,26 @@ void Transfer::onReplyFinished() { return; } + + const QNetworkReply::NetworkError error = m_reply->error(); + const QString errorString = m_reply->errorString(); + + if ((m_reply->isOpen()) && (error == QNetworkReply::NoError) && (m_file.isOpen())) { + const qint64 bytes = m_reply->bytesAvailable(); + + if ((bytes > 0) && (m_metadataSet)) { + m_file.write(m_reply->read(bytes)); + m_bytesTransferred += bytes; + + if (m_size > 0) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } + + m_file.close(); + m_reply->deleteLater(); + m_reply = 0; switch (error) { case QNetworkReply::NoError: @@ -743,5 +677,31 @@ void Transfer::onReplyFinished() { return; } - moveDownloadedFiles(); + if (!executeCustomCommands()) { + moveDownloadedFiles(); + } +} + +void Transfer::onCustomCommandFinished(int exitCode) { + if (exitCode != 0) { + Logger::log("Transfer::onCustomCommandFinished(): Error: " + m_process->readAllStandardError()); + } + + if (!m_commands.isEmpty()) { + executeCustomCommand(m_commands.takeFirst()); + } + else { + moveDownloadedFiles(); + } +} + +void Transfer::onCustomCommandError() { + Logger::log("Transfer::onCustomCommandError(): " + m_process->errorString()); + + if (!m_commands.isEmpty()) { + executeCustomCommand(m_commands.takeFirst()); + } + else { + moveDownloadedFiles(); + } } diff --git a/app/src/desktop/transfer.h b/app/src/desktop/transfer.h new file mode 100644 index 0000000..b3cf3ae --- /dev/null +++ b/app/src/desktop/transfer.h @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRANSFER_H +#define TRANSFER_H + +#include +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QProcess; + +struct Command +{ + Command(const QString &dir, const QString &com) : + workingDirectory(dir), + command(com) + { + } + + QString workingDirectory; + QString command; +}; + +typedef QList CommandList; + +class Transfer : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qint64 bytesTransferred READ bytesTransferred NOTIFY bytesTransferredChanged) + Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) + Q_PROPERTY(QString customCommand READ customCommand WRITE setCustomCommand NOTIFY customCommandChanged) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled WRITE setCustomCommandOverrideEnabled + NOTIFY customCommandOverrideEnabledChanged) + Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged) + Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) + Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) + Q_PROPERTY(QString priorityString READ priorityString NOTIFY priorityChanged) + Q_PROPERTY(int progress READ progress NOTIFY progressChanged) + Q_PROPERTY(QString progressString READ progressString NOTIFY progressChanged) + Q_PROPERTY(QString service READ service NOTIFY serviceChanged) + Q_PROPERTY(qint64 size READ size WRITE setSize NOTIFY sizeChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString statusString READ statusString NOTIFY statusChanged) + Q_PROPERTY(QString streamId READ streamId WRITE setStreamId NOTIFY streamIdChanged) + Q_PROPERTY(QUrl streamUrl READ streamUrl WRITE setStreamUrl NOTIFY streamUrlChanged) + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QString trackId READ trackId WRITE setTrackId NOTIFY trackIdChanged) + Q_PROPERTY(TransferType transferType READ transferType WRITE setTransferType NOTIFY transferTypeChanged) + Q_PROPERTY(QUrl url READ url NOTIFY statusChanged) + + Q_ENUMS(Priority Status TransferType) + +public: + enum Priority { + HighPriority = 0, + NormalPriority, + LowPriority + }; + + enum Status { + Paused = 0, + Canceled, + Failed, + Completed, + Queued, + Connecting, + Downloading, + Uploading, + ExecutingCustomCommand, + Unknown + }; + + enum TransferType { + Download = 0, + Upload + }; + + explicit Transfer(QObject *parent = 0); + + void setNetworkAccessManager(QNetworkAccessManager *manager); + + qint64 bytesTransferred() const; + + QString category() const; + void setCategory(const QString &c); + + QString customCommand() const; + void setCustomCommand(const QString &c); + bool customCommandOverrideEnabled() const; + void setCustomCommandOverrideEnabled(bool enabled); + + QString downloadPath() const; + void setDownloadPath(const QString &path); + + QString errorString() const; + + QString fileName() const; + void setFileName(const QString &fn); + + QString id() const; + void setId(const QString &i); + + Priority priority() const; + void setPriority(Priority p); + QString priorityString() const; + + int progress() const; + QString progressString() const; + + QString service() const; + + qint64 size() const; + void setSize(qint64 s); + + Status status() const; + QString statusString() const; + + QString streamId() const; + void setStreamId(const QString &si); + QUrl streamUrl() const; + void setStreamUrl(const QUrl &url); + + QString title() const; + void setTitle(const QString &title); + + QString trackId() const; + void setTrackId(const QString &i); + + TransferType transferType() const; + void setTransferType(TransferType type); + + QUrl url() const; + +public Q_SLOTS: + void queue(); + void start(); + void pause(); + void cancel(); + +protected: + virtual void listStreams() = 0; + + void setErrorString(const QString &es); + + void setProgress(int p); + + void setService(const QString &s); + + void setStatus(Status s); + + void startDownload(const QUrl &u); + void followRedirect(const QUrl &u); + + bool executeCustomCommands(); + void executeCustomCommand(const Command &command); + + void moveDownloadedFiles(); + +private Q_SLOTS: + void onReplyMetaDataChanged(); + void onReplyReadyRead(); + void onReplyFinished(); + void onCustomCommandFinished(int exitCode); + void onCustomCommandError(); + +Q_SIGNALS: + void bytesTransferredChanged(); + void categoryChanged(); + void customCommandChanged(); + void customCommandOverrideEnabledChanged(); + void downloadPathChanged(); + void fileNameChanged(); + void idChanged(); + void priorityChanged(); + void progressChanged(); + void serviceChanged(); + void sizeChanged(); + void statusChanged(); + void streamIdChanged(); + void streamUrlChanged(); + void titleChanged(); + void trackIdChanged(); + void transferTypeChanged(); + +private: + QPointer m_nam; + QNetworkReply *m_reply; + QProcess *m_process; + + QFile m_file; + + bool m_ownNetworkAccessManager; + bool m_canceled; + + QString m_category; + + QString m_customCommand; + bool m_customCommandOverrideEnabled; + + QString m_downloadPath; + + QString m_errorString; + + QString m_fileName; + + QString m_id; + + Priority m_priority; + + int m_progress; + + QString m_service; + + qint64 m_size; + qint64 m_bytesTransferred; + + int m_redirects; + + Status m_status; + + QString m_streamId; + QUrl m_streamUrl; + + QString m_title; + + QString m_trackId; + + TransferType m_transferType; + + CommandList m_commands; + + bool m_metadataSet; +}; + +#endif // TRANSFER_H diff --git a/app/src/desktop/transferdelegate.cpp b/app/src/desktop/transferdelegate.cpp new file mode 100644 index 0000000..f0aba0c --- /dev/null +++ b/app/src/desktop/transferdelegate.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "transferdelegate.h" +#include "transfermodel.h" +#include +#include + +TransferDelegate::TransferDelegate(QObject *parent) : + QStyledItemDelegate(parent) +{ +} + +void TransferDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + if (index.column() == 3) { + QStyleOptionProgressBar bar; + bar.rect = option.rect; + bar.minimum = 0; + bar.maximum = 100; + bar.progress = index.data(TransferModel::ProgressRole).toInt(); + bar.textVisible = true; + bar.text = index.data(TransferModel::ProgressStringRole).toString(); + QApplication::style()->drawControl(QStyle::CE_ProgressBar, &bar, painter); + } + else { + QStyledItemDelegate::paint(painter, option, index); + } +} diff --git a/app/src/desktop/transferdelegate.h b/app/src/desktop/transferdelegate.h new file mode 100644 index 0000000..6410d0b --- /dev/null +++ b/app/src/desktop/transferdelegate.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRANSFERDELEGATE_H +#define TRANSFERDELEGATE_H + +#include + +class TransferDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit TransferDelegate(QObject *parent = 0); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +#endif // TRANSFERDELEGATE_H diff --git a/app/src/desktop/transferspage.cpp b/app/src/desktop/transferspage.cpp new file mode 100644 index 0000000..ab7a74b --- /dev/null +++ b/app/src/desktop/transferspage.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "transferspage.h" +#include "customcommanddialog.h" +#include "definitions.h" +#include "settings.h" +#include "transferdelegate.h" +#include "transfermodel.h" +#include "transfers.h" +#include +#include +#include +#include +#include +#include +#include + +TransfersPage::TransfersPage(QWidget *parent) : + Page(parent), + m_model(new TransferModel(this)), + m_transferMenu(new QMenu(tr("&Transfer"), this)), + m_categoryMenu(new QMenu(tr("&Category"), this)), + m_priorityMenu(new QMenu(tr("&Priority"), this)), + m_categoryGroup(new QActionGroup(this)), + m_priorityGroup(new QActionGroup(this)), + m_transferCommandAction(new QAction(QIcon::fromTheme("system-run"), tr("Set &custom command"), this)), + m_transferStartAction(new QAction(QIcon::fromTheme("media-playback-start"), tr("&Start"), this)), + m_transferPauseAction(new QAction(QIcon::fromTheme("media-playback-pause"), tr("&Pause"), this)), + m_transferRemoveAction(new QAction(QIcon::fromTheme("edit-delete"), tr("&Remove"), this)), + m_view(new QTreeView(this)), + m_layout(new QVBoxLayout(this)) +{ + updateWindowTitle(); + + m_transferMenu->addAction(m_transferCommandAction); + m_transferMenu->addAction(m_transferStartAction); + m_transferMenu->addAction(m_transferPauseAction); + m_transferMenu->addMenu(m_categoryMenu); + m_transferMenu->addMenu(m_priorityMenu); + m_transferMenu->addAction(m_transferRemoveAction); + m_transferMenu->setEnabled(false); + + setCategoryMenuActions(); + + const QStringList priorities = QStringList() << tr("High") << tr("Normal") << tr("Low"); + + for (int i = 0; i < priorities.size(); i++) { + QAction *action = m_priorityMenu->addAction(priorities.at(i), this, SLOT(setTransferPriority())); + action->setCheckable(true); + action->setData(i); + m_priorityGroup->addAction(action); + } + + m_view->setModel(m_model); + m_view->setItemDelegate(new TransferDelegate(m_view)); + m_view->setAlternatingRowColors(true); + m_view->setSelectionBehavior(QTreeView::SelectRows); + m_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_view->setEditTriggers(QTreeView::NoEditTriggers); + m_view->setItemsExpandable(false); + m_view->setUniformRowHeights(true); + m_view->setAllColumnsShowFocus(true); + m_view->setRootIsDecorated(false); + m_view->header()->restoreState(Settings::transfersHeaderViewState()); + + m_layout->addWidget(m_view); + m_layout->setContentsMargins(0, 0, 0, 0); + + connect(m_categoryMenu, SIGNAL(aboutToShow()), this, SLOT(setActiveCategoryMenuAction())); + connect(m_priorityMenu, SIGNAL(aboutToShow()), this, SLOT(setActivePriorityMenuAction())); + connect(m_transferCommandAction, SIGNAL(triggered()), this, SLOT(setTransferCustomCommand())); + connect(m_transferStartAction, SIGNAL(triggered()), this, SLOT(queueTransfer())); + connect(m_transferPauseAction, SIGNAL(triggered()), this, SLOT(pauseTransfer())); + connect(m_transferRemoveAction, SIGNAL(triggered()), this, SLOT(removeTransfer())); + connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + connect(m_view->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), + this, SLOT(onCurrentTransferChanged(QModelIndex))); + connect(Settings::instance(), SIGNAL(categoriesChanged()), this, SLOT(setCategoryMenuActions())); + connect(Transfers::instance(), SIGNAL(activeChanged(int)), this, SLOT(updateWindowTitle())); + connect(Transfers::instance(), SIGNAL(countChanged(int)), this, SLOT(updateWindowTitle())); +} + +void TransfersPage::closeEvent(QCloseEvent *e) { + Settings::setTransfersHeaderViewState(m_view->header()->saveState()); + Page::closeEvent(e); +} + +void TransfersPage::queueTransfer() { + if (m_view->currentIndex().isValid()) { + queueTransfer(m_view->currentIndex()); + } +} + +void TransfersPage::queueTransfer(const QModelIndex &index) { + if (Transfer *transfer = Transfers::instance()->get(index.row())) { + transfer->queue(); + } +} + +void TransfersPage::pauseTransfer() { + if (m_view->currentIndex().isValid()) { + pauseTransfer(m_view->currentIndex()); + } +} + +void TransfersPage::pauseTransfer(const QModelIndex &index) { + if (Transfer *transfer = Transfers::instance()->get(index.row())) { + transfer->pause(); + } +} + +void TransfersPage::removeTransfer() { + if (m_view->currentIndex().isValid()) { + removeTransfer(m_view->currentIndex()); + } +} + +void TransfersPage::removeTransfer(const QModelIndex &index) { + if (Transfer *transfer = Transfers::instance()->get(index.row())) { + if (QMessageBox::question(this, tr("Remove?"), + tr("Do you want to remove '%1'?").arg(transfer->title())) == QMessageBox::Yes) { + transfer->cancel(); + } + } +} + +void TransfersPage::setTransferCategory() { + if (m_view->currentIndex().isValid()) { + if (const QAction *action = m_categoryGroup->checkedAction()) { + setTransferCategory(m_view->currentIndex(), action->text()); + } + } +} + +void TransfersPage::setTransferCategory(const QModelIndex &index, const QString &category) { + m_model->setData(index, category, TransferModel::CategoryRole); +} + +void TransfersPage::setTransferCustomCommand() { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { + CustomCommandDialog dialog(this); + dialog.setCommand(index.data(TransferModel::CustomCommandRole).toString()); + dialog.setOverrideEnabled(index.data(TransferModel::CustomCommandOverrideEnabledRole).toBool()); + + if (dialog.exec() == QDialog::Accepted) { + setTransferCustomCommand(index, dialog.command(), dialog.overrideEnabled()); + } + } +} + +void TransfersPage::setTransferCustomCommand(const QModelIndex &index, const QString &command, bool overrideEnabled) { + m_model->setData(index, command, TransferModel::CustomCommandRole); + m_model->setData(index, overrideEnabled, TransferModel::CustomCommandOverrideEnabledRole); +} + +void TransfersPage::setTransferPriority() { + if (m_view->currentIndex().isValid()) { + if (const QAction *action = m_priorityGroup->checkedAction()) { + setTransferPriority(m_view->currentIndex(), action->data().toInt()); + } + } +} + +void TransfersPage::setTransferPriority(const QModelIndex &index, int priority) { + m_model->setData(index, priority, TransferModel::PriorityRole); +} + +void TransfersPage::setCategoryMenuActions() { + const QStringList categories = Settings::categoryNames(); + m_categoryMenu->clear(); + + foreach (const QString &category, categories) { + QAction *action = m_categoryMenu->addAction(category, this, SLOT(setTransferCategory())); + action->setCheckable(true); + action->setData(category); + m_categoryGroup->addAction(action); + } +} + +void TransfersPage::setActiveCategoryMenuAction() { + if (!m_view->currentIndex().isValid()) { + return; + } + + const QVariant category = m_view->currentIndex().data(TransferModel::CategoryRole).toString(); + + foreach (QAction *action, m_categoryGroup->actions()) { + if (action->data() == category) { + action->setChecked(true); + break; + } + } +} + +void TransfersPage::setActivePriorityMenuAction() { + if (!m_view->currentIndex().isValid()) { + return; + } + + const QVariant priority = m_view->currentIndex().data(TransferModel::PriorityRole); + + foreach (QAction *action, m_priorityGroup->actions()) { + if (action->data() == priority) { + action->setChecked(true); + break; + } + } +} + +void TransfersPage::showContextMenu(const QPoint &pos) { + if (m_view->currentIndex().isValid()) { + m_transferMenu->popup(m_view->mapToGlobal(pos)); + } +} + +void TransfersPage::updateWindowTitle() { + if (Transfers::instance()->count() > 0) { + setWindowTitle(tr("Transfers (%1/%2)").arg(Transfers::instance()->active()) + .arg(Transfers::instance()->count())); + } + else { + setWindowTitle(tr("Transfers")); + } +} + +void TransfersPage::onCurrentTransferChanged(const QModelIndex &index) { + m_transferMenu->setEnabled(index.isValid()); +} diff --git a/app/src/desktop/transferspage.h b/app/src/desktop/transferspage.h new file mode 100644 index 0000000..5d22f98 --- /dev/null +++ b/app/src/desktop/transferspage.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRANSFERSPAGE_H +#define TRANSFERSPAGE_H + +#include "page.h" + +class TransferModel; +class QActionGroup; +class QMenu; +class QTreeView; +class QVBoxLayout; + +class TransfersPage : public Page +{ + Q_OBJECT + +public: + explicit TransfersPage(QWidget *parent = 0); + +private Q_SLOTS: + void queueTransfer(); + void queueTransfer(const QModelIndex &index); + + void pauseTransfer(); + void pauseTransfer(const QModelIndex &index); + + void removeTransfer(); + void removeTransfer(const QModelIndex &index); + + void setTransferCategory(); + void setTransferCategory(const QModelIndex &index, const QString &category); + + void setTransferCustomCommand(); + void setTransferCustomCommand(const QModelIndex &index, const QString &command, bool overrideEnabled); + + void setTransferPriority(); + void setTransferPriority(const QModelIndex &index, int priority); + + void setCategoryMenuActions(); + + void setActiveCategoryMenuAction(); + void setActivePriorityMenuAction(); + + void showContextMenu(const QPoint &pos); + + void updateWindowTitle(); + + void onCurrentTransferChanged(const QModelIndex &index); + +private: + virtual void closeEvent(QCloseEvent *e); + + TransferModel *m_model; + + QMenu *m_transferMenu; + QMenu *m_categoryMenu; + QMenu *m_priorityMenu; + + QActionGroup *m_categoryGroup; + QActionGroup *m_priorityGroup; + + QAction *m_transferCommandAction; + QAction *m_transferStartAction; + QAction *m_transferPauseAction; + QAction *m_transferRemoveAction; + + QTreeView *m_view; + + QVBoxLayout *m_layout; +}; + +#endif // TRANSFERSPAGE_H diff --git a/app/src/desktop/treeview.cpp b/app/src/desktop/treeview.cpp new file mode 100644 index 0000000..aace569 --- /dev/null +++ b/app/src/desktop/treeview.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "treeview.h" +#include "itemmetadata.h" +#include +#include +#include + +ItemMetaDataRole::ItemMetaDataRole(const QString &name, int role) +{ + this->name = name; + this->role = role; +} + +MKTreeView::MKTreeView(QWidget *parent) : + QTreeView(parent), + m_itemMetaData(0), + m_itemMetaDataEnabled(false), + m_itemMetaDataThumbnailRole(Qt::DecorationRole), + m_itemMetaDataRow(-1) +{ +} + +bool MKTreeView::itemMetaDataEnabled() const { + return m_itemMetaDataEnabled; +} + +void MKTreeView::setItemMetaDataEnabled(bool enabled) { + m_itemMetaDataEnabled = enabled; + viewport()->setMouseTracking(enabled); +} + +int MKTreeView::itemMetaDataThumbnailRole() const { + return m_itemMetaDataThumbnailRole; +} + +void MKTreeView::setItemMetaDataThumbnailRole(int role) { + m_itemMetaDataThumbnailRole = role; +} + +QList MKTreeView::itemMetaDataTextRoles() const { + return m_itemMetaDataTextRoles; +} + +void MKTreeView::setItemMetaDataTextRoles(const QList &roles) { + m_itemMetaDataTextRoles = roles; +} + +bool MKTreeView::viewportEvent(QEvent *event) { + if ((event->type() == QEvent::ToolTip) && (itemMetaDataEnabled())) { + if (QHelpEvent *he = static_cast(event)) { + const QModelIndex index = indexAt(he->pos()); + + if ((index.isValid()) && ((!m_itemMetaData) || (!m_itemMetaData->isVisible()))) { + if (!m_itemMetaData) { + m_itemMetaData = new ItemMetaData(this); + m_itemMetaData->setWindowFlags(Qt::ToolTip); + m_itemMetaData->setThumbnailSize(QSize(96, 96)); + } + + m_itemMetaDataRow = index.row(); + QList metaData; + + for (int i = 0; i < m_itemMetaDataTextRoles.size(); i++) { + metaData << ItemMetaDataValue(m_itemMetaDataTextRoles.at(i).name, + index.data(m_itemMetaDataTextRoles.at(i).role)); + } + + m_itemMetaData->setMetaData(metaData); + m_itemMetaData->setThumbnailSource(index.data(itemMetaDataThumbnailRole()).toString()); + + const QRect rect = QApplication::desktop()->availableGeometry(m_itemMetaData); + QPoint pos = he->globalPos(); + pos.setX(pos.x() + 1); + pos.setY(pos.y() + 1); + + if (pos.x() + m_itemMetaData->width() > rect.right()) { + pos.setX(pos.x() - m_itemMetaData->width() - 2); + } + + if (pos.y() + m_itemMetaData->height() > rect.bottom()) { + pos.setY(pos.y() - m_itemMetaData->height() - 2); + } + + m_itemMetaData->move(pos); + m_itemMetaData->show(); + return true; + } + } + } + else if ((event->type() == QEvent::MouseMove) && (m_itemMetaData)) { + if (QMouseEvent *me = static_cast(event)) { + const QModelIndex index = indexAt(me->pos()); + + if ((!index.isValid()) || (index.row() != m_itemMetaDataRow)) { + m_itemMetaData->hide(); + } + } + } + else if (((event->type() == QEvent::MouseButtonPress) || (event->type() == QEvent::Leave) + || (event->type() == QEvent::Hide) || (event->type() == QEvent::Close)) && (m_itemMetaData)) { + m_itemMetaData->hide(); + } + + return QTreeView::viewportEvent(event); +} diff --git a/app/src/desktop/treeview.h b/app/src/desktop/treeview.h new file mode 100644 index 0000000..02348c7 --- /dev/null +++ b/app/src/desktop/treeview.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREEVIEW_H +#define TREEVIEW_H + +#include + +class ItemMetaData; + +class ItemMetaDataRole +{ + +public: + explicit ItemMetaDataRole(const QString &name, int role); + + QString name; + int role; +}; + +class MKTreeView : public QTreeView +{ + Q_OBJECT + + Q_PROPERTY(bool itemMetaDataEnabled READ itemMetaDataEnabled WRITE setItemMetaDataEnabled) + Q_PROPERTY(int itemMetaDataThumbnailRole READ itemMetaDataThumbnailRole WRITE setItemMetaDataThumbnailRole) + +public: + explicit MKTreeView(QWidget *parent = 0); + + bool itemMetaDataEnabled() const; + void setItemMetaDataEnabled(bool enabled); + + int itemMetaDataThumbnailRole() const; + void setItemMetaDataThumbnailRole(int role); + + QList itemMetaDataTextRoles() const; + void setItemMetaDataTextRoles(const QList &roles); + +private: + virtual bool viewportEvent(QEvent *event); + +private: + ItemMetaData *m_itemMetaData; + + bool m_itemMetaDataEnabled; + + int m_itemMetaDataThumbnailRole; + + QList m_itemMetaDataTextRoles; + + int m_itemMetaDataRow; +}; + +#endif // TREEVIEW_H diff --git a/app/src/harmattan/activecolormodel.h b/app/src/harmattan/activecolormodel.h deleted file mode 100644 index 16a3de1..0000000 --- a/app/src/harmattan/activecolormodel.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#ifndef ACTIVECOLORMODEL_H -#define ACTIVECOLORMODEL_H - -#include "selectionmodel.h" - -class ActiveColorModel : public SelectionModel -{ - Q_OBJECT - -public: - explicit ActiveColorModel(QObject *parent = 0) : - SelectionModel(parent) - { - append("#66b907", "#66b907"); - append("#418b11", "#418b11"); - append("#37790c", "#37790c"); - append("#346905", "#346905"); - append("#0fa9cd", "#0fa9cd"); - append("#0881cb", "#0881cb"); - append("#066bbe", "#066bbe"); - append("#2054b1", "#2054b1"); - append("#6705bd", "#6705bd"); - append("#8a12bc", "#8a12bc"); - append("#cd0fbc", "#cd0fbc"); - append("#e805a3", "#e805a3"); - append("#ef5906", "#ef5906"); - append("#ea6910", "#ea6910"); - append("#f7751e", "#f7751e"); - append("#ff8806", "#ff8806"); - append("#ed970c", "#ed970c"); - append("#f2b317", "#f2b317"); - } -}; - -#endif // ACTIVECOLORMODEL_H diff --git a/app/src/harmattan/cookiejar.cpp b/app/src/harmattan/cookiejar.cpp deleted file mode 100644 index 27412ee..0000000 --- a/app/src/harmattan/cookiejar.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "cookiejar.h" -#ifdef MUSIKLOUD_DEBUG -#include -#endif - -CookieJar::CookieJar(QObject *parent) : - QNetworkCookieJar(parent) -{ -} - -void CookieJar::setAllCookies(const QVariantList &cookies) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "CookieJar::setAllCookies" << cookies; -#endif - QList list; - - foreach (QVariant v, cookies) { - QVariantMap cookie = v.toMap(); - list << QNetworkCookie(cookie.value("name").toString().toUtf8(), cookie.value("value").toString().toUtf8()); - } - - QNetworkCookieJar::setAllCookies(list); -} - -bool CookieJar::setCookiesFromUrl(const QVariantList &cookies, const QUrl &url) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "CookieJar::setCookiesFromUrl" << cookies << url; -#endif - QList list; - - foreach (QVariant v, cookies) { - QVariantMap cookie = v.toMap(); - list << QNetworkCookie(cookie.value("name").toString().toUtf8(), cookie.value("value").toString().toUtf8()); - } - - return QNetworkCookieJar::setCookiesFromUrl(list, url); -} diff --git a/app/src/harmattan/cookiejar.h b/app/src/harmattan/cookiejar.h deleted file mode 100644 index cba4028..0000000 --- a/app/src/harmattan/cookiejar.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef COOKIEJAR_H -#define COOKIEJAR_H - -#include -#include - -class CookieJar : public QNetworkCookieJar -{ - Q_OBJECT - -public: - explicit CookieJar(QObject *parent = 0); - - Q_INVOKABLE void setAllCookies(const QVariantList &cookies); - Q_INVOKABLE bool setCookiesFromUrl(const QVariantList &cookies, const QUrl &url); -}; - -#endif // COOKIEJAR_H diff --git a/app/src/harmattan/definitions.h b/app/src/harmattan/definitions.h deleted file mode 100644 index 8aee3a0..0000000 --- a/app/src/harmattan/definitions.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#ifndef DEFINITIONS_H -#define DEFINITIONS_H - -#include -#include -#if QT_VERSION >= 0x050000 -#include -#else -#include -#endif - -static const int MAX_CONCURRENT_TRANSFERS = 4; -static const int MAX_REDIRECTS = 8; - -static const int MAX_RESULTS = 20; - -static const int LARGE_THUMBNAIL_SIZE = 500; -static const int THUMBNAIL_SIZE = 64; - -static const QRegExp ILLEGAL_FILENAME_CHARS_RE("[\"@&~=\\/:?#!|<>*^]"); - -static const QString VERSION_NUMBER("0.0.4"); - -static const QStringList SUPPORTED_AUDIO_FORMATS = QStringList() << "*.aiff" << "*.ape" << "*.flac" << "*.m4a" << "*.mp3" - << "*.ogg" << "*.wav" << "*.wma"; - -#if QT_VERSION >= 0x050000 -static const QString DATABASE_PATH(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/MusiKloud2/"); -static const QString DOWNLOAD_PATH("/home/user/MyDocs/MusiKloud2/"); -static const QString STORAGE_PATH(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/MusiKloud2/"); -static const QStringList PLUGIN_PATHS = QStringList() << "/opt/musikloud2/plugins/" - << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) - + "/MusiKloud2/plugins/"; -#else -static const QString DATABASE_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); -static const QString DOWNLOAD_PATH("/home/user/MyDocs/MusiKloud2/"); -static const QString STORAGE_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); -static const QStringList PLUGIN_PATHS = QStringList() << "/opt/musikloud2/plugins/" - << QDesktopServices::storageLocation(QDesktopServices::HomeLocation) - + "/.config/MusiKloud2/plugins/"; -#endif - -#endif // DEFINITIONS_H diff --git a/app/src/harmattan/networkaccessmanagerfactory.cpp b/app/src/harmattan/networkaccessmanagerfactory.cpp deleted file mode 100644 index 15d982a..0000000 --- a/app/src/harmattan/networkaccessmanagerfactory.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "networkaccessmanagerfactory.h" -#include "cookiejar.h" -#include - -NetworkAccessManagerFactory::NetworkAccessManagerFactory() : - m_cookieJar(new CookieJar) -{ -} - -NetworkAccessManagerFactory::~NetworkAccessManagerFactory() { - if (m_cookieJar) { - delete m_cookieJar; - m_cookieJar = 0; - } -} - -QNetworkCookieJar* NetworkAccessManagerFactory::cookieJar() const { - return m_cookieJar; -} - -QNetworkAccessManager* NetworkAccessManagerFactory::create(QObject *parent) { - QNetworkAccessManager *manager = new QNetworkAccessManager(parent); - - if (!m_cookieJar) { - m_cookieJar = new CookieJar; - } - - manager->setCookieJar(m_cookieJar); - m_cookieJar->setParent(0); - - return manager; -} diff --git a/app/src/harmattan/networkaccessmanagerfactory.h b/app/src/harmattan/networkaccessmanagerfactory.h deleted file mode 100644 index 2953ac8..0000000 --- a/app/src/harmattan/networkaccessmanagerfactory.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef NETWORKACCESSMANAGERFACTORY_H -#define NETWORKACCESSMANAGERFACTORY_H - -#include - -class QNetworkCookieJar; - -class NetworkAccessManagerFactory : public QDeclarativeNetworkAccessManagerFactory -{ - -public: - explicit NetworkAccessManagerFactory(); - ~NetworkAccessManagerFactory(); - - QNetworkCookieJar *cookieJar() const; - - QNetworkAccessManager* create(QObject *parent); - -private: - QNetworkCookieJar *m_cookieJar; -}; - -#endif // NETWORKACCESSMANAGERFACTORY_H diff --git a/app/src/harmattan/qml/AppWindow.qml b/app/src/harmattan/qml/AppWindow.qml deleted file mode 100644 index ebe3d54..0000000 --- a/app/src/harmattan/qml/AppWindow.qml +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -Window { - id: window - - property bool showStatusBar: true - property bool showToolBar: true - property variant initialPage - property alias pageStack: stack - property Style platformStyle: PageStackWindowStyle{} - - //Deprecated, TODO Remove this on w13 - property alias style: window.platformStyle - - objectName: "pageStackWindow" - - StatusBar { - id: statusBar - anchors { - top: parent.top - topMargin: window.showStatusBar ? 0 : -height - } - width: parent.width - } - - onOrientationChangeStarted: statusBar.orientation = screen.currentOrientation - - Image { - id: backgroundImage - source: window.inPortrait ? platformStyle.portraiteBackground : platformStyle.landscapeBackground - fillMode: platformStyle.backgroundFillMode - width: window.inPortrait ? screen.displayHeight : screen.displayWidth - height: window.inPortrait ? screen.displayWidth : screen.displayHeight - anchors { top: statusBar.bottom; left: parent.left; } - } - - Item { - objectName: "appWindowContent" - width: parent.width - anchors.top: window.showStatusBar ? statusBar.bottom : parent.top - anchors.bottom: parent.bottom - - // content area - Item { - id: contentArea - anchors { - top: parent.top - left: parent.left - right: parent.right - bottom: toolBar.top - } - PageStack { - id: stack - anchors.fill: parent - toolBar: toolBar - } - } - - Image { - z: 1001 - visible: platformStyle.cornersVisible - anchors.top : parent.top - anchors.left: parent.left - source: "image://theme/meegotouch-applicationwindow-corner-top-left" - } - - Image { - z: 1001 - visible: platformStyle.cornersVisible - anchors.top: parent.top - anchors.right: parent.right - source: "image://theme/meegotouch-applicationwindow-corner-top-right" - } - - Image { - z: 1001 - visible: (platformStyle.cornersVisible) || (window.inPortrait) - anchors.bottom : parent.bottom - anchors.left: parent.left - source: "image://theme/meegotouch-applicationwindow-corner-bottom-left" - } - - Image { - z: 1001 - visible: (platformStyle.cornersVisible) || (window.inPortrait) - anchors.bottom : parent.bottom - anchors.right: parent.right - source: "image://theme/meegotouch-applicationwindow-corner-bottom-right" - } - - ToolBar { - id: toolBar - - anchors.bottom: parent.bottom - states: State { - name: "hide" - when: (!window.showToolBar) || (inputContext.softwareInputPanelVisible) || (inputContext.customSoftwareInputPanelVisible) - PropertyChanges { target: toolBar; height: 0; opacity: 0.0 } - } - } - } - - // event preventer when page transition is active - MouseArea { - anchors.fill: parent - enabled: pageStack.busy - } - - Component.onCompleted: { - if (initialPage) pageStack.push(initialPage); - } -} diff --git a/app/src/harmattan/qml/AppearanceSettingsPage.qml b/app/src/harmattan/qml/AppearanceSettingsPage.qml deleted file mode 100644 index 5b9fd05..0000000 --- a/app/src/harmattan/qml/AppearanceSettingsPage.qml +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Appearance") - tools: ToolBarLayout { - id: toolBar - - BackToolIcon {} - } - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - top: parent.top - left: parent.left - right: parent.right - } - spacing: UI.PADDING_DOUBLE - - PageHeader { - title: root.title - } - - ValueSelector { - width: parent.width - title: qsTr("Screen orientation") - model: ScreenOrientationModel {} - value: Settings.screenOrientation - onValueChanged: Settings.screenOrientation = value - } - - Label { - x: UI.PADDING_DOUBLE - font.bold: true - text: qsTr("Active color") - } - - Flow { - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE * 2 - spacing: UI.PADDING_DOUBLE - - Repeater { - model: ActiveColorModel {} - - Rectangle { - width: 50 - height: 50 - color: value - border.color: "#fff" - border.width: color == Settings.activeColor ? 2 : 0 - - MouseArea { - anchors.fill: parent - onClicked: { - Settings.activeColor = value; - Settings.activeColorString = "color" + (index + 2).toString(); - } - } - } - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } -} diff --git a/app/src/harmattan/qml/ArtistDelegate.qml b/app/src/harmattan/qml/ArtistDelegate.qml deleted file mode 100644 index 5bb9af2..0000000 --- a/app/src/harmattan/qml/ArtistDelegate.qml +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - height: avatar.height + UI.PADDING_DOUBLE * 2 - - Avatar { - id: avatar - - width: 64 - height: 64 - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - source: thumbnailUrl - enabled: false - } - - Label { - anchors { - left: avatar.right - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - font.bold: true - elide: Text.ElideRight - text: name - } -} diff --git a/app/src/harmattan/qml/BackToolIcon.qml b/app/src/harmattan/qml/BackToolIcon.qml deleted file mode 100644 index 2733580..0000000 --- a/app/src/harmattan/qml/BackToolIcon.qml +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -MyToolIcon { - property Item pageToDestroy: null - - platformIconId: "toolbar-back" - onClicked: { - pageToDestroy = appWindow.pageStack.currentPage; - appWindow.pageStack.pop(); - } - - Connections { - target: pageToDestroy === null ? null : appWindow.pageStack - onBusyChanged: { - if ((!appWindow.pageStack.busy) && (pageToDestroy !== null)) { - pageToDestroy.destroy(); - pageToDestroy = null; - } - } - } -} diff --git a/app/src/harmattan/qml/CategoriesPage.qml b/app/src/harmattan/qml/CategoriesPage.qml deleted file mode 100644 index bbfc39b..0000000 --- a/app/src/harmattan/qml/CategoriesPage.qml +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Categories") - tools: ToolBarLayout { - - BackToolIcon {} - - MyToolIcon { - platformIconId: "toolbar-add" - onClicked: { - loader.sourceComponent = editDialog; - loader.item.name = ""; - loader.item.path = ""; - loader.item.open(); - } - } - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Edit") - onClicked: { - loader.sourceComponent = editDialog; - loader.item.name = categoryModel.data(view.currentIndex, "name"); - loader.item.path = categoryModel.data(view.currentIndex, "path"); - loader.item.open(); - } - } - - MenuItem { - text: qsTr("Remove") - onClicked: categoryModel.removeCategory(view.currentIndex) - } - } - } - - ListView { - id: view - - anchors.fill: parent - interactive: count > 0 - highlightFollowsCurrentItem: false - model: CategoryModel { - id: categoryModel - } - header: PageHeader { - title: root.title - } - delegate: CategoryDelegate { - onClicked: { - loader.sourceComponent = editDialog; - loader.item.name = name; - loader.item.path = path; - loader.item.open(); - } - onPressAndHold: { - view.currentIndex = index; - contextMenu.open(); - } - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No categories") - visible: categoryModel.count == 0 - } - } - - ScrollDecorator { - flickableItem: view - } - - Loader { - id: loader - } - - Component { - id: editDialog - - EditCategoryDialog {} - } -} - diff --git a/app/src/harmattan/qml/CategoryDelegate.qml b/app/src/harmattan/qml/CategoryDelegate.qml deleted file mode 100644 index cd17d88..0000000 --- a/app/src/harmattan/qml/CategoryDelegate.qml +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - Column { - id: column - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - Label { - width: parent.width - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: name - } - - Label { - width: parent.width - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: path - } - } -} diff --git a/app/src/harmattan/qml/CommentDelegate.qml b/app/src/harmattan/qml/CommentDelegate.qml deleted file mode 100644 index 6afd0f1..0000000 --- a/app/src/harmattan/qml/CommentDelegate.qml +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - signal thumbnailClicked - - height: userLabel.height + commentLabel.height + UI.PADDING_DOUBLE * 2 - - Avatar { - id: avatar - - z: 100 - width: 48 - height: 48 - anchors { - left: parent.left - top: parent.top - margins: UI.PADDING_DOUBLE - } - source: thumbnailUrl ? thumbnailUrl : "images/avatar.png" - onClicked: root.thumbnailClicked() - } - - Label { - id: userLabel - - anchors { - left: avatar.right - right: parent.right - top: parent.top - margins: UI.PADDING_DOUBLE - } - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - text: qsTr("by") + " " + artist + " " + qsTr("on") + " " + date - } - - Label { - id: commentLabel - - anchors { - left: userLabel.left - right: parent.right - top: userLabel.bottom - rightMargin: UI.PADDING_DOUBLE - } - font.pixelSize: UI.FONT_SMALL - text: "\"" + body + "\"" - } -} diff --git a/app/src/harmattan/qml/DrillDownDelegate.qml b/app/src/harmattan/qml/DrillDownDelegate.qml deleted file mode 100644 index 7be0e0b..0000000 --- a/app/src/harmattan/qml/DrillDownDelegate.qml +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - property alias text: label.text - - subItemIndicator: true - - Label { - id: label - - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - font.bold: true - text: display - } -} diff --git a/app/src/harmattan/qml/EditCategoryDialog.qml b/app/src/harmattan/qml/EditCategoryDialog.qml deleted file mode 100644 index 9ee0ca2..0000000 --- a/app/src/harmattan/qml/EditCategoryDialog.qml +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MySheet { - id: root - - property alias name: nameField.text - property string path - - rejectButtonText: qsTr("Cancel") - acceptButtonText: (nameField.text == "") || (root.path == "") ? "" : qsTr("Done") - content: Item { - id: contentItem - - anchors.fill: parent - clip: true - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - top: parent.top - left: parent.left - right: parent.right - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - Label { - font.bold: true - text: qsTr("Name") - } - - MyTextField { - id: nameField - - width: parent.width - } - - ValueListItem { - id: pathSelector - - x: -UI.PADDING_DOUBLE - width: parent.width + UI.PADDING_DOUBLE * 2 - title: qsTr("Download path") - subTitle: root.path == "" ? qsTr("None chosen") : root.path - onClicked: { - loader.sourceComponent = pathDialog; - loader.item.open(); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - } - - Loader { - id: loader - } - - Component { - id: pathDialog - - FileBrowserDialog { - startFolder: root.path == "" ? "/home/user/MyDocs/" : root.path - onFileChosen: root.path = filePath - } - } - - onAccepted: Settings.addCategory(nameField.text, root.path) -} diff --git a/app/src/harmattan/qml/FileBrowserDelegate.qml b/app/src/harmattan/qml/FileBrowserDelegate.qml deleted file mode 100644 index 1e7f52c..0000000 --- a/app/src/harmattan/qml/FileBrowserDelegate.qml +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - Image { - id: icon - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - source: parent.ListView.view.model.isFolder(index) ? "image://theme/icon-m-toolbar-directory-white" - : "image://theme/icon-m-toolbar-attachment-white" - } - - Label { - anchors { - left: icon.right - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - font.bold: true - elide: Text.ElideRight - text: fileName - } -} diff --git a/app/src/harmattan/qml/FileBrowserDialog.qml b/app/src/harmattan/qml/FileBrowserDialog.qml deleted file mode 100644 index 48cc3c7..0000000 --- a/app/src/harmattan/qml/FileBrowserDialog.qml +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import Qt.labs.folderlistmodel 1.0 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - - -MySheet { - id: root - - property bool showFiles: false - property string startFolder: Settings.downloadPath - - signal fileChosen(string filePath) - - acceptButtonText: folderText.text == "" ? "" : qsTr("Done") - rejectButtonText: qsTr("Cancel") - content: Item { - anchors.fill: parent - - Label { - id: folderText - - anchors { - left: parent.left - right: parent.right - top: parent.top - margins: UI.PADDING_DOUBLE - } - - font.bold: true - elide: Text.ElideRight - color: "#0881cb" - text: showFiles ? fileList.chosenFile.slice(fileList.chosenFile.lastIndexOf("/") + 1) - : folderListModel.folder.toString().slice(7) - } - - SeparatorLabel { - id: separator - - anchors { - left: parent.left - right: parent.right - top: folderText.bottom - topMargin: UI.PADDING_DOUBLE - } - - text: showFiles ? qsTr("Files") : qsTr("Folders") - } - - ListView { - id: fileList - - property string chosenFile: "" - - anchors { - top: separator.bottom - topMargin: UI.PADDING_DOUBLE - left: parent.left - right: parent.right - bottom: backButton.top - bottomMargin: UI.PADDING_DOUBLE - } - clip: true - model: FolderListModel { - id: folderListModel - - nameFilters: root.showFiles ? [] : ["*.foo_bar"] - folder: root.startFolder - showDotAndDotDot: false - showDirs: true - } - delegate: FileBrowserDelegate { - id: delegate - - onClicked: folderListModel.isFolder(index) ? folderListModel.folder = filePath - : fileList.chosenFile = filePath - } - } - - ScrollDecorator { - flickableItem: fileList - } - - MyToolIcon { - id: backButton - - z: 1000 - anchors { - left: parent.left - bottom: parent.bottom - } - platformIconId: "toolbar-back" - onClicked: folderListModel.folder = folderListModel.parentFolder - } - } - - Label { - id: noResultsText - - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("Folder empty") - visible: (fileList.count == 0) && (root.status == DialogStatus.Open) - } - - onAccepted: root.fileChosen(root.showFiles ? fileList.chosenFile.slice(7) - : folderListModel.folder.toString().slice(7)) -} diff --git a/app/src/harmattan/qml/LabelDelegate.qml b/app/src/harmattan/qml/LabelDelegate.qml deleted file mode 100644 index bbd73ab..0000000 --- a/app/src/harmattan/qml/LabelDelegate.qml +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - property alias text: label.text - - Label { - id: label - - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - font.bold: true - text: display - } -} diff --git a/app/src/harmattan/qml/ListItem.qml b/app/src/harmattan/qml/ListItem.qml deleted file mode 100644 index 86aa965..0000000 --- a/app/src/harmattan/qml/ListItem.qml +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property bool subItemIndicator: false - property bool isSelected: false - - signal clicked - signal pressAndHold - - height: 64 + UI.PADDING_DOUBLE * 2 - width: parent ? parent.width : screen.displayWidth - opacity: enabled ? UI.OPACITY_ENABLED : UI.OPACITY_DISABLED - - Loader { - anchors { - fill: parent - topMargin: 1 - bottomMargin: 1 - } - sourceComponent: (mouseArea.pressed) || (root.isSelected) ? highlight : undefined - } - - Component { - id: highlight - - Rectangle { - anchors.fill: parent - color: root.isSelected ? Settings.activeColor : UI.COLOR_INVERTED_SECONDARY_FOREGROUND - opacity: 0.5 - } - } - - Loader { - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - sourceComponent: root.subItemIndicator ? indicator : undefined - } - - Component { - id: indicator - - Image { - source: "image://theme/icon-m-common-drilldown-arrow-inverse" - } - } - - MouseArea { - id: mouseArea - - z: 1 - anchors.fill: parent - enabled: root.enabled - onClicked: root.clicked() - onPressAndHold: root.pressAndHold() - } -} diff --git a/app/src/harmattan/qml/MainPage.qml b/app/src/harmattan/qml/MainPage.qml deleted file mode 100644 index 15eef67..0000000 --- a/app/src/harmattan/qml/MainPage.qml +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "plugins" -import "soundcloud" - -MyPage { - id: root - - function search(service, query, type, order) { - setService(service); - appWindow.pageStack.pop(null, true); - viewLoader.item.search(query, type, order); - } - - function setService(service) { - if (!service) { - return; - } - - Settings.currentService = service; - - if (service == Resources.SOUNDCLOUD) { - viewLoader.sourceComponent = soundcloudView; - } - else { - viewLoader.sourceComponent = pluginView; - } - } - - function showResource(resource) { - if (!resource.service) { - return false; - } - - setService(resource.service); - appWindow.pageStack.pop(null, true); - MainWindow.raise(); - viewLoader.item.showResource(resource); - return true; - } - - function showResourceFromUrl(url) { - return showResource(Resources.getResourceFromUrl(url)); - } - - title: Settings.currentService ? serviceModel.data(serviceModel.match("value", Settings.currentService), "name") - : "MusiKloud2" - tools: ToolBarLayout { - MyToolIcon { - platformIconId: "toolbar-settings" - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) - } - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-list" - onClicked: { - dialogLoader.sourceComponent = serviceDialog; - dialogLoader.item.open(); - } - } - } - - ServiceModel { - id: serviceModel - } - - Loader { - id: viewLoader - - anchors.fill: parent - } - - Loader { - id: dialogLoader - } - - Component { - id: soundcloudView - - ListView { - - function search(query, type, order) { - var filters = {}; - filters["q"] = query; - filters["limit"] = MAX_RESULTS; - - if (type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudArtistsPage.qml"), - {title: qsTr("Search") + " ('" + query + "')"}).model.get("/users", filters); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudTracksPage.qml"), - {title: qsTr("Search") + " ('" + query + "')"}).model.get("/tracks", filters); - } - } - - function showResource(resource) { - var url; - - if (resource.type == Resources.PLAYLIST) { - url = Qt.resolvedUrl("soundcloud/SoundCloudPlaylistPage.qml"); - } - else if (resource.type == Resources.ARTIST) { - url = Qt.resolvedUrl("soundcloud/SoundCloudArtistPage.qml"); - } - else { - url = Qt.resolvedUrl("soundcloud/SoundCloudTrackPage.qml"); - } - - appWindow.pageStack.push(url).load(resource.id); - } - - anchors.fill: parent - model: SoundCloudNavModel {} - header: PageHeader { - title: root.title - } - delegate: DrillDownDelegate { - onClicked: { - switch (index) { - case 0: - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudAccountsPage.qml")); - break; - case 1: { - dialogLoader.sourceComponent = soundcloudSearchDialog; - dialogLoader.item.open(); - break; - } - case 2: - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudTracksPage.qml"), {title: qsTr("Tracks")}) - .model.get("/me/tracks", {limit: MAX_RESULTS}); - break; - case 3: - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudTracksPage.qml"), {title: qsTr("Favourites")}) - .model.get("/me/favorites", {limit: MAX_RESULTS}); - break; - case 4: - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudPlaylistsPage.qml"), {title: qsTr("Playlists")}) - .model.get("/me/playlists", {limit: MAX_RESULTS}); - break; - case 5: - appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudArtistsPage.qml"), {title: qsTr("Followings")}) - .model.get("/me/followings", {limit: MAX_RESULTS}); - break; - } - } - } - - Connections { - target: SoundCloud - onCommentAdded: infoBanner.showMessage(qsTr("Your comment has been added")) - onArtistFollowed: infoBanner.showMessage(qsTr("You have followed") + " " + artist.name) - onArtistUnfollowed: infoBanner.showMessage(qsTr("You have unfollowed") + " " + artist.name) - onTrackFavourited: infoBanner.showMessage("'" + track.title + "' " + qsTr("added to favourites")) - onTrackUnfavourited: infoBanner.showMessage("'" + track.title + "' " + qsTr("removed from favourites")) - } - } - } - - Component { - id: pluginView - - ListView { - - function search(query, type, order) { - if (type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginPlaylistsPage.qml"), - {title: qsTr("Search") + " ('" + query + "')"}).model.search(query, order); - } - else if (type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginArtistsPage.qml"), - {title: qsTr("Search") + " ('" + query + "')"}).model.search(query, order); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginTracksPage.qml"), - {title: qsTr("Search") + " ('" + query + "')"}).model.search(query, order); - } - } - - function showResource(resource) { - var url; - - if (resource.type == Resources.PLAYLIST) { - url = Qt.resolvedUrl("plugins/PluginPlaylistPage.qml"); - } - else if (resource.type == Resources.ARTIST) { - url = Qt.resolvedUrl("plugins/PluginArtistPage.qml"); - } - else { - url = Qt.resolvedUrl("plugins/PluginTrackPage.qml"); - } - - appWindow.pageStack.push(url).load(resource.id); - } - - anchors.fill: parent - model: PluginNavModel { - service: Settings.currentService - } - header: PageHeader { - title: root.title - } - delegate: DrillDownDelegate { - text: name - onClicked: { - if (!value) { - dialogLoader.sourceComponent = pluginSearchDialog; - dialogLoader.item.open(); - } - else if (value.type == Resources.CATEGORY) { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginCategoriesPage.qml"), - {title: value.name}).model.list(value.id); - } - else if (value.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginPlaylistsPage.qml"), - {title: value.name}).model.list(value.id); - } - else if (value.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginArtistsPage.qml"), - {title: value.name}).model.list(value.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginTracksPage.qml"), - {title: value.name}).model.list(value.id); - } - } - } - } - } - - Component { - id: serviceDialog - - ValueDialog { - titleText: qsTr("Service") - model: serviceModel - value: Settings.currentService - onValueChanged: root.setService(value) - } - } - - Component { - id: soundcloudSearchDialog - - SoundCloudSearchDialog {} - } - - Component { - id: pluginSearchDialog - - PluginSearchDialog {} - } -} diff --git a/app/src/harmattan/qml/MediaButton.qml b/app/src/harmattan/qml/MediaButton.qml deleted file mode 100644 index 3008683..0000000 --- a/app/src/harmattan/qml/MediaButton.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick 1.1 - -Image { - id: root - - property alias pressed: mouseArea.pressed - - signal clicked - - smooth: true - - MouseArea { - id: mouseArea - - anchors.fill: parent - enabled: root.enabled - onClicked: root.clicked() - } -} diff --git a/app/src/harmattan/qml/MediaSettingsPage.qml b/app/src/harmattan/qml/MediaSettingsPage.qml deleted file mode 100644 index 2bf81ad..0000000 --- a/app/src/harmattan/qml/MediaSettingsPage.qml +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Media/content") - tools: ToolBarLayout { - - BackToolIcon {} - } - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - - PageHeader { - title: root.title - } - - ValueListItem { - width: parent.width - title: qsTr("Default download path") - subTitle: Settings.downloadPath - onClicked: { - loader.sourceComponent = pathDialog; - loader.item.open(); - } - } - - DrillDownDelegate { - width: parent.width - text: qsTr("Categories") - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("CategoriesPage.qml")) - } - - Item { - width: parent.width - height: UI.PADDING_DOUBLE - } - - MySwitch { - width: parent.width - text: qsTr("Monitor clipboard for URLs") - checked: Settings.clipboardMonitorEnabled - onCheckedChanged: Settings.clipboardMonitorEnabled = checked - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - Loader { - id: loader - } - - Component { - id: pathDialog - - FileBrowserDialog { - onFileChosen: Settings.downloadPath = filePath - } - } -} diff --git a/app/src/harmattan/qml/MyBusyIndicator.qml b/app/src/harmattan/qml/MyBusyIndicator.qml deleted file mode 100644 index 12f6a93..0000000 --- a/app/src/harmattan/qml/MyBusyIndicator.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 1.1 -import com.nokia.meego 1.0 - -BusyIndicator { - property alias size: style.size - property alias inverted: style.inverted - - running: visible - platformStyle: BusyIndicatorStyle { - id: style - } -} diff --git a/app/src/harmattan/qml/MyButton.qml b/app/src/harmattan/qml/MyButton.qml deleted file mode 100644 index 4182152..0000000 --- a/app/src/harmattan/qml/MyButton.qml +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -Button { - property string value - property string color: Settings.activeColorString - - platformStyle: ButtonStyle { - pressedBackground: "image://theme/" + color + "-meegotouch-button" + __invertedString + "-background-pressed" + (position ? "-" + position : "") - checkedBackground: "image://theme/" + color + "-meegotouch-button" + __invertedString + "-background-selected" + (position ? "-" + position : "") - checkedDisabledBackground: "image://theme/" + color + "-meegotouch-button" + __invertedString + "-background-disabled-selected" + (position ? "-" + position : "") - } -} diff --git a/app/src/harmattan/qml/MyCheckBox.qml b/app/src/harmattan/qml/MyCheckBox.qml deleted file mode 100644 index 2a83f2e..0000000 --- a/app/src/harmattan/qml/MyCheckBox.qml +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -CheckBox { - id: root - - platformStyle: CheckBoxStyle { - backgroundPressed: "image://theme/" + Settings.activeColorString + "-meegotouch-button-checkbox" + __invertedString + "-background-pressed" - backgroundSelected: "image://theme/" + Settings.activeColorString + "-meegotouch-button-checkbox" + __invertedString + "-background-selected" - } - - MouseArea { - z: root.enabled ? -1 : 10 - anchors.fill: parent - enabled: !root.enabled - } -} diff --git a/app/src/harmattan/qml/MyInfoBanner.qml b/app/src/harmattan/qml/MyInfoBanner.qml deleted file mode 100644 index c40e29e..0000000 --- a/app/src/harmattan/qml/MyInfoBanner.qml +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.extras 1.1 - -InfoBanner { - id: banner - - function showMessage(message) { - banner.text = message; - banner.show(); - } - - topMargin: 40 -} diff --git a/app/src/harmattan/qml/MyPage.qml b/app/src/harmattan/qml/MyPage.qml deleted file mode 100644 index 9962596..0000000 --- a/app/src/harmattan/qml/MyPage.qml +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -Page { - id: root - - property string title: "" - - orientationLock: Settings.screenOrientation -} diff --git a/app/src/harmattan/qml/MyProgressBar.qml b/app/src/harmattan/qml/MyProgressBar.qml deleted file mode 100644 index f8486be..0000000 --- a/app/src/harmattan/qml/MyProgressBar.qml +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -ProgressBar { - property alias inverted: style.inverted - - platformStyle: ProgressBarStyle { - id: style - - unknownTexture: "image://theme/" + Settings.activeColorString + "-meegotouch-progressindicator" + __invertedString + "-bar-unknown-texture" - knownTexture: "image://theme/" + Settings.activeColorString + "-meegotouch-progressindicator" + __invertedString + "-bar-known-texture" - } -} diff --git a/app/src/harmattan/qml/MySelectionDialog.qml b/app/src/harmattan/qml/MySelectionDialog.qml deleted file mode 100644 index ae94867..0000000 --- a/app/src/harmattan/qml/MySelectionDialog.qml +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -SelectionDialog { - id: root - - property bool showProgressIndicator: false - - title: Item { - id: header - - anchors.fill: parent - - Item { - id: labelField - - anchors.fill: parent - - Item { - id: labelWrapper - - height: titleLabel.height - anchors { - left: labelField.left - right: progressIndicator.visible ? progressIndicator.left : closeButton.left - bottom: parent.bottom - bottomMargin: root.platformStyle.titleBarLineMargin - } - - Label { - id: titleLabel - - anchors { - left: parent.left - leftMargin: root.platformStyle.titleBarIndent - right: parent.right - } - font: root.platformStyle.titleBarFont - color: root.platformStyle.commonLabelColor - elide: root.platformStyle.titleElideMode - text: root.titleText - } - } - - BusyIndicator { - id: progressIndicator - - anchors { - right: closeButton.left - rightMargin: root.platformStyle.titleBarLineMargin - bottom: parent.bottom - bottomMargin: root.platformStyle.titleBarLineMargin - } - running: visible - visible: root.showProgressIndicator - platformStyle: BusyIndicatorStyle { - inverted: true - } - } - - Image { - id: closeButton - - anchors { - right: labelField.right - bottom: parent.bottom - bottomMargin: root.platformStyle.titleBarLineMargin - 6 - } - opacity: closeButtonArea.pressed ? 0.5 : 1.0 - source: "image://theme/icon-m-common-dialog-close" - - MouseArea { - id: closeButtonArea - anchors.fill: parent - onClicked: root.reject() - } - - } - } - - Rectangle { - id: headerLine - - height: 1 - anchors { - left: parent.left - right: parent.right - bottom: header.bottom - } - color: "#4d4d4d" - } - } -} diff --git a/app/src/harmattan/qml/MySheet.qml b/app/src/harmattan/qml/MySheet.qml deleted file mode 100644 index dcff4c3..0000000 --- a/app/src/harmattan/qml/MySheet.qml +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -Sheet { - id: root - - property bool acceptWhenDone: true - property bool showProgressIndicator: false - - signal done - - platformStyle: SheetStyle { - background: appStyle.background - } - - buttons: [ - SheetButton { - anchors.left: parent.left - anchors.leftMargin: root.platformStyle.rejectButtonLeftMargin - anchors.verticalCenter: parent.verticalCenter - text: root.rejectButtonText - visible: text != "" - onClicked: root.reject() - }, - - BusyIndicator { - anchors.centerIn: parent - running: visible - visible: root.showProgressIndicator - }, - - SheetButton { - anchors.right: parent.right - anchors.rightMargin: root.platformStyle.acceptButtonRightMargin - anchors.verticalCenter: parent.verticalCenter - platformStyle: SheetButtonAccentStyle { - background: "image://theme/" + Settings.activeColorString + "-meegotouch-sheet-button-accent"+__invertedString+"-background" - pressedBackground: "image://theme/" + Settings.activeColorString + "-meegotouch-sheet-button-accent"+__invertedString+"-background-pressed" - disabledBackground: "image://theme/" + Settings.activeColorString + "-meegotouch-sheet-button-accent"+__invertedString+"-background-disabled" - } - text: root.acceptButtonText - visible: text != "" - onClicked: root.acceptWhenDone ? root.accept() : root.done() - } - ] - - MouseArea { - z: -1 - anchors.fill: parent - } -} diff --git a/app/src/harmattan/qml/MySwitch.qml b/app/src/harmattan/qml/MySwitch.qml deleted file mode 100644 index 132ae7b..0000000 --- a/app/src/harmattan/qml/MySwitch.qml +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property alias text: title.text - property alias checked: switcher.checked - - signal checkedChanged - - width: parent.width - height: 84 - - Label { - id: title - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: switcher.left - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - font.bold: true - verticalAlignment: Text.AlignVCenter - maximumLineCount: 2 - elide: Text.ElideRight - wrapMode: Text.WordWrap - } - - Switch { - id: switcher - - platformStyle: SwitchStyle { - switchOn: "image://theme/" + Settings.activeColorString + "-meegotouch-switch-on" + __invertedString - } - - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - onCheckedChanged: root.checkedChanged() - } -} diff --git a/app/src/harmattan/qml/MyTextArea.qml b/app/src/harmattan/qml/MyTextArea.qml deleted file mode 100644 index 283f751..0000000 --- a/app/src/harmattan/qml/MyTextArea.qml +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -TextArea { - platformStyle: TextAreaStyle { - backgroundSelected: "image://theme/" + Settings.activeColorString + "-meegotouch-textedit-background-selected" - backgroundDisabled: "image://theme/" + Settings.activeColorString + "-meegotouch-textedit-background-disabled" - backgroundError: "image://theme/" + Settings.activeColorString + "-meegotouch-textedit-background-error" - } -} diff --git a/app/src/harmattan/qml/MyTextField.qml b/app/src/harmattan/qml/MyTextField.qml deleted file mode 100644 index c39d93d..0000000 --- a/app/src/harmattan/qml/MyTextField.qml +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -TextField { - id: root - - property bool clearButtonEnabled: false - property alias actionLabel: sipatts.actionKeyLabel - property alias actionIcon: sipatts.actionKeyIcon - property alias actionEnabled: sipatts.actionKeyEnabled - property alias actionHighlighted: sipatts.actionKeyHighlighted - property alias rightMargin: style.paddingRight - - signal accepted - - Loader { - id: loader - - z: 1000 - anchors { - right: parent.right - rightMargin: 5 - verticalCenter: parent.verticalCenter - } - sourceComponent: root.clearButtonEnabled ? clearButton : undefined - } - - Component { - id: clearButton - - Image { - id: icon - - source: "image://theme/icon-m-input-clear" - opacity: mouseArea.pressed ? UI.OPACITY_DISABLED : UI.OPACITY_ENABLED - visible: root.text != "" - - MouseArea { - id: mouseArea - - width: 60 - height: 60 - anchors.centerIn: parent - onClicked: { - root.text = ""; - root.platformCloseSoftwareInputPanel(); - } - } - } - } - - platformStyle: TextFieldStyle { - id: style - - backgroundSelected: "image://theme/" + Settings.activeColorString + "-meegotouch-textedit-background-selected" - backgroundDisabled: "image://theme/" + Settings.activeColorString + "-meegotouch-textedit-background-disabled" - paddingRight: loader.item ? loader.item.width : UI.PADDING_DOUBLE - } - - platformSipAttributes: SipAttributes { - id: sipatts - - actionKeyEnabled: acceptableInput - actionKeyHighlighted: true - actionKeyLabel: qsTr("Done") - actionKeyIcon: "" - } - - Keys.onEnterPressed: { - if (acceptableInput) { - accepted(); - } - - event.accepted = true; - } - Keys.onReturnPressed: { - if (acceptableInput) { - accepted(); - } - - event.accepted = true; - } -} diff --git a/app/src/harmattan/qml/MyToolIcon.qml b/app/src/harmattan/qml/MyToolIcon.qml deleted file mode 100644 index d5e692e..0000000 --- a/app/src/harmattan/qml/MyToolIcon.qml +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ToolIcon { - opacity: enabled ? UI.OPACITY_ENABLED : UI.OPACITY_DISABLED -} diff --git a/app/src/harmattan/qml/NetworkSettingsPage.qml b/app/src/harmattan/qml/NetworkSettingsPage.qml deleted file mode 100644 index 9e11134..0000000 --- a/app/src/harmattan/qml/NetworkSettingsPage.qml +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Network") - tools: ToolBarLayout { - - BackToolIcon { - onClicked: Settings.setNetworkProxy() - } - } - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - spacing: UI.PADDING_DOUBLE - - PageHeader { - title: root.title - } - - MySwitch { - width: parent.width - text: qsTr("Enable network proxy") - checked: Settings.networkProxyEnabled - onCheckedChanged: Settings.networkProxyEnabled = checked - } - - ValueSelector { - id: typeSelector - - width: parent.width - title: qsTr("Proxy type") - model: NetworkProxyTypeModel {} - value: Settings.networkProxyType - } - - Label { - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - font.bold: true - text: qsTr("Host") - } - - MyTextField { - id: hostField - - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - text: Settings.networkProxyHost - onTextChanged: Settings.networkProxyHost = text - onAccepted: platformCloseSoftwareInputPanel() - } - - Label { - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - font.bold: true - text: qsTr("Port") - } - - MyTextField { - id: portField - - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - text: Settings.networkProxyPort - inputMethodHints: Qt.ImhDigitsOnly - onTextChanged: Settings.networkProxyPort = text - onAccepted: platformCloseSoftwareInputPanel() - } - - Label { - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - font.bold: true - text: qsTr("Username") - } - - MyTextField { - id: userField - - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - text: Settings.networkProxyUsername - onTextChanged: Settings.networkProxyUsername = text - onAccepted: platformCloseSoftwareInputPanel() - } - - Label { - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - font.bold: true - text: qsTr("Password") - } - - MyTextField { - id: passwordField - - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE - echoMode: TextInput.Password - text: Settings.networkProxyPassword - onTextChanged: Settings.networkProxyPassword = text - onAccepted: platformCloseSoftwareInputPanel() - } - } - } - - ScrollDecorator { - flickableItem: flicker - } -} diff --git a/app/src/harmattan/qml/NowPlayingButton.qml b/app/src/harmattan/qml/NowPlayingButton.qml deleted file mode 100644 index e43aad0..0000000 --- a/app/src/harmattan/qml/NowPlayingButton.qml +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -ToolButton { - id: button - - platformStyle: ToolButtonStyle { - textColor: Settings.activeColor - } - width: 300 - anchors.centerIn: parent - iconSource: player.paused ? "images/pause-accent-" + Settings.activeColorString + ".png" : player.playing - ? "images/play-accent-" + Settings.activeColorString + ".png" : "images/stop-accent-" - + Settings.activeColorString + ".png" - text: player.queueCount > 0 ? player.currentTrack.title : qsTr("Unknown track") - visible: player.queueCount > 0 - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("NowPlayingPage.qml")) -} diff --git a/app/src/harmattan/qml/NowPlayingDelegate.qml b/app/src/harmattan/qml/NowPlayingDelegate.qml deleted file mode 100644 index a662ac6..0000000 --- a/app/src/harmattan/qml/NowPlayingDelegate.qml +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - height: thumbnail.height + UI.PADDING_DOUBLE * 2 - - Thumbnail { - id: thumbnail - - z: 100 - width: 64 - height: 64 - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - source: thumbnailUrl - placeholderText: title - enabled: false - } - - Flow { - id: flow - - anchors { - left: thumbnail.right - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - spacing: UI.PADDING_DOUBLE - - Label { - id: titleLabel - - width: parent.width - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - color: player.currentIndex == index ? Settings.activeColor : UI.COLOR_INVERTED_FOREGROUND - text: title - } - - Label { - id: artistLabel - - width: parent.width - durationLabel.width - parent.spacing - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - color: player.currentIndex == index ? Settings.activeColor : UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: artist ? artist : qsTr("Unknown artist") - } - - Label { - id: durationLabel - - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - color: player.currentIndex == index ? Settings.activeColor : UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: durationString - } - } -} diff --git a/app/src/harmattan/qml/NowPlayingPage.qml b/app/src/harmattan/qml/NowPlayingPage.qml deleted file mode 100644 index 0e6ab57..0000000 --- a/app/src/harmattan/qml/NowPlayingPage.qml +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property bool showQueue: false - property bool autoPlay: false - - tools: ToolBarLayout { - - BackToolIcon {} - - ToolIcon { - iconSource: "images/shuffle" + (player.shuffle ? "-" + Settings.activeColorString : "") + ".png" - onClicked: player.shuffle = !player.shuffle - } - - ToolIcon { - iconSource: "images/repeat" + (player.repeat ? "-" + Settings.activeColorString : "") + ".png" - onClicked: player.repeat = !player.repeat - } - } - - Connections { - target: player - onQueueCountChanged: if (!player.queueCount) appWindow.pageStack.pop(); - } - - Image { - id: leftBackground - - anchors { - left: parent.left - right: thumbnail.right - top: parent.top - bottom: parent.bottom - } - visible: !appWindow.inPortrait - source: visible ? "image://theme/meegotouch-applicationpage-background-inverted" : "" - smooth: true - fillMode: Image.Stretch - } - - Image { - id: rightBackground - - anchors { - left: flow.left - right: flow.right - top: parent.top - bottom: parent.bottom - } - visible: !appWindow.inPortrait - source: visible ? "image://theme/meegotouch-applicationpage-background-inverted" : "" - smooth: true - fillMode: Image.Stretch - } - - Thumbnail { - id: thumbnail - - width: appWindow.inPortrait ? 480 : parent.height - UI.PADDING_DOUBLE * 2 - height: appWindow.inPortrait ? 480 : parent.height - UI.PADDING_DOUBLE * 2 - anchors { - top: parent.top - topMargin: appWindow.inPortrait ? 0 : UI.PADDING_DOUBLE - left: parent.left - leftMargin: appWindow.inPortrait ? 0 : UI.PADDING_DOUBLE - } - source: player.currentTrack.largeThumbnailUrl - placeholderText: player.currentTrack.title - swipeEnabled: true - onClicked: root.showQueue = true - onLeftSwipe: player.next() - onRightSwipe: player.previous() - - Image { - z: -1 - anchors.fill: parent - visible: thumbnail.text != "" - source: visible ? "image://theme/meegotouch-applicationpage-background-inverted" : "" - smooth: true - fillMode: Image.Stretch - } - } - - Image { - anchors.fill: flow - visible: appWindow.inPortrait - source: visible ? "image://theme/meegotouch-applicationpage-background-inverted" : "" - smooth: true - fillMode: Image.Stretch - } - - Flow { - id: flow - - spacing: UI.PADDING_DOUBLE - anchors { - left: appWindow.inPortrait ? parent.left : thumbnail.right - right: parent.right - top: appWindow.inPortrait ? thumbnail.bottom : parent.top - margins: UI.PADDING_DOUBLE - } - - Label { - id: titleLabel - - width: parent.width - indexLabel.width - UI.PADDING_DOUBLE - font.bold: true - elide: Text.ElideRight - text: player.currentTrack.title - } - - Label { - id: indexLabel - - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - font.family: UI.FONT_FAMILY_LIGHT - text: (player.currentIndex + 1) + "/" + player.queueCount - } - - Label { - id: artistLabel - - width: parent.width - elide: Text.ElideRight - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - font.family: UI.FONT_FAMILY_LIGHT - text: !player.currentTrack.artist ? qsTr("Unknown artist") : player.currentTrack.artist - } - } - - Image { - anchors { - left: flow.left - right: flow.right - top: flow2.top - bottom: flow2.bottom - } - visible: appWindow.inPortrait - source: visible ? "image://theme/meegotouch-applicationpage-background-inverted" : "" - smooth: true - fillMode: Image.Stretch - } - - Flow { - id: flow2 - - anchors { - left: flow.left - right: flow.right - bottom: parent.bottom - margins: UI.PADDING_DOUBLE - } - - Item { - width: parent.width - height: 100 - - MediaButton { - id: previousButton - - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - } - source: "image://theme/icon-m-toolbar-mediacontrol-previous" + (pressed ? "-dimmed" : "") + "-white" - onClicked: player.previous() - } - - MediaButton { - id: playButton - - anchors.centerIn: parent - source: "image://theme/icon-m-toolbar-mediacontrol" - + (player.playing ? player.seekable ? "-pause" : "-stop" : "-play") - + (pressed ? "-dimmed" : "") + "-white" - onClicked: player.playing = !player.playing - } - - MediaButton { - id: nextButton - - anchors { - right: parent.right - verticalCenter: parent.verticalCenter - } - source: "image://theme/icon-m-toolbar-mediacontrol-next" + (pressed ? "-dimmed" : "") + "-white" - onClicked: player.next() - } - } - - MyProgressBar { - id: progressBar - - width: parent.width - maximumValue: Math.max(1, player.duration) - indeterminate: (player.status == AudioPlayer.Loading) || (player.status == AudioPlayer.Buffering) - - SeekBubble { - id: seekBubble - - anchors.bottom: parent.top - opacity: value != "" ? 1 : 0 - value: (seekMouseArea.drag.active) && (seekMouseArea.posInsideDragArea) - ? Utils.formatMSecs(Math.floor((seekMouseArea.mouseX / seekMouseArea.width) * player.duration)) - : "" - } - - MouseArea { - id: seekMouseArea - - property bool posInsideMouseArea: false - property bool posInsideDragArea: (seekMouseArea.mouseX >= 0) - && (seekMouseArea.mouseX <= seekMouseArea.drag.maximumX) - - width: parent.width - height: 60 - anchors.centerIn: parent - drag.target: seekBubble - drag.axis: Drag.XAxis - drag.minimumX: -40 - drag.maximumX: width - 10 - enabled: player.seekable - onExited: posInsideMouseArea = false - onEntered: posInsideMouseArea = true - onPressed: { - posInsideMouseArea = true; - seekBubble.x = mouseX - 40; - } - onReleased: { - if (posInsideMouseArea) { - player.position = Math.floor((mouseX / width) * player.duration); - } - } - } - - Component.onCompleted: value = player.position - } - - Label { - id: positionLabel - - height: 40 - width: parent.width - durationLabel.width - verticalAlignment: Text.AlignVCenter - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - - Component.onCompleted: text = player.positionString - } - - Label { - id: durationLabel - - height: 40 - verticalAlignment: Text.AlignVCenter - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - text: player.durationString - } - } - - Item { - id: queueItem - - z: -1 - visible: false - anchors { - top: parent.top - left: parent.left - right: parent.right - bottom: appWindow.inPortrait ? flow2.top : parent.bottom - } - - Image { - id: header - - z: 1 - height: 72 - anchors { - top: parent.top - left: parent.left - right: parent.right - } - source: "image://theme/meegotouch-view-header-fixed-inverted" - fillMode: Image.Stretch - smooth: true - - MouseArea { - anchors.fill: parent - } - - Label { - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - font.pixelSize: UI.FONT_XLARGE - text: qsTr("Playback queue") - } - - ToolButton { - width: 50 - height: 50 - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - iconSource: "images/play-accent-" + Settings.activeColorString + ".png" - onClicked: root.showQueue = false - } - } - - ListView { - id: view - - property int selectedIndex - - anchors { - top: header.bottom - left: parent.left - right: parent.right - bottom: parent.bottom - } - - onVisibleChanged: if (visible) positionViewAtIndex(player.currentIndex, ListView.Center); - model: player.queue - delegate: NowPlayingDelegate { - onClicked: player.currentIndex = index - onPressAndHold: queueMenu.open() - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: queueMenu - - MenuLayout { - - MenuItem { - text: qsTr("Remove from playback queue") - onClicked: player.removeTrack(view.selectedIndex) - } - - MenuItem { - text: qsTr("Clear playback queue") - onClicked: player.clearQueue() - } - } - } - } - - Timer { - id: positionTimer - - interval: 10000 - repeat: true - running: (platformWindow.viewMode === WindowState.Thumbnail) && (player.playing) - onTriggered: { - progressBar.value = player.position; - positionLabel.text = player.positionString; - } - } - - Binding { - target: progressBar - when: (platformWindow.viewMode === WindowState.Fullsize) && (player.duration > 0) - property: "value" - value: player.position - } - - Binding { - target: positionLabel - when: platformWindow.viewMode === WindowState.Fullsize - property: "text" - value: player.positionString - } - - states: [ - State { - name: "showQueue" - when: (root.showQueue) && (appWindow.inPortrait) - - PropertyChanges { - target: queueItem - visible: true - } - - AnchorChanges { - target: thumbnail - anchors { - top: undefined - bottom: parent.top - left: parent.left - } - } - - AnchorChanges { - target: flow - anchors { - top: flow2.top - left: parent.left - right: parent.right - } - } - }, - - State { - name: "showQueueLandscape" - when: (root.showQueue) && (!appWindow.inPortrait) - - PropertyChanges { - target: queueItem - visible: true - } - - AnchorChanges { - target: thumbnail - anchors { - top: parent.top - bottom: undefined - left: undefined - right: parent.left - } - } - - AnchorChanges { - target: flow - anchors { - top: undefined - left: parent.right - right: undefined - } - } - } - ] - - transitions: [ - Transition { - AnchorAnimation { - duration: root.status === PageStatus.Active ? 250 : 0 - } - } - ] - - onStatusChanged: { - if (status === PageStatus.Active) { - if ((autoPlay) && (!player.paused)) { - player.play(); - } - - autoPlay = false; - } - } -} diff --git a/app/src/harmattan/qml/PageHeader.qml b/app/src/harmattan/qml/PageHeader.qml deleted file mode 100644 index 9faec8d..0000000 --- a/app/src/harmattan/qml/PageHeader.qml +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property alias title: titleLabel.text - property alias showProgressIndicator: progressIndicator.visible - - width: parent ? parent.width : implicitWidth - height: 72 - - Label { - id: titleLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - right: progressIndicator.visible ? progressIndicator.left : parent.right - rightMargin: UI.PADDING_DOUBLE - } - font.pixelSize: UI.FONT_XLARGE - elide: Text.ElideRight - } - - BusyIndicator { - id: progressIndicator - - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - running: visible - visible: false - platformStyle: BusyIndicatorStyle { - inverted: true - } - } - - Rectangle { - height: 1 - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - } -} diff --git a/app/src/harmattan/qml/PlaylistDelegate.qml b/app/src/harmattan/qml/PlaylistDelegate.qml deleted file mode 100644 index 0b86582..0000000 --- a/app/src/harmattan/qml/PlaylistDelegate.qml +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - height: thumbnail.height + UI.PADDING_DOUBLE * 2 - - Thumbnail { - id: thumbnail - - width: 64 - height: 64 - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - source: thumbnailUrl - placeholderText: title - enabled: false - } - - Flow { - id: flow - - anchors { - left: thumbnail.right - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - spacing: UI.PADDING_DOUBLE - - Label { - id: titleLabel - - width: parent.width - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: title - } - - Label { - id: artistLabel - - width: parent.width - trackCountLabel.width - parent.spacing - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: artist ? artist : qsTr("Unknown artist") - } - - Label { - id: trackCountLabel - - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - text: trackCount - } - } -} diff --git a/app/src/harmattan/qml/SeekBubble.qml b/app/src/harmattan/qml/SeekBubble.qml deleted file mode 100644 index f76b4b3..0000000 --- a/app/src/harmattan/qml/SeekBubble.qml +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Image { - - property string value - - source: "images/seekbubble.png" - opacity: 0 - - Behavior on opacity { PropertyAnimation { properties: "opacity"; duration: 200 } } - - Label { - anchors { fill: parent; bottomMargin: UI.PADDING_DOUBLE } - text: parent.value - font.pixelSize: text.length > 5 ? UI.FONT_SMALL : UI.FONT_SMALL - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } -} diff --git a/app/src/harmattan/qml/SeparatorLabel.qml b/app/src/harmattan/qml/SeparatorLabel.qml deleted file mode 100644 index db97924..0000000 --- a/app/src/harmattan/qml/SeparatorLabel.qml +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property alias text: label.text - - height: 20 - - Label { - id: label - - anchors { - right: parent.right - rightMargin: UI.PADDING_XLARGE - top: parent.top - } - font.pixelSize: UI.FONT_SMALL - font.bold: true - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - horizontalAlignment: Text.AlignRight - verticalAlignment: Text.AlignVCenter - } - - Rectangle { - height: 1 - anchors { - left: parent.left - right: label.left - rightMargin: UI.PADDING_XXLARGE - verticalCenter: label.verticalCenter - } - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - } -} diff --git a/app/src/harmattan/qml/Tab.qml b/app/src/harmattan/qml/Tab.qml deleted file mode 100644 index 5118275..0000000 --- a/app/src/harmattan/qml/Tab.qml +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 - -Item { - id: root - - property string title: "" - property Item tools: null - - width: parent ? parent.width : undefined - height: parent ? parent.height : undefined -} diff --git a/app/src/harmattan/qml/TabLoader.qml b/app/src/harmattan/qml/TabLoader.qml deleted file mode 100644 index 8c9377d..0000000 --- a/app/src/harmattan/qml/TabLoader.qml +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 - -Loader { - id: root - - property string title - property Component tab: null - property Item tools: (item) && (loaded) ? item.tools : null - property bool loaded: sourceComponent == tab - - function loadTab() { - sourceComponent = tab; - } - - function unloadTab() { - sourceComponent = placeholder; - } - - width: parent ? parent.width : undefined - height: parent ? parent.height : undefined - sourceComponent: placeholder - - Component { - id: placeholder - - Item { - width: parent ? parent.width : undefined - height: parent ? parent.height : undefined - - PageHeader { - title: root.title - } - } - } -} diff --git a/app/src/harmattan/qml/TabView.qml b/app/src/harmattan/qml/TabView.qml deleted file mode 100644 index f656c00..0000000 --- a/app/src/harmattan/qml/TabView.qml +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 - -Item { - id: root - - default property alias tabs: tabModel.children - property alias currentTab: view.currentItem - property alias currentIndex: view.currentIndex - property alias delayInterval: loadingTimer.interval - - function unloadTabs() { - for (var i = 0; i < tabs.length; i++) { - if (tabs[i].hasOwnProperty("loaded")) { - tabs[i].unloadTab(); - } - } - } - - clip: true - - ListView { - id: view - - anchors.fill: parent - model: VisualItemModel { - id: tabModel - } - orientation: ListView.Horizontal - snapMode: ListView.SnapOneItem - boundsBehavior: ListView.StopAtBounds - highlightRangeMode: ListView.StrictlyEnforceRange - preferredHighlightBegin: 0 - preferredHighlightEnd: width - onCurrentIndexChanged: { - if ((currentItem) && (currentItem.hasOwnProperty("loaded")) && (!currentItem.loaded)) { - loadingTimer.restart(); - } - else { - loadingTimer.stop(); - } - } - } - - Timer { - id: loadingTimer - - interval: 1000 - onTriggered: if (view.currentItem) view.currentItem.loadTab(); - } -} diff --git a/app/src/harmattan/qml/Thumbnail.qml b/app/src/harmattan/qml/Thumbnail.qml deleted file mode 100644 index c4e8e88..0000000 --- a/app/src/harmattan/qml/Thumbnail.qml +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Image { - id: root - - property string placeholderText - property bool swipeEnabled: false - - signal clicked - signal leftSwipe - signal rightSwipe - - opacity: mouseArea.pressed ? UI.OPACITY_DISABLED : UI.OPACITY_ENABLED - fillMode: Image.PreserveAspectFit - asynchronous: true - smooth: true - - Loader { - id: loader - - anchors.fill: parent - sourceComponent: (!root.source.toString()) || (root.status == Image.Error) ? placeholder : undefined - } - - Component { - id: placeholder - - Rectangle { - anchors.fill: parent - color: "#000" - - Label { - - function repeatedString(s) { - if (s.length > 0) { - var r = Math.floor(48 / s.length) + 1; - var c = Settings.activeColor; - var d = Qt.darker(c); - var t = ""; - var u = s.toUpperCase(); - - for (var i = 1; i <= r; i++) { - t += ""; - t += u; - t += " " - } - - return t; - } - - return s; - } - - anchors.fill: parent - clip: true - lineHeight: Math.floor(height / 4) - lineHeightMode: Text.FixedHeight - horizontalAlignment: Text.AlignJustify - wrapMode: Text.WrapAnywhere - textFormat: Text.RichText - font { - bold: true - pixelSize: Math.floor(lineHeight / 1.3) - } - text: repeatedString(root.placeholderText) - } - } - } - - MouseArea { - id: mouseArea - - property int xPos - - anchors.fill: parent - enabled: root.enabled - onPressed: xPos = mouseX - onReleased: { - if (containsMouse) { - if (root.swipeEnabled) { - if ((mouseX - xPos) > 100) { - root.rightSwipe(); - } - else if ((xPos - mouseX) > 100) { - root.leftSwipe(); - } - else { - root.clicked(); - } - } - else { - root.clicked(); - } - } - } - } -} diff --git a/app/src/harmattan/qml/TrackDelegate.qml b/app/src/harmattan/qml/TrackDelegate.qml deleted file mode 100644 index c65e106..0000000 --- a/app/src/harmattan/qml/TrackDelegate.qml +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - signal thumbnailClicked - - height: thumbnail.height + UI.PADDING_DOUBLE * 2 - - Thumbnail { - id: thumbnail - - z: 100 - width: 64 - height: 64 - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - source: thumbnailUrl - placeholderText: title - onClicked: root.thumbnailClicked() - } - - Flow { - id: flow - - anchors { - left: thumbnail.right - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - spacing: UI.PADDING_DOUBLE - - Label { - id: titleLabel - - width: parent.width - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: title - } - - Label { - id: artistLabel - - width: parent.width - durationLabel.width - parent.spacing - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: artist ? artist : qsTr("Unknown artist") - } - - Label { - id: durationLabel - - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - text: durationString - } - } -} diff --git a/app/src/harmattan/qml/TransfersSettingsPage.qml b/app/src/harmattan/qml/TransfersSettingsPage.qml deleted file mode 100644 index 58323d5..0000000 --- a/app/src/harmattan/qml/TransfersSettingsPage.qml +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Transfers") - tools: ToolBarLayout { - - BackToolIcon {} - } - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - - PageHeader { - title: root.title - } - - ValueSelector { - width: parent.width - title: qsTr("Maximum concurrent transfers") - model: ConcurrentTransfersModel {} - value: Settings.maximumConcurrentTransfers - onValueChanged: Settings.maximumConcurrentTransfers = value - } - - Item { - width: parent.width - height: UI.PADDING_DOUBLE - } - - MySwitch { - width: parent.width - text: qsTr("Start transfers automatically") - checked: Settings.startTransfersAutomatically - onCheckedChanged: Settings.startTransfersAutomatically = checked - } - } - } - - ScrollDecorator { - flickableItem: flicker - } -} diff --git a/app/src/harmattan/qml/ValueListItem.qml b/app/src/harmattan/qml/ValueListItem.qml deleted file mode 100644 index c37a086..0000000 --- a/app/src/harmattan/qml/ValueListItem.qml +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -ListItem { - id: root - - property alias title: title.text - property alias subTitle: subTitle.text - - Column { - id: column - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: icon.left - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - Label { - id: title - - width: parent.width - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - Label { - id: subTitle - - width: parent.width - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - text: qsTr("None chosen") - } - } - - Image { - id: icon - - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - source: "image://theme/icon-m-textinput-combobox-arrow" - } -} diff --git a/app/src/harmattan/qml/ValueMenuItem.qml b/app/src/harmattan/qml/ValueMenuItem.qml deleted file mode 100644 index a993811..0000000 --- a/app/src/harmattan/qml/ValueMenuItem.qml +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MenuItem { - id: root - - property alias title: title.text - property alias subTitle: subTitle.text - - Column { - id: column - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: icon.left - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - Label { - id: title - - width: parent.width - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - Label { - id: subTitle - - width: parent.width - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - } - - Image { - id: icon - - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - - source: "image://theme/icon-m-textinput-combobox-arrow" - } -} diff --git a/app/src/harmattan/qml/images/avatar.png b/app/src/harmattan/qml/images/avatar.png deleted file mode 100644 index 385d7ee..0000000 Binary files a/app/src/harmattan/qml/images/avatar.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/comment-outgoing-inverted.png b/app/src/harmattan/qml/images/comment-outgoing-inverted.png deleted file mode 100644 index 9cbe6e3..0000000 Binary files a/app/src/harmattan/qml/images/comment-outgoing-inverted.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/comment-outgoing-pressed-inverted.png b/app/src/harmattan/qml/images/comment-outgoing-pressed-inverted.png deleted file mode 100644 index 40bccc6..0000000 Binary files a/app/src/harmattan/qml/images/comment-outgoing-pressed-inverted.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/comment-outgoing-pressed.png b/app/src/harmattan/qml/images/comment-outgoing-pressed.png deleted file mode 100644 index 4c1c0f6..0000000 Binary files a/app/src/harmattan/qml/images/comment-outgoing-pressed.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/comment-outgoing.png b/app/src/harmattan/qml/images/comment-outgoing.png deleted file mode 100644 index 37c9faa..0000000 Binary files a/app/src/harmattan/qml/images/comment-outgoing.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color10.png b/app/src/harmattan/qml/images/pause-accent-color10.png deleted file mode 100644 index a9e6ce7..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color10.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color11.png b/app/src/harmattan/qml/images/pause-accent-color11.png deleted file mode 100644 index cbae1ac..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color11.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color12.png b/app/src/harmattan/qml/images/pause-accent-color12.png deleted file mode 100644 index 372d928..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color12.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color13.png b/app/src/harmattan/qml/images/pause-accent-color13.png deleted file mode 100644 index cc12ebd..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color13.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color14.png b/app/src/harmattan/qml/images/pause-accent-color14.png deleted file mode 100644 index a7e7cf6..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color14.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color15.png b/app/src/harmattan/qml/images/pause-accent-color15.png deleted file mode 100644 index fb1f04d..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color15.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color16.png b/app/src/harmattan/qml/images/pause-accent-color16.png deleted file mode 100644 index a9dcb61..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color16.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color17.png b/app/src/harmattan/qml/images/pause-accent-color17.png deleted file mode 100644 index 01eb469..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color17.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color18.png b/app/src/harmattan/qml/images/pause-accent-color18.png deleted file mode 100644 index 645d715..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color18.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color19.png b/app/src/harmattan/qml/images/pause-accent-color19.png deleted file mode 100644 index c194a14..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color19.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color2.png b/app/src/harmattan/qml/images/pause-accent-color2.png deleted file mode 100644 index 6864623..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color2.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color3.png b/app/src/harmattan/qml/images/pause-accent-color3.png deleted file mode 100644 index 6e0f1d3..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color3.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color4.png b/app/src/harmattan/qml/images/pause-accent-color4.png deleted file mode 100644 index d1184ad..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color4.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color5.png b/app/src/harmattan/qml/images/pause-accent-color5.png deleted file mode 100644 index d5ed534..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color5.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color6.png b/app/src/harmattan/qml/images/pause-accent-color6.png deleted file mode 100644 index a026c5d..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color6.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color8.png b/app/src/harmattan/qml/images/pause-accent-color8.png deleted file mode 100644 index 0e4a214..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color8.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-color9.png b/app/src/harmattan/qml/images/pause-accent-color9.png deleted file mode 100644 index 1c90028..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-color9.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent-pressed.png b/app/src/harmattan/qml/images/pause-accent-pressed.png deleted file mode 100644 index ee027ff..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent-pressed.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/pause-accent.png b/app/src/harmattan/qml/images/pause-accent.png deleted file mode 100644 index 8cc1e57..0000000 Binary files a/app/src/harmattan/qml/images/pause-accent.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color10.png b/app/src/harmattan/qml/images/play-accent-color10.png deleted file mode 100644 index 3006301..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color10.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color11.png b/app/src/harmattan/qml/images/play-accent-color11.png deleted file mode 100644 index 22be2cb..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color11.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color12.png b/app/src/harmattan/qml/images/play-accent-color12.png deleted file mode 100644 index 4646ff7..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color12.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color13.png b/app/src/harmattan/qml/images/play-accent-color13.png deleted file mode 100644 index 2149a7d..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color13.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color14.png b/app/src/harmattan/qml/images/play-accent-color14.png deleted file mode 100644 index bd8b73d..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color14.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color15.png b/app/src/harmattan/qml/images/play-accent-color15.png deleted file mode 100644 index d6faa21..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color15.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color16.png b/app/src/harmattan/qml/images/play-accent-color16.png deleted file mode 100644 index 47cb859..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color16.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color17.png b/app/src/harmattan/qml/images/play-accent-color17.png deleted file mode 100644 index 7aba494..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color17.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color18.png b/app/src/harmattan/qml/images/play-accent-color18.png deleted file mode 100644 index fb48517..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color18.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color19.png b/app/src/harmattan/qml/images/play-accent-color19.png deleted file mode 100644 index e6dd0c0..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color19.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color2.png b/app/src/harmattan/qml/images/play-accent-color2.png deleted file mode 100644 index fcba016..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color2.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color3.png b/app/src/harmattan/qml/images/play-accent-color3.png deleted file mode 100644 index 798aba4..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color3.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color4.png b/app/src/harmattan/qml/images/play-accent-color4.png deleted file mode 100644 index 725e3db..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color4.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color5.png b/app/src/harmattan/qml/images/play-accent-color5.png deleted file mode 100644 index 913a8b0..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color5.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color6.png b/app/src/harmattan/qml/images/play-accent-color6.png deleted file mode 100644 index 205a9e1..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color6.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color8.png b/app/src/harmattan/qml/images/play-accent-color8.png deleted file mode 100644 index 8d6fabd..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color8.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-color9.png b/app/src/harmattan/qml/images/play-accent-color9.png deleted file mode 100644 index 82fe1a6..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-color9.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent-pressed.png b/app/src/harmattan/qml/images/play-accent-pressed.png deleted file mode 100644 index 68e6170..0000000 Binary files a/app/src/harmattan/qml/images/play-accent-pressed.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/play-accent.png b/app/src/harmattan/qml/images/play-accent.png deleted file mode 100644 index 501a7aa..0000000 Binary files a/app/src/harmattan/qml/images/play-accent.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color10.png b/app/src/harmattan/qml/images/repeat-color10.png deleted file mode 100644 index 2f9cfae..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color10.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color11.png b/app/src/harmattan/qml/images/repeat-color11.png deleted file mode 100644 index acb6c20..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color11.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color12.png b/app/src/harmattan/qml/images/repeat-color12.png deleted file mode 100644 index a362583..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color12.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color13.png b/app/src/harmattan/qml/images/repeat-color13.png deleted file mode 100644 index 1d3f699..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color13.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color14.png b/app/src/harmattan/qml/images/repeat-color14.png deleted file mode 100644 index 9ee24c6..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color14.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color15.png b/app/src/harmattan/qml/images/repeat-color15.png deleted file mode 100644 index d6b2ca6..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color15.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color16.png b/app/src/harmattan/qml/images/repeat-color16.png deleted file mode 100644 index 2bc05e5..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color16.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color17.png b/app/src/harmattan/qml/images/repeat-color17.png deleted file mode 100644 index a7f5268..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color17.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color18.png b/app/src/harmattan/qml/images/repeat-color18.png deleted file mode 100644 index 48c1e1e..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color18.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color19.png b/app/src/harmattan/qml/images/repeat-color19.png deleted file mode 100644 index 9ae1089..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color19.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color2.png b/app/src/harmattan/qml/images/repeat-color2.png deleted file mode 100644 index b26c14d..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color2.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color3.png b/app/src/harmattan/qml/images/repeat-color3.png deleted file mode 100644 index 11ec2bc..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color3.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color4.png b/app/src/harmattan/qml/images/repeat-color4.png deleted file mode 100644 index 842c423..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color4.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color5.png b/app/src/harmattan/qml/images/repeat-color5.png deleted file mode 100644 index 66cdf7d..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color5.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color6.png b/app/src/harmattan/qml/images/repeat-color6.png deleted file mode 100644 index 9c48d34..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color6.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color8.png b/app/src/harmattan/qml/images/repeat-color8.png deleted file mode 100644 index 4fdbc45..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color8.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/repeat-color9.png b/app/src/harmattan/qml/images/repeat-color9.png deleted file mode 100644 index 63231bb..0000000 Binary files a/app/src/harmattan/qml/images/repeat-color9.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/seekbubble.png b/app/src/harmattan/qml/images/seekbubble.png deleted file mode 100644 index 50bae4b..0000000 Binary files a/app/src/harmattan/qml/images/seekbubble.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color10.png b/app/src/harmattan/qml/images/shuffle-color10.png deleted file mode 100644 index de472bc..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color10.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color11.png b/app/src/harmattan/qml/images/shuffle-color11.png deleted file mode 100644 index ea56c45..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color11.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color12.png b/app/src/harmattan/qml/images/shuffle-color12.png deleted file mode 100644 index 5adb672..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color12.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color13.png b/app/src/harmattan/qml/images/shuffle-color13.png deleted file mode 100644 index c6b6aa8..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color13.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color14.png b/app/src/harmattan/qml/images/shuffle-color14.png deleted file mode 100644 index 6b88594..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color14.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color15.png b/app/src/harmattan/qml/images/shuffle-color15.png deleted file mode 100644 index 5d5948c..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color15.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color16.png b/app/src/harmattan/qml/images/shuffle-color16.png deleted file mode 100644 index 7d52c25..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color16.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color17.png b/app/src/harmattan/qml/images/shuffle-color17.png deleted file mode 100644 index 56704d1..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color17.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color18.png b/app/src/harmattan/qml/images/shuffle-color18.png deleted file mode 100644 index 1da4ea5..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color18.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color19.png b/app/src/harmattan/qml/images/shuffle-color19.png deleted file mode 100644 index e376956..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color19.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color2.png b/app/src/harmattan/qml/images/shuffle-color2.png deleted file mode 100644 index 4ec0201..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color2.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color3.png b/app/src/harmattan/qml/images/shuffle-color3.png deleted file mode 100644 index 3535b16..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color3.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color4.png b/app/src/harmattan/qml/images/shuffle-color4.png deleted file mode 100644 index 84e97a8..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color4.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color5.png b/app/src/harmattan/qml/images/shuffle-color5.png deleted file mode 100644 index 9079f03..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color5.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color6.png b/app/src/harmattan/qml/images/shuffle-color6.png deleted file mode 100644 index 56c04fb..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color6.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color8.png b/app/src/harmattan/qml/images/shuffle-color8.png deleted file mode 100644 index eb5d0c7..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color8.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/shuffle-color9.png b/app/src/harmattan/qml/images/shuffle-color9.png deleted file mode 100644 index 73df35a..0000000 Binary files a/app/src/harmattan/qml/images/shuffle-color9.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color10.png b/app/src/harmattan/qml/images/stop-accent-color10.png deleted file mode 100644 index 673d77a..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color10.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color11.png b/app/src/harmattan/qml/images/stop-accent-color11.png deleted file mode 100644 index 32b931b..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color11.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color12.png b/app/src/harmattan/qml/images/stop-accent-color12.png deleted file mode 100644 index bcf8f19..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color12.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color13.png b/app/src/harmattan/qml/images/stop-accent-color13.png deleted file mode 100644 index e33de6d..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color13.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color14.png b/app/src/harmattan/qml/images/stop-accent-color14.png deleted file mode 100644 index 03f7fde..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color14.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color15.png b/app/src/harmattan/qml/images/stop-accent-color15.png deleted file mode 100644 index ba2f13f..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color15.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color16.png b/app/src/harmattan/qml/images/stop-accent-color16.png deleted file mode 100644 index a3d3f17..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color16.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color17.png b/app/src/harmattan/qml/images/stop-accent-color17.png deleted file mode 100644 index 655bf08..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color17.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color18.png b/app/src/harmattan/qml/images/stop-accent-color18.png deleted file mode 100644 index 7a02df6..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color18.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color19.png b/app/src/harmattan/qml/images/stop-accent-color19.png deleted file mode 100644 index f19b783..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color19.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color2.png b/app/src/harmattan/qml/images/stop-accent-color2.png deleted file mode 100644 index 837ca2f..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color2.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color3.png b/app/src/harmattan/qml/images/stop-accent-color3.png deleted file mode 100644 index 5e35589..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color3.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color4.png b/app/src/harmattan/qml/images/stop-accent-color4.png deleted file mode 100644 index 936e4eb..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color4.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color5.png b/app/src/harmattan/qml/images/stop-accent-color5.png deleted file mode 100644 index 0cec7ee..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color5.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color6.png b/app/src/harmattan/qml/images/stop-accent-color6.png deleted file mode 100644 index a99da95..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color6.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color8.png b/app/src/harmattan/qml/images/stop-accent-color8.png deleted file mode 100644 index f0dbab1..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color8.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-color9.png b/app/src/harmattan/qml/images/stop-accent-color9.png deleted file mode 100644 index 8ff3650..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-color9.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent-pressed.png b/app/src/harmattan/qml/images/stop-accent-pressed.png deleted file mode 100644 index 8725935..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent-pressed.png and /dev/null differ diff --git a/app/src/harmattan/qml/images/stop-accent.png b/app/src/harmattan/qml/images/stop-accent.png deleted file mode 100644 index b22f911..0000000 Binary files a/app/src/harmattan/qml/images/stop-accent.png and /dev/null differ diff --git a/app/src/harmattan/qml/main.qml b/app/src/harmattan/qml/main.qml deleted file mode 100644 index a3141c1..0000000 --- a/app/src/harmattan/qml/main.qml +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import com.nokia.extras 1.1 -import MusiKloud 2.0 - -AppWindow { - id: appWindow - - showStatusBar: true - showToolBar: true - initialPage: MainPage { - id: mainPage - } - platformStyle: PageStackWindowStyle { - id: appStyle - - background: "image://theme/meegotouch-applicationpage-background-inverted" - backgroundFillMode: Image.Stretch - cornersVisible: true - } - - InfoBanner { - id: infoBanner - - function showMessage(message) { - text = message; - show(); - } - - topMargin: 40 - } - - AudioPlayer { - id: player - - onStatusChanged: if (status == AudioPlayer.Failed) infoBanner.showMessage(errorString); - } - - Connections { - id: clipboardConnections - - target: Clipboard - onTextChanged: mainPage.showResourceFromUrl(text) - } - - Connections { - id: dbusConnections - - target: DBus - onResourceRequested: mainPage.showResource(resource) - } - - Connections { - id: transferConnections - - target: null - onTransferAdded: infoBanner.showMessage("'" + transfer.title + "' " + qsTr("added to transfers")) - } - - Component.onCompleted: { - theme.inverted = true; - Transfers.restoreTransfers(); - transferConnections.target = Transfers; - - if (DBus.requestedResource.service) { - mainPage.showResource(DBus.requestedResource); - } - else { - var service = Settings.currentService; - - if (service) { - mainPage.setService(service); - } - else { - mainPage.setService(Resources.SOUNDCLOUD); - } - } - } -} diff --git a/app/src/harmattan/qml/plugins/PluginArtistPage.qml b/app/src/harmattan/qml/plugins/PluginArtistPage.qml deleted file mode 100644 index bf25b0f..0000000 --- a/app/src/harmattan/qml/plugins/PluginArtistPage.qml +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function load(artistOrId) { - if (artistOrId.hasOwnProperty("id")) { - artist.loadArtist(artistOrId); - } - else { - artist.loadArtist(Settings.currentService, artistOrId); - } - } - - tools: view.currentTab.tools ? view.currentTab.tools : ownTools - onToolsChanged: if (status == PageStatus.Active) appWindow.pageStack.toolBar.setTools(tools, "set"); - - ToolBarLayout { - id: ownTools - - visible: false - - BackToolIcon {} - - NowPlayingButton {} - } - - PluginArtist { - id: artist - - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - TabView { - id: view - - anchors.fill: parent - enabled: artist.id != "" - - Tab { - id: infoTab - - width: view.width - height: view.height - - PageHeader { - id: header - - title: artist.name - showProgressIndicator: artist.status == ResourcesRequest.Loading - } - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: header.bottom - margins: UI.PADDING_DOUBLE - } - - spacing: UI.PADDING_DOUBLE - - Avatar { - id: avatar - - width: height - height: Math.floor(parent.width / 4) - source: artist.thumbnailUrl - enabled: false - } - - Label { - id: nameLabel - - width: parent.width - font.bold: true - text: artist.name - } - } - - Flickable { - id: flicker - - anchors { - left: parent.left - right: parent.right - top: column.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - clip: true - contentHeight: descriptionLabel.height + UI.PADDING_DOUBLE * 2 - - Label { - id: descriptionLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - text: artist.description ? Utils.toRichText(artist.description) : qsTr("No description") - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Settings.currentService) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: column - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: flicker - anchors { - left: column.right - top: header.bottom - } - } - } - } - - TabLoader { - id: tracksTab - - width: view.width - height: view.height - title: artist.name + "'s " + qsTr("tracks") - tab: Component { - PluginTracksTab { - title: artist.name + "'s " + qsTr("tracks") - Component.onCompleted: model.list(artist.id) - } - } - } - - TabLoader { - id: playlistsTab - - width: view.width - height: view.height - title: artist.name + "'s " + qsTr("playlists") - tab: Component { - PluginPlaylistsTab { - title: artist.name + "'s " + qsTr("playlists") - Component.onCompleted: { - if (Plugins.resourceTypeIsSupported(Settings.currentService, Resources.PLAYLIST)) { - model.list(artist.id); - } - else { - infoBanner.showMessage(qsTr("This artist does not have any playlists")); - } - } - } - } - } - } -} diff --git a/app/src/harmattan/qml/plugins/PluginArtistsPage.qml b/app/src/harmattan/qml/plugins/PluginArtistsPage.qml deleted file mode 100644 index 62543c0..0000000 --- a/app/src/harmattan/qml/plugins/PluginArtistsPage.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: artistModel - - title: qsTr("Artists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: artistModel.status != ResourcesRequest.Loading - onClicked: artistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginArtistModel { - id: artistModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: artistModel.status == ResourcesRequest.Loading - } - delegate: ArtistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(artistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No artists found") - visible: (artistModel.status >= ResourcesRequest.Ready) && (artistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/plugins/PluginArtistsTab.qml b/app/src/harmattan/qml/plugins/PluginArtistsTab.qml deleted file mode 100644 index db9e094..0000000 --- a/app/src/harmattan/qml/plugins/PluginArtistsTab.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: artistModel - - title: qsTr("Artists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: artistModel.status != ResourcesRequest.Loading - onClicked: artistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginArtistModel { - id: artistModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: artistModel.status == ResourcesRequest.Loading - } - delegate: ArtistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(artistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No artists found") - visible: (artistModel.status >= ResourcesRequest.Ready) && (artistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/plugins/PluginCategoriesPage.qml b/app/src/harmattan/qml/plugins/PluginCategoriesPage.qml deleted file mode 100644 index d1c502f..0000000 --- a/app/src/harmattan/qml/plugins/PluginCategoriesPage.qml +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: categoryModel - - title: qsTr("Categories") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: categoryModel.status != ResourcesRequest.Loading - onClicked: categoryModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginCategoryModel { - id: categoryModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: categoryModel.status == ResourcesRequest.Loading - } - delegate: LabelDelegate { - text: name - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTracksPage.qml"), {title: name}).model.list(value) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No categories found") - visible: (categoryModel.status >= ResourcesRequest.Ready) && (categoryModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/plugins/PluginCommentsTab.qml b/app/src/harmattan/qml/plugins/PluginCommentsTab.qml deleted file mode 100644 index 002324a..0000000 --- a/app/src/harmattan/qml/plugins/PluginCommentsTab.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: commentModel - - title: qsTr("Comments") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: commentModel.status != ResourcesRequest.Loading - onClicked: commentModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginCommentModel { - id: commentModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: commentModel.status == ResourcesRequest.Loading - } - delegate: CommentDelegate { - onThumbnailClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(userId) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No comments found") - visible: (commentModel.status >= ResourcesRequest.Ready) && (commentModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/plugins/PluginDownloadDialog.qml b/app/src/harmattan/qml/plugins/PluginDownloadDialog.qml deleted file mode 100644 index d0445d3..0000000 --- a/app/src/harmattan/qml/plugins/PluginDownloadDialog.qml +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MySheet { - id: root - - property string resourceId - property string resourceTitle - property string streamUrl - - showProgressIndicator: streamModel.status == ResourcesRequest.Loading - acceptButtonText: streamModel.count > 0 ? qsTr("Done") : "" - rejectButtonText: qsTr("Cancel") - content: Item { - anchors.fill: parent - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - - ValueSelector { - id: streamSelector - - width: parent.width - title: qsTr("Audio format") - model: PluginStreamModel { - id: streamModel - - service: Settings.currentService - onStatusChanged: { - switch (status) { - case ResourcesRequest.Loading: { - streamSelector.showProgressIndicator = true; - return; - } - case ResourcesRequest.Ready: - if (count > 0) { - streamSelector.selectedIndex = Math.max(0, match("name", - Settings.defaultDownloadFormat(service))); - } - else { - infoBanner.showMessage(qsTr("No streams found")); - } - - break; - case ResourcesRequest.Failed: { - infoBanner.showMessage(errorString); - break; - } - default: - break; - } - - streamSelector.showProgressIndicator = false; - } - } - onValueChanged: Settings.setDefaultDownloadFormat(streamModel.service, - streamModel.data(selectedIndex, "name")) - } - - ValueSelector { - id: categorySelector - - width: parent.width - title: qsTr("Category") - model: CategoryNameModel { - id: categoryModel - } - value: Settings.defaultCategory - onValueChanged: Settings.defaultCategory = value - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - } - - onAccepted: Transfers.addDownloadTransfer(streamModel.service, resourceId, streamUrl ? "" : streamSelector.value.id, - streamUrl, resourceTitle, Settings.defaultCategory) - - onStatusChanged: { - switch (status) { - case DialogStatus.Opening: { - if (streamUrl) { - streamModel.clear(); - streamModel.append(qsTr("Default format"), streamUrl); - } - else { - streamModel.list(resourceId); - } - - break; - } - case DialogStatus.Closing: - streamModel.cancel(); - break; - default: - break; - } - } -} diff --git a/app/src/harmattan/qml/plugins/PluginPlaylistPage.qml b/app/src/harmattan/qml/plugins/PluginPlaylistPage.qml deleted file mode 100644 index 35b9282..0000000 --- a/app/src/harmattan/qml/plugins/PluginPlaylistPage.qml +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function load(playlistOrId) { - if (playlistOrId.hasOwnProperty("id")) { - playlist.loadPlaylist(playlistOrId); - tracksTab.model.list(playlist.id); - } - else { - playlist.loadPlaylist(Settings.currentService, playlistOrId) - tracksTab.model.list(playlistOrId); - } - } - - tools: view.currentTab.tools ? view.currentTab.tools : ownTools - onToolsChanged: if (status == PageStatus.Active) appWindow.pageStack.toolBar.setTools(tools, "set"); - - ToolBarLayout { - id: ownTools - - visible: false - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-view-menu" - onClicked: menu.open() - } - } - - Menu { - id: menu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - enabled: tracksTab.model.count > 0 - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.model.count; i ++) { - tracks.push(tracksTab.model.get(i)); - } - - player.addTracks(tracks); - infoBanner.showMessage(tracks.length + " " + qsTr("tracks added to queue")); - } - } - } - } - - PluginPlaylist { - id: playlist - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - TabView { - id: view - - anchors.fill: parent - enabled: playlist.id != "" - - Tab { - id: infoTab - - width: view.width - height: view.height - - PageHeader { - id: header - - title: playlist.title - showProgressIndicator: (playlist.status == ResourcesRequest.Loading) - || (tracksTab.model.status == ResourcesRequest.Loading) - } - - Row { - id: row - - anchors { - left: parent.left - right: parent.right - top: header.bottom - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - Thumbnail { - id: thumbnail - - z: 10 - width: 150 - height: 150 - source: playlist.largeThumbnailUrl - placeholderText: playlist.title - enabled: tracksTab.model.count > 0 - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.model.count; i ++) { - tracks.push(tracksTab.model.get(i)); - } - - player.clearQueue(); - player.addTracks(tracks); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - } - - Column { - id: column - - width: row.width - thumbnail.width - row.spacing - spacing: UI.PADDING_DOUBLE - - Label { - width: parent.width - clip: true - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - text: qsTr("Artist") + ": " - + (playlist.artist ? Plugins.resourceTypeIsSupported(Settings.currentService, Resources.ARTIST) - ? "" + playlist.artist + "" - : playlist.artist : qsTr("Unknown")) - onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")) - .load(playlist.artistId) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Date") + ": " + (playlist.date ? playlist.date : qsTr("Unknown")) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Genre") + ": " + (playlist.genre ? playlist.genre : qsTr("Unknown")) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Tracks") + ": " + playlist.trackCount - } - } - } - - Flickable { - id: flicker - - anchors { - left: parent.left - right: parent.right - top: row.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - contentHeight: descriptionLabel.height + UI.PADDING_DOUBLE - clip: true - - Label { - id: descriptionLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - text: playlist.description ? Utils.toRichText(playlist.description) : qsTr("No description") - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Settings.currentService) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: row - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: flicker - anchors { - left: row.right - top: header.bottom - } - } - } - } - - PluginTracksTab { - id: tracksTab - - width: view.width - height: view.height - title: qsTr("Tracks") - } - } - - Connections { - target: tracksTab.model - onStatusChanged: { - if ((tracksTab.model.status == ResourcesRequest.Ready) && (tracksTab.model.canFetchMore)) { - tracksTab.model.fetchMore(); - } - } - } -} diff --git a/app/src/harmattan/qml/plugins/PluginPlaylistsPage.qml b/app/src/harmattan/qml/plugins/PluginPlaylistsPage.qml deleted file mode 100644 index 48a1707..0000000 --- a/app/src/harmattan/qml/plugins/PluginPlaylistsPage.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: playlistModel - - title: qsTr("Playlists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: playlistModel.status != ResourcesRequest.Loading - onClicked: playlistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginPlaylistModel { - id: playlistModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: playlistModel.status == ResourcesRequest.Loading - } - delegate: PlaylistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(playlistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No playlists found") - visible: (playlistModel.status >= ResourcesRequest.Ready) && (playlistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/plugins/PluginPlaylistsTab.qml b/app/src/harmattan/qml/plugins/PluginPlaylistsTab.qml deleted file mode 100644 index 867577f..0000000 --- a/app/src/harmattan/qml/plugins/PluginPlaylistsTab.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: playlistModel - - title: qsTr("Playlists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: playlistModel.status != ResourcesRequest.Loading - onClicked: playlistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginPlaylistModel { - id: playlistModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: playlistModel.status == ResourcesRequest.Loading - } - delegate: PlaylistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(playlistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No playlists found") - visible: (playlistModel.status >= ResourcesRequest.Ready) && (playlistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/plugins/PluginSearchDialog.qml b/app/src/harmattan/qml/plugins/PluginSearchDialog.qml deleted file mode 100644 index aeb6a6b..0000000 --- a/app/src/harmattan/qml/plugins/PluginSearchDialog.qml +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MySheet { - id: root - - acceptButtonText: searchField.text ? qsTr("Done") : "" - rejectButtonText: qsTr("Cancel") - content: Item { - anchors.fill: parent - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - MyTextField { - id: searchField - - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE * 2 - clearButtonEnabled: true - inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText - onTextChanged: searchModel.setFilterFixedString(text) - onAccepted: platformCloseSoftwareInputPanel() - } - - ValueSelector { - id: typeSelector - - width: parent.width - title: qsTr("Search for") - model: PluginSearchTypeModel { - service: Settings.currentService - } - selectedIndex: Math.max(0, model.match("name",Settings.defaultSearchType(model.service))) - onAccepted: Settings.setDefaultSearchType(model.service, model.data(selectedIndex, "name")) - } - } - - ListView { - id: view - - anchors { - left: parent.left - right: parent.right - top: column.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - clip: true - model: SearchHistoryModel { - id: searchModel - } - header: SeparatorLabel { - text: qsTr("Search history") - x: UI.PADDING_DOUBLE - width: view.width - UI.PADDING_DOUBLE - } - delegate: LabelDelegate { - onClicked: searchField.text = display - onPressAndHold: { - view.currentIndex = -1; - view.currentIndex = index; - contextMenu.open(); - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Remove") - onClicked: searchModel.removeSearch(view.currentIndex) - } - - MenuItem { - text: qsTr("Clear") - onClicked: searchModel.clear() - } - } - } - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: column - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: view - anchors { - left: column.right - top: parent.top - } - } - } - } - - onAccepted: { - if (!mainPage.showResourceFromUrl(searchField.text)) { - Settings.addSearch(searchField.text); - mainPage.search(Settings.currentService, searchField.text, typeSelector.value.type, typeSelector.value.order); - } - } - onStatusChanged: if (status == DialogStatus.Opening) searchField.text = ""; -} diff --git a/app/src/harmattan/qml/plugins/PluginSettingsGroupLabel.qml b/app/src/harmattan/qml/plugins/PluginSettingsGroupLabel.qml deleted file mode 100644 index 209e85e..0000000 --- a/app/src/harmattan/qml/plugins/PluginSettingsGroupLabel.qml +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property alias text: label.text - - height: 84 - width: !parent ? implicitWidth : parent.width - - Label { - id: label - - anchors { - right: parent.right - rightMargin: UI.PADDING_DOUBLE - verticalCenter: parent.verticalCenter - } - font.pixelSize: UI.FONT_XSMALL - font.bold: true - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - horizontalAlignment: Text.AlignRight - verticalAlignment: Text.AlignVCenter - } - - Rectangle { - height: 1 - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: label.left - rightMargin: UI.PADDING_DOUBLE - verticalCenter: label.verticalCenter - } - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - } -} diff --git a/app/src/harmattan/qml/plugins/PluginSettingsPage.qml b/app/src/harmattan/qml/plugins/PluginSettingsPage.qml deleted file mode 100644 index 394daf4..0000000 --- a/app/src/harmattan/qml/plugins/PluginSettingsPage.qml +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function loadSettings(pluginName, fileName) { - title = pluginName; - - var request = new XMLHttpRequest(); - request.onreadystatechange = function() { - if (request.readyState === XMLHttpRequest.DONE) { - var doc = request.responseXML.documentElement; - - for (var i = 0; i < doc.childNodes.length; i++) { - var node = doc.childNodes[i]; - - if (node.nodeName === "group") { - root.addGroup(node.attributes[0].value); - - for (var ii = 0; ii < node.childNodes.length; ii++) { - var groupNode = node.childNodes[ii]; - - if (groupNode.nodeName === "list") { - var key = pluginName + "/" + root.findAttributeValue(groupNode, "key"); - var defaultValue = root.findAttributeValue(groupNode, "default"); - var title = root.findAttributeValue(groupNode, "title"); - var list = []; - - for (var iii = 0; iii < groupNode.childNodes.length; iii++) { - var listNode = groupNode.childNodes[iii]; - - if (listNode.nodeName === "element") { - var name = root.findAttributeValue(listNode, "name"); - var value = root.findAttributeValue(listNode, "value"); - var element = { "name": name, "value": value }; - list.push(element); - } - } - - root.addSelectionItem(key, defaultValue, title, list); - } - else if (groupNode.nodeName === "boolean") { - var key = pluginName + "/" + root.findAttributeValue(groupNode, "key"); - var defaultValue = root.findAttributeValue(groupNode, "default"); - var title = root.findAttributeValue(groupNode, "title"); - root.addSwitch(key, defaultValue, title); - } - else if (groupNode.nodeName === "integer") { - var key = pluginName + "/" + root.findAttributeValue(groupNode, "key"); - var defaultValue = root.findAttributeValue(groupNode, "default"); - var title = root.findAttributeValue(groupNode, "title"); - var min = parseInt(root.findAttributeValue(groupNode, "min")); - var max = parseInt(root.findAttributeValue(groupNode, "max")); - var step = parseInt(root.findAttributeValue(groupNode, "step")); - root.addSlider(key, defaultValue, title, min, max, step); - } - else if (groupNode.nodeName === "text") { - var key = pluginName + "/" + root.findAttributeValue(groupNode, "key"); - var defaultValue = root.findAttributeValue(groupNode, "default"); - var title = root.findAttributeValue(groupNode, "title"); - root.addTextField(key, defaultValue, title); - } - } - } - if (node.nodeName === "list") { - var key = pluginName + "/" + root.findAttributeValue(node, "key"); - var defaultValue = root.findAttributeValue(node, "default"); - var title = root.findAttributeValue(node, "title"); - var list = []; - - for (var iii = 0; iii < node.childNodes.length; iii++) { - var listNode = node.childNodes[iii]; - - if (listNode.nodeName === "element") { - var name = root.findAttributeValue(listNode, "name"); - var value = root.findAttributeValue(listNode, "value"); - var element = { "name": name, "value": value }; - list.push(element); - } - } - - root.addSelectionItem(key, defaultValue, title, list); - } - else if (node.nodeName === "boolean") { - var key = pluginName + "/" + root.findAttributeValue(node, "key"); - var defaultValue = root.findAttributeValue(node, "default"); - var title = root.findAttributeValue(node, "title"); - root.addSwitch(key, defaultValue, title); - } - else if (node.nodeName === "integer") { - var key = pluginName + "/" + root.findAttributeValue(node, "key"); - var defaultValue = root.findAttributeValue(node, "default"); - var title = root.findAttributeValue(node, "title"); - var min = parseInt(root.findAttributeValue(node, "min")); - var max = parseInt(root.findAttributeValue(node, "max")); - var step = parseInt(root.findAttributeValue(node, "step")); - root.addSlider(key, defaultValue, title, min, max, step); - } - else if (node.nodeName === "text") { - var key = pluginName + "/" + root.findAttributeValue(node, "key"); - var defaultValue = root.findAttributeValue(node, "default"); - var title = root.findAttributeValue(node, "title"); - root.addTextField(key, defaultValue, title); - } - } - } - } - - request.open("GET", fileName); - request.send(); - } - - function findAttributeValue(node, name) { - for (var i = 0; i < node.attributes.length; i++) { - var att = node.attributes[i]; - - if (att.name === name) { - return att.value; - } - } - - return ""; - } - - function findEmptyLoader() { - for (var i = 1; i < column.children.length; i++) { - var child = column.children[i]; - - if ((child.hasOwnProperty("item")) && (!child.item)) { - return child; - } - } - } - - function addGroup(title) { - var loader = root.findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("PluginSettingsGroupLabel.qml"); - loader.item.text = title; - } - } - - function addSelectionItem(key, defaultValue, title, list) { - var loader = root.findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("PluginSettingsValueSelector.qml"); - loader.item.title = title; - loader.item.setList(key, defaultValue, list); - } - } - - function addSwitch(key, defaultValue, title) { - var loader = root.findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("PluginSettingsSwitch.qml"); - loader.item.title = title; - loader.item.setKey(key, defaultValue); - } - } - - function addSlider(key, defaultValue, title, min, max, step) { - var loader = root.findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("PluginSettingsSlider.qml"); - loader.item.title = title; - loader.item.minimumValue = min; - loader.item.maximumValue = max; - loader.item.stepSize = step; - loader.item.setKey(key, defaultValue); - } - } - - function addTextField(key, defaultValue, title) { - var loader = root.findEmptyLoader(); - - if (loader) { - loader.source = Qt.resolvedUrl("PluginSettingsTextField.qml"); - loader.item.title = title; - loader.item.setKey(key, defaultValue); - } - } - - title: qsTr("Plugin settings") - tools: ToolBarLayout { - - BackToolIcon {} - } - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - top: parent.top - topMargin: UI.PADDING_DOUBLE - left: parent.left - right: parent.right - } - - PageHeader { - title: root.title - } - - Repeater { - id: repeater - - model: 20 - - Loader { - property string key: "" - - objectName: "loader" + (modelData + 1) - width: column.width - height: item ? Math.max(84, item.height) : 0 - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } -} diff --git a/app/src/harmattan/qml/plugins/PluginSettingsSlider.qml b/app/src/harmattan/qml/plugins/PluginSettingsSlider.qml deleted file mode 100644 index 0d2e072..0000000 --- a/app/src/harmattan/qml/plugins/PluginSettingsSlider.qml +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property alias title: titleLabel.text - property alias minimumValue: slider.minimumValue - property alias maximumValue: slider.maximumValue - property alias stepSize: slider.stepSize - property string key - - function setKey(key, defaultValue) { - root.key = key; - var value = Settings.value(key); - - if (value === undefined) { - slider.value = parseFloat(defaultValue); - } - else { - slider.value = parseFloat(value); - } - } - - height: 84 - width: !parent ? implicitWidth : parent.width - - Label { - id: titleLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - - font.bold: true - elide: Text.ElideRight - } - - Slider { - id: slider - - anchors { - left: parent.left - leftMargin: 30 - right: parent.right - rightMargin: 30 - top: parent.verticalCenter - } - orientation: Qt.Horizontal - valueIndicatorVisible: true - onValueChanged: Settings.setValue(root.key, slider.value) - } -} diff --git a/app/src/harmattan/qml/plugins/PluginSettingsSwitch.qml b/app/src/harmattan/qml/plugins/PluginSettingsSwitch.qml deleted file mode 100644 index f88643e..0000000 --- a/app/src/harmattan/qml/plugins/PluginSettingsSwitch.qml +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import ".." - -MySwitch { - id: root - - property string key - - function setKey(key, defaultValue) { - root.key = key; - - var value = Settings.value(key); - - if (value === undefined) { - root.checked = (defaultValue === "true"); - } - else { - root.checked = (value === "true"); - } - } - - onCheckedChanged: Settings.setValue(root.key, root.checked) -} diff --git a/app/src/harmattan/qml/plugins/PluginSettingsTextField.qml b/app/src/harmattan/qml/plugins/PluginSettingsTextField.qml deleted file mode 100644 index 3a09efd..0000000 --- a/app/src/harmattan/qml/plugins/PluginSettingsTextField.qml +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Item { - id: root - - property alias title: titleLabel.text - property string key - - function setKey(key, defaultValue) { - root.key = key; - var value = Settings.value(key); - - if (value === undefined) { - value = defaultValue; - } - - textField.text = value; - } - - height: titleLabel.height + textField.height + UI.PADDING_DOUBLE * 2 - width: !parent ? implicitWidth : parent.width - - Label { - id: titleLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - - font.bold: true - elide: Text.ElideRight - } - - MyTextField { - id: textField - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: titleLabel.bottom - topMargin: UI.PADDING_DOUBLE - } - - inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase - onTextChanged: Settings.setValue(root.key, textField.text) - onAccepted: platformCloseSoftwareInputPanel() - } -} diff --git a/app/src/harmattan/qml/plugins/PluginSettingsValueSelector.qml b/app/src/harmattan/qml/plugins/PluginSettingsValueSelector.qml deleted file mode 100644 index a71d4fd..0000000 --- a/app/src/harmattan/qml/plugins/PluginSettingsValueSelector.qml +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." - -ValueListItem { - id: root - - property string key - property variant value - property int selectedIndex: -1 - - function setList(key, defaultValue, list) { - root.key = key; - - var value = Settings.value(key); - - if (value === undefined) { - value = defaultValue; - Settings.setValue(key, value); - } - - for (var i = 0; i < list.length; i++) { - selectionModel.append(list[i].name, list[i].value); - - if (list[i].value === value) { - root.value = value; - root.subTitle = list[i].name; - } - } - } - - - - SelectionModel { - id: selectionModel - } - - Loader { - id: loader - } - - Component { - id: dialog - - ValueDialog { - titleText: root.title - model: selectionModel - value: root.value - selectedIndex: root.selectedIndex - onSelectedIndexChanged: root.selectedIndex = selectedIndex - onValueChanged: { - root.value = value; - Settings.setValue(root.key, value); - } - } - } - - onSelectedIndexChanged: if (selectionModel) value = selectionModel.data(selectedIndex, "value"); - onValueChanged: if (selectionModel.count > 0) subTitle = selectionModel.data(selectionModel.match("value", value), "name"); - onClicked: { - loader.sourceComponent = dialog; - loader.item.open(); - } -} diff --git a/app/src/harmattan/qml/plugins/PluginTrackPage.qml b/app/src/harmattan/qml/plugins/PluginTrackPage.qml deleted file mode 100644 index 52bdbea..0000000 --- a/app/src/harmattan/qml/plugins/PluginTrackPage.qml +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function load(trackOrId) { - if (trackOrId.hasOwnProperty("id")) { - track.loadTrack(trackOrId); - } - else { - track.loadTrack(Settings.currentService, trackOrId); - } - } - - tools: view.currentTab.tools ? view.currentTab.tools : ownTools - onToolsChanged: if (status == PageStatus.Active) appWindow.pageStack.toolBar.setTools(tools, "set"); - - ToolBarLayout { - id: ownTools - - visible: false - - BackToolIcon {} - - NowPlayingButton {} - } - - PluginTrack { - id: track - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - TabView { - id: view - - anchors.fill: parent - enabled: track.id != "" - - Tab { - id: infoTab - - width: view.width - height: view.height - tools: ToolBarLayout { - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-view-menu" - onClicked: menu.open() - } - } - - Menu { - id: menu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - onClicked: { - player.addTrack(track); - infoBanner.showMessage(qsTr("1 track added to queue")); - } - } - - MenuItem { - text: qsTr("Download") - enabled: track.downloadable - onClicked: { - dialogLoader.sourceComponent = downloadDialog; - dialogLoader.item.resourceId = track.id; - dialogLoader.item.resourceTitle = track.title; - dialogLoader.item.streamUrl = track.streamUrl; - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Share") - onClicked: if (!ShareUi.shareTrack(track)) infoBanner.showMessage(qsTr("Unable to share track")); - } - } - } - - PageHeader { - id: header - - title: track.title - showProgressIndicator: track.status == ResourcesRequest.Loading - } - - Row { - id: row - - anchors { - left: parent.left - right: parent.right - top: header.bottom - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - Thumbnail { - id: thumbnail - - z: 10 - width: 150 - height: 150 - source: track.largeThumbnailUrl - placeholderText: track.title - onClicked: { - player.clearQueue(); - player.addTrack(track); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - } - - Column { - id: column - - width: row.width - thumbnail.width - row.spacing - spacing: UI.PADDING_DOUBLE - - Label { - width: parent.width - clip: true - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - text: qsTr("Artist") + ": " - + (track.artist ? Plugins.resourceTypeIsSupported(Settings.currentService, Resources.ARTIST) - ? "" + track.artist + "" - : track.artist : qsTr("Unknown")) - onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")) - .load(track.artistId) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Date") + ": " + (track.date ? track.date : qsTr("Unknown")) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Duration") + ": " + track.durationString - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Genre") + ": " + (track.genre ? track.genre : qsTr("Unknown")) - } - } - } - - Flickable { - id: flicker - - anchors { - left: parent.left - right: parent.right - top: row.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - contentHeight: descriptionLabel.height + UI.PADDING_DOUBLE - clip: true - - Label { - id: descriptionLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - text: track.description ? Utils.toRichText(track.description) : qsTr("No description") - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Settings.currentService) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - Loader { - id: dialogLoader - } - - Component { - id: downloadDialog - - PluginDownloadDialog {} - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: row - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: flicker - anchors { - left: row.right - top: header.bottom - } - } - } - } - - TabLoader { - id: relatedTab - - width: view.width - height: view.height - title: qsTr("Related") - tab: Component { - PluginTracksTab { - title: qsTr("Related") - Component.onCompleted: model.list(track.id) - } - } - } - - TabLoader { - id: commentsTab - - width: view.width - height: view.height - title: qsTr("Comments") - tab: Component { - PluginCommentsTab { - title: qsTr("Comments") - Component.onCompleted: { - if (Plugins.resourceTypeIsSupported(Settings.currentService, Resources.COMMENT)) { - model.list(track.id); - } - else { - infoBanner.showMessage(qsTr("This track does not have any comments")); - } - } - } - } - } - } -} diff --git a/app/src/harmattan/qml/plugins/PluginTracksPage.qml b/app/src/harmattan/qml/plugins/PluginTracksPage.qml deleted file mode 100644 index 1facab6..0000000 --- a/app/src/harmattan/qml/plugins/PluginTracksPage.qml +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: trackModel - - title: qsTr("Tracks") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: trackModel.status != ResourcesRequest.Loading - onClicked: trackModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginTrackModel { - id: trackModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: trackModel.status == ResourcesRequest.Loading - } - delegate: TrackDelegate { - onThumbnailClicked: { - player.clearQueue(); - player.addTrack(trackModel.get(index)); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(trackModel.get(index)) - onPressAndHold: { - view.currentIndex = -1; - view.currentIndex = index; - contextMenu.open(); - } - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No tracks found") - visible: (trackModel.status >= ResourcesRequest.Ready) && (trackModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - onClicked: { - player.addTrack(trackModel.get(view.currentIndex)); - infoBanner.showMessage(qsTr("1 track added to queue")); - } - } - - MenuItem { - text: qsTr("Download") - enabled: (view.currentIndex >= 0) && (trackModel.data(view.currentIndex, "downloadable")) - onClicked: { - dialogLoader.sourceComponent = downloadDialog; - dialogLoader.item.resourceId = trackModel.data(view.currentIndex, "id"); - dialogLoader.item.resourceTitle = trackModel.data(view.currentIndex, "title"); - dialogLoader.item.streamUrl = trackModel.data(view.currentIndex, "streamUrl"); - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Share") - onClicked: if (!ShareUi.shareTrack(trackModel.get(view.currentIndex))) - infoBanner.showMessage(qsTr("Unable to share track")); - } - } - } - - Loader { - id: dialogLoader - } - - Component { - id: downloadDialog - - PluginDownloadDialog {} - } -} diff --git a/app/src/harmattan/qml/plugins/PluginTracksTab.qml b/app/src/harmattan/qml/plugins/PluginTracksTab.qml deleted file mode 100644 index 6aa496b..0000000 --- a/app/src/harmattan/qml/plugins/PluginTracksTab.qml +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: trackModel - - title: qsTr("Tracks") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: trackModel.status != ResourcesRequest.Loading - onClicked: trackModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: PluginTrackModel { - id: trackModel - - service: Settings.currentService - onStatusChanged: if (status == ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: trackModel.status == ResourcesRequest.Loading - } - delegate: TrackDelegate { - onThumbnailClicked: { - player.clearQueue(); - player.addTrack(trackModel.get(index)); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(trackModel.get(index)) - onPressAndHold: { - view.currentIndex = -1; - view.currentIndex = index; - contextMenu.open(); - } - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No tracks found") - visible: (trackModel.status >= ResourcesRequest.Ready) && (trackModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - onClicked: { - player.addTrack(trackModel.get(view.currentIndex)); - infoBanner.showMessage(qsTr("1 track added to queue")); - } - } - - MenuItem { - text: qsTr("Download") - enabled: (view.currentIndex >= 0) && (trackModel.data(view.currentIndex, "downloadable")) - onClicked: { - dialogLoader.sourceComponent = downloadDialog; - dialogLoader.item.resourceId = trackModel.data(view.currentIndex, "id"); - dialogLoader.item.resourceTitle = trackModel.data(view.currentIndex, "title"); - dialogLoader.item.streamUrl = trackModel.data(view.currentIndex, "streamUrl"); - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Share") - onClicked: if (!ShareUi.shareTrack(trackModel.get(view.currentIndex))) - infoBanner.showMessage(qsTr("Unable to share track")); - } - } - } - - Loader { - id: dialogLoader - } - - Component { - id: downloadDialog - - PluginDownloadDialog {} - } -} diff --git a/app/src/harmattan/qml/plugins/PluginsSettingsPage.qml b/app/src/harmattan/qml/plugins/PluginsSettingsPage.qml deleted file mode 100644 index a0bc948..0000000 --- a/app/src/harmattan/qml/plugins/PluginsSettingsPage.qml +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Plugins") - tools: ToolBarLayout { - - BackToolIcon {} - } - - ListView { - id: view - - anchors.fill: parent - interactive: count > 0 - model: PluginSettingsModel { - id: pluginModel - } - header: PageHeader { - title: root.title - } - delegate: DrillDownDelegate { - text: name - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginSettingsPage.qml")).loadSettings(name, value) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No plugins") - visible: pluginModel.count == 0 - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudAccountsPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudAccountsPage.qml deleted file mode 100644 index a84f5ad..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudAccountsPage.qml +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - title: qsTr("Accounts") - tools: ToolBarLayout { - - BackToolIcon {} - - MyToolIcon { - platformIconId: "toolbar-add" - onClicked: { - loader.sourceComponent = authDialog; - loader.item.open(); - } - } - } - - ListView { - id: view - - anchors.fill: parent - interactive: count > 0 - highlightFollowsCurrentItem: false - enabled: !root.showProgressIndicator - model: SoundCloudAccountModel { - id: accountModel - } - header: PageHeader { - title: root.title - showProgressIndicator: (authRequest.status == QSoundCloud.AuthenticationRequest.Loading) - || (userRequest.status == QSoundCloud.AuthenticationRequest.Loading) - } - delegate: LabelDelegate { - text: username - isSelected: active - onClicked: infoBanner.showMessage(accountModel.selectAccount(index) - ? qsTr("You have selected account") + " '" + username + "'" - : accountModel.errorString) - onPressAndHold: { - view.currentIndex = index; - contextMenu.open(); - } - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No accounts") - visible: accountModel.count == 0 - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Remove") - onClicked: infoBanner.showMessage(accountModel.removeAccount(view.currentIndex) - ? qsTr("Account removed. Please visit the SoundCloud website to revoke the access token") - : accountModel.errorString) - } - } - } - - Loader { - id: loader - } - - QSoundCloud.AuthenticationRequest { - id: authRequest - - clientId: SoundCloud.clientId - clientSecret: SoundCloud.clientSecret - redirectUri: SoundCloud.redirectUri - scopes: SoundCloud.scopes - onFinished: { - if (status == QSoundCloud.AuthenticationRequest.Ready) { - if (result.access_token) { - userRequest.accessToken = result.access_token; - userRequest.refreshToken = (result.refresh_token ? result.refresh_token : ""); - userRequest.get("/me"); - return; - } - } - - infoBanner.showMessage(SoundCloud.getErrorString(result)); - } - } - - QSoundCloud.ResourcesRequest { - id: userRequest - - clientId: SoundCloud.clientId - clientSecret: SoundCloud.clientSecret - onFinished: { - if (status == QSoundCloud.ResourcesRequest.Ready) { - if (accountModel.addAccount(result.id, result.username, accessToken, refreshToken, - SoundCloud.scopes.join(" "))) { - infoBanner.showMessage(qsTr("You are signed in to account") + " '" + result.username + "'"); - } - else { - infoBanner.showMessage(accountModel.errorString); - } - - return; - } - - messageBox.showError(SoundCloud.getErrorString(result)); - } - } - - Component { - id: authDialog - - SoundCloudAuthDialog { - onCodeReady: authRequest.exchangeCodeForAccessToken(code) - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudArtistPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudArtistPage.qml deleted file mode 100644 index 0355aa5..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudArtistPage.qml +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function load(artistOrId) { - artist.loadArtist(artistOrId); - - if ((artist.id) && (!artist.followed) && (SoundCloud.userId)) { - artist.checkIfFollowed(); - } - } - - tools: view.currentTab.tools ? view.currentTab.tools : ownTools - onToolsChanged: if (status == PageStatus.Active) appWindow.pageStack.toolBar.setTools(tools, "set"); - - ToolBarLayout { - id: ownTools - - visible: false - - BackToolIcon {} - - NowPlayingButton {} - } - - SoundCloudArtist { - id: artist - - onStatusChanged: { - switch (status) { - case QSoundCloud.ResourcesRequest.Ready: - if ((!followed) && (SoundCloud.userId)) { - checkIfFollowed(); - } - - break; - case QSoundCloud.ResourcesRequest.Failed: - infoBanner.showMessage(errorString); - break; - default: - break; - } - } - } - - TabView { - id: view - - anchors.fill: parent - enabled: artist.id != "" - - Tab { - id: infoTab - - width: view.width - height: view.height - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: artist.followed ? "toolbar-cancle" : "toolbar-add" - enabled: (SoundCloud.userId) && (artist.id) - && (artist.id != SoundCloud.userId) - onClicked: artist.followed ? artist.unfollow() : artist.follow() - } - } - - PageHeader { - id: header - - title: artist.name - showProgressIndicator: artist.status == QSoundCloud.ResourcesRequest.Loading - } - - Flow { - id: flow - - anchors { - left: parent.left - right: parent.right - top: header.bottom - margins: UI.PADDING_DOUBLE - } - - spacing: UI.PADDING_DOUBLE - - Avatar { - id: avatar - - width: height - height: Math.floor(parent.width / 4) - source: artist.thumbnailUrl - enabled: false - } - - Item { - width: parent.width - avatar.width - UI.PADDING_DOUBLE - height: avatar.height - } - - Label { - id: nameLabel - - font.bold: true - text: artist.name - } - - Label { - id: statsLabel - - width: parent.width - nameLabel.width - UI.PADDING_DOUBLE - horizontalAlignment: Text.AlignRight - font.pixelSize: UI.FONT_SMALL - font.family: UI.FONT_FAMILY_LIGHT - text: Utils.formatLargeNumber(artist.followersCount) + " " + qsTr("followers") - } - } - - Flickable { - id: flicker - - anchors { - left: parent.left - right: parent.right - top: flow.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - clip: true - contentHeight: descriptionLabel.height + UI.PADDING_DOUBLE * 2 - - Label { - id: descriptionLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - text: artist.description ? Utils.toRichText(artist.description) : qsTr("No description") - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Resources.SOUNDCLOUD) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: flow - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: flicker - anchors { - left: flow.right - top: header.bottom - } - } - } - } - - TabLoader { - id: tracksTab - - width: view.width - height: view.height - title: artist.name + "'s " + qsTr("tracks") - tab: Component { - SoundCloudTracksTab { - title: artist.name + "'s " + qsTr("tracks") - Component.onCompleted: model.get("/users/" + artist.id + "/tracks", {limit: MAX_RESULTS}) - } - } - } - - TabLoader { - id: favouritesTab - - width: view.width - height: view.height - title: artist.name + "'s " + qsTr("favourites") - tab: Component { - SoundCloudTracksTab { - title: artist.name + "'s " + qsTr("favourites") - Component.onCompleted: model.get("/users/" + artist.id + "/favorites", {limit: MAX_RESULTS}) - } - } - } - - TabLoader { - id: playlistsTab - - width: view.width - height: view.height - title: artist.name + "'s " + qsTr("sets") - tab: Component { - SoundCloudPlaylistsTab { - title: artist.name + "'s " + qsTr("sets") - Component.onCompleted: model.get("/users/" + artist.id + "/playlists", {limit: MAX_RESULTS}) - } - } - } - - TabLoader { - id: followingsTab - - width: view.width - height: view.height - title: artist.name + "'s " + qsTr("followings") - tab: Component { - SoundCloudArtistsTab { - title: artist.name + "'s " + qsTr("followings") - Component.onCompleted: model.get("/users/" + artist.id + "/followings", {limit: MAX_RESULTS}) - } - } - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudArtistsPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudArtistsPage.qml deleted file mode 100644 index 40bd398..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudArtistsPage.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: artistModel - - title: qsTr("Artists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: artistModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: artistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudArtistModel { - id: artistModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: artistModel.status == QSoundCloud.ResourcesRequest.Loading - } - delegate: ArtistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(artistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No artists found") - visible: (artistModel.status >= QSoundCloud.ResourcesRequest.Ready) && (artistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudArtistsTab.qml b/app/src/harmattan/qml/soundcloud/SoundCloudArtistsTab.qml deleted file mode 100644 index 0113698..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudArtistsTab.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: artistModel - - title: qsTr("Artists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: artistModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: artistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudArtistModel { - id: artistModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: artistModel.status == QSoundCloud.ResourcesRequest.Loading - } - delegate: ArtistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(artistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No artists found") - visible: (artistModel.status >= QSoundCloud.ResourcesRequest.Ready) && (artistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudAuthDialog.qml b/app/src/harmattan/qml/soundcloud/SoundCloudAuthDialog.qml deleted file mode 100644 index 085f6f8..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudAuthDialog.qml +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import QtWebKit 1.0 -import ".." - -MySheet { - id: root - - signal codeReady(string code) - - rejectButtonText: qsTr("Cancel") - content: Item { - anchors.fill: parent - - Flickable { - id: flicker - - anchors.fill: parent - contentWidth: webView.width - contentHeight: webView.height - boundsBehavior: Flickable.DragOverBounds - clip: true - visible: webView.url != "" - - WebView { - id: webView - - preferredWidth: flicker.width - preferredHeight: flicker.height - settings.privateBrowsingEnabled: true - opacity: status == WebView.Loading ? 0 : 1 - onUrlChanged: { - var s = url.toString(); - - if (/code=/i.test(s)) { - root.codeReady(s.split("code=")[1].split("#")[0]); - root.accept(); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - MyBusyIndicator { - anchors.centerIn: parent - size: "large" - visible: (webView.status == WebView.Loading) && (root.status === DialogStatus.Open) - } - } - - onStatusChanged: { - switch (status) { - case DialogStatus.Open: { - CookieJar.setAllCookies([]); - webView.url = SoundCloud.authUrl(); - return; - } - case DialogStatus.Closed: - webView.url = ""; - return; - default: - return; - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudCommentDialog.qml b/app/src/harmattan/qml/soundcloud/SoundCloudCommentDialog.qml deleted file mode 100644 index 19a625e..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudCommentDialog.qml +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MySheet { - id: root - - property string trackId - - acceptWhenDone: false - showProgressIndicator: comment.status == QSoundCloud.ResourcesRequest.Loading - rejectButtonText: qsTr("Cancel") - acceptButtonText: commentField.text ? qsTr("Done") : "" - content: Item { - id: contentItem - - anchors.fill: parent - clip: true - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - top: parent.top - left: parent.left - right: parent.right - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - Label { - font.bold: true - text: qsTr("Comment") - } - - MyTextArea { - id: commentField - - width: parent.width - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - } - - SoundCloudComment { - id: comment - - onStatusChanged: { - switch (status) { - case QSoundCloud.ResourcesRequest.Ready: - root.accept(); - break; - case QSoundCloud.ResourcesRequest.Failed: - infoBanner.showMessage(errorString); - break; - default: - break; - } - } - } - - onDone: { - var c = {}; - c["track_id"] = trackId; - c["body"] = commentField.text; - comment.addComment(c); - } - - onStatusChanged: { - if (status == DialogStatus.Closed) { - trackId = ""; - commentField.text = ""; - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudCommentsTab.qml b/app/src/harmattan/qml/soundcloud/SoundCloudCommentsTab.qml deleted file mode 100644 index 2f61e61..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudCommentsTab.qml +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: commentModel - - title: qsTr("Comments") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-view-menu" - onClicked: menu.open() - } - } - - Menu { - id: menu - - MenuItem { - text: qsTr("Add comment") - enabled: SoundCloud.userId != "" - onClicked: { - dialogLoader.sourceComponent = commentDialog; - dialogLoader.item.trackId = track.id; - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Reload") - enabled: commentModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: commentModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudCommentModel { - id: commentModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: commentModel.status == QSoundCloud.ResourcesRequest.Loading - } - delegate: CommentDelegate { - onThumbnailClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(artistId) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No comments found") - visible: (commentModel.status >= QSoundCloud.ResourcesRequest.Ready) && (commentModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } - - Loader { - id: dialogLoader - } - - Component { - id: commentDialog - - SoundCloudCommentDialog {} - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudDownloadDialog.qml b/app/src/harmattan/qml/soundcloud/SoundCloudDownloadDialog.qml deleted file mode 100644 index 76c7ad1..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudDownloadDialog.qml +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MySheet { - id: root - - property string resourceId - property string resourceTitle - - showProgressIndicator: streamModel.status == QSoundCloud.StreamsRequest.Loading - acceptButtonText: streamModel.count > 0 ? qsTr("Done") : "" - rejectButtonText: qsTr("Cancel") - content: Item { - anchors.fill: parent - - Flickable { - id: flicker - - anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - } - - ValueSelector { - id: streamSelector - - width: parent.width - title: qsTr("Audio format") - model: SoundCloudStreamModel { - id: streamModel - - onStatusChanged: { - switch (status) { - case QSoundCloud.StreamsRequest.Loading: - streamSelector.showProgressIndicator = true; - return; - case QSoundCloud.ResourcesRequest.Ready: - if (count > 0) { - streamSelector.selectedIndex = Math.max(0, match("name", - Settings.defaultDownloadFormat(Resources.SOUNDCLOUD))); - } - else { - infoBanner.showMessage(qsTr("No streams found")); - } - - break; - case QSoundCloud.StreamsRequest.Failed: { - infoBanner.showMessage(errorString); - break; - } - default: - break; - } - - streamSelector.showProgressIndicator = false; - } - } - onAccepted: Settings.setDefaultDownloadFormat(Resources.SOUNDCLOUD, - streamModel.data(selectedIndex, "name")) - } - - ValueSelector { - id: categorySelector - - width: parent.width - title: qsTr("Category") - model: CategoryNameModel { - id: categoryModel - } - value: Settings.defaultCategory - onValueChanged: Settings.defaultCategory = value - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - } - - onAccepted: Transfers.addDownloadTransfer(Resources.SOUNDCLOUD, resourceId, streamSelector.value.id, "", - resourceTitle, Settings.defaultCategory) - - onStatusChanged: { - switch (status) { - case DialogStatus.Opening: - streamModel.get(resourceId); - break; - case DialogStatus.Closing: { - streamModel.cancel(); - break; - } - default: - break; - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistPage.qml deleted file mode 100644 index b038bfc..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistPage.qml +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function load(playlistOrId) { - playlist.loadPlaylist(playlistOrId); - tracksTab.model.get("/playlists/" + (playlistOrId.id ? playlistOrId.id : playlistOrId)); - } - - tools: view.currentTab.tools ? view.currentTab.tools : ownTools - onToolsChanged: if (status == PageStatus.Active) appWindow.pageStack.toolBar.setTools(tools, "set"); - - ToolBarLayout { - id: ownTools - - visible: false - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-view-menu" - onClicked: menu.open() - } - } - - Menu { - id: menu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - enabled: tracksTab.model.count > 0 - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.model.count; i ++) { - tracks.push(tracksTab.model.get(i)); - } - - player.addTracks(tracks); - infoBanner.showMessage(tracks.length + " " + qsTr("tracks added to queue")); - } - } - } - } - - SoundCloudPlaylist { - id: playlist - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - TabView { - id: view - - anchors.fill: parent - enabled: playlist.id != "" - - Tab { - id: infoTab - - width: view.width - height: view.height - - PageHeader { - id: header - - title: playlist.title - showProgressIndicator: (playlist.status == QSoundCloud.ResourcesRequest.Loading) - || (tracksTab.model.status == QSoundCloud.ResourcesRequest.Loading) - } - - Row { - id: row - - anchors { - left: parent.left - right: parent.right - top: header.bottom - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - Thumbnail { - id: thumbnail - - z: 10 - width: 150 - height: 150 - source: playlist.largeThumbnailUrl - placeholderText: playlist.title - enabled: tracksTab.model.count > 0 - onClicked: { - var tracks = []; - - for (var i = 0; i < tracksTab.model.count; i ++) { - tracks.push(tracksTab.model.get(i)); - } - - player.clearQueue(); - player.addTracks(tracks); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - } - - Column { - id: column - - width: row.width - thumbnail.width - row.spacing - spacing: UI.PADDING_DOUBLE - - Label { - width: parent.width - clip: true - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - text: qsTr("Artist") + ": " + (playlist.artist ? "" - + playlist.artist + "" : qsTr("Unknown")) - onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")) - .load(playlist.artistId) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Date") + ": " + (playlist.date ? playlist.date : qsTr("Unknown")) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Genre") + ": " + (playlist.genre ? playlist.genre : qsTr("Unknown")) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Tracks") + ": " + playlist.trackCount - } - } - } - - Flickable { - id: flicker - - anchors { - left: parent.left - right: parent.right - top: row.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - contentHeight: descriptionLabel.height + UI.PADDING_DOUBLE - clip: true - - Label { - id: descriptionLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - text: playlist.description ? Utils.toRichText(playlist.description) : qsTr("No description") - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Resources.SOUNDCLOUD) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: row - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: flicker - anchors { - left: row.right - top: header.bottom - } - } - } - } - - SoundCloudTracksTab { - id: tracksTab - - width: view.width - height: view.height - title: qsTr("Tracks") - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistsPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistsPage.qml deleted file mode 100644 index 71366f2..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistsPage.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: playlistModel - - title: qsTr("Playlists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: playlistModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: playlistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudPlaylistModel { - id: playlistModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: playlistModel.status == QSoundCloud.ResourcesRequest.Loading - } - delegate: PlaylistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(playlistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No playlists found") - visible: (playlistModel.status >= QSoundCloud.ResourcesRequest.Ready) && (playlistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistsTab.qml b/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistsTab.qml deleted file mode 100644 index e2b8298..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudPlaylistsTab.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: playlistModel - - title: qsTr("Playlists") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: playlistModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: playlistModel.reload() - } - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudPlaylistModel { - id: playlistModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: playlistModel.status == QSoundCloud.ResourcesRequest.Loading - } - delegate: PlaylistDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(playlistModel.get(index)) - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No playlists found") - visible: (playlistModel.status >= QSoundCloud.ResourcesRequest.Ready) && (playlistModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudSearchDialog.qml b/app/src/harmattan/qml/soundcloud/SoundCloudSearchDialog.qml deleted file mode 100644 index 529b6b6..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudSearchDialog.qml +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MySheet { - id: root - - acceptButtonText: searchField.text ? qsTr("Done") : "" - rejectButtonText: qsTr("Cancel") - content: Item { - anchors.fill: parent - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: UI.PADDING_DOUBLE - } - - MyTextField { - id: searchField - - x: UI.PADDING_DOUBLE - width: parent.width - UI.PADDING_DOUBLE * 2 - clearButtonEnabled: true - inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText - onTextChanged: searchModel.setFilterFixedString(text) - onAccepted: platformCloseSoftwareInputPanel() - } - - Item { - width: parent.width - height: UI.PADDING_DOUBLE - } - - ValueSelector { - id: typeSelector - - width: parent.width - title: qsTr("Search for") - model: SoundCloudSearchTypeModel {} - selectedIndex: Math.max(0, model.match("name",Settings.defaultSearchType(Resources.SOUNDCLOUD))) - onAccepted: Settings.setDefaultSearchType(Resources.SOUNDCLOUD, model.data(selectedIndex, "name")) - } - } - - ListView { - id: view - - anchors { - left: parent.left - right: parent.right - top: column.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - clip: true - model: SearchHistoryModel { - id: searchModel - } - header: SeparatorLabel { - text: qsTr("Search history") - x: UI.PADDING_DOUBLE - width: view.width - UI.PADDING_DOUBLE - } - delegate: LabelDelegate { - onClicked: searchField.text = display - onPressAndHold: { - view.currentIndex = -1; - view.currentIndex = index; - contextMenu.open(); - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Remove") - onClicked: searchModel.removeSearch(view.currentIndex) - } - - MenuItem { - text: qsTr("Clear") - onClicked: searchModel.clear() - } - } - } - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: column - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: view - anchors { - left: column.right - top: parent.top - } - } - } - } - - onAccepted: { - if (!mainPage.showResourceFromUrl(searchField.text)) { - Settings.addSearch(searchField.text); - mainPage.search(Resources.SOUNDCLOUD, searchField.text, typeSelector.value.type, typeSelector.value.order); - } - } - onStatusChanged: if (status == DialogStatus.Opening) searchField.text = ""; -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudTrackPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudTrackPage.qml deleted file mode 100644 index ef13f93..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudTrackPage.qml +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - function load(trackOrId) { - track.loadTrack(trackOrId); - } - - tools: view.currentTab.tools ? view.currentTab.tools : ownTools - onToolsChanged: if (status == PageStatus.Active) appWindow.pageStack.toolBar.setTools(tools, "set"); - - ToolBarLayout { - id: ownTools - - visible: false - - BackToolIcon {} - - NowPlayingButton {} - } - - SoundCloudTrack { - id: track - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - TabView { - id: view - - anchors.fill: parent - enabled: track.id != "" - - Tab { - id: infoTab - - width: view.width - height: view.height - tools: ToolBarLayout { - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-view-menu" - onClicked: menu.open() - } - } - - Menu { - id: menu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - onClicked: { - player.addTrack(track); - infoBanner.showMessage(qsTr("1 track added to queue")); - } - } - - MenuItem { - text: qsTr("Download") - onClicked: { - dialogLoader.sourceComponent = downloadDialog; - dialogLoader.item.resourceId = track.id; - dialogLoader.item.resourceTitle = track.title; - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Share") - onClicked: if (!ShareUi.shareTrack(track)) infoBanner.showMessage(qsTr("Unable to share track")); - } - - MenuItem { - text: track.favourited ? qsTr("Unfavourite") : qsTr("Favourite") - enabled: SoundCloud.userId != "" - onClicked: track.favourited ? track.unfavourite() : track.favourite() - } - } - } - - PageHeader { - id: header - - title: track.title - showProgressIndicator: track.status == QSoundCloud.ResourcesRequest.Loading - } - - Row { - id: row - - anchors { - left: parent.left - right: parent.right - top: header.bottom - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - Thumbnail { - id: thumbnail - - z: 10 - width: 150 - height: 150 - source: track.largeThumbnailUrl - placeholderText: track.title - onClicked: { - player.clearQueue(); - player.addTrack(track); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - } - - Column { - id: column - - width: row.width - thumbnail.width - row.spacing - spacing: UI.PADDING_DOUBLE - - Label { - width: parent.width - clip: true - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - text: qsTr("Artist") + ": " + (track.artist ? "" + track.artist - + "" : qsTr("Unknown")) - onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")) - .load(track.artistId) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Date") + ": " + (track.date ? track.date : qsTr("Unknown")) - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Duration") + ": " + track.durationString - } - - Label { - width: parent.width - font.family: UI.FONT_FAMILY_LIGHT - font.pixelSize: UI.FONT_SMALL - elide: Text.ElideRight - text: qsTr("Genre") + ": " + (track.genre ? track.genre : qsTr("Unknown")) - } - } - } - - Flickable { - id: flicker - - anchors { - left: parent.left - right: parent.right - top: row.bottom - topMargin: UI.PADDING_DOUBLE - bottom: parent.bottom - } - contentHeight: descriptionLabel.height + UI.PADDING_DOUBLE - clip: true - - Label { - id: descriptionLabel - - anchors { - left: parent.left - leftMargin: UI.PADDING_DOUBLE - right: parent.right - rightMargin: UI.PADDING_DOUBLE - top: parent.top - } - text: track.description ? Utils.toRichText(track.description) : qsTr("No description") - onLinkActivated: { - var resource = Resources.getResourceFromUrl(link); - - if (resource.service != Resources.SOUNDCLOUD) { - Qt.openUrlExternally(link); - return; - } - - if (resource.type == Resources.ARTIST) { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); - } - else if (resource.type == Resources.PLAYLIST) { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); - } - else { - appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); - } - } - } - } - - ScrollDecorator { - flickableItem: flicker - } - - Loader { - id: dialogLoader - } - - Component { - id: downloadDialog - - SoundCloudDownloadDialog {} - } - - states: State { - name: "landscape" - when: !appWindow.inPortrait - - AnchorChanges { - target: row - anchors.right: parent.horizontalCenter - } - - AnchorChanges { - target: flicker - anchors { - left: row.right - top: header.bottom - } - } - } - } - - TabLoader { - id: relatedTab - - width: view.width - height: view.height - title: qsTr("Related") - tab: Component { - SoundCloudTracksTab { - title: qsTr("Related") - Component.onCompleted: model.get("/users/" + track.userId + "/tracks", {limit: MAX_RESULTS}) - } - } - } - - TabLoader { - id: commentsTab - - width: view.width - height: view.height - title: qsTr("Comments") - tab: Component { - SoundCloudCommentsTab { - title: qsTr("Comments") - Component.onCompleted: model.get("/tracks/" + track.id + "/comments", {limit: MAX_RESULTS}) - } - } - } - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudTracksPage.qml b/app/src/harmattan/qml/soundcloud/SoundCloudTracksPage.qml deleted file mode 100644 index 38e52c6..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudTracksPage.qml +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -MyPage { - id: root - - property alias model: trackModel - - title: qsTr("Tracks") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: trackModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: trackModel.reload() - } - } - - SoundCloudTrack { - id: track - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudTrackModel { - id: trackModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: (track.status == QSoundCloud.ResourcesRequest.Loading) - || (trackModel.status == QSoundCloud.ResourcesRequest.Loading) - } - delegate: TrackDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(trackModel.get(index)) - onThumbnailClicked: { - player.clearQueue(); - player.addTrack(trackModel.get(index)); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - onPressAndHold: { - view.currentIndex = -1; - view.currentIndex = index; - contextMenu.open(); - } - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No tracks found") - visible: (trackModel.status >= QSoundCloud.ResourcesRequest.Ready) && (trackModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - onClicked: { - player.addTrack(trackModel.get(view.currentIndex)); - infoBanner.showMessage(qsTr("1 track added to queue")); - } - } - - MenuItem { - text: qsTr("Download") - onClicked: { - dialogLoader.sourceComponent = downloadDialog; - dialogLoader.item.resourceId = trackModel.data(view.currentIndex, "id"); - dialogLoader.item.resourceTitle = trackModel.data(view.currentIndex, "title"); - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Share") - onClicked: if (!ShareUi.shareTrack(trackModel.get(view.currentIndex))) - infoBanner.showMessage(qsTr("Unable to share track")); - } - - MenuItem { - text: trackModel.data(view.currentIndex, "favourited") ? qsTr("Unfavourite") : qsTr("Favourite") - enabled: SoundCloud.userId != "" - onClicked: { - track.loadTrack(trackModel.get(view.currentIndex)) - - if (track.favourited) { - track.unfavourite(); - } - else { - track.favourite(); - } - } - } - } - } - - Loader { - id: dialogLoader - } - - Component { - id: downloadDialog - - SoundCloudDownloadDialog {} - } -} diff --git a/app/src/harmattan/qml/soundcloud/SoundCloudTracksTab.qml b/app/src/harmattan/qml/soundcloud/SoundCloudTracksTab.qml deleted file mode 100644 index d014566..0000000 --- a/app/src/harmattan/qml/soundcloud/SoundCloudTracksTab.qml +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 -import QSoundCloud 1.0 as QSoundCloud -import ".." -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI - -Tab { - id: root - - property alias model: trackModel - - title: qsTr("Tracks") - tools: ToolBarLayout { - - BackToolIcon {} - - NowPlayingButton {} - - MyToolIcon { - platformIconId: "toolbar-refresh" - enabled: trackModel.status != QSoundCloud.ResourcesRequest.Loading - onClicked: trackModel.reload() - } - } - - SoundCloudTrack { - id: track - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - - ListView { - id: view - - anchors.fill: parent - cacheBuffer: 400 - interactive: count > 0 - highlightFollowsCurrentItem: false - model: SoundCloudTrackModel { - id: trackModel - - onStatusChanged: if (status == QSoundCloud.ResourcesRequest.Failed) infoBanner.showMessage(errorString); - } - header: PageHeader { - title: root.title - showProgressIndicator: (track.status == QSoundCloud.ResourcesRequest.Loading) - || (trackModel.status == QSoundCloud.ResourcesRequest.Loading) - } - delegate: TrackDelegate { - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(trackModel.get(index)) - onThumbnailClicked: { - player.clearQueue(); - player.addTrack(trackModel.get(index)); - appWindow.pageStack.push(Qt.resolvedUrl("../NowPlayingPage.qml"), {autoPlay: true}); - } - onPressAndHold: { - view.currentIndex = -1; - view.currentIndex = index; - contextMenu.open(); - } - } - - Label { - anchors { - fill: parent - margins: UI.PADDING_DOUBLE - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - font.pixelSize: 60 - color: UI.COLOR_INVERTED_SECONDARY_FOREGROUND - text: qsTr("No tracks found") - visible: (trackModel.status >= QSoundCloud.ResourcesRequest.Ready) && (trackModel.count == 0) - } - } - - ScrollDecorator { - flickableItem: view - } - - ContextMenu { - id: contextMenu - - MenuLayout { - - MenuItem { - text: qsTr("Add to queue") - onClicked: { - player.addTrack(trackModel.get(view.currentIndex)); - infoBanner.showMessage(qsTr("1 track added to queue")); - } - } - - MenuItem { - text: qsTr("Download") - onClicked: { - dialogLoader.sourceComponent = downloadDialog; - dialogLoader.item.resourceId = trackModel.data(view.currentIndex, "id"); - dialogLoader.item.resourceTitle = trackModel.data(view.currentIndex, "title"); - dialogLoader.item.open(); - } - } - - MenuItem { - text: qsTr("Share") - onClicked: if (!ShareUi.shareTrack(trackModel.get(view.currentIndex))) - infoBanner.showMessage(qsTr("Unable to share track")); - } - - MenuItem { - text: trackModel.data(view.currentIndex, "favourited") ? qsTr("Unfavourite") : qsTr("Favourite") - enabled: SoundCloud.userId != "" - onClicked: { - track.loadTrack(trackModel.get(view.currentIndex)) - - if (track.favourited) { - track.unfavourite(); - } - else { - track.favourite(); - } - } - } - } - } - - Loader { - id: dialogLoader - } - - Component { - id: downloadDialog - - SoundCloudDownloadDialog {} - } -} diff --git a/app/src/harmattan/shareui.cpp b/app/src/harmattan/shareui.cpp deleted file mode 100644 index 833d4a6..0000000 --- a/app/src/harmattan/shareui.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "shareui.h" -#include "utils.h" -#include "track.h" -#include -#include -#ifdef MUSIKLOUD_DEBUG -#include -#endif - -ShareUi::ShareUi(QObject *parent) : - QObject(parent) -{ -} - -bool ShareUi::shareTrack(MKTrack *track) { - if (Utils::isLocalFile(track->url())) { - return share(Utils::unescape(track->url().toString())); - } - - QVariantMap attributes; - attributes["title"] = track->title(); - attributes["source"] = track->url(); - return share("text/url", track->url().toString(), attributes); -} - -bool ShareUi::share(const QString &uri) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ShareUI:share" << uri; -#endif - ShareUiInterface shareIf("com.nokia.ShareUi"); - - if (shareIf.isValid()) { - shareIf.share(QStringList(uri.startsWith("file") ? uri.mid(7) : uri)); - return true; - } - - return false; -} - -bool ShareUi::share(const QString &mimeType, const QString &textData, const QVariantMap &attributes) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ShareUI:share" << mimeType << textData << attributes; -#endif - MDataUri duri; - duri.setMimeType(mimeType); - duri.setTextData(textData); - - QMapIterator iterator(attributes); - - while (iterator.hasNext()) { - iterator.next(); - duri.setAttribute(iterator.key(), iterator.value().toString()); - } - - if (duri.isValid()) { - ShareUiInterface shareIf("com.nokia.ShareUi"); - - if (shareIf.isValid()) { - shareIf.share(QStringList(duri.toString())); - return true; - } - } - - return false; -} diff --git a/app/src/harmattan/shareui.h b/app/src/harmattan/shareui.h deleted file mode 100644 index 373ce23..0000000 --- a/app/src/harmattan/shareui.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef SHAREUI_H -#define SHAREUI_H - -#include -#include - -class MKTrack; - -class ShareUi : public QObject -{ - Q_OBJECT - -public: - explicit ShareUi(QObject *parent = 0); - - Q_INVOKABLE static bool shareTrack(MKTrack *track); - Q_INVOKABLE static bool share(const QString &uri); - Q_INVOKABLE static bool share(const QString &mimeType, const QString &textData, const QVariantMap &attributes); -}; - -#endif // SHAREUI_H diff --git a/app/src/maemo5/aboutdialog.cpp b/app/src/maemo5/aboutdialog.cpp index 1baab44..f382244 100644 --- a/app/src/maemo5/aboutdialog.cpp +++ b/app/src/maemo5/aboutdialog.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -37,27 +36,16 @@ AboutDialog::AboutDialog(QWidget *parent) : QLabel *descriptionLabel = new QLabel("A SoundCloud client and music player that can be extended via plugins.\

© Stuart Howarth 2015", this); descriptionLabel->setWordWrap(true); - QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Vertical, this); - QPushButton *donateButton = new QPushButton(tr("Donate"), this); QPushButton *bugButton = new QPushButton(tr("Report bug"), this); - buttonBox->addButton(donateButton, QDialogButtonBox::ActionRole); - buttonBox->addButton(bugButton, QDialogButtonBox::ActionRole); - QGridLayout *grid = new QGridLayout(this); grid->addWidget(imageLabel, 0, 0); grid->addWidget(titleLabel, 0, 1); grid->addWidget(descriptionLabel, 1, 0, 1, 2); - grid->addWidget(buttonBox, 1, 2); + grid->addWidget(bugButton, 1, 2, Qt::AlignBottom); - connect(donateButton, SIGNAL(clicked()), this, SLOT(donate())); connect(bugButton, SIGNAL(clicked()), this, SLOT(reportBug())); } -void AboutDialog::donate() { - QDesktopServices::openUrl(QUrl("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=stuhowarth77@gmail.com&lc=GB&item_name=MusiKloud2¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted")); - accept(); -} - void AboutDialog::reportBug() { QDesktopServices::openUrl(QUrl(QString("mailto:showarth@marxoft.co.uk?subject=MusiKloud2 %1 for Maemo5") .arg(VERSION_NUMBER))); diff --git a/app/src/maemo5/aboutdialog.h b/app/src/maemo5/aboutdialog.h index 0c41b10..78b89ca 100644 --- a/app/src/maemo5/aboutdialog.h +++ b/app/src/maemo5/aboutdialog.h @@ -28,7 +28,6 @@ class AboutDialog : public Dialog explicit AboutDialog(QWidget *parent = 0); private Q_SLOTS: - void donate(); void reportBug(); }; diff --git a/app/src/maemo5/accountdelegate.cpp b/app/src/maemo5/accountdelegate.cpp index 7d93af4..81417f8 100644 --- a/app/src/maemo5/accountdelegate.cpp +++ b/app/src/maemo5/accountdelegate.cpp @@ -24,7 +24,15 @@ AccountDelegate::AccountDelegate(int activeRole, QObject *parent) : } void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QStyledItemDelegate::paint(painter, option, index); + if ((option.state) & (QStyle::State_Selected)) { + painter->drawImage(option.rect, QImage("/etc/hildon/theme/images/TouchListBackgroundPressed.png")); + } + else { + painter->drawImage(option.rect, QImage("/etc/hildon/theme/images/TouchListBackgroundNormal.png")); + } + + QRect textRect = option.rect; + textRect.moveLeft(textRect.left() + 8); if (index.data(m_activeRole).toBool()) { QRect iconRect = option.rect; @@ -32,5 +40,14 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio iconRect.moveTop(iconRect.top() + (iconRect.height() - 48) / 2); iconRect.setSize(QSize(48, 48)); painter->drawImage(iconRect, QImage("/usr/share/icons/hicolor/48x48/hildon/widgets_tickmark_grid.png")); + + textRect.setRight(iconRect.left() - 8); + } + else { + textRect.setRight(textRect.right() - 8); } + + const QString text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, + textRect.width()); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); } diff --git a/app/src/maemo5/artistdelegate.h b/app/src/maemo5/artistdelegate.h index e6fca6a..59d849a 100644 --- a/app/src/maemo5/artistdelegate.h +++ b/app/src/maemo5/artistdelegate.h @@ -34,7 +34,7 @@ class ArtistDelegate : public QStyledItemDelegate ImageCache *m_cache; int m_nameRole; - int m_thumbnailRole; + int m_thumbnailRole; }; #endif // ARTISTDELEGATE_H diff --git a/app/src/maemo5/categoriesdialog.cpp b/app/src/maemo5/categoriesdialog.cpp index 58d5b65..0015d2a 100644 --- a/app/src/maemo5/categoriesdialog.cpp +++ b/app/src/maemo5/categoriesdialog.cpp @@ -30,7 +30,7 @@ CategoriesDialog::CategoriesDialog(QWidget *parent) : m_view(new QTreeView(this)) { setWindowTitle(tr("Categories")); - setMinimumHeight(340); + setMinimumHeight(360); QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Vertical, this); QPushButton *addButton = buttonBox->addButton(tr("New"), QDialogButtonBox::ActionRole); @@ -56,11 +56,11 @@ CategoriesDialog::CategoriesDialog(QWidget *parent) : } void CategoriesDialog::editCategory(const QModelIndex &index) { - NewCategoryDialog *dialog = new NewCategoryDialog(this); - dialog->setWindowTitle(tr("Edit category")); - dialog->setName(index.data(CategoryModel::NameRole).toString()); - dialog->setPath(index.data(CategoryModel::PathRole).toString()); - dialog->open(); + NewCategoryDialog dialog(this); + dialog.setWindowTitle(tr("Edit category")); + dialog.setName(index.data(CategoryModel::NameRole).toString()); + dialog.setPath(index.data(CategoryModel::PathRole).toString()); + dialog.exec(); } void CategoriesDialog::removeCategory() { @@ -70,6 +70,5 @@ void CategoriesDialog::removeCategory() { } void CategoriesDialog::showNewCategoryDialog() { - NewCategoryDialog *dialog = new NewCategoryDialog(this); - dialog->open(); + NewCategoryDialog(this).exec(); } diff --git a/app/src/maemo5/categorydelegate.cpp b/app/src/maemo5/categorydelegate.cpp new file mode 100644 index 0000000..a20f943 --- /dev/null +++ b/app/src/maemo5/categorydelegate.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "categorydelegate.h" +#include + +CategoryDelegate::CategoryDelegate(QObject *parent) : + QStyledItemDelegate(parent) +{ +} + +void CategoryDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + if ((option.state) & (QStyle::State_Selected)) { + painter->drawImage(option.rect, QImage("/etc/hildon/theme/images/TouchListBackgroundPressed.png")); + } + else { + painter->drawImage(option.rect, QImage("/etc/hildon/theme/images/TouchListBackgroundNormal.png")); + } + + QRect textRect = option.rect; + textRect.moveLeft(textRect.left() + 8); + textRect.setRight(textRect.right() - 8); + + const QString text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, + textRect.width()); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); +} diff --git a/app/src/desktop-qml/qml/plugins/PluginSearchLabel.qml b/app/src/maemo5/categorydelegate.h similarity index 64% rename from app/src/desktop-qml/qml/plugins/PluginSearchLabel.qml rename to app/src/maemo5/categorydelegate.h index 02afe2b..ebe9b8a 100644 --- a/app/src/desktop-qml/qml/plugins/PluginSearchLabel.qml +++ b/app/src/maemo5/categorydelegate.h @@ -14,13 +14,19 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 +#ifndef CATEGORYDELEGATE_H +#define CATEGORYDELEGATE_H -Label { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - text: qsTr("Search") + " " + Settings.currentService + " " + qsTr("using the tools above.") -} +#include + +class CategoryDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit CategoryDelegate(QObject *parent = 0); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +#endif // CATEGORYDELEGATE_H diff --git a/app/src/maemo5/commentdelegate.cpp b/app/src/maemo5/commentdelegate.cpp index 3948e75..c5b7d6b 100644 --- a/app/src/maemo5/commentdelegate.cpp +++ b/app/src/maemo5/commentdelegate.cpp @@ -18,7 +18,6 @@ #include "drawing.h" #include "imagecache.h" #include -#include #include #include @@ -40,8 +39,8 @@ bool CommentDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QSt QMouseEvent *mouse = static_cast(event); QRect imageRect = option.rect; - imageRect.setLeft(imageRect.left() + 24); - imageRect.setTop(imageRect.top() + 16); + imageRect.setLeft(imageRect.left() + 8); + imageRect.setTop(imageRect.top() + 8); imageRect.setWidth(40); imageRect.setHeight(40); @@ -55,8 +54,8 @@ bool CommentDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QSt QMouseEvent *mouse = static_cast(event); QRect imageRect = option.rect; - imageRect.setLeft(imageRect.left() + 24); - imageRect.setTop(imageRect.top() + 16); + imageRect.setLeft(imageRect.left() + 8); + imageRect.setTop(imageRect.top() + 8); imageRect.setWidth(40); imageRect.setHeight(40); @@ -75,36 +74,31 @@ bool CommentDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QSt void CommentDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QPixmap background("/etc/hildon/theme/images/ContactsAppletBubble.png"); - - if (!background.isNull()) { - qDrawBorderPixmap(painter, option.rect, QMargins(30, 60, 30, 30), background); - } + + QRect imageRect = option.rect; + imageRect.setLeft(imageRect.left() + 8); + imageRect.setTop(imageRect.top() + 8); + imageRect.setWidth(40); + imageRect.setHeight(40); QImage image = m_cache->image(index.data(m_thumbnailRole).toString(), QSize(40, 40)); if (image.isNull()) { - image = QImage("/usr/share/icons/hicolor/48x48/hildon/general_default_avatar.png"); + image = QImage("/usr/share/icons/hicolor/48x48/hildon/general_default_avatar.png") + .scaled(40, 40, Qt::KeepAspectRatio, Qt::SmoothTransformation); } - QRect imageRect = option.rect; - imageRect.setLeft(imageRect.left() + 24); - imageRect.setTop(imageRect.top() + 16); - imageRect.setWidth(40); - imageRect.setHeight(40); - drawCenteredImage(painter, imageRect, image); QRect textRect = option.rect; textRect.setLeft(imageRect.right() + 8); - textRect.setRight(textRect.right() - 16); - textRect.setTop(textRect.top() + 16); + textRect.setRight(textRect.right() - 8); + textRect.setTop(textRect.top() + 8); textRect.setHeight(40); + painter->save(); QFont font; font.setPointSize(13); - - painter->save(); painter->setFont(font); painter->setPen(QApplication::palette().color(QPalette::Mid)); painter->drawText(textRect, Qt::AlignVCenter | Qt::TextWordWrap, @@ -113,19 +107,18 @@ void CommentDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio textRect = option.rect; textRect.setLeft(imageRect.left()); - textRect.setRight(textRect.right() - 16); + textRect.setRight(textRect.right() - 8); textRect.setTop(imageRect.bottom() + 8); - textRect.setBottom(textRect.bottom() - 16); + textRect.setBottom(textRect.bottom() - 8); - painter->setPen(QApplication::palette().color(background.isNull() ? QPalette::Text : QPalette::Dark)); - painter->drawText(textRect, Qt::TextWordWrap, index.data(m_bodyRole).toString()); painter->restore(); + painter->drawText(textRect, Qt::TextWordWrap, index.data(m_bodyRole).toString()); } QSize CommentDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - return QSize(option.rect.width(), option.fontMetrics.boundingRect(QRect(option.rect.left() + 72, - option.rect.top() + 64, - option.rect.width() - 40, + return QSize(option.rect.width(), option.fontMetrics.boundingRect(QRect(option.rect.left() + 8, + option.rect.top() + 56, + option.rect.width() - 16, 1000), - Qt::TextWordWrap, index.data(m_bodyRole).toString()).height() + 80); + Qt::TextWordWrap, index.data(m_bodyRole).toString()).height() + 64); } diff --git a/app/src/maemo5/customcommanddialog.cpp b/app/src/maemo5/customcommanddialog.cpp new file mode 100644 index 0000000..48de600 --- /dev/null +++ b/app/src/maemo5/customcommanddialog.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "customcommanddialog.h" +#include +#include +#include +#include +#include + +CustomCommandDialog::CustomCommandDialog(QWidget *parent) : + Dialog(parent), + m_commandEdit(new QLineEdit(this)), + m_overrideCheckBox(new QCheckBox(tr("&Override global command"), this)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this)), + m_layout(new QGridLayout(this)) +{ + setWindowTitle(tr("Set custom command")); + + m_layout->addWidget(new QLabel(tr("Command"), this), 0, 0); + m_layout->addWidget(m_commandEdit, 1, 0); + m_layout->addWidget(m_overrideCheckBox, 2, 0); + m_layout->addWidget(m_buttonBox, 2, 1); + m_layout->setColumnStretch(0, 1); + + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +QString CustomCommandDialog::command() const { + return m_commandEdit->text(); +} + +void CustomCommandDialog::setCommand(const QString &command) { + m_commandEdit->setText(command); +} + +bool CustomCommandDialog::overrideEnabled() const { + return m_overrideCheckBox->isChecked(); +} + +void CustomCommandDialog::setOverrideEnabled(bool enabled) { + m_overrideCheckBox->setChecked(enabled); +} diff --git a/app/src/maemo5/customcommanddialog.h b/app/src/maemo5/customcommanddialog.h new file mode 100644 index 0000000..6ea03bd --- /dev/null +++ b/app/src/maemo5/customcommanddialog.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CUSTOMCOMMANDDIALOG_H +#define CUSTOMCOMMANDDIALOG_H + +#include "dialog.h" + +class QCheckBox; +class QDialogButtonBox; +class QGridLayout; +class QLineEdit; + +class CustomCommandDialog : public Dialog +{ + Q_OBJECT + + Q_PROPERTY(QString command READ command WRITE setCommand) + Q_PROPERTY(bool overrideEnabled READ overrideEnabled WRITE setOverrideEnabled) + +public: + explicit CustomCommandDialog(QWidget *parent = 0); + + QString command() const; + + bool overrideEnabled() const; + +public Q_SLOTS: + void setCommand(const QString &command); + + void setOverrideEnabled(bool enabled); + +private: + QLineEdit *m_commandEdit; + + QCheckBox *m_overrideCheckBox; + + QDialogButtonBox *m_buttonBox; + + QGridLayout *m_layout; +}; + +#endif // CUSTOMCOMMANDDIALOG_H diff --git a/app/src/maemo5/database.h b/app/src/maemo5/database.h new file mode 100644 index 0000000..2c9273a --- /dev/null +++ b/app/src/maemo5/database.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DATABASE_H +#define DATABASE_H + +#include "definitions.h" +#include "logger.h" +#include +#include +#include +#include + +inline void initDatabase() { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + QDir().mkpath(APP_CONFIG_PATH); + db.setDatabaseName(APP_CONFIG_PATH + "musikloud2.db"); + + if (!db.isOpen()) { + db.open(); + } + + QSqlQuery query = db.exec("CREATE TABLE IF NOT EXISTS soundcloudAccounts (userId TEXT UNIQUE, username TEXT, \ + accessToken TEXT, refreshToken TEXT, scopes TEXT)"); + + if (query.lastError().isValid()) { + Logger::log("initDatabase: database error: " + query.lastError().text()); + } +} + +inline QSqlDatabase getDatabase() { + QSqlDatabase db = QSqlDatabase::database(); + + if (!db.isOpen()) { + db.open(); + } + + return db; +} + +#endif // DATABASE_H diff --git a/app/src/maemo5/definitions.h b/app/src/maemo5/definitions.h index 672fb26..264be93 100644 --- a/app/src/maemo5/definitions.h +++ b/app/src/maemo5/definitions.h @@ -2,15 +2,15 @@ * Copyright (C) 2015 Stuart Howarth * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as + * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -19,41 +19,37 @@ #include #include -#if QT_VERSION >= 0x050000 -#include -#else -#include -#endif -static const int MAX_CONCURRENT_TRANSFERS = 4; -static const int MAX_REDIRECTS = 8; +// Home +static const QString HOME_PATH("/home/user/"); -static const int MAX_RESULTS = 20; +// Plugins +static const QStringList PLUGIN_PATHS = QStringList() << QString("/opt/musikloud2/plugins/") + << QString(HOME_PATH + "musikloud2/plugins/"); -static const int LARGE_THUMBNAIL_SIZE = 300; -static const int THUMBNAIL_SIZE = 64; +static const QString LIB_PREFIX("lib"); +static const QString LIB_SUFFIX(".so"); + +// Config +static const QString APP_CONFIG_PATH(HOME_PATH + ".config/MusiKloud2/"); +static const QString PLUGIN_CONFIG_PATH(APP_CONFIG_PATH + "plugins/"); +// Downloads +static const QString DOWNLOAD_PATH(HOME_PATH + "MyDocs/MusiKloud2/"); static const QRegExp ILLEGAL_FILENAME_CHARS_RE("[\"@&~=\\/:?#!|<>*^]"); -static const QString VERSION_NUMBER("0.0.4"); - -static const QStringList SUPPORTED_AUDIO_FORMATS = QStringList() << "*.aiff" << "*.ape" << "*.flac" << "*.m4a" << "*.mp3" - << "*.ogg" << "*.wav" << "*.wma"; - -#if QT_VERSION >= 0x050000 -static const QString DATABASE_PATH(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/MusiKloud2/"); -static const QString DOWNLOAD_PATH("/home/user/MyDocs/MusiKloud2/"); -static const QString STORAGE_PATH(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/MusiKloud2/"); -static const QStringList PLUGIN_PATHS = QStringList() << "/opt/musikloud2/plugins/" - << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) - + "/MusiKloud2/plugins/"; -#else -static const QString DATABASE_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); -static const QString DOWNLOAD_PATH("/home/user/MyDocs/MusiKloud2/"); -static const QString STORAGE_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); -static const QStringList PLUGIN_PATHS = QStringList() << "/opt/musikloud2/plugins/" - << QDesktopServices::storageLocation(QDesktopServices::HomeLocation) - + "/.config/MusiKloud2/plugins/"; -#endif +// Content +static const int LARGE_THUMBNAIL_SIZE = 300; +static const int THUMBNAIL_SIZE = 80; + +// Network +static const int DOWNLOAD_BUFFER_SIZE = 64000; +static const int MAX_CONCURRENT_TRANSFERS = 4; +static const int MAX_REDIRECTS = 8; +static const int MAX_RESULTS = 20; +static const QByteArray USER_AGENT("Wget/1.13.4 (linux-gnu)"); + +// Version +static const QString VERSION_NUMBER("0.2.0"); #endif // DEFINITIONS_H diff --git a/app/src/maemo5/dialog.cpp b/app/src/maemo5/dialog.cpp index e9c81c8..0588e8f 100644 --- a/app/src/maemo5/dialog.cpp +++ b/app/src/maemo5/dialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -20,7 +20,6 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), m_progressCount(0) { - setAttribute(Qt::WA_DeleteOnClose, true); } bool Dialog::isBusy() const { @@ -29,7 +28,10 @@ bool Dialog::isBusy() const { void Dialog::showProgressIndicator() { m_progressCount++; - setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + + if (isVisible()) { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + } } void Dialog::hideProgressIndicator() { @@ -41,3 +43,11 @@ void Dialog::hideProgressIndicator() { } } } + +void Dialog::showEvent(QShowEvent *e) { + QDialog::showEvent(e); + + if (m_progressCount > 0) { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + } +} diff --git a/app/src/maemo5/dialog.h b/app/src/maemo5/dialog.h index 18bc6cc..7d39459 100644 --- a/app/src/maemo5/dialog.h +++ b/app/src/maemo5/dialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -31,7 +31,10 @@ class Dialog : public QDialog public Q_SLOTS: void showProgressIndicator(); void hideProgressIndicator(); - + +protected: + virtual void showEvent(QShowEvent *e); + private: int m_progressCount; }; diff --git a/app/src/maemo5/imagecache.cpp b/app/src/maemo5/imagecache.cpp index 97e09f0..46fb628 100644 --- a/app/src/maemo5/imagecache.cpp +++ b/app/src/maemo5/imagecache.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -40,7 +40,7 @@ QCache ImageCache::cache; int ImageCache::requestCount = 0; int ImageCache::refCount = 0; -static const int MAX_REQUESTS = 6; +const int ImageCache::MAX_REQUESTS = 8; ImageCache::ImageCache() : QObject(), @@ -92,7 +92,6 @@ void ImageCache::getImage(const QUrl &url) { requestCount++; ImageRequest *request = new ImageRequest(m_manager, url); connect(request, SIGNAL(finished(ImageRequest*)), this, SLOT(onRequestFinished(ImageRequest*))); - connect(this, SIGNAL(destroyed()), request, SLOT(deleteLater())); } void ImageCache::onRequestFinished(ImageRequest *request) { @@ -111,21 +110,13 @@ void ImageCache::onRequestFinished(ImageRequest *request) { } } -ImageRequest::ImageRequest(QNetworkAccessManager *m, const QUrl &u) : +ImageRequest::ImageRequest(QNetworkAccessManager *manager, const QUrl &u) : QObject() { - manager = m; url = u; - redirects = 0; - - QUrl imageUrl(url); - - if (imageUrl.host().endsWith(".sndcdn.com")) { - // Hotfix for SoundCloud - imageUrl.setScheme("http"); - } - - reply = manager->get(QNetworkRequest(imageUrl)); + QNetworkRequest request(url); + request.setRawHeader("User-Agent", USER_AGENT); + reply = manager->get(request); connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); } @@ -135,21 +126,5 @@ ImageRequest::~ImageRequest() { } void ImageRequest::onReplyFinished() { - if (redirects < MAX_REDIRECTS) { - QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - - if (redirect.isNull()) { - redirect = reply->header(QNetworkRequest::LocationHeader); - } - - if (!redirect.isNull()) { - redirects++; - reply->deleteLater(); - reply = manager->get(QNetworkRequest(redirect.toString())); - connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); - return; - } - } - emit finished(this); } diff --git a/app/src/maemo5/imagecache.h b/app/src/maemo5/imagecache.h index b9abe75..a6f48a8 100644 --- a/app/src/maemo5/imagecache.h +++ b/app/src/maemo5/imagecache.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -56,6 +56,8 @@ private Q_SLOTS: static int requestCount; static int refCount; + static const int MAX_REQUESTS; + QNetworkAccessManager *m_manager; }; @@ -64,13 +66,11 @@ class ImageRequest : public QObject Q_OBJECT private: - ImageRequest(QNetworkAccessManager *m, const QUrl &u); + ImageRequest(QNetworkAccessManager *manager, const QUrl &u); ~ImageRequest(); - QNetworkAccessManager *manager; QNetworkReply *reply; QUrl url; - int redirects; friend class ImageCache; diff --git a/app/src/maemo5/listview.cpp b/app/src/maemo5/listview.cpp index e6e7ba9..66b590c 100644 --- a/app/src/maemo5/listview.cpp +++ b/app/src/maemo5/listview.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as diff --git a/app/src/maemo5/listview.h b/app/src/maemo5/listview.h index 427fec7..25b952a 100644 --- a/app/src/maemo5/listview.h +++ b/app/src/maemo5/listview.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as diff --git a/app/src/maemo5/logger.cpp b/app/src/maemo5/logger.cpp new file mode 100644 index 0000000..a070d61 --- /dev/null +++ b/app/src/maemo5/logger.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "logger.h" +#include +#include +#include +#include + +QString Logger::fn; +int Logger::vb = 0; + +Logger::Logger(QObject *parent) : + QObject(parent) +{ +} + +QString Logger::fileName() { + return fn; +} + +void Logger::setFileName(const QString &f) { + fn = f; +} + +QString Logger::text() { + QString output; + + if (!fn.isEmpty()) { + QFile file(fn); + + if (file.open(QFile::ReadOnly | QFile::Text)) { + QTextStream stream(&file); + + while (!stream.atEnd()) { + output.append(stream.readLine()); + output.append("\n"); + } + + file.close(); + } + } + + return output; +} + +int Logger::verbosity() { + return vb; +} + +void Logger::setVerbosity(int v) { + vb = v; +} + +void Logger::clear() { + if (!fn.isEmpty()) { + QFile::remove(fn); + } +} + +void Logger::log(const QString &message, int minimumVerbosity) { + if (minimumVerbosity <= vb) { + const QString date = QDateTime::currentDateTime().toString(Qt::ISODate); + QString output = QString("%1: %2\n").arg(date).arg(message); + + if (!fn.isEmpty()) { + QFile file(fn); + + if (file.open(QFile::Append | QFile::Text)) { + QTextStream stream(&file); + stream << output; + file.close(); + return; + } + + output = tr("%1: Cannot write to log file '%2'. Error: %3").arg(date).arg(fn).arg(file.errorString()); + } + + std::cout << output.toUtf8().constData(); + } +} diff --git a/app/src/maemo5/logger.h b/app/src/maemo5/logger.h new file mode 100644 index 0000000..eb93509 --- /dev/null +++ b/app/src/maemo5/logger.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include + +class Logger : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(int verbosity READ verbosity WRITE setVerbosity) + + Q_ENUMS(Verbosity) + +public: + enum Verbosity { + NoVerbosity = 0, + LowestVerbosity, + LowVerbosity, + MediumVerbosity, + HighVerbosity, + HighestVerbosity + }; + + explicit Logger(QObject *parent = 0); + + static QString fileName(); + + static QString text(); + + static int verbosity(); + +public Q_SLOTS: + static void setFileName(const QString &f); + + static void setVerbosity(int v); + + static void clear(); + + static void log(const QString &message, int minimumVerbosity = LowestVerbosity); + +private: + static QString fn; + static int vb; +}; + +#endif // LOGGER_H diff --git a/app/src/maemo5/main.cpp b/app/src/maemo5/main.cpp index 81d5317..5c562ff 100644 --- a/app/src/maemo5/main.cpp +++ b/app/src/maemo5/main.cpp @@ -18,8 +18,9 @@ #include "clipboard.h" #include "database.h" #include "dbusservice.h" +#include "logger.h" #include "mainwindow.h" -#include "resourcesplugins.h" +#include "pluginmanager.h" #include "screen.h" #include "settings.h" #include "soundcloud.h" @@ -33,29 +34,52 @@ int main(int argc, char *argv[]) { app.setOrganizationName("MusiKloud2"); app.setApplicationName("MusiKloud2"); + const QStringList args = app.arguments(); + const int verbosity = args.indexOf("-v") + 1; + + if ((verbosity > 1) && (verbosity < args.size())) { + Logger::setVerbosity(qMax(1, args.at(verbosity).toInt())); + } + else { + Logger::setFileName(Settings::loggerFileName()); + Logger::setVerbosity(Settings::loggerVerbosity()); + } + QSslConfiguration config = QSslConfiguration::defaultConfiguration(); config.setProtocol(QSsl::TlsV1); QSslConfiguration::setDefaultConfiguration(config); - Settings settings; - AudioPlayer player; - Clipboard clipboard; + QScopedPointer settings(Settings::instance()); + QScopedPointer player(AudioPlayer::instance()); + QScopedPointer clipboard(Clipboard::instance()); + QScopedPointer plugins(PluginManager::instance()); + QScopedPointer screen(Screen::instance()); + QScopedPointer soundcloud(SoundCloud::instance()); + QScopedPointer transfers(Transfers::instance()); DBusService dbus; - ResourcesPlugins plugins; - Screen screen; - SoundCloud soundcloud; - Transfers transfers; initDatabase(); - plugins.load(); - settings.setNetworkProxy(); - transfers.restoreTransfers(); + Settings::setNetworkProxy(); + SoundCloud::init(); + + player.data()->setSleepTimerDuration(Settings::sleepTimerDuration()); + clipboard.data()->setEnabled(Settings::clipboardMonitorEnabled()); + plugins.data()->load(); + transfers.data()->restore(); + + if (Settings::restorePlaybackQueueOnStartup()) { + player.data()->restoreQueue(); + } - MainWindow window; - window.show(); + QScopedPointer window(MainWindow::instance()); + window.data()->show(); - QObject::connect(&clipboard, SIGNAL(textChanged(QString)), &window, SLOT(showResource(QString))); - QObject::connect(&dbus, SIGNAL(resourceRequested(QVariantMap)), &window, SLOT(showResource(QVariantMap))); + QObject::connect(settings.data(), SIGNAL(sleepTimerDurationChanged(int)), + player.data(), SLOT(setSleepTimerDuration(int))); + QObject::connect(settings.data(), SIGNAL(clipboardMonitorEnabledChanged(bool)), + clipboard.data(), SLOT(setEnabled(bool))); + QObject::connect(clipboard.data(), SIGNAL(textChanged(QString)), window.data(), SLOT(showResource(QString))); + QObject::connect(&dbus, SIGNAL(resourceRequested(QVariantMap)), window.data(), SLOT(showResource(QVariantMap))); return app.exec(); } diff --git a/app/src/maemo5/mainwindow.cpp b/app/src/maemo5/mainwindow.cpp index 845703b..7e9b130 100644 --- a/app/src/maemo5/mainwindow.cpp +++ b/app/src/maemo5/mainwindow.cpp @@ -27,6 +27,7 @@ #include "transfers.h" #include "transferswindow.h" #include "valueselectoraction.h" +#include #include #include #include @@ -35,36 +36,38 @@ MainWindow* MainWindow::self = 0; -MainWindow::MainWindow(StackedWindow *parent) : - StackedWindow(parent), +MainWindow::MainWindow() : + StackedWindow(), m_serviceModel(new ServiceModel(this)), m_serviceAction(new ValueSelectorAction(this)), m_nowPlayingAction(new NowPlayingAction(this)), - m_queueAction(new QAction(tr("Queue folder"), this)), - m_playAction(new QAction(tr("Play folder"), this)), + m_playFolderAction(new QAction(tr("Play folder"), this)), + m_queueFolderAction(new QAction(tr("Queue folder"), this)), + m_playUrlAction(new QAction(tr("Play URL"), this)), + m_queueUrlAction(new QAction(tr("Queue URL"), this)), m_transfersAction(new QAction(tr("Transfers"), this)), m_settingsAction(new QAction(tr("Settings"), this)), m_aboutAction(new QAction(tr("About"), this)) { - if (!self) { - self = this; - } - m_serviceAction->setText(tr("Service")); m_serviceAction->setModel(m_serviceModel); - m_serviceAction->setValue(Settings::instance()->currentService()); + m_serviceAction->setValue(Settings::currentService()); menuBar()->addAction(m_serviceAction); - menuBar()->addAction(m_queueAction); - menuBar()->addAction(m_playAction); + menuBar()->addAction(m_playFolderAction); + menuBar()->addAction(m_queueFolderAction); + menuBar()->addAction(m_playUrlAction); + menuBar()->addAction(m_queueUrlAction); menuBar()->addAction(m_transfersAction); menuBar()->addAction(m_settingsAction); menuBar()->addAction(m_aboutAction); menuBar()->addAction(m_nowPlayingAction); connect(m_serviceAction, SIGNAL(valueChanged(QVariant)), this, SLOT(setService(QVariant))); - connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueFolder())); - connect(m_playAction, SIGNAL(triggered()), this, SLOT(playFolder())); + connect(m_playFolderAction, SIGNAL(triggered()), this, SLOT(playFolder())); + connect(m_queueFolderAction, SIGNAL(triggered()), this, SLOT(queueFolder())); + connect(m_playUrlAction, SIGNAL(triggered()), this, SLOT(playUrl())); + connect(m_queueUrlAction, SIGNAL(triggered()), this, SLOT(queueUrl())); connect(m_transfersAction, SIGNAL(triggered()), this, SLOT(showTransfers())); connect(m_settingsAction, SIGNAL(triggered()), this, SLOT(showSettingsDialog())); connect(m_aboutAction, SIGNAL(triggered()), this, SLOT(showAboutDialog())); @@ -72,23 +75,21 @@ MainWindow::MainWindow(StackedWindow *parent) : this, SLOT(onPlayerStatusChanged(AudioPlayer::Status))); connect(Transfers::instance(), SIGNAL(transferAdded(Transfer*)), this, SLOT(onTransferAdded(Transfer*))); - setService(Settings::instance()->currentService()); + setService(Settings::currentService()); } MainWindow::~MainWindow() { - if (self == this) { - self = 0; - } + self = 0; } MainWindow* MainWindow::instance() { - return self; + return self ? self : self = new MainWindow; } bool MainWindow::search(const QString &service, const QString &query, const QString &type, const QString &order) { clearWindows(); - if (service != Settings::instance()->currentService()) { + if (service != Settings::currentService()) { setService(service); } @@ -97,7 +98,7 @@ bool MainWindow::search(const QString &service, const QString &query, const QStr } bool MainWindow::showResource(const QString &url) { - QVariantMap resource = Resources::getResourceFromUrl(url); + const QVariantMap resource = Resources::getResourceFromUrl(url); if (resource.isEmpty()) { return false; @@ -110,9 +111,9 @@ bool MainWindow::showResource(const QVariantMap &resource) { clearWindows(); activateWindow(); - QVariant service = resource.value("service"); + const QVariant service = resource.value("service"); - if (service != Settings::instance()->currentService()) { + if (service != Settings::currentService()) { setService(service); } @@ -122,11 +123,25 @@ bool MainWindow::showResource(const QVariantMap &resource) { } void MainWindow::playFolder() { - QString folder = QFileDialog::getExistingDirectory(this, tr("Play folder"), "/home/user/MyDocs/", - QFileDialog::ShowDirsOnly | QFileDialog::ReadOnly); + const QString folder = QFileDialog::getExistingDirectory(this, tr("Play folder"), "/home/user/MyDocs/", + QFileDialog::ShowDirsOnly | QFileDialog::ReadOnly); if (!folder.isEmpty()) { - if (AudioPlayer::instance()->playFolder(folder)) { + if (AudioPlayer::instance()->playUrl(QUrl::fromLocalFile(folder)) > 0) { + NowPlayingWindow *window = new NowPlayingWindow(this); + window->show(); + } + else { + QMaemo5InformationBox::information(this, tr("No tracks added")); + } + } +} + +void MainWindow::playUrl() { + const QString url = QInputDialog::getText(this, tr("Play URL"), tr("URL")); + + if (!url.isEmpty()) { + if (AudioPlayer::instance()->playUrl(url) > 0) { NowPlayingWindow *window = new NowPlayingWindow(this); window->show(); } @@ -137,15 +152,29 @@ void MainWindow::playFolder() { } void MainWindow::queueFolder() { - QString folder = QFileDialog::getExistingDirectory(this, tr("Queue folder"), "/home/user/MyDocs/", - QFileDialog::ShowDirsOnly | QFileDialog::ReadOnly); + const QString folder = QFileDialog::getExistingDirectory(this, tr("Queue folder"), "/home/user/MyDocs/", + QFileDialog::ShowDirsOnly | QFileDialog::ReadOnly); if (!folder.isEmpty()) { - const int count = AudioPlayer::instance()->queueCount(); + const int added = AudioPlayer::instance()->addUrl(QUrl::fromLocalFile(folder)); + + if (added > 0) { + QMaemo5InformationBox::information(this, tr("%1 tracks added to playback queue").arg(added)); + } + else { + QMaemo5InformationBox::information(this, tr("No tracks added")); + } + } +} + +void MainWindow::queueUrl() { + const QString url = QInputDialog::getText(this, tr("Queue URL"), tr("URL")); + + if (!url.isEmpty()) { + const int added = AudioPlayer::instance()->addUrl(url); - if (AudioPlayer::instance()->addFolder(folder)) { - QMaemo5InformationBox::information(this, tr("%1 tracks added to playback queue") - .arg(AudioPlayer::instance()->queueCount() - count)); + if (added > 0) { + QMaemo5InformationBox::information(this, tr("%1 tracks added to playback queue").arg(added)); } else { QMaemo5InformationBox::information(this, tr("No tracks added")); @@ -162,22 +191,20 @@ void MainWindow::setService(const QVariant &service) { } m_serviceAction->setValue(service); - Settings::instance()->setCurrentService(service.toString()); + Settings::setCurrentService(service.toString()); - QString text = m_serviceModel->data(m_serviceModel->index(m_serviceModel->match("value", service)), - ServiceModel::NameRole).toString(); + const QString text = m_serviceModel->data(m_serviceModel->index(m_serviceModel->match("value", service)), + ServiceModel::NameRole).toString(); setWindowTitle(text.isEmpty() ? "MusiKloud" : text); } void MainWindow::showAboutDialog() { - AboutDialog *dialog = new AboutDialog(this); - dialog->open(); + AboutDialog(this).exec(); } void MainWindow::showSettingsDialog() { - SettingsDialog *dialog = new SettingsDialog(this); - dialog->open(); + SettingsDialog(this).exec(); } void MainWindow::showTransfers() { @@ -187,7 +214,7 @@ void MainWindow::showTransfers() { void MainWindow::onPlayerStatusChanged(AudioPlayer::Status status) { if (status == AudioPlayer::Failed) { - QMessageBox::critical(this, tr("Error"), AudioPlayer::instance()->errorString()); + QMessageBox::critical(this, tr("Playback error"), AudioPlayer::instance()->errorString()); } } diff --git a/app/src/maemo5/mainwindow.h b/app/src/maemo5/mainwindow.h index c2ebc17..f2113a9 100644 --- a/app/src/maemo5/mainwindow.h +++ b/app/src/maemo5/mainwindow.h @@ -33,7 +33,6 @@ class MainWindow : public StackedWindow Q_OBJECT public: - explicit MainWindow(StackedWindow *parent = 0); ~MainWindow(); static MainWindow* instance(); @@ -45,7 +44,9 @@ public Q_SLOTS: private Q_SLOTS: void playFolder(); + void playUrl(); void queueFolder(); + void queueUrl(); void setService(const QVariant &service); @@ -58,14 +59,18 @@ private Q_SLOTS: void onTransferAdded(Transfer *transfer); private: + MainWindow(); + static MainWindow *self; ServiceModel *m_serviceModel; ValueSelectorAction *m_serviceAction; NowPlayingAction *m_nowPlayingAction; - QAction *m_queueAction; - QAction *m_playAction; + QAction *m_playFolderAction; + QAction *m_queueFolderAction; + QAction *m_playUrlAction; + QAction *m_queueUrlAction; QAction *m_transfersAction; QAction *m_settingsAction; QAction *m_aboutAction; diff --git a/app/src/maemo5/navdelegate.cpp b/app/src/maemo5/navdelegate.cpp index 2da71e1..79b4aac 100644 --- a/app/src/maemo5/navdelegate.cpp +++ b/app/src/maemo5/navdelegate.cpp @@ -23,11 +23,24 @@ NavDelegate::NavDelegate(QObject *parent) : } void NavDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QStyledItemDelegate::paint(painter, option, index); + if ((option.state) & (QStyle::State_Selected)) { + painter->drawImage(option.rect, QImage("/etc/hildon/theme/images/TouchListBackgroundPressed.png")); + } + else { + painter->drawImage(option.rect, QImage("/etc/hildon/theme/images/TouchListBackgroundNormal.png")); + } QRect iconRect = option.rect; iconRect.moveLeft(iconRect.right() - 56); iconRect.moveTop(iconRect.top() + (iconRect.height() - 48) / 2); iconRect.setSize(QSize(48, 48)); painter->drawImage(iconRect, QImage("/usr/share/icons/hicolor/48x48/hildon/general_forward.png")); + + QRect textRect = option.rect; + textRect.moveLeft(textRect.left() + 8); + textRect.setRight(iconRect.left() - 8); + + const QString text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, + textRect.width()); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); } diff --git a/app/src/maemo5/networkproxydialog.cpp b/app/src/maemo5/networkproxydialog.cpp index a45dcaf..8a5ab63 100644 --- a/app/src/maemo5/networkproxydialog.cpp +++ b/app/src/maemo5/networkproxydialog.cpp @@ -36,21 +36,22 @@ NetworkProxyDialog::NetworkProxyDialog(QWidget *parent) : m_passEdit(new QLineEdit(this)) { setWindowTitle(tr("Network proxy")); + setMinimumHeight(360); m_proxyTypeSelector->setModel(new NetworkProxyTypeModel(this)); - m_proxyTypeSelector->setValue(Settings::instance()->networkProxyType()); + m_proxyTypeSelector->setValue(Settings::networkProxyType()); m_hostEdit->setMinimumWidth(380); - m_hostEdit->setText(Settings::instance()->networkProxyHost()); + m_hostEdit->setText(Settings::networkProxyHost()); m_portEdit->setValidator(new QIntValidator(0, 100000, this)); - m_portEdit->setText(QString::number(Settings::instance()->networkProxyPort())); + m_portEdit->setText(QString::number(Settings::networkProxyPort())); m_passEdit->setEchoMode(QLineEdit::Password); - m_passEdit->setText(Settings::instance()->networkProxyPassword()); - m_userEdit->setText(Settings::instance()->networkProxyUsername()); + m_passEdit->setText(Settings::networkProxyPassword()); + m_userEdit->setText(Settings::networkProxyUsername()); QGroupBox *proxyGroup = new QGroupBox(tr("Use network proxy"), this); proxyGroup->setCheckable(true); - proxyGroup->setChecked(Settings::instance()->networkProxyEnabled()); + proxyGroup->setChecked(Settings::networkProxyEnabled()); QGridLayout *proxyGrid = new QGridLayout(proxyGroup); proxyGrid->setContentsMargins(0, 0, 0, 0); @@ -72,7 +73,8 @@ NetworkProxyDialog::NetworkProxyDialog(QWidget *parent) : scrollArea->setWidgetResizable(true); scrollArea->setWidget(scrollWidget); - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this); + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Vertical, this); QHBoxLayout *hbox = new QHBoxLayout(this); hbox->addWidget(scrollArea); hbox->addWidget(buttonBox); @@ -88,14 +90,14 @@ NetworkProxyDialog::NetworkProxyDialog(QWidget *parent) : } void NetworkProxyDialog::accept() { - Settings::instance()->setNetworkProxy(); + Settings::setNetworkProxy(); Dialog::accept(); } void NetworkProxyDialog::setNetworkProxyPort(const QString &port) { - Settings::instance()->setNetworkProxyPort(port.toInt()); + Settings::setNetworkProxyPort(port.toInt()); } void NetworkProxyDialog::setNetworkProxyType(const QVariant &type) { - Settings::instance()->setNetworkProxyType(type.toInt()); + Settings::setNetworkProxyType(type.toInt()); } diff --git a/app/src/maemo5/newcategorydialog.cpp b/app/src/maemo5/newcategorydialog.cpp index 5c0039e..e6759c6 100644 --- a/app/src/maemo5/newcategorydialog.cpp +++ b/app/src/maemo5/newcategorydialog.cpp @@ -59,13 +59,13 @@ void NewCategoryDialog::setPath(const QString &path) { } void NewCategoryDialog::addCategory() { - Settings::instance()->addCategory(m_nameEdit->text(), m_path); + Settings::addCategory(m_nameEdit->text(), m_path); accept(); } void NewCategoryDialog::showFileDialog() { - QString path = QFileDialog::getExistingDirectory(this, tr("Download path"), - m_path.isEmpty() ? Settings::instance()->downloadPath() : m_path); + const QString path = QFileDialog::getExistingDirectory(this, tr("Download path"), + m_path.isEmpty() ? Settings::downloadPath() : m_path); if (!path.isEmpty()) { m_path = path; @@ -80,4 +80,3 @@ void NewCategoryDialog::showFileDialog() { void NewCategoryDialog::onNameTextChanged(const QString &text) { m_doneButton->setEnabled((!text.isEmpty()) && (!m_path.isEmpty())); } - diff --git a/app/src/maemo5/nowplayingaction.cpp b/app/src/maemo5/nowplayingaction.cpp index cde877b..3b56e45 100644 --- a/app/src/maemo5/nowplayingaction.cpp +++ b/app/src/maemo5/nowplayingaction.cpp @@ -15,12 +15,9 @@ */ #include "nowplayingaction.h" +#include "audioplayer.h" #include "imagecache.h" #include "nowplayingwindow.h" -#include "utils.h" -#ifdef MUSIKLOUD_DEBUG -#include -#endif NowPlayingAction::NowPlayingAction(QWidget *parent) : QWidgetAction(parent) @@ -61,18 +58,12 @@ void NowPlayingButton::hideEvent(QHideEvent *e) { } void NowPlayingButton::connectPlaybackSignals() { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "NowPlayingButton::connectPlaybackSignals"; -#endif - connect(AudioPlayer::instance(), SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentIndexChanged(int))); - onCurrentIndexChanged(AudioPlayer::instance()->currentIndex()); + connect(AudioPlayer::instance(), SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged())); + onMetaDataChanged(); } void NowPlayingButton::disconnectPlaybackSignals() { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "NowPlayingButton::disconnectPlaybackSignals"; -#endif - disconnect(AudioPlayer::instance(), SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentIndexChanged(int))); + disconnect(AudioPlayer::instance(), SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged())); } void NowPlayingButton::showNowPlayingWindow() { @@ -80,26 +71,22 @@ void NowPlayingButton::showNowPlayingWindow() { window->show(); } -void NowPlayingButton::onCurrentIndexChanged(int index) { - if (MKTrack *track = AudioPlayer::instance()->currentTrack()) { +void NowPlayingButton::onMetaDataChanged() { + if (AudioPlayerMetaData::isAvailable()) { const int maxWidth = width() - iconSize().width() - 36; QFont small = font(); small.setPointSize(13); - const QString title = fontMetrics().elidedText(QString("%1/%2 - %3").arg(index + 1) - .arg(AudioPlayer::instance()->queueCount()).arg(track->title().isEmpty() - ? tr("Unknown title") : track->title()), Qt::ElideRight, maxWidth); + const QString title = fontMetrics().elidedText(QString("%1/%2 - %3") + .arg(AudioPlayer::instance()->currentIndex() + 1) + .arg(AudioPlayer::instance()->queueCount()) + .arg(AudioPlayerMetaData::title()), Qt::ElideRight, + maxWidth); - const QString artist = QFontMetrics(small).elidedText(track->artist().isEmpty() - ? tr("Unknown artist") : track->artist(), Qt::ElideRight, maxWidth); - - QUrl thumbnailUrl = track->thumbnailUrl(); - - if (thumbnailUrl.isEmpty()) { - thumbnailUrl = Utils::findThumbnailUrl(track->url()); - } - - const QImage thumbnail = m_cache->image(thumbnailUrl, iconSize()); + const QString artist = QFontMetrics(small).elidedText(AudioPlayerMetaData::artist(), Qt::ElideRight, + maxWidth); + + const QImage thumbnail = m_cache->image(AudioPlayerMetaData::thumbnailUrl(), iconSize()); if (!thumbnail.isNull()) { setIcon(QIcon(QPixmap::fromImage(thumbnail))); @@ -114,20 +101,12 @@ void NowPlayingButton::onCurrentIndexChanged(int index) { } void NowPlayingButton::onImageReady() { - if (MKTrack *track = AudioPlayer::instance()->currentTrack()) { - QUrl thumbnailUrl = track->thumbnailUrl(); - - if (thumbnailUrl.isEmpty()) { - thumbnailUrl = Utils::findThumbnailUrl(track->url()); - } - - const QImage thumbnail = m_cache->image(thumbnailUrl, iconSize()); + const QImage thumbnail = m_cache->image(AudioPlayerMetaData::thumbnailUrl(), iconSize()); - if (!thumbnail.isNull()) { - setIcon(QIcon(QPixmap::fromImage(thumbnail))); - return; - } + if (!thumbnail.isNull()) { + setIcon(QIcon(QPixmap::fromImage(thumbnail))); + } + else { + setIcon(QIcon::fromTheme("mediaplayer_default_album")); } - - setIcon(QIcon::fromTheme("mediaplayer_default_album")); } diff --git a/app/src/maemo5/nowplayingaction.h b/app/src/maemo5/nowplayingaction.h index a200a59..c109383 100644 --- a/app/src/maemo5/nowplayingaction.h +++ b/app/src/maemo5/nowplayingaction.h @@ -17,7 +17,6 @@ #ifndef NOWPLAYINGACTION_H #define NOWPLAYINGACTION_H -#include "audioplayer.h" #include #include @@ -51,7 +50,7 @@ class NowPlayingButton : public QMaemo5ValueButton private Q_SLOTS: void showNowPlayingWindow(); - void onCurrentIndexChanged(int index); + void onMetaDataChanged(); void onImageReady(); private: diff --git a/app/src/maemo5/nowplayingdelegate.cpp b/app/src/maemo5/nowplayingdelegate.cpp index 3d634f6..ab96912 100644 --- a/app/src/maemo5/nowplayingdelegate.cpp +++ b/app/src/maemo5/nowplayingdelegate.cpp @@ -39,11 +39,10 @@ void NowPlayingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op textRect.setTop(textRect.top() + 8); textRect.setBottom(textRect.bottom() - 8); - QFontMetrics fm = painter->fontMetrics(); - - QString duration = index.data(TrackModel::DurationStringRole).toString(); - QString title = fm.elidedText(index.data(TrackModel::TitleRole).toString(), Qt::ElideRight, - textRect.width() - fm.width(duration) - 8); + const QFontMetrics fm = painter->fontMetrics(); + const QString duration = index.data(TrackModel::DurationStringRole).toString(); + const QString title = fm.elidedText(index.data(TrackModel::TitleRole).toString(), Qt::ElideRight, + textRect.width() - fm.width(duration) - 8); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, title); painter->drawText(textRect, Qt::AlignRight | Qt::AlignTop, duration); @@ -56,7 +55,7 @@ void NowPlayingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op if (artist.isEmpty()) { artist = tr("Unknown artist"); } - + painter->save(); painter->setFont(font); painter->setPen(QApplication::palette().color(QPalette::Mid)); @@ -64,3 +63,7 @@ void NowPlayingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op QFontMetrics(font).elidedText(artist, Qt::ElideRight, textRect.width())); painter->restore(); } + +QSize NowPlayingDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const { + return QSize(option.rect.width(), 70); +} diff --git a/app/src/maemo5/nowplayingdelegate.h b/app/src/maemo5/nowplayingdelegate.h index d9ca691..87d4920 100644 --- a/app/src/maemo5/nowplayingdelegate.h +++ b/app/src/maemo5/nowplayingdelegate.h @@ -28,6 +28,8 @@ class NowPlayingDelegate : public QStyledItemDelegate explicit NowPlayingDelegate(QObject *parent = 0); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; }; #endif // NOWPLAYINGDELEGATE_H diff --git a/app/src/maemo5/nowplayingwindow.cpp b/app/src/maemo5/nowplayingwindow.cpp index 3b38400..e47f31b 100644 --- a/app/src/maemo5/nowplayingwindow.cpp +++ b/app/src/maemo5/nowplayingwindow.cpp @@ -18,7 +18,11 @@ #include "nowplayingwindow.h" #include "image.h" #include "nowplayingdelegate.h" +#include "plugindownloaddialog.h" +#include "resources.h" +#include "soundclouddownloaddialog.h" #include "screen.h" +#include "transfers.h" #include "utils.h" #include #include @@ -29,13 +33,10 @@ #include #include #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif -static const QString TOOL_BUTTON_STYLE_SHEET("QToolButton { background: transparent; image: url(%1); } \ - QToolButton:pressed { image: url(%2); } \ - QToolButton:checked { image: url(%3); }"); +const QString NowPlayingWindow::TOOL_BUTTON_STYLE_SHEET("QToolButton { background: transparent; image: url(%1); } \ + QToolButton:pressed { image: url(%2); } \ + QToolButton:checked { image: url(%3); }"); NowPlayingWindow::NowPlayingWindow(StackedWindow *parent) : StackedWindow(parent), @@ -45,14 +46,14 @@ NowPlayingWindow::NowPlayingWindow(StackedWindow *parent) : m_trackLabel(new QLabel(this)), m_titleLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), - m_positionLabel(new QLabel(this)), - m_durationLabel(new QLabel(this)), + m_positionLabel(new QLabel("--:--", this)), + m_durationLabel(new QLabel("--:2--", this)), m_positionSlider(new QSlider(Qt::Horizontal, this)), m_bufferBar(new QProgressBar(this)), m_view(new QListView(this)), - m_contextMenu(new QMenu(this)), - m_removeAction(new QAction(tr("Delete from now playing"), this)), m_clearAction(new QAction(tr("Clear now playing"), this)), + m_stopAfterCurrentAction(new QAction(tr("Stop after current track"), this)), + m_sleepTimerAction(new QAction(tr("Sleep timer"), this)), m_toolBar(new QWidget(this)), m_hbox(new QHBoxLayout(m_toolBar)), m_previousButton(new QToolButton(this)), @@ -64,6 +65,8 @@ NowPlayingWindow::NowPlayingWindow(StackedWindow *parent) : setWindowTitle(tr("Now playing")); menuBar()->addAction(m_clearAction); + menuBar()->addAction(m_stopAfterCurrentAction); + menuBar()->addAction(m_sleepTimerAction); m_thumbnail->setFallbackSource(QUrl::fromLocalFile("/usr/share/icons/hicolor/295x295/hildon/mediaplayer_default_album.png")); m_thumbnail->setGeometry(30, 30, 295, 295); @@ -89,10 +92,14 @@ NowPlayingWindow::NowPlayingWindow(StackedWindow *parent) : m_view->setModel(AudioPlayer::instance()->queue()); m_view->setItemDelegate(new NowPlayingDelegate(m_view)); m_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_view->setUniformItemSizes(true); m_view->hide(); - m_contextMenu->addAction(m_removeAction); - m_contextMenu->addAction(m_clearAction); + m_stopAfterCurrentAction->setCheckable(true); + m_stopAfterCurrentAction->setChecked(AudioPlayer::instance()->stopAfterCurrentTrack()); + + m_sleepTimerAction->setCheckable(true); + m_sleepTimerAction->setChecked(AudioPlayer::instance()->sleepTimerEnabled()); m_previousButton->setFixedSize(64, 64); m_previousButton->setIconSize(QSize(64, 64)); @@ -149,10 +156,13 @@ NowPlayingWindow::NowPlayingWindow(StackedWindow *parent) : connect(m_thumbnail, SIGNAL(clicked()), this, SLOT(toggleView())); connect(m_positionSlider, SIGNAL(sliderReleased()), this, SLOT(setPosition())); + connect(m_positionSlider, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int))); connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(setCurrentIndex(QModelIndex))); connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - connect(m_removeAction, SIGNAL(triggered()), this, SLOT(removeTrack())); connect(m_clearAction, SIGNAL(triggered()), AudioPlayer::instance(), SLOT(clearQueue())); + connect(m_stopAfterCurrentAction, SIGNAL(triggered(bool)), + AudioPlayer::instance(), SLOT(setStopAfterCurrentTrack(bool))); + connect(m_sleepTimerAction, SIGNAL(triggered(bool)), AudioPlayer::instance(),SLOT(setSleepTimerEnabled(bool))); connect(m_previousButton, SIGNAL(clicked()), AudioPlayer::instance(), SLOT(previous())); connect(m_playButton, SIGNAL(clicked(bool)), AudioPlayer::instance(), SLOT(setPlaying(bool))); connect(m_nextButton, SIGNAL(clicked()), AudioPlayer::instance(), SLOT(next())); @@ -165,39 +175,69 @@ NowPlayingWindow::NowPlayingWindow(StackedWindow *parent) : } void NowPlayingWindow::connectPlaybackSignals() { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "NowPlayingWindow::connectPlaybackSignals"; -#endif connect(AudioPlayer::instance(), SIGNAL(bufferStatusChanged(int)), this, SLOT(onBufferStatusChanged(int))); connect(AudioPlayer::instance(), SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentIndexChanged(int))); connect(AudioPlayer::instance(), SIGNAL(durationChanged(qint64)), this, SLOT(onDurationChanged(qint64))); connect(AudioPlayer::instance(), SIGNAL(positionChanged(qint64)), this, SLOT(onPositionChanged(qint64))); + connect(AudioPlayer::instance(), SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged())); connect(AudioPlayer::instance(), SIGNAL(seekableChanged(bool)), this, SLOT(onSeekableChanged(bool))); + connect(AudioPlayer::instance(), SIGNAL(sleepTimerRemainingChanged(int)), + this, SLOT(onSleepTimerRemainingChanged(int))); connect(AudioPlayer::instance(), SIGNAL(statusChanged(AudioPlayer::Status)), this, SLOT(onStatusChanged(AudioPlayer::Status))); onCurrentIndexChanged(AudioPlayer::instance()->currentIndex()); onDurationChanged(AudioPlayer::instance()->duration()); + onMetaDataChanged(); onPositionChanged(AudioPlayer::instance()->position()); onSeekableChanged(AudioPlayer::instance()->isSeekable()); + onSleepTimerRemainingChanged(AudioPlayer::instance()->sleepTimerRemaining()); onStatusChanged(AudioPlayer::instance()->status()); } void NowPlayingWindow::disconnectPlaybackSignals() { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "NowPlayingWindow::disconnectPlaybackSignals"; -#endif disconnect(AudioPlayer::instance(), SIGNAL(bufferStatusChanged(int)), this, SLOT(onBufferStatusChanged(int))); disconnect(AudioPlayer::instance(), SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentIndexChanged(int))); disconnect(AudioPlayer::instance(), SIGNAL(durationChanged(qint64)), this, SLOT(onDurationChanged(qint64))); + disconnect(AudioPlayer::instance(), SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged())); disconnect(AudioPlayer::instance(), SIGNAL(positionChanged(qint64)), this, SLOT(onPositionChanged(qint64))); disconnect(AudioPlayer::instance(), SIGNAL(seekableChanged(bool)), this, SLOT(onSeekableChanged(bool))); + disconnect(AudioPlayer::instance(), SIGNAL(sleepTimerRemainingChanged(int)), + this, SLOT(onSleepTimerRemainingChanged(int))); disconnect(AudioPlayer::instance(), SIGNAL(statusChanged(AudioPlayer::Status)), this, SLOT(onStatusChanged(AudioPlayer::Status))); } -void NowPlayingWindow::removeTrack() { - AudioPlayer::instance()->removeTrack(m_view->currentIndex().row()); +void NowPlayingWindow::downloadTrack(const QModelIndex &index) { + const QString trackId = index.data(TrackModel::IdRole).toString(); + const QString title = index.data(TrackModel::TitleRole).toString(); + const QString service = index.data(TrackModel::ServiceRole).toString(); + + if (service == Resources::SOUNDCLOUD) { + SoundCloudDownloadDialog dialog(this); + dialog.get(trackId); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, trackId, dialog.streamId(), + QUrl(), title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } + } + else { + const QUrl streamUrl = index.data(TrackModel::StreamUrlRole).toString(); + PluginDownloadDialog dialog(service, this); + dialog.list(trackId, streamUrl.isEmpty()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(service, trackId, dialog.streamId(), streamUrl, + title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } + } +} + +void NowPlayingWindow::removeTrack(const QModelIndex &index) { + AudioPlayer::instance()->removeTrack(index.row()); } void NowPlayingWindow::setCurrentIndex(const QModelIndex &index) { @@ -209,8 +249,37 @@ void NowPlayingWindow::setPosition() { } void NowPlayingWindow::showContextMenu(const QPoint &pos) { - if (m_view->currentIndex().isValid()) { - m_contextMenu->popup(pos, m_removeAction); + const QModelIndex index = m_view->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (index.data(TrackModel::DownloadableRole).toBool()) { + QMenu menu(this); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *removeAction = menu.addAction(tr("Remove")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == downloadAction) { + downloadTrack(index); + } + else if (action == removeAction) { + removeTrack(index); + } + } + else { + QMenu menu(this); + QAction *removeAction = menu.addAction(tr("Remove")); + QAction *action = menu.exec(pos); + + if (action == removeAction) { + removeTrack(index); + } } } @@ -250,42 +319,35 @@ void NowPlayingWindow::onCurrentIndexChanged(int index) { m_trackLabel->setText(QString("%1/%2 %3").arg(index + 1).arg(AudioPlayer::instance()->queueCount()) .arg(tr("tracks"))); - - if (MKTrack *track = AudioPlayer::instance()->currentTrack()) { - m_thumbnail->setSource(track->largeThumbnailUrl()); - m_titleLabel->setText(m_titleLabel->fontMetrics().elidedText(track->title().isEmpty() - ? tr("Unknown title") : track->title(), Qt::ElideRight, 415)); - - const QString artist = m_artistLabel->fontMetrics().elidedText(track->artist().isEmpty() - ? tr("Unknown artist") : track->artist(), Qt::ElideRight, 415); - - const QString genre = m_artistLabel->fontMetrics().elidedText(track->genre().isEmpty() - ? tr("Unknown genre") : track->genre(), Qt::ElideRight, 415); - - m_artistLabel->setText(QString("

%1
%3

").arg(artist) - .arg(palette().color(QPalette::Mid).name()).arg(genre)); - - QUrl thumbnail = track->largeThumbnailUrl(); - - if (thumbnail.isEmpty()) { - thumbnail = Utils::findThumbnailUrl(track->url()); - } - - m_thumbnail->setSource(thumbnail); - } } void NowPlayingWindow::onDurationChanged(qint64 duration) { - m_positionSlider->setMaximum(duration); - m_durationLabel->setText(AudioPlayer::instance()->durationString()); + m_positionSlider->setMaximum(qMax(1, int(duration))); + m_durationLabel->setText(AudioPlayerMetaData::durationString()); +} + +void NowPlayingWindow::onMetaDataChanged() { + m_titleLabel->setText(m_titleLabel->fontMetrics().elidedText(AudioPlayerMetaData::title(), + Qt::ElideRight, 415)); + const QString artist = m_artistLabel->fontMetrics().elidedText(AudioPlayerMetaData::artist(), + Qt::ElideRight, 415); + const QString genre = m_artistLabel->fontMetrics().elidedText(AudioPlayerMetaData::genre(), + Qt::ElideRight, 415); + m_durationLabel->setText(AudioPlayerMetaData::durationString()); + m_artistLabel->setText(QString("

%1
%3

").arg(artist) + .arg(palette().color(QPalette::Mid).name()).arg(genre)); + m_thumbnail->setSource(AudioPlayerMetaData::largeThumbnailUrl()); } void NowPlayingWindow::onPositionChanged(qint64 position) { - if ((m_positionSlider->isEnabled()) && (!m_positionSlider->isSliderDown())) { - m_positionSlider->setValue(position); + if (m_positionSlider->isEnabled()) { + if (!m_positionSlider->isSliderDown()) { + m_positionSlider->setValue(int(position)); + } + } + else { + m_positionLabel->setText(AudioPlayer::instance()->positionString()); } - - m_positionLabel->setText(AudioPlayer::instance()->positionString()); } void NowPlayingWindow::onSeekableChanged(bool isSeekable) { @@ -306,6 +368,20 @@ void NowPlayingWindow::onSeekableChanged(bool isSeekable) { } } +void NowPlayingWindow::onSleepTimerRemainingChanged(int remaining) { + if (remaining > 0) { + m_sleepTimerAction->setText(tr("Sleep timer (%1)").arg(AudioPlayer::instance()->sleepTimerRemainingString())); + } + else { + m_sleepTimerAction->setChecked(false); + m_sleepTimerAction->setText(tr("Sleep timer")); + } +} + +void NowPlayingWindow::onSliderValueChanged(int value) { + m_positionLabel->setText(Utils::formatMSecs(qint64(value))); +} + void NowPlayingWindow::onStatusChanged(AudioPlayer::Status status) { switch (status) { case AudioPlayer::Stopped: diff --git a/app/src/maemo5/nowplayingwindow.h b/app/src/maemo5/nowplayingwindow.h index 306b4f7..dd0738f 100644 --- a/app/src/maemo5/nowplayingwindow.h +++ b/app/src/maemo5/nowplayingwindow.h @@ -27,8 +27,6 @@ class QSlider; class QProgressBar; class QListView; class QLabel; -class QAction; -class QMenu; class QHBoxLayout; class QGridLayout; @@ -40,7 +38,8 @@ class NowPlayingWindow : public StackedWindow explicit NowPlayingWindow(StackedWindow *parent = 0); private Q_SLOTS: - void removeTrack(); + void downloadTrack(const QModelIndex &index); + void removeTrack(const QModelIndex &index); void setCurrentIndex(const QModelIndex &index); @@ -54,15 +53,18 @@ private Q_SLOTS: void onCountChanged(int count); void onCurrentIndexChanged(int index); void onDurationChanged(qint64 duration); + void onMetaDataChanged(); void onPositionChanged(qint64 position); void onSeekableChanged(bool isSeekable); + void onSleepTimerRemainingChanged(int remaining); + void onSliderValueChanged(int value); void onStatusChanged(AudioPlayer::Status status); void onScreenLockStateChanged(bool isLocked); private: void connectPlaybackSignals(); void disconnectPlaybackSignals(); - + Image *m_thumbnail; QWidget *m_container; QGridLayout *m_grid; @@ -74,9 +76,9 @@ private Q_SLOTS: QSlider *m_positionSlider; QProgressBar *m_bufferBar; QListView *m_view; - QMenu *m_contextMenu; - QAction *m_removeAction; QAction *m_clearAction; + QAction *m_stopAfterCurrentAction; + QAction *m_sleepTimerAction; QWidget *m_toolBar; QHBoxLayout *m_hbox; QToolButton *m_previousButton; @@ -84,6 +86,8 @@ private Q_SLOTS: QToolButton *m_nextButton; QToolButton *m_shuffleButton; QToolButton *m_repeatButton; + + static const QString TOOL_BUTTON_STYLE_SHEET; }; #endif // NOWPLAYINGWINDOW_H diff --git a/app/src/maemo5/playlistdelegate.cpp b/app/src/maemo5/playlistdelegate.cpp index 88ffaa1..752c98f 100644 --- a/app/src/maemo5/playlistdelegate.cpp +++ b/app/src/maemo5/playlistdelegate.cpp @@ -58,11 +58,10 @@ void PlaylistDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti textRect.setTop(textRect.top() + 8); textRect.setBottom(textRect.bottom() - 8); - QFontMetrics fm = painter->fontMetrics(); - - QString trackCount = tr("%1 tracks").arg(index.data(m_trackCountRole).toInt()); - QString title = fm.elidedText(index.data(m_titleRole).toString(), Qt::ElideRight, - textRect.width() - fm.width(trackCount) - 8); + const QFontMetrics fm = painter->fontMetrics(); + const QString trackCount = tr("%1 tracks").arg(index.data(m_trackCountRole).toInt()); + const QString title = fm.elidedText(index.data(m_titleRole).toString(), Qt::ElideRight, + textRect.width() - fm.width(trackCount) - 8); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, title); painter->drawText(textRect, Qt::AlignRight | Qt::AlignTop, trackCount); diff --git a/app/src/maemo5/plugins/pluginartistswindow.cpp b/app/src/maemo5/plugins/pluginartistswindow.cpp index fe7dc18..21559fe 100644 --- a/app/src/maemo5/plugins/pluginartistswindow.cpp +++ b/app/src/maemo5/plugins/pluginartistswindow.cpp @@ -20,7 +20,6 @@ #include "listview.h" #include "nowplayingaction.h" #include "pluginartistwindow.h" -#include "settings.h" #include #include #include @@ -34,7 +33,7 @@ PluginArtistsWindow::PluginArtistsWindow(StackedWindow *parent) : m_view(new ListView(this)), m_reloadAction(new QAction(tr("Reload"), this)), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No artists found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Artists")); setCentralWidget(new QWidget); diff --git a/app/src/maemo5/plugins/pluginartistwindow.cpp b/app/src/maemo5/plugins/pluginartistwindow.cpp index ff370d7..ab89111 100644 --- a/app/src/maemo5/plugins/pluginartistwindow.cpp +++ b/app/src/maemo5/plugins/pluginartistwindow.cpp @@ -25,8 +25,6 @@ #include "plugintrackswindow.h" #include "plugintrackwindow.h" #include "resources.h" -#include "resourcesplugins.h" -#include "settings.h" #include "textbrowser.h" #include "utils.h" #include @@ -116,31 +114,35 @@ void PluginArtistWindow::loadArtistUi() { } void PluginArtistWindow::showPlaylists() { - if (!ResourcesPlugins::instance()->resourceTypeIsSupported(m_artist->service(), Resources::PLAYLIST)) { + const QString playlistsId = m_artist->playlistsId(); + + if (playlistsId.isEmpty()) { QMaemo5InformationBox::information(this, tr("This artist does not have any playlists")); return; } PluginPlaylistsWindow *window = new PluginPlaylistsWindow(this); window->setWindowTitle(tr("%1's playlists").arg(m_artist->name())); - window->list(m_artist->service(), m_artist->id()); + window->list(m_artist->service(), playlistsId); window->show(); } void PluginArtistWindow::showTracks() { - if (!ResourcesPlugins::instance()->resourceTypeIsSupported(m_artist->service(), Resources::TRACK)) { + const QString tracksId = m_artist->tracksId(); + + if (tracksId.isEmpty()) { QMaemo5InformationBox::information(this, tr("This artist does not have any tracks")); return; } PluginTracksWindow *window = new PluginTracksWindow(this); window->setWindowTitle(tr("%1's tracks").arg(m_artist->name())); - window->list(m_artist->service(), m_artist->id()); + window->list(m_artist->service(), tracksId); window->show(); } void PluginArtistWindow::showResource(const QUrl &url) { - QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); if (resource.value("service") != m_artist->service()) { QDesktopServices::openUrl(url); diff --git a/app/src/maemo5/plugins/plugincategorieswindow.cpp b/app/src/maemo5/plugins/plugincategorieswindow.cpp index dc1cef0..015ef34 100644 --- a/app/src/maemo5/plugins/plugincategorieswindow.cpp +++ b/app/src/maemo5/plugins/plugincategorieswindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -15,8 +15,8 @@ */ #include "plugincategorieswindow.h" +#include "categorydelegate.h" #include "listview.h" -#include "nowplayingaction.h" #include "plugintrackswindow.h" #include #include @@ -27,16 +27,16 @@ PluginCategoriesWindow::PluginCategoriesWindow(StackedWindow *parent) : StackedWindow(parent), m_model(new PluginCategoryModel(this)), - m_nowPlayingAction(new NowPlayingAction(this)), m_view(new ListView(this)), m_reloadAction(new QAction(tr("Reload"), this)), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No categories found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Categories")); setCentralWidget(new QWidget); m_view->setModel(m_model); + m_view->setItemDelegate(new CategoryDelegate(m_view)); m_reloadAction->setEnabled(false); @@ -48,7 +48,6 @@ PluginCategoriesWindow::PluginCategoriesWindow(StackedWindow *parent) : m_layout->setContentsMargins(0, 0, 0, 0); menuBar()->addAction(m_reloadAction); - menuBar()->addAction(m_nowPlayingAction); connect(m_model, SIGNAL(statusChanged(ResourcesRequest::Status)), this, SLOT(onModelStatusChanged(ResourcesRequest::Status))); @@ -64,7 +63,7 @@ void PluginCategoriesWindow::list(const QString &service, const QString &id) { void PluginCategoriesWindow::showCategory(const QModelIndex &index) { PluginTracksWindow *window = new PluginTracksWindow(this); window->setWindowTitle(index.data(PluginCategoryModel::NameRole).toString()); - window->list(m_model->service(), index.data(PluginCategoryModel::ValueRole).toString()); + window->list(m_model->service(), index.data(PluginCategoryModel::ValueRole).toMap().value("tracksId").toString()); window->show(); } diff --git a/app/src/maemo5/plugins/plugincategorieswindow.h b/app/src/maemo5/plugins/plugincategorieswindow.h index 73bb299..efc1c70 100644 --- a/app/src/maemo5/plugins/plugincategorieswindow.h +++ b/app/src/maemo5/plugins/plugincategorieswindow.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -20,7 +20,6 @@ #include "stackedwindow.h" #include "plugincategorymodel.h" -class NowPlayingAction; class ListView; class QLabel; class QVBoxLayout; @@ -43,7 +42,6 @@ private Q_SLOTS: private: PluginCategoryModel *m_model; - NowPlayingAction *m_nowPlayingAction; ListView *m_view; QAction *m_reloadAction; QLabel *m_label; diff --git a/app/src/maemo5/plugins/plugindownloaddialog.cpp b/app/src/maemo5/plugins/plugindownloaddialog.cpp index 79b84b7..6bf9143 100644 --- a/app/src/maemo5/plugins/plugindownloaddialog.cpp +++ b/app/src/maemo5/plugins/plugindownloaddialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -16,89 +16,117 @@ #include "plugindownloaddialog.h" #include "categorynamemodel.h" -#include "resourcesplugins.h" #include "settings.h" -#include "transfers.h" #include "valueselector.h" +#include +#include +#include +#include #include #include -#include +#include #include -PluginDownloadDialog::PluginDownloadDialog(const QString &service, const QString &resourceId, const QUrl &streamUrl, - const QString &title, QWidget *parent) : +PluginDownloadDialog::PluginDownloadDialog(const QString &service, QWidget *parent) : Dialog(parent), - m_id(resourceId), - m_url(streamUrl), - m_title(title), m_streamModel(new PluginStreamModel(this)), m_categoryModel(new CategoryNameModel(this)), + m_scrollArea(new QScrollArea(this)), + m_commandCheckBox(new QCheckBox(tr("Override global custom command"), this)), + m_commandEdit(new QLineEdit(this)), m_streamSelector(new ValueSelector(tr("Audio format"), this)), m_categorySelector(new ValueSelector(tr("Category"), this)), m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this)), - m_layout(new QGridLayout(this)) + m_layout(new QHBoxLayout(this)) { - setWindowTitle(tr("Download track")); + setWindowTitle(tr("Download")); + setMinimumHeight(360); m_streamModel->setService(service); m_streamSelector->setModel(m_streamModel); m_categorySelector->setModel(m_categoryModel); - m_categorySelector->setValue(Settings::instance()->defaultCategory()); + m_categorySelector->setValue(Settings::defaultCategory()); m_categorySelector->setEnabled(m_categoryModel->rowCount() > 0); - - m_layout->addWidget(m_streamSelector, 0, 0); - m_layout->addWidget(m_categorySelector, 1, 0); - m_layout->addWidget(m_buttonBox, 1, 1); - m_layout->setColumnStretch(0, 1); + + QWidget *scrollWidget = new QWidget(m_scrollArea); + QVBoxLayout *vbox = new QVBoxLayout(scrollWidget); + vbox->addWidget(m_streamSelector); + vbox->addWidget(m_categorySelector); + vbox->addWidget(new QLabel(tr("Custom command (%f for filename)"), this)); + vbox->addWidget(m_commandEdit); + vbox->addWidget(m_commandCheckBox); + vbox->setContentsMargins(0, 0, 0, 0); + m_scrollArea->setWidget(scrollWidget); + m_scrollArea->setWidgetResizable(true); + + m_layout->addWidget(m_scrollArea); + m_layout->addWidget(m_buttonBox, Qt::AlignBottom); + m_layout->setStretch(0, 1); connect(m_streamModel, SIGNAL(statusChanged(ResourcesRequest::Status)), this, SLOT(onStreamModelStatusChanged(ResourcesRequest::Status))); - connect(m_categorySelector, SIGNAL(valueChanged(QVariant)), this, SLOT(onCategoryChanged())); - connect(m_streamSelector, SIGNAL(valueChanged(QVariant)), this, SLOT(onStreamChanged())); - connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(addDownload())); + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } -void PluginDownloadDialog::showEvent(QShowEvent *e) { - Dialog::showEvent(e); +QString PluginDownloadDialog::trackId() const { + return m_trackId; +} + +QString PluginDownloadDialog::streamId() const { + return m_streamSelector->currentValue().toMap().value("id").toString(); +} + +QString PluginDownloadDialog::category() const { + return m_categorySelector->valueText(); +} + +QString PluginDownloadDialog::customCommand() const { + return m_commandEdit->text(); +} + +bool PluginDownloadDialog::customCommandOverrideEnabled() const { + return m_commandCheckBox->isChecked(); +} + +void PluginDownloadDialog::accept() { + Settings::setDefaultDownloadFormat(m_streamModel->service(), m_streamSelector->valueText()); + Settings::setDefaultCategory(category()); + Dialog::accept(); +} + +void PluginDownloadDialog::list(const QString &trackId, bool listStreams) { + m_trackId = trackId; - if (m_url.isEmpty()) { - m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - m_streamModel->list(m_id); + if (listStreams) { + m_streamModel->list(trackId); } else { m_streamModel->clear(); - m_streamModel->append(tr("Default format"), m_url); + m_streamModel->append(tr("Default format"), QVariant()); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } } -void PluginDownloadDialog::onCategoryChanged() { - Settings::instance()->setDefaultCategory(m_categorySelector->valueText()); -} - -void PluginDownloadDialog::onStreamChanged() { - Settings::instance()->setDefaultDownloadFormat(m_streamModel->service(), m_streamSelector->valueText()); -} - void PluginDownloadDialog::onStreamModelStatusChanged(ResourcesRequest::Status status) { switch (status) { case ResourcesRequest::Loading: + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); showProgressIndicator(); return; case ResourcesRequest::Ready: if (m_streamModel->rowCount() > 0) { m_streamSelector->setCurrentIndex(qMax(0, m_streamModel->match("name", - Settings::instance()->defaultDownloadFormat(m_streamModel->service())))); + Settings::defaultDownloadFormat(m_streamModel->service())))); } else { - QMessageBox::critical(this, tr("Error"), tr("No streams available for '%1'").arg(m_title)); + QMessageBox::critical(this, tr("Error"), tr("No streams available")); } break; case ResourcesRequest::Failed: - QMessageBox::critical(this, tr("Error"), tr("No streams available for '%1'").arg(m_title)); + QMessageBox::critical(this, tr("Error"), tr("No streams available")); break; default: break; @@ -107,10 +135,3 @@ void PluginDownloadDialog::onStreamModelStatusChanged(ResourcesRequest::Status s m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_streamModel->rowCount() > 0); hideProgressIndicator(); } - -void PluginDownloadDialog::addDownload() { - QString streamId = m_url.isEmpty() ? m_streamSelector->currentValue().toMap().value("id").toString() : QString(); - QString category = m_categorySelector->valueText(); - Transfers::instance()->addDownloadTransfer(m_streamModel->service(), m_id, streamId, m_url, m_title, category); - accept(); -} diff --git a/app/src/maemo5/plugins/plugindownloaddialog.h b/app/src/maemo5/plugins/plugindownloaddialog.h index e7703e3..e15c26f 100644 --- a/app/src/maemo5/plugins/plugindownloaddialog.h +++ b/app/src/maemo5/plugins/plugindownloaddialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -19,42 +19,58 @@ #include "dialog.h" #include "pluginstreammodel.h" -#include class CategoryNameModel; class ValueSelector; +class QScrollArea; +class QCheckBox; +class QLineEdit; class QDialogButtonBox; -class QGridLayout; +class QHBoxLayout; class PluginDownloadDialog : public Dialog { Q_OBJECT + + Q_PROPERTY(QString trackId READ trackId) + Q_PROPERTY(QString streamId READ streamId) + Q_PROPERTY(QString category READ category) + Q_PROPERTY(QString customCommand READ customCommand) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled) public: - explicit PluginDownloadDialog(const QString &service, const QString &resourceId, const QUrl &streamUrl, - const QString &title, QWidget *parent = 0); + explicit PluginDownloadDialog(const QString &service, QWidget *parent = 0); + + QString trackId() const; -protected: - void showEvent(QShowEvent *e); + QString streamId() const; + + QString category() const; + + QString customCommand() const; + bool customCommandOverrideEnabled() const; + +public Q_SLOTS: + virtual void accept(); + + void list(const QString &trackId, bool listStreams = true); private Q_SLOTS: - void onCategoryChanged(); - void onStreamChanged(); void onStreamModelStatusChanged(ResourcesRequest::Status status); - - void addDownload(); - + private: - QString m_id; - QUrl m_url; - QString m_title; PluginStreamModel *m_streamModel; CategoryNameModel *m_categoryModel; + QScrollArea *m_scrollArea; + QCheckBox *m_commandCheckBox; + QLineEdit *m_commandEdit; ValueSelector *m_streamSelector; ValueSelector *m_categorySelector; QDialogButtonBox *m_buttonBox; - QGridLayout *m_layout; + QHBoxLayout *m_layout; + + QString m_trackId; }; #endif // PLUGINDOWNLOADDIALOG_H diff --git a/app/src/maemo5/plugins/pluginplaylistswindow.cpp b/app/src/maemo5/plugins/pluginplaylistswindow.cpp index 9fbfb49..5b232a5 100644 --- a/app/src/maemo5/plugins/pluginplaylistswindow.cpp +++ b/app/src/maemo5/plugins/pluginplaylistswindow.cpp @@ -20,7 +20,6 @@ #include "nowplayingaction.h" #include "playlistdelegate.h" #include "pluginplaylistwindow.h" -#include "settings.h" #include #include #include @@ -34,7 +33,7 @@ PluginPlaylistsWindow::PluginPlaylistsWindow(StackedWindow *parent) : m_view(new ListView(this)), m_reloadAction(new QAction(tr("Reload"), this)), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No playlists found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Playlists")); setCentralWidget(new QWidget); diff --git a/app/src/maemo5/plugins/pluginplaylistwindow.cpp b/app/src/maemo5/plugins/pluginplaylistwindow.cpp index 4b044ec..0339f57 100644 --- a/app/src/maemo5/plugins/pluginplaylistwindow.cpp +++ b/app/src/maemo5/plugins/pluginplaylistwindow.cpp @@ -29,8 +29,8 @@ #include "plugintrackmodel.h" #include "plugintrackwindow.h" #include "resources.h" -#include "settings.h" #include "textbrowser.h" +#include "transfers.h" #include "utils.h" #include "trackdelegate.h" #include @@ -60,13 +60,9 @@ PluginPlaylistWindow::PluginPlaylistWindow(const QString &service, const QString m_dateLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), m_noTracksLabel(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)), m_reloadAction(new QAction(tr("Reload"), this)), - m_queuePlaylistAction(new QAction(tr("Queue"), this)), - m_contextMenu(new QMenu(this)), - m_queueAction(new QAction(tr("Queue"), this)), - m_downloadAction(new QAction(tr("Download"), this)), - m_shareAction(new QAction(tr("Copy URL"), this)) + m_queuePlaylistAction(new QAction(tr("Queue"), this)) { loadBaseUi(); connect(m_playlist, SIGNAL(statusChanged(ResourcesRequest::Status)), @@ -96,11 +92,7 @@ PluginPlaylistWindow::PluginPlaylistWindow(PluginPlaylist *playlist, StackedWind m_noTracksLabel(new QLabel(QString("

%2

") .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), m_reloadAction(new QAction(tr("Reload"), this)), - m_queuePlaylistAction(new QAction(tr("Queue"), this)), - m_contextMenu(new QMenu(this)), - m_queueAction(new QAction(tr("Queue"), this)), - m_downloadAction(new QAction(tr("Download"), this)), - m_shareAction(new QAction(tr("Copy URL"), this)) + m_queuePlaylistAction(new QAction(tr("Queue"), this)) { loadBaseUi(); loadPlaylistUi(); @@ -135,16 +127,13 @@ void PluginPlaylistWindow::loadBaseUi() { m_titleLabel->setWordWrap(true); m_dateLabel->setStyleSheet(QString("color: %1; font-size: 13pt").arg(palette().color(QPalette::Mid).name())); + m_dateLabel->hide(); m_artistLabel->setStyleSheet("font-size: 13pt"); m_artistLabel->hide(); m_noTracksLabel->hide(); m_reloadAction->setEnabled(false); - m_contextMenu->addAction(m_queueAction); - m_contextMenu->addAction(m_downloadAction); - m_contextMenu->addAction(m_shareAction); - QWidget *scrollWidget = new QWidget(m_scrollArea); QGridLayout *grid = new QGridLayout(scrollWidget); grid->addWidget(m_thumbnail, 0, 0, 1, 2, Qt::AlignLeft); @@ -182,18 +171,21 @@ void PluginPlaylistWindow::loadBaseUi() { connect(m_reloadAction, SIGNAL(triggered()), m_model, SLOT(reload())); connect(m_queuePlaylistAction, SIGNAL(triggered()), this, SLOT(queuePlaylist())); connect(m_descriptionLabel, SIGNAL(anchorClicked(QUrl)), this, SLOT(showResource(QUrl))); - connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueTrack())); - connect(m_downloadAction, SIGNAL(triggered()), this, SLOT(downloadTrack())); - connect(m_shareAction, SIGNAL(triggered()), this, SLOT(shareTrack())); } void PluginPlaylistWindow::loadPlaylistUi() { setWindowTitle(m_playlist->title()); m_titleLabel->setText(m_playlist->title()); - m_dateLabel->setText(tr("Published on %1").arg(m_playlist->date())); m_descriptionLabel->setHtml(Utils::toRichText(m_playlist->description())); m_thumbnail->setSource(m_playlist->largeThumbnailUrl()); + + const QString date = m_playlist->date(); + + if (!date.isEmpty()) { + m_dateLabel->setText(tr("Published on %1").arg(date)); + m_dateLabel->show(); + } } void PluginPlaylistWindow::loadArtistUi() { @@ -205,8 +197,18 @@ void PluginPlaylistWindow::loadArtistUi() { } void PluginPlaylistWindow::getTracks() { - m_model->setService(m_playlist->service()); - m_model->list(m_playlist->id()); + const QString id = m_playlist->tracksId(); + + if (!id.isEmpty()) { + m_model->setService(m_playlist->service()); + m_model->list(id); + m_noTracksLabel->hide(); + m_view->show(); + } + else { + m_view->hide(); + m_noTracksLabel->show(); + } } void PluginPlaylistWindow::playPlaylist() { @@ -248,14 +250,24 @@ void PluginPlaylistWindow::queuePlaylist() { } } -void PluginPlaylistWindow::downloadTrack() { - if ((!isBusy()) && (m_view->currentIndex().isValid())) { - QString id = m_view->currentIndex().data(PluginTrackModel::IdRole).toString(); - QString title = m_view->currentIndex().data(PluginTrackModel::TitleRole).toString(); - QUrl streamUrl = m_view->currentIndex().data(PluginTrackModel::StreamUrlRole).toString(); +void PluginPlaylistWindow::downloadTrack(const QModelIndex &index) { + if (isBusy()) { + return; + } + + if (index.isValid()) { + const QString id = index.data(PluginTrackModel::IdRole).toString(); + const QString title = index.data(PluginTrackModel::TitleRole).toString(); + const QUrl streamUrl = index.data(PluginTrackModel::StreamUrlRole).toString(); + + PluginDownloadDialog dialog(m_model->service(), this); + dialog.list(id, streamUrl.isEmpty()); - PluginDownloadDialog *dialog = new PluginDownloadDialog(m_playlist->service(), id, streamUrl, title, this); - dialog->open(); + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(m_model->service(), id, dialog.streamId(), streamUrl, title, + dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } } } @@ -271,19 +283,19 @@ void PluginPlaylistWindow::playTrack(const QModelIndex &index) { } } -void PluginPlaylistWindow::queueTrack() { +void PluginPlaylistWindow::queueTrack(const QModelIndex &index) { if (isBusy()) { return; } - if (PluginTrack *track = m_model->get(m_view->currentIndex().row())) { + if (PluginTrack *track = m_model->get(index.row())) { AudioPlayer::instance()->addTrack(track); QMaemo5InformationBox::information(this, tr("'%1' added to playback queue").arg(track->title())); } } -void PluginPlaylistWindow::shareTrack() { - if (const PluginTrack *track = m_model->get(m_view->currentIndex().row())) { +void PluginPlaylistWindow::shareTrack(const QModelIndex &index) { + if (const PluginTrack *track = m_model->get(index.row())) { Clipboard::instance()->setText(track->url().toString()); QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } @@ -301,19 +313,67 @@ void PluginPlaylistWindow::showTrack(const QModelIndex &index) { } void PluginPlaylistWindow::showArtist() { + if (isBusy()) { + return; + } + PluginArtistWindow *window = new PluginArtistWindow(m_artist, this); window->show(); } void PluginPlaylistWindow::showContextMenu(const QPoint &pos) { - if ((!isBusy()) && (m_view->currentIndex().isValid())) { - m_downloadAction->setEnabled(m_model->data(m_view->currentIndex(), PluginTrackModel::DownloadableRole).toBool()); - m_contextMenu->popup(pos, m_queueAction); + if (isBusy()) { + return; + } + + const QModelIndex index = m_view->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (index.data(PluginTrackModel::DownloadableRole).toBool()) { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueTrack(index); + } + else if (action == downloadAction) { + downloadTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } + } + else { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } } } void PluginPlaylistWindow::showResource(const QUrl &url) { - QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); if (resource.value("service") != m_playlist->service()) { QDesktopServices::openUrl(url); diff --git a/app/src/maemo5/plugins/pluginplaylistwindow.h b/app/src/maemo5/plugins/pluginplaylistwindow.h index 55f0656..cc4f3ea 100644 --- a/app/src/maemo5/plugins/pluginplaylistwindow.h +++ b/app/src/maemo5/plugins/pluginplaylistwindow.h @@ -53,10 +53,10 @@ private Q_SLOTS: void playPlaylist(); void queuePlaylist(); - void downloadTrack(); + void downloadTrack(const QModelIndex &index); void playTrack(const QModelIndex &index); - void queueTrack(); - void shareTrack(); + void queueTrack(const QModelIndex &index); + void shareTrack(const QModelIndex &index); void showTrack(const QModelIndex &index); void showArtist(); @@ -89,10 +89,6 @@ private Q_SLOTS: QLabel *m_noTracksLabel; QAction *m_reloadAction; QAction *m_queuePlaylistAction; - QMenu *m_contextMenu; - QAction *m_queueAction; - QAction *m_downloadAction; - QAction *m_shareAction; QHBoxLayout *m_layout; }; diff --git a/app/src/maemo5/plugins/pluginsearchdialog.cpp b/app/src/maemo5/plugins/pluginsearchdialog.cpp index 700c872..9c54864 100644 --- a/app/src/maemo5/plugins/pluginsearchdialog.cpp +++ b/app/src/maemo5/plugins/pluginsearchdialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -15,7 +15,6 @@ */ #include "pluginsearchdialog.h" -#include "mainwindow.h" #include "pluginsearchtypemodel.h" #include "searchhistorydialog.h" #include "settings.h" @@ -55,39 +54,42 @@ PluginSearchDialog::PluginSearchDialog(const QString &service, QWidget *parent) m_layout->addWidget(m_typeSelector, 1, 0); m_layout->addWidget(m_buttonBox, 0, 1, 2, 1, Qt::AlignBottom); - connect(m_typeSelector, SIGNAL(valueChanged(QVariant)), this, SLOT(onSearchTypeChanged())); - connect(m_searchEdit, SIGNAL(textChanged(QString)), this, SLOT(onSearchTextChanged(QString))); + connect(m_searchEdit, SIGNAL(textChanged(QString)), this, SLOT(onQueryChanged(QString))); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(m_historyButton, SIGNAL(clicked()), this, SLOT(showHistoryDialog())); - connect(m_searchButton, SIGNAL(clicked()), this, SLOT(search())); + connect(m_searchButton, SIGNAL(clicked()), this, SLOT(accept())); } -void PluginSearchDialog::showEvent(QShowEvent *e) { - Dialog::showEvent(e); - m_searchEdit->setFocus(Qt::OtherFocusReason); +QString PluginSearchDialog::query() const { + return m_searchEdit->text(); } -void PluginSearchDialog::search() { - if (!MainWindow::instance()->showResource(m_searchEdit->text())) { - QVariantMap type = m_typeSelector->currentValue().toMap(); - Settings::instance()->addSearch(m_searchEdit->text()); - MainWindow::instance()->search(m_typeModel->service(), m_searchEdit->text(), type.value("type").toString(), - type.value("order").toString()); - } - - accept(); +void PluginSearchDialog::setQuery(const QString &query) { + m_searchEdit->setText(query); } -void PluginSearchDialog::showHistoryDialog() { - SearchHistoryDialog *dialog = new SearchHistoryDialog(this); - dialog->open(); - connect(dialog, SIGNAL(searchChosen(QString)), m_searchEdit, SLOT(setText(QString))); +QString PluginSearchDialog::order() const { + return m_typeSelector->currentValue().toMap().value("order").toString(); +} + +QString PluginSearchDialog::type() const { + return m_typeSelector->currentValue().toMap().value("type").toString(); } -void PluginSearchDialog::onSearchTextChanged(const QString &text) { - m_searchButton->setEnabled(!text.isEmpty()); +void PluginSearchDialog::accept() { + Settings::addSearch(m_searchEdit->text()); + Settings::setDefaultSearchType(m_typeModel->service(), m_typeSelector->valueText()); + Dialog::accept(); +} + +void PluginSearchDialog::showHistoryDialog() { + SearchHistoryDialog dialog(this); + + if (dialog.exec() == QDialog::Accepted) { + setQuery(dialog.query()); + } } -void PluginSearchDialog::onSearchTypeChanged() { - Settings::instance()->setDefaultSearchType(m_typeModel->service(), m_typeSelector->valueText()); +void PluginSearchDialog::onQueryChanged(const QString &query) { + m_searchButton->setEnabled(!query.isEmpty()); } diff --git a/app/src/maemo5/plugins/pluginsearchdialog.h b/app/src/maemo5/plugins/pluginsearchdialog.h index c470e30..7ddc0ca 100644 --- a/app/src/maemo5/plugins/pluginsearchdialog.h +++ b/app/src/maemo5/plugins/pluginsearchdialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -29,21 +29,30 @@ class QGridLayout; class PluginSearchDialog : public Dialog { Q_OBJECT + + Q_PROPERTY(QString query READ query WRITE setQuery) + Q_PROPERTY(QString order READ order) + Q_PROPERTY(QString type READ type) public: explicit PluginSearchDialog(const QString &service, QWidget *parent = 0); -protected: - void showEvent(QShowEvent *e); + QString query() const; + + QString type() const; + + QString order() const; + +public Q_SLOTS: + virtual void accept(); -private Q_SLOTS: - void search(); + void setQuery(const QString &query); +private Q_SLOTS: + void onQueryChanged(const QString &query); + void showHistoryDialog(); - void onSearchTextChanged(const QString &text); - void onSearchTypeChanged(); - private: PluginSearchTypeModel *m_typeModel; diff --git a/app/src/maemo5/plugins/pluginsettingscheckbox.cpp b/app/src/maemo5/plugins/pluginsettingscheckbox.cpp deleted file mode 100644 index 3a4586a..0000000 --- a/app/src/maemo5/plugins/pluginsettingscheckbox.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "pluginsettingscheckbox.h" -#include - -PluginSettingsCheckbox::PluginSettingsCheckbox(QWidget *parent) : - QCheckBox(parent) -{ - connect(this, SIGNAL(clicked(bool)), this, SLOT(onClicked(bool))); -} - -void PluginSettingsCheckbox::setKey(const QString &key) { - m_key = key; -} - -void PluginSettingsCheckbox::setDefaultValue(const QVariant &value) { - m_default = value; -} - -void PluginSettingsCheckbox::load() { - bool enabled = QSettings().value(key(), defaultValue()).toBool(); - setChecked(enabled); -} - -void PluginSettingsCheckbox::onClicked(bool checked) { - if (!key().isEmpty()) { - QSettings().setValue(key(), checked); - } -} diff --git a/app/src/maemo5/plugins/pluginsettingscheckbox.h b/app/src/maemo5/plugins/pluginsettingscheckbox.h deleted file mode 100644 index c91c5f0..0000000 --- a/app/src/maemo5/plugins/pluginsettingscheckbox.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PLUGINSETTINGSCHECKBOX_H -#define PLUGINSETTINGSCHECKBOX_H - -#include -#include - -class PluginSettingsCheckbox : public QCheckBox -{ - Q_OBJECT - -public: - explicit PluginSettingsCheckbox(QWidget *parent = 0); - - inline QString key() const { return m_key; } - inline QVariant defaultValue() const { return m_default; } - -public Q_SLOTS: - void setKey(const QString &key); - void setDefaultValue(const QVariant &value); - void load(); - -private Q_SLOTS: - void onClicked(bool checked); - -private: - QString m_key; - QVariant m_default; -}; - -#endif // PLUGINSETTINGSCHECKBOX_H diff --git a/app/src/maemo5/plugins/pluginsettingsdialog.cpp b/app/src/maemo5/plugins/pluginsettingsdialog.cpp deleted file mode 100644 index c3d7518..0000000 --- a/app/src/maemo5/plugins/pluginsettingsdialog.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "pluginsettingsdialog.h" -#include "pluginsettingsselector.h" -#include "pluginsettingscheckbox.h" -#include "pluginsettingsspinbox.h" -#include "pluginsettingslineedit.h" -#include "selectionmodel.h" -#include -#include -#include -#include -#include -#include -#include - -PluginSettingsDialog::PluginSettingsDialog(const QString &name, const QString &fileName, QWidget *parent) : - Dialog(parent) -{ - setWindowTitle(name); - QHBoxLayout *hbox = new QHBoxLayout(this); - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this); - QScrollArea *scrollArea = new QScrollArea(this); - QWidget *scrollWidget = new QWidget(scrollArea); - QVBoxLayout *vbox = new QVBoxLayout(scrollWidget); - vbox->setContentsMargins(0, 0, 0, 0); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setWidgetResizable(true); - scrollArea->setWidget(scrollWidget); - - QFile file(fileName); - - if (file.open(QIODevice::ReadOnly)) { - QXmlStreamReader reader; - reader.setDevice(&file); - reader.readNextStartElement(); - - while (!reader.atEnd()) { - if (!reader.attributes().isEmpty()) { - if (reader.name() == "group") { - vbox->addWidget(new QLabel(reader.attributes().value("title").toString(), this)); - } - else if (reader.name() == "list") { - PluginSettingsSelector *selector = new PluginSettingsSelector(reader.attributes().value("title") - .toString(), this); - selector->setKey(QString("%1/%2").arg(windowTitle()).arg(reader.attributes().value("key").toString())); - selector->setDefaultValue(reader.attributes().value("default").toString()); - reader.readNextStartElement(); - - while (reader.name() == "element") { - if (!reader.attributes().isEmpty()) { - selector->model()->append(reader.attributes().value("name").toString(), - reader.attributes().value("value").toString()); - } - - reader.readNextStartElement(); - } - - selector->load(); - vbox->addWidget(selector); - } - else if (reader.name() == "boolean") { - PluginSettingsCheckbox *checkbox = new PluginSettingsCheckbox(this); - checkbox->setText(reader.attributes().value("title").toString()); - checkbox->setKey(QString("%1/%2").arg(windowTitle()).arg(reader.attributes().value("key").toString())); - checkbox->setDefaultValue(reader.attributes().value("default").toString()); - checkbox->load(); - vbox->addWidget(checkbox); - } - else if (reader.name() == "integer") { - PluginSettingsSpinbox *spinbox = new PluginSettingsSpinbox(this); - spinbox->setKey(QString("%1/%2").arg(windowTitle()).arg(reader.attributes().value("key").toString())); - spinbox->setDefaultValue(reader.attributes().value("default").toString()); - spinbox->setMinimum(reader.attributes().value("min").toString().toInt()); - spinbox->setMaximum(reader.attributes().value("max").toString().toInt()); - spinbox->setSingleStep(reader.attributes().value("step").toString().toInt()); - spinbox->load(); - vbox->addWidget(new QLabel(reader.attributes().value("title").toString(), this)); - vbox->addWidget(spinbox); - } - else if (reader.name() == "text") { - PluginSettingsLineEdit *lineEdit = new PluginSettingsLineEdit(this); - lineEdit->setKey(QString("%1/%2").arg(windowTitle()).arg(reader.attributes().value("key").toString())); - lineEdit->setDefaultValue(reader.attributes().value("default").toString()); - lineEdit->load(); - vbox->addWidget(new QLabel(reader.attributes().value("title").toString(), this)); - vbox->addWidget(lineEdit); - } - } - - reader.readNextStartElement(); - } - - file.close(); - - hbox->addWidget(scrollArea, 0, Qt::AlignBottom); - hbox->addWidget(buttonBox, 0, Qt::AlignBottom); - } - else { - QMessageBox::critical(this, tr("Error"), file.errorString()); - } - - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); -} diff --git a/app/src/maemo5/plugins/pluginsettingslineedit.cpp b/app/src/maemo5/plugins/pluginsettingslineedit.cpp deleted file mode 100644 index a33858c..0000000 --- a/app/src/maemo5/plugins/pluginsettingslineedit.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "pluginsettingslineedit.h" -#include - -PluginSettingsLineEdit::PluginSettingsLineEdit(QWidget *parent) : - QLineEdit(parent) -{ - connect(this, SIGNAL(textEdited(QString)), this, SLOT(onTextEdited(QString))); -} - -void PluginSettingsLineEdit::setKey(const QString &key) { - m_key = key; -} - -void PluginSettingsLineEdit::setDefaultValue(const QVariant &value) { - m_default = value; -} - -void PluginSettingsLineEdit::load() { - QString text = QSettings().value(key(), defaultValue()).toString(); - setText(text); -} - -void PluginSettingsLineEdit::onTextEdited(const QString &text) { - if (!key().isEmpty()) { - QSettings().setValue(key(), text); - } -} diff --git a/app/src/maemo5/plugins/pluginsettingslineedit.h b/app/src/maemo5/plugins/pluginsettingslineedit.h deleted file mode 100644 index 41d06a0..0000000 --- a/app/src/maemo5/plugins/pluginsettingslineedit.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PLUGINSETTINGSLINEEDIT_H -#define PLUGINSETTINGSLINEEDIT_H - -#include -#include - -class PluginSettingsLineEdit : public QLineEdit -{ - Q_OBJECT - -public: - explicit PluginSettingsLineEdit(QWidget *parent = 0); - - inline QString key() const { return m_key; } - inline QVariant defaultValue() const { return m_default; } - -public Q_SLOTS: - void setKey(const QString &key); - void setDefaultValue(const QVariant &value); - void load(); - -private Q_SLOTS: - void onTextEdited(const QString &text); - -private: - QString m_key; - QVariant m_default; -}; - -#endif // PLUGINSETTINGSLINEEDIT_H diff --git a/app/src/maemo5/plugins/pluginsettingsselector.cpp b/app/src/maemo5/plugins/pluginsettingsselector.cpp deleted file mode 100644 index 6852b33..0000000 --- a/app/src/maemo5/plugins/pluginsettingsselector.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "pluginsettingsselector.h" -#include "selectionmodel.h" -#include -#include - -PluginSettingsSelector::PluginSettingsSelector(const QString &text, QWidget *parent) : - ValueSelector(text, parent) -{ - setModel(new SelectionModel(this)); -} - -void PluginSettingsSelector::setKey(const QString &key) { - m_key = key; -} - -void PluginSettingsSelector::setDefaultValue(const QVariant &value) { - m_default = value; -} - -void PluginSettingsSelector::load() { - if (!model()) { - return; - } - - QVariant value = QSettings().value(key(), defaultValue()); - - bool found = false; - int i = 0; - - while ((!found) && (i < model()->rowCount())) { - found = model()->data(model()->index(i, 0), SelectionModel::ValueRole) == value; - - if (found) { - m_selector->setCurrentIndex(i); - } - - i++; - } - - if (!found) { - m_selector->setCurrentIndex(0); - } -} - -void PluginSettingsSelector::onSelected() { - if (!key().isEmpty()) { - QSettings().setValue(key(), currentValue()); - } -} diff --git a/app/src/maemo5/plugins/pluginsettingsselector.h b/app/src/maemo5/plugins/pluginsettingsselector.h deleted file mode 100644 index 320644a..0000000 --- a/app/src/maemo5/plugins/pluginsettingsselector.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PLUGINSETTINGSSELECTOR_H -#define PLUGINSETTINGSSELECTOR_H - -#include "valueselector.h" -#include - -class PluginSettingsSelector : public ValueSelector -{ - Q_OBJECT - -public: - explicit PluginSettingsSelector(const QString &text, QWidget *parent = 0); - - inline QString key() const { return m_key; } - inline QVariant defaultValue() const { return m_default; } - -public Q_SLOTS: - void setKey(const QString &key); - void setDefaultValue(const QVariant &value); - void load(); - -private Q_SLOTS: - void onSelected(); - -private: - QString m_key; - QVariant m_default; -}; - -#endif // PLUGINSETTINGSSELECTOR_H diff --git a/app/src/maemo5/plugins/pluginsettingsslider.cpp b/app/src/maemo5/plugins/pluginsettingsslider.cpp deleted file mode 100644 index 08cb035..0000000 --- a/app/src/maemo5/plugins/pluginsettingsslider.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "pluginsettingsslider.h" -#include - -PluginSettingsSlider::PluginSettingsSlider(QWidget *parent) : - QSlider(parent) -{ - connect(this, SIGNAL(sliderReleased()), this, SLOT(onReleased())); -} - -void PluginSettingsSlider::setKey(const QString &key) { - m_key = key; -} - -void PluginSettingsSlider::setDefaultValue(const QVariant &value) { - m_default = value; -} - -void PluginSettingsSlider::load() { - int value = QSettings().value(key(), defaultValue()).toInt(); - setValue(value); -} - -void PluginSettingsSlider::onReleased() { - if (!key().isEmpty()) { - QSettings().setValue(key(), value()); - } -} diff --git a/app/src/maemo5/plugins/pluginsettingsspinbox.cpp b/app/src/maemo5/plugins/pluginsettingsspinbox.cpp deleted file mode 100644 index 6623117..0000000 --- a/app/src/maemo5/plugins/pluginsettingsspinbox.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "pluginsettingsspinbox.h" -#include - -PluginSettingsSpinbox::PluginSettingsSpinbox(QWidget *parent) : - QSpinBox(parent) -{ - connect(this, SIGNAL(editingFinished()), this, SLOT(onValueEdited())); -} - -void PluginSettingsSpinbox::setKey(const QString &key) { - m_key = key; -} - -void PluginSettingsSpinbox::setDefaultValue(const QVariant &value) { - m_default = value; -} - -void PluginSettingsSpinbox::load() { - int value = QSettings().value(key(), defaultValue()).toInt(); - setValue(value); -} - -void PluginSettingsSpinbox::onValueEdited() { - if (!key().isEmpty()) { - QSettings().setValue(key(), value()); - } -} diff --git a/app/src/maemo5/plugins/plugintrackswindow.cpp b/app/src/maemo5/plugins/plugintrackswindow.cpp index a9a378a..e302237 100644 --- a/app/src/maemo5/plugins/plugintrackswindow.cpp +++ b/app/src/maemo5/plugins/plugintrackswindow.cpp @@ -23,8 +23,8 @@ #include "nowplayingwindow.h" #include "plugindownloaddialog.h" #include "plugintrackwindow.h" -#include "settings.h" #include "trackdelegate.h" +#include "transfers.h" #include #include #include @@ -42,12 +42,8 @@ PluginTracksWindow::PluginTracksWindow(StackedWindow *parent) : PluginTrackModel::DurationStringRole, PluginTrackModel::ThumbnailUrlRole, PluginTrackModel::TitleRole, this)), m_reloadAction(new QAction(tr("Reload"), this)), - m_contextMenu(new QMenu(this)), - m_queueAction(new QAction(tr("Queue"), this)), - m_downloadAction(new QAction(tr("Download"), this)), - m_shareAction(new QAction(tr("Copy URL"), this)), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Tracks")); setCentralWidget(new QWidget); @@ -58,10 +54,6 @@ PluginTracksWindow::PluginTracksWindow(StackedWindow *parent) : m_reloadAction->setEnabled(false); - m_contextMenu->addAction(m_queueAction); - m_contextMenu->addAction(m_downloadAction); - m_contextMenu->addAction(m_shareAction); - m_label->hide(); m_layout = new QVBoxLayout(centralWidget()); @@ -79,9 +71,6 @@ PluginTracksWindow::PluginTracksWindow(StackedWindow *parent) : connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(m_delegate, SIGNAL(thumbnailClicked(QModelIndex)), this, SLOT(playTrack(QModelIndex))); connect(m_reloadAction, SIGNAL(triggered()), m_model, SLOT(reload())); - connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueTrack())); - connect(m_downloadAction, SIGNAL(triggered()), this, SLOT(downloadTrack())); - connect(m_shareAction, SIGNAL(triggered()), this, SLOT(shareTrack())); } PluginTracksWindow::~PluginTracksWindow() { @@ -99,18 +88,24 @@ void PluginTracksWindow::search(const QString &service, const QString &query, co m_model->search(query, order); } -void PluginTracksWindow::downloadTrack() { +void PluginTracksWindow::downloadTrack(const QModelIndex &index) { if (isBusy()) { return; } - - if (m_view->currentIndex().isValid()) { - QString id = m_view->currentIndex().data(PluginTrackModel::IdRole).toString(); - QString title = m_view->currentIndex().data(PluginTrackModel::TitleRole).toString(); - QUrl streamUrl = m_view->currentIndex().data(PluginTrackModel::StreamUrlRole).toString(); - PluginDownloadDialog *dialog = new PluginDownloadDialog(m_model->service(), id, streamUrl, title, this); - dialog->open(); + if (index.isValid()) { + const QString id = index.data(PluginTrackModel::IdRole).toString(); + const QString title = index.data(PluginTrackModel::TitleRole).toString(); + const QUrl streamUrl = index.data(PluginTrackModel::StreamUrlRole).toString(); + + PluginDownloadDialog dialog(m_model->service(), this); + dialog.list(id, streamUrl.isEmpty()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(m_model->service(), id, dialog.streamId(), streamUrl, title, + dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } } } @@ -126,19 +121,19 @@ void PluginTracksWindow::playTrack(const QModelIndex &index) { } } -void PluginTracksWindow::queueTrack() { +void PluginTracksWindow::queueTrack(const QModelIndex &index) { if (isBusy()) { return; } - if (PluginTrack *track = m_model->get(m_view->currentIndex().row())) { + if (PluginTrack *track = m_model->get(index.row())) { AudioPlayer::instance()->addTrack(track); QMaemo5InformationBox::information(this, tr("'%1' added to playback queue").arg(track->title())); } } -void PluginTracksWindow::shareTrack() { - if (const PluginTrack *track = m_model->get(m_view->currentIndex().row())) { +void PluginTracksWindow::shareTrack(const QModelIndex &index) { + if (const PluginTrack *track = m_model->get(index.row())) { Clipboard::instance()->setText(track->url().toString()); QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } @@ -156,9 +151,53 @@ void PluginTracksWindow::showTrack(const QModelIndex &index) { } void PluginTracksWindow::showContextMenu(const QPoint &pos) { - if ((!isBusy()) && (m_view->currentIndex().isValid())) { - m_downloadAction->setEnabled(m_model->data(m_view->currentIndex(), PluginTrackModel::DownloadableRole).toBool()); - m_contextMenu->popup(pos, m_queueAction); + if (isBusy()) { + return; + } + + const QModelIndex index = m_view->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (index.data(PluginTrackModel::DownloadableRole).toBool()) { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueTrack(index); + } + else if (action == downloadAction) { + downloadTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } + } + else { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } } } diff --git a/app/src/maemo5/plugins/plugintrackswindow.h b/app/src/maemo5/plugins/plugintrackswindow.h index 8c38c11..93f2263 100644 --- a/app/src/maemo5/plugins/plugintrackswindow.h +++ b/app/src/maemo5/plugins/plugintrackswindow.h @@ -26,7 +26,6 @@ class NowPlayingAction; class ListView; class QLabel; class QVBoxLayout; -class QMenu; class PluginTracksWindow : public StackedWindow { @@ -41,10 +40,10 @@ public Q_SLOTS: void search(const QString &service, const QString &query, const QString &order); private Q_SLOTS: - void downloadTrack(); + void downloadTrack(const QModelIndex &index); void playTrack(const QModelIndex &index); - void queueTrack(); - void shareTrack(); + void queueTrack(const QModelIndex &index); + void shareTrack(const QModelIndex &index); void showTrack(const QModelIndex &index); void showContextMenu(const QPoint &pos); @@ -60,10 +59,6 @@ private Q_SLOTS: ListView *m_view; TrackDelegate *m_delegate; QAction *m_reloadAction; - QMenu *m_contextMenu; - QAction *m_queueAction; - QAction *m_downloadAction; - QAction *m_shareAction; QLabel *m_label; QVBoxLayout *m_layout; }; diff --git a/app/src/maemo5/plugins/plugintrackwindow.cpp b/app/src/maemo5/plugins/plugintrackwindow.cpp index 38ba187..aa97fff 100644 --- a/app/src/maemo5/plugins/plugintrackwindow.cpp +++ b/app/src/maemo5/plugins/plugintrackwindow.cpp @@ -29,11 +29,10 @@ #include "plugindownloaddialog.h" #include "pluginplaylistwindow.h" #include "resources.h" -#include "resourcesplugins.h" -#include "settings.h" #include "textbrowser.h" -#include "utils.h" #include "trackdelegate.h" +#include "transfers.h" +#include "utils.h" #include #include #include @@ -67,22 +66,17 @@ PluginTrackWindow::PluginTrackWindow(const QString &service, const QString &id, m_descriptionLabel(new TextBrowser(this)), m_dateLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), - m_noTracksLabel(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), - m_noCommentsLabel(0), + m_noResultsLabel(new QLabel(QString("

%2

") + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)), m_reloadAction(new QAction(tr("Reload"), this)), m_queueAction(new QAction(tr("Queue"), this)), m_downloadAction(0), - m_shareAction(new QAction(tr("Copy URL"), this)), - m_contextMenu(new QMenu(this)), - m_relatedQueueAction(new QAction(tr("Queue"), this)), - m_relatedDownloadAction(new QAction(tr("Download"), this)), - m_relatedShareAction(new QAction(tr("Copy URL"), this)) + m_shareAction(new QAction(tr("Copy URL"), this)) { loadBaseUi(); connect(m_track, SIGNAL(statusChanged(ResourcesRequest::Status)), this, SLOT(onTrackStatusChanged(ResourcesRequest::Status))); - + m_track->loadTrack(service, id); } @@ -109,17 +103,12 @@ PluginTrackWindow::PluginTrackWindow(PluginTrack *track, StackedWindow *parent) m_descriptionLabel(new TextBrowser(this)), m_dateLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), - m_noTracksLabel(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), - m_noCommentsLabel(0), + m_noResultsLabel(new QLabel(QString("

%2

") + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)), m_reloadAction(new QAction(tr("Reload"), this)), m_queueAction(new QAction(tr("Queue"), this)), m_downloadAction(0), - m_shareAction(new QAction(tr("Copy URL"), this)), - m_contextMenu(new QMenu(this)), - m_relatedQueueAction(new QAction(tr("Queue"), this)), - m_relatedDownloadAction(new QAction(tr("Download"), this)), - m_relatedShareAction(new QAction(tr("Copy URL"), this)) + m_shareAction(new QAction(tr("Copy URL"), this)) { loadBaseUi(); loadTrackUi(); @@ -128,7 +117,7 @@ PluginTrackWindow::PluginTrackWindow(PluginTrack *track, StackedWindow *parent) if (!track->artistId().isEmpty()) { connect(m_artist, SIGNAL(statusChanged(ResourcesRequest::Status)), this, SLOT(onArtistStatusChanged(ResourcesRequest::Status))); - + m_artist->loadArtist(track->service(), track->artistId()); } } @@ -154,15 +143,12 @@ void PluginTrackWindow::loadBaseUi() { m_titleLabel->setWordWrap(true); m_dateLabel->setStyleSheet(QString("color: %1; font-size: 13pt").arg(palette().color(QPalette::Mid).name())); + m_dateLabel->hide(); m_artistLabel->setStyleSheet("font-size: 13pt"); m_artistLabel->hide(); m_reloadAction->setEnabled(false); - m_contextMenu->addAction(m_relatedQueueAction); - m_contextMenu->addAction(m_relatedDownloadAction); - m_contextMenu->addAction(m_relatedShareAction); - QWidget *scrollWidget = new QWidget(m_scrollArea); QGridLayout *grid = new QGridLayout(scrollWidget); grid->addWidget(m_thumbnail, 0, 0, 1, 2, Qt::AlignLeft); @@ -184,7 +170,7 @@ void PluginTrackWindow::loadBaseUi() { m_tabBar->setStyleSheet("QTabBar::tab { height: 40px; }"); m_stack->addWidget(m_relatedView); - m_stack->addWidget(m_noTracksLabel); + m_stack->addWidget(m_noResultsLabel); m_layout = new QGridLayout(centralWidget()); m_layout->addWidget(m_scrollArea, 0, 0, 2, 1); @@ -211,9 +197,6 @@ void PluginTrackWindow::loadBaseUi() { connect(m_descriptionLabel, SIGNAL(anchorClicked(QUrl)), this, SLOT(showResource(QUrl))); connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueTrack())); connect(m_shareAction, SIGNAL(triggered()), this, SLOT(shareTrack())); - connect(m_relatedQueueAction, SIGNAL(triggered()), this, SLOT(queueRelatedTrack())); - connect(m_relatedDownloadAction, SIGNAL(triggered()), this, SLOT(downloadRelatedTrack())); - connect(m_relatedShareAction, SIGNAL(triggered()), this, SLOT(shareRelatedTrack())); if (m_track->isDownloadable()) { m_downloadAction = new QAction(tr("Download"), this); @@ -234,18 +217,31 @@ void PluginTrackWindow::loadTrackUi() { setWindowTitle(m_track->title()); m_titleLabel->setText(m_track->title()); - m_dateLabel->setText(tr("Published on %1").arg(m_track->date())); m_descriptionLabel->setHtml(Utils::toRichText(m_track->description())); m_thumbnail->setSource(m_track->largeThumbnailUrl()); - if (ResourcesPlugins::instance()->resourceTypeIsSupported(m_track->service(), Resources::COMMENT)) { + const QString date = m_track->date(); + + if (!date.isEmpty()) { + m_dateLabel->setText(tr("Published on %1").arg(date)); + m_dateLabel->show(); + } + + if (!m_track->commentsId().isEmpty()) { m_tabBar->addTab(tr("Comments")); } } void PluginTrackWindow::getRelatedTracks() { - m_relatedModel->setService(m_track->service()); - m_relatedModel->list(m_track->id()); + const QString id = m_track->relatedTracksId(); + + if (!id.isEmpty()) { + m_relatedModel->setService(m_track->service()); + m_relatedModel->list(id); + } + else { + m_stack->setCurrentWidget(m_noResultsLabel); + } } void PluginTrackWindow::downloadTrack() { @@ -253,9 +249,14 @@ void PluginTrackWindow::downloadTrack() { return; } - PluginDownloadDialog *dialog = new PluginDownloadDialog(m_track->service(), m_track->id(), m_track->streamUrl(), - m_track->title(), this); - dialog->open(); + PluginDownloadDialog dialog(m_track->service(), this); + dialog.list(m_track->id(), m_track->streamUrl().isEmpty()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(m_track->service(), m_track->id(), dialog.streamId(), + m_track->streamUrl(), m_track->title(), dialog.category(), + dialog.customCommand(), dialog.customCommandOverrideEnabled()); + } } void PluginTrackWindow::playTrack() { @@ -282,14 +283,24 @@ void PluginTrackWindow::shareTrack() { QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } -void PluginTrackWindow::downloadRelatedTrack() { - if ((!isBusy()) && (m_relatedView->currentIndex().isValid())) { - QString id = m_relatedView->currentIndex().data(PluginTrackModel::IdRole).toString(); - QString title = m_relatedView->currentIndex().data(PluginTrackModel::TitleRole).toString(); - QUrl streamUrl = m_relatedView->currentIndex().data(PluginTrackModel::StreamUrlRole).toString(); +void PluginTrackWindow::downloadRelatedTrack(const QModelIndex &index) { + if (isBusy()) { + return; + } + + if (index.isValid()) { + const QString id = index.data(PluginTrackModel::IdRole).toString(); + const QString title = index.data(PluginTrackModel::TitleRole).toString(); + const QUrl streamUrl = index.data(PluginTrackModel::StreamUrlRole).toString(); - PluginDownloadDialog *dialog = new PluginDownloadDialog(m_track->service(), id, streamUrl, title, this); - dialog->open(); + PluginDownloadDialog dialog(m_relatedModel->service(), this); + dialog.list(id, streamUrl.isEmpty()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(m_relatedModel->service(), id, dialog.streamId(), streamUrl, + title, dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } } } @@ -305,19 +316,19 @@ void PluginTrackWindow::playRelatedTrack(const QModelIndex &index) { } } -void PluginTrackWindow::queueRelatedTrack() { +void PluginTrackWindow::queueRelatedTrack(const QModelIndex &index) { if (isBusy()) { return; } - if (PluginTrack *track = m_relatedModel->get(m_relatedView->currentIndex().row())) { + if (PluginTrack *track = m_relatedModel->get(index.row())) { AudioPlayer::instance()->addTrack(track); QMaemo5InformationBox::information(this, tr("'%1' added to playback queue").arg(track->title())); } } -void PluginTrackWindow::shareRelatedTrack() { - if (const PluginTrack *track = m_relatedModel->get(m_relatedView->currentIndex().row())) { +void PluginTrackWindow::shareRelatedTrack(const QModelIndex &index) { + if (const PluginTrack *track = m_relatedModel->get(index.row())) { Clipboard::instance()->setText(track->url().toString()); QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } @@ -346,10 +357,53 @@ void PluginTrackWindow::reload() { } void PluginTrackWindow::showContextMenu(const QPoint &pos) { - if ((!isBusy()) && (m_relatedView->currentIndex().isValid())) { - m_relatedDownloadAction->setEnabled(m_relatedModel->data(m_relatedView->currentIndex(), - PluginTrackModel::DownloadableRole).toBool()); - m_contextMenu->popup(pos, m_relatedQueueAction); + if (isBusy()) { + return; + } + + const QModelIndex index = m_relatedView->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (index.data(PluginTrackModel::DownloadableRole).toBool()) { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueRelatedTrack(index); + } + else if (action == downloadAction) { + downloadRelatedTrack(index); + } + else if (action == shareAction) { + shareRelatedTrack(index); + } + } + else { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueRelatedTrack(index); + } + else if (action == shareAction) { + shareRelatedTrack(index); + } } } @@ -367,9 +421,12 @@ void PluginTrackWindow::showArtist(const QModelIndex &index) { return; } - PluginArtistWindow *window = new PluginArtistWindow(m_track->service(), - index.data(PluginCommentModel::ArtistIdRole).toString(), this); - window->show(); + const QString id = index.data(PluginCommentModel::ArtistIdRole).toString(); + + if (!id.isEmpty()) { + PluginArtistWindow *window = new PluginArtistWindow(m_track->service(), id, this); + window->show(); + } } void PluginTrackWindow::showComments() { @@ -382,20 +439,17 @@ void PluginTrackWindow::showComments() { m_commentView->setUniformItemSizes(false); m_commentView->setModel(m_commentModel); m_commentView->setItemDelegate(m_commentDelegate); - m_noCommentsLabel = new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No comments found")), this); m_stack->addWidget(m_commentView); - m_stack->addWidget(m_noCommentsLabel); connect(m_commentDelegate, SIGNAL(thumbnailClicked(QModelIndex)), this, SLOT(showArtist(QModelIndex))); connect(m_commentModel, SIGNAL(statusChanged(ResourcesRequest::Status)), this, SLOT(onCommentModelStatusChanged(ResourcesRequest::Status))); - m_commentModel->list(m_track->id()); + m_commentModel->list(m_track->commentsId()); } if ((m_commentModel->rowCount() == 0) && (m_commentModel->status() != ResourcesRequest::Loading)) { - m_stack->setCurrentWidget(m_noCommentsLabel); + m_stack->setCurrentWidget(m_noResultsLabel); } else { m_stack->setCurrentWidget(m_commentView); @@ -404,7 +458,7 @@ void PluginTrackWindow::showComments() { void PluginTrackWindow::showRelatedTracks() { if ((m_relatedModel->rowCount() == 0) && (m_relatedModel->status() != ResourcesRequest::Loading)) { - m_stack->setCurrentWidget(m_noTracksLabel); + m_stack->setCurrentWidget(m_noResultsLabel); } else { m_stack->setCurrentWidget(m_relatedView); @@ -412,7 +466,7 @@ void PluginTrackWindow::showRelatedTracks() { } void PluginTrackWindow::showResource(const QUrl &url) { - QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); if (resource.value("service") != m_track->service()) { QDesktopServices::openUrl(url); diff --git a/app/src/maemo5/plugins/plugintrackwindow.h b/app/src/maemo5/plugins/plugintrackwindow.h index eeefff5..c34fc6f 100644 --- a/app/src/maemo5/plugins/plugintrackwindow.h +++ b/app/src/maemo5/plugins/plugintrackwindow.h @@ -58,10 +58,10 @@ private Q_SLOTS: void queueTrack(); void shareTrack(); - void downloadRelatedTrack(); + void downloadRelatedTrack(const QModelIndex &index); void playRelatedTrack(const QModelIndex &index); - void queueRelatedTrack(); - void shareRelatedTrack(); + void queueRelatedTrack(const QModelIndex &index); + void shareRelatedTrack(const QModelIndex &index); void showRelatedTrack(const QModelIndex &index); void reload(); @@ -105,16 +105,11 @@ private Q_SLOTS: TextBrowser *m_descriptionLabel; QLabel *m_dateLabel; QLabel *m_artistLabel; - QLabel *m_noTracksLabel; - QLabel *m_noCommentsLabel; + QLabel *m_noResultsLabel; QAction *m_reloadAction; QAction *m_queueAction; QAction *m_downloadAction; QAction *m_shareAction; - QMenu *m_contextMenu; - QAction *m_relatedQueueAction; - QAction *m_relatedDownloadAction; - QAction *m_relatedShareAction; QGridLayout *m_layout; }; diff --git a/app/src/maemo5/plugins/pluginview.cpp b/app/src/maemo5/plugins/pluginview.cpp index a30a506..a80241d 100644 --- a/app/src/maemo5/plugins/pluginview.cpp +++ b/app/src/maemo5/plugins/pluginview.cpp @@ -104,8 +104,18 @@ void PluginView::showPlaylists(const QString &name, const QString &id) { } void PluginView::showSearchDialog() { - PluginSearchDialog *dialog = new PluginSearchDialog(m_model->service(), StackedWindow::currentWindow()); - dialog->open(); + PluginSearchDialog dialog(m_model->service(), StackedWindow::currentWindow()); + + if (dialog.exec() == QDialog::Accepted) { + const QVariantMap resource = Resources::getResourceFromUrl(dialog.query()); + + if (resource.value("service") == m_model->service()) { + showResource(resource.value("type").toString(), resource.value("id").toString()); + } + else { + search(dialog.query(), dialog.type(), dialog.order()); + } + } } void PluginView::showTracks(const QString &name, const QString &id) { @@ -116,7 +126,7 @@ void PluginView::showTracks(const QString &name, const QString &id) { } void PluginView::onItemActivated(const QModelIndex &index) { - QVariantMap type = index.data(PluginNavModel::ValueRole).toMap(); + const QVariantMap type = index.data(PluginNavModel::ValueRole).toMap(); if (type.isEmpty()) { showSearchDialog(); diff --git a/app/src/maemo5/pluginsettingsdialog.cpp b/app/src/maemo5/pluginsettingsdialog.cpp new file mode 100644 index 0000000..26f2c6e --- /dev/null +++ b/app/src/maemo5/pluginsettingsdialog.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pluginsettingsdialog.h" +#include "pluginsettings.h" +#include "selectionmodel.h" +#include "valueselector.h" +#include +#include +#include +#include +#include +#include +#include +#include + +PluginSettingsDialog::PluginSettingsDialog(const QString &pluginId, const QVariantList &settings, QWidget *parent) : + QDialog(parent), + m_plugin(new PluginSettings(pluginId, this)), + m_scrollArea(new QScrollArea(this)), + m_container(new QWidget(m_scrollArea)), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this)), + m_vbox(new QVBoxLayout(m_container)), + m_layout(new QHBoxLayout(this)) +{ + setWindowTitle(tr("Plugin settings")); + + m_scrollArea->setWidget(m_container); + m_scrollArea->setWidgetResizable(true); + + m_vbox->setContentsMargins(0, 0, 0, 0); + + m_layout->addWidget(m_scrollArea); + m_layout->addWidget(m_buttonBox); + + foreach (const QVariant &setting, settings) { + addWidget(setting.toMap()); + } + + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +void PluginSettingsDialog::addCheckBox(const QString &label, const QString &key, bool value) { + QCheckBox *checkbox = new QCheckBox(label, m_container); + checkbox->setProperty("key", key); + checkbox->setChecked(value); + m_vbox->addWidget(checkbox); + connect(checkbox, SIGNAL(toggled(bool)), this, SLOT(setBooleanValue(bool))); +} + +void PluginSettingsDialog::addGroup(const QString &label, const QString &key, const QVariantList &settings) { + m_vbox->addWidget(new QLabel(QString("

%2

") + .arg(palette().color(QPalette::Mid).name()).arg(label), m_container)); + + foreach (const QVariant &setting, settings) { + addWidget(setting.toMap(), key); + } +} + +void PluginSettingsDialog::addLineEdit(const QString &label, const QString &key, const QString &value, bool isPassword) { + QLineEdit *edit = new QLineEdit(value, m_container); + edit->setProperty("key", key); + + if (isPassword) { + edit->setEchoMode(QLineEdit::Password); + } + + m_vbox->addWidget(new QLabel(label, m_container)); + m_vbox->addWidget(edit); + connect(edit, SIGNAL(textChanged(QString)), this, SLOT(setTextValue(QString))); +} + +void PluginSettingsDialog::addSpinBox(const QString &label, const QString &key, int minimum, int maximum, int step, + int value) { + QSpinBox *spinbox = new QSpinBox(m_container); + spinbox->setProperty("key", key); + spinbox->setMinimum(minimum); + spinbox->setMaximum(maximum); + spinbox->setSingleStep(step); + spinbox->setValue(value); + m_vbox->addWidget(new QLabel(label, m_container)); + m_vbox->addWidget(spinbox); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(setIntegerValue(int))); +} + +void PluginSettingsDialog::addValueSelector(const QString &label, const QString &key, const QVariantList &options, + const QVariant &value) { + ValueSelector *selector = new ValueSelector(label, m_container); + SelectionModel *model = new SelectionModel(selector); + selector->setProperty("key", key); + selector->setModel(model); + + foreach (const QVariant &var, options) { + const QVariantMap option = var.toMap(); + model->append(option.value("label").toString(), option.value("value")); + } + + selector->setValue(value); + m_vbox->addWidget(selector); + connect(selector, SIGNAL(valueChanged(QVariant)), this, SLOT(setListValue(QVariant))); +} + +void PluginSettingsDialog::addWidget(const QVariantMap &setting, const QString &group) { + QString key = setting.value("key").toString(); + + if (key.isEmpty()) { + return; + } + + if (!group.isEmpty()) { + key.prepend("/"); + key.prepend(group); + } + + const QString type = setting.value("type").toString(); + const QVariant value = m_plugin->value(key, setting.value("value")); + + if (type == "boolean") { + addCheckBox(setting.value("label").toString(), key, value.toBool()); + } + else if (type == "group") { + addGroup(setting.value("label").toString(), key, setting.value("settings").toList()); + } + else if (type == "integer") { + addSpinBox(setting.value("label").toString(), key, setting.value("minimum", 0).toInt(), + setting.value("maximum", 100).toInt(), setting.value("step", 1).toInt(), value.toInt()); + } + else if (type == "list") { + addValueSelector(setting.value("label").toString(), key, setting.value("options").toList(), value); + } + else if (type == "password") { + addLineEdit(setting.value("label").toString(), key, value.toString(), true); + } + else if (type == "text") { + addLineEdit(setting.value("label").toString(), key, value.toString()); + } +} + +void PluginSettingsDialog::setBooleanValue(bool value) { + if (const QObject *obj = sender()) { + m_plugin->setValue(obj->property("key").toString(), value); + } +} + +void PluginSettingsDialog::setIntegerValue(int value) { + if (const QObject *obj = sender()) { + m_plugin->setValue(obj->property("key").toString(), value); + } +} + +void PluginSettingsDialog::setListValue(const QVariant &value) { + if (const QObject *obj = sender()) { + m_plugin->setValue(obj->property("key").toString(), value); + } +} + +void PluginSettingsDialog::setTextValue(const QString &value) { + if (const QObject *obj = sender()) { + m_plugin->setValue(obj->property("key").toString(), value); + } +} diff --git a/app/src/maemo5/pluginsettingsdialog.h b/app/src/maemo5/pluginsettingsdialog.h new file mode 100644 index 0000000..b889471 --- /dev/null +++ b/app/src/maemo5/pluginsettingsdialog.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINSETTINGSDIALOG_H +#define PLUGINSETTINGSDIALOG_H + +#include +#include + +class PluginSettings; +class QDialogButtonBox; +class QHBoxLayout; +class QScrollArea; +class QVBoxLayout; + +class PluginSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PluginSettingsDialog(const QString &pluginId, const QVariantList &settings, QWidget *parent = 0); + +private Q_SLOTS: + void setBooleanValue(bool value); + void setIntegerValue(int value); + void setListValue(const QVariant &value); + void setTextValue(const QString &value); + +private: + void addCheckBox(const QString &label, const QString &key, bool value); + void addGroup(const QString &label, const QString &key, const QVariantList &settings); + void addLineEdit(const QString &label, const QString &key, const QString &value, bool isPassword = false); + void addSpinBox(const QString &label, const QString &key, int minimum, int maximum, int step, int value); + void addValueSelector(const QString &label, const QString &key, const QVariantList &options, const QVariant &value); + void addWidget(const QVariantMap &setting, const QString &group = QString()); + + PluginSettings *m_plugin; + + QScrollArea *m_scrollArea; + + QWidget *m_container; + + QDialogButtonBox *m_buttonBox; + + QVBoxLayout *m_vbox; + QHBoxLayout *m_layout; +}; + +#endif // PLUGINSETTINGSDIALOG_H diff --git a/app/src/maemo5/screen.cpp b/app/src/maemo5/screen.cpp index 56bf9df..41e772a 100644 --- a/app/src/maemo5/screen.cpp +++ b/app/src/maemo5/screen.cpp @@ -19,13 +19,9 @@ Screen* Screen::self = 0; -Screen::Screen(QObject *parent) : - QObject(parent) +Screen::Screen() : + QObject() { - if (!self) { - self = this; - } - QDBusConnection::systemBus().connect("", "/com/nokia/mce/signal", "com.nokia.mce.signal", @@ -35,13 +31,11 @@ Screen::Screen(QObject *parent) : } Screen::~Screen() { - if (self == this) { - self = 0; - } + self = 0; } Screen* Screen::instance() { - return self; + return self ? self : self = new Screen; } void Screen::onScreenLockStateChanged(const QString &state) { diff --git a/app/src/maemo5/screen.h b/app/src/maemo5/screen.h index 143f837..b71378b 100644 --- a/app/src/maemo5/screen.h +++ b/app/src/maemo5/screen.h @@ -24,7 +24,6 @@ class Screen : public QObject Q_OBJECT public: - explicit Screen(QObject *parent = 0); ~Screen(); static Screen* instance(); @@ -36,6 +35,8 @@ private slots: void screenLockStateChanged(bool locked); private: + Screen(); + static Screen* self; }; diff --git a/app/src/maemo5/searchhistorydialog.cpp b/app/src/maemo5/searchhistorydialog.cpp index 03cafd9..6927ce8 100644 --- a/app/src/maemo5/searchhistorydialog.cpp +++ b/app/src/maemo5/searchhistorydialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -35,7 +35,7 @@ SearchHistoryDialog::SearchHistoryDialog(QWidget *parent) : m_layout(new QHBoxLayout(this)) { setWindowTitle(tr("Search history")); - setMinimumHeight(340); + setMinimumHeight(360); m_view->setModel(m_model); m_view->addAction(m_removeAction); @@ -58,7 +58,7 @@ SearchHistoryDialog::SearchHistoryDialog(QWidget *parent) : m_filterBox->hide(); connect(m_model, SIGNAL(countChanged(int)), this, SLOT(onCountChanged(int))); - connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(chooseSearch(QModelIndex))); + connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(accept())); connect(m_filterBox, SIGNAL(textChanged(QString)), this, SLOT(onFilterTextChanged(QString))); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(removeSearch())); connect(m_clearButton, SIGNAL(clicked()), m_model, SLOT(clear())); @@ -66,6 +66,19 @@ SearchHistoryDialog::SearchHistoryDialog(QWidget *parent) : onCountChanged(m_model->rowCount()); } +QString SearchHistoryDialog::query() const { + return m_query; +} + +void SearchHistoryDialog::setQuery(const QString &query) { + m_query = query; +} + +void SearchHistoryDialog::accept() { + setQuery(m_view->currentIndex().data().toString()); + Dialog::accept(); +} + void SearchHistoryDialog::keyPressEvent(QKeyEvent *e) { if ((m_filterBox->isHidden()) && (e->key() >= Qt::Key_0) && (e->key() <= Qt::Key_Z)) { m_filterBox->setText(e->text()); @@ -73,11 +86,6 @@ void SearchHistoryDialog::keyPressEvent(QKeyEvent *e) { } } -void SearchHistoryDialog::chooseSearch(const QModelIndex &index) { - emit searchChosen(index.data().toString()); - accept(); -} - void SearchHistoryDialog::removeSearch() { m_model->removeSearch(m_view->currentIndex().data().toString()); } diff --git a/app/src/maemo5/searchhistorydialog.h b/app/src/maemo5/searchhistorydialog.h index f7f2407..60a9f34 100644 --- a/app/src/maemo5/searchhistorydialog.h +++ b/app/src/maemo5/searchhistorydialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -30,24 +30,29 @@ class QModelIndex; class SearchHistoryDialog : public Dialog { Q_OBJECT + + Q_PROPERTY(QString query READ query) public: explicit SearchHistoryDialog(QWidget *parent = 0); - + + QString query() const; + +public Q_SLOTS: + virtual void accept(); + protected: - void keyPressEvent(QKeyEvent *e); + virtual void keyPressEvent(QKeyEvent *e); private Q_SLOTS: - void chooseSearch(const QModelIndex &index); void removeSearch(); void onCountChanged(int count); void onFilterTextChanged(const QString &text); -Q_SIGNALS: - void searchChosen(const QString &query); - private: + void setQuery(const QString &query); + SearchHistoryModel *m_model; ListView *m_view; @@ -56,6 +61,8 @@ private Q_SLOTS: QDialogButtonBox *m_buttonBox; QPushButton *m_clearButton; QHBoxLayout *m_layout; + + QString m_query; }; #endif // SEARCHHISTORYDIALOG_H diff --git a/app/src/maemo5/settings.cpp b/app/src/maemo5/settings.cpp new file mode 100644 index 0000000..206f1ae --- /dev/null +++ b/app/src/maemo5/settings.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "settings.h" +#include "definitions.h" +#include "resources.h" +#include +#include + +Settings* Settings::self = 0; + +Settings::Settings() : + QObject() +{ +} + +Settings::~Settings() { + self = 0; +} + +Settings* Settings::instance() { + return self ? self : self = new Settings; +} + +QStringList Settings::categoryNames() { + QSettings settings; + settings.beginGroup("Categories"); + QStringList names = settings.childKeys(); + names.prepend(tr("Default")); + settings.endGroup(); + + return names; +} + +QList Settings::categories() { + QList list; + QSettings settings; + settings.beginGroup("Categories"); + + foreach (QString key, settings.childKeys()) { + Category category; + category.name = key; + category.path = settings.value(key).toString(); + list << category; + } + + settings.endGroup(); + + return list; +} + +void Settings::setCategories(const QList &c) { + QSettings settings; + settings.remove("Categories"); + settings.beginGroup("Categories"); + + foreach (Category category, c) { + settings.setValue(category.name, category.path); + } + + settings.endGroup(); + + if (self) { + emit self->categoriesChanged(); + } +} + +void Settings::addCategory(const QString &name, const QString &path) { + if (path != downloadPath(name)) { + setValue("Categories/" + name, path); + + if (self) { + emit self->categoriesChanged(); + } + } +} + +void Settings::removeCategory(const QString &name) { + QSettings settings; + settings.beginGroup("Categories"); + + if (settings.contains(name)) { + settings.remove(name); + + if (self) { + emit self->categoriesChanged(); + } + } + + settings.endGroup(); +} + +QString Settings::defaultCategory() { + return value("Transfers/defaultCategory", tr("Default")).toString(); +} + +void Settings::setDefaultCategory(const QString &category) { + if (category != defaultCategory()) { + setValue("Transfers/defaultCategory", category); + + if (self) { + emit self->defaultCategoryChanged(category); + } + } +} + +bool Settings::clipboardMonitorEnabled() { + return value("Content/clipboardMonitorEnabled", false).toBool(); +} + +void Settings::setClipboardMonitorEnabled(bool enabled) { + if (enabled != clipboardMonitorEnabled()) { + setValue("Content/clipboardMonitorEnabled", enabled); + + if (self) { + emit self->clipboardMonitorEnabledChanged(enabled); + } + } +} + +QString Settings::currentService() { + return value("Content/currentService", Resources::SOUNDCLOUD).toString(); +} + +void Settings::setCurrentService(const QString &service) { + if (service != currentService()) { + setValue("Content/currentService", service); + + if (self) { + emit self->currentServiceChanged(service); + } + } +} + +QString Settings::customTransferCommand() { + return value("Transfers/customCommand").toString(); +} + +void Settings::setCustomTransferCommand(const QString &command) { + if (command != customTransferCommand()) { + setValue("Transfers/customCommand", command); + + if (self) { + emit self->customTransferCommandChanged(command); + } + } +} + +bool Settings::customTransferCommandEnabled() { + return value("Transfers/customCommandEnabled", false).toBool(); +} + +void Settings::setCustomTransferCommandEnabled(bool enabled) { + if (enabled != customTransferCommandEnabled()) { + setValue("Transfers/customCommandEnabled", enabled); + + if (self) { + emit self->customTransferCommandEnabledChanged(enabled); + } + } +} + +QString Settings::defaultDownloadFormat(const QString &service) { + return value("DownloadFormats/" + service).toString(); +} + +void Settings::setDefaultDownloadFormat(const QString &service, const QString &format) { + if (format != defaultDownloadFormat(service)) { + setValue("DownloadFormats/" + service, format); + + if (self) { + emit self->downloadFormatsChanged(); + } + } +} + +QString Settings::defaultPlaybackFormat(const QString &service) { + return value("PlaybackFormats/" + service).toString(); +} + +void Settings::setDefaultPlaybackFormat(const QString &service, const QString &format) { + if (format != defaultPlaybackFormat(service)) { + setValue("PlaybackFormats/" + service, format); + + if (self) { + emit self->playbackFormatsChanged(); + } + } +} + +QString Settings::defaultSearchType(const QString &service) { + return value("Search/searchType/" + service).toString(); +} + +void Settings::setDefaultSearchType(const QString &service, const QString &type) { + if (type != defaultSearchType(service)) { + setValue("Search/searchType/" + service, type); + + if (self) { + emit self->defaultSearchTypeChanged(); + } + } +} + +QString Settings::downloadPath() { + QString path = value("Transfers/downloadPath", DOWNLOAD_PATH).toString(); + + if (!path.endsWith("/")) { + path.append("/"); + } + + return path; +} + +QString Settings::downloadPath(const QString &category) { + return value("Categories/" + category, downloadPath()).toString(); +} + +void Settings::setDownloadPath(const QString &path) { + if (path != downloadPath()) { + setValue("Transfers/downloadPath", path); + + if (self) { + emit self->downloadPathChanged(path); + } + } +} + +QString Settings::loggerFileName() { + return value("Logger/fileName", APP_CONFIG_PATH + "log").toString(); +} + +void Settings::setLoggerFileName(const QString &fileName) { + if (fileName != loggerFileName()) { + setValue("Logger/fileName", fileName); + + if (self) { + emit self->loggerFileNameChanged(fileName); + } + } +} + +int Settings::loggerVerbosity() { + return value("Logger/verbosity", 0).toInt(); +} + +void Settings::setLoggerVerbosity(int verbosity) { + if (verbosity != loggerVerbosity()) { + setValue("Logger/verbosity", verbosity); + + if (self) { + emit self->loggerVerbosityChanged(verbosity); + } + } +} + +int Settings::maximumConcurrentTransfers() { + return qBound(1, value("Transfers/maximumConcurrentTransfers", 1).toInt(), MAX_CONCURRENT_TRANSFERS); +} + +void Settings::setMaximumConcurrentTransfers(int maximum) { + if (maximum != maximumConcurrentTransfers()) { + maximum = qBound(1, maximum, MAX_CONCURRENT_TRANSFERS); + setValue("Transfers/maximumConcurrentTransfers", maximum); + + if (self) { + emit self->maximumConcurrentTransfersChanged(maximum); + } + } +} + +void Settings::setNetworkProxy() { + if (networkProxyEnabled()) { + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::ProxyType(networkProxyType()), + networkProxyHost(), networkProxyPort(), networkProxyUsername(), + networkProxyPassword())); + } + else { + QNetworkProxy::setApplicationProxy(QNetworkProxy()); + } +} + +bool Settings::networkProxyEnabled() { + return value("Network/networkProxyEnabled", false).toBool(); +} + +void Settings::setNetworkProxyEnabled(bool enabled) { + if (enabled != networkProxyEnabled()) { + setValue("Network/networkProxyEnabled", enabled); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +QString Settings::networkProxyHost() { + return value("Network/networkProxyHost").toString(); +} + +void Settings::setNetworkProxyHost(const QString &host) { + if (host != networkProxyHost()) { + setValue("Network/networkProxyHost", host); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +QString Settings::networkProxyPassword() { + return QByteArray::fromBase64(value("Network/networkProxyPassword").toByteArray()); +} + +void Settings::setNetworkProxyPassword(const QString &password) { + QByteArray pass = password.toUtf8().toBase64(); + + if (pass != networkProxyPassword()) { + setValue("Network/networkProxyPassword", pass); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +int Settings::networkProxyPort() { + return value("Network/networkProxyPort", 80).toInt(); +} + +void Settings::setNetworkProxyPort(int port) { + if (port != networkProxyPort()) { + setValue("Network/networkProxyPort", port); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +int Settings::networkProxyType() { + return value("Network/networkProxyType", QNetworkProxy::ProxyType(QNetworkProxy::HttpProxy)).toInt(); +} + +void Settings::setNetworkProxyType(int type) { + if (type != networkProxyType()) { + setValue("Network/networkProxyType", type); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +QString Settings::networkProxyUsername() { + return value("Network/networkProxyUsername").toString(); +} + +void Settings::setNetworkProxyUsername(const QString &username) { + if (username != networkProxyUsername()) { + setValue("Network/networkProxyUsername", username); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +bool Settings::restorePlaybackQueueOnStartup() { + return value("Playback/restorePlaybackQueueOnStartup", true).toBool(); +} + +void Settings::setRestorePlaybackQueueOnStartup(bool enabled) { + if (enabled != restorePlaybackQueueOnStartup()) { + setValue("Playback/restorePlaybackQueueOnStartup", enabled); + + if (self) { + emit self->restorePlaybackQueueOnStartupChanged(enabled); + } + } +} + +QStringList Settings::searchHistory() { + return value("Search/searchHistory").toStringList(); +} + +void Settings::setSearchHistory(const QStringList &searches) { + setValue("Search/searchHistory", searches); + + if (self) { + emit self->searchHistoryChanged(); + } +} + +void Settings::addSearch(const QString &query) { + QStringList searches = searchHistory(); + searches.removeOne(query); + searches.prepend(query); + setSearchHistory(searches); +} + +void Settings::removeSearch(const QString &query) { + QStringList searches = searchHistory(); + searches.removeOne(query); + setSearchHistory(searches); +} + +int Settings::sleepTimerDuration() { + return qMax(1, value("Playback/sleepTimerDuration", 30).toInt()); +} + +void Settings::setSleepTimerDuration(int duration) { + if ((duration != sleepTimerDuration()) && (duration > 0)) { + setValue("Playback/sleepTimerDuration", duration); + + if (self) { + emit self->sleepTimerDurationChanged(duration); + } + } +} + +bool Settings::startTransfersAutomatically() { + return value("Transfers/startTransfersAutomatically", true).toBool(); +} + +void Settings::setStartTransfersAutomatically(bool enabled) { + if (enabled != startTransfersAutomatically()) { + setValue("Transfers/startTransfersAutomatically", enabled); + + if (self) { + emit self->startTransfersAutomaticallyChanged(enabled); + } + } +} + +QByteArray Settings::transfersHeaderViewState() { + return value("UI/transfersHeaderViewState").toByteArray(); +} + +void Settings::setTransfersHeaderViewState(const QByteArray &state) { + setValue("UI/transfersHeaderViewState", state); +} + +QVariant Settings::value(const QString &key, const QVariant &defaultValue) { + return QSettings().value(key, defaultValue); +} + +void Settings::setValue(const QString &key, const QVariant &value) { + QSettings().setValue(key, value); +} diff --git a/app/src/maemo5/settings.h b/app/src/maemo5/settings.h new file mode 100644 index 0000000..771fef9 --- /dev/null +++ b/app/src/maemo5/settings.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include + +struct Category { + QString name; + QString path; +}; + +class Settings : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QStringList categoryNames READ categoryNames NOTIFY categoriesChanged) + Q_PROPERTY(QString defaultCategory READ defaultCategory WRITE setDefaultCategory NOTIFY defaultCategoryChanged) + Q_PROPERTY(bool clipboardMonitorEnabled READ clipboardMonitorEnabled WRITE setClipboardMonitorEnabled + NOTIFY clipboardMonitorEnabledChanged) + Q_PROPERTY(QString currentService READ currentService WRITE setCurrentService NOTIFY currentServiceChanged) + Q_PROPERTY(QString customTransferCommand READ customTransferCommand WRITE setCustomTransferCommand + NOTIFY customTransferCommandChanged) + Q_PROPERTY(bool customTransferCommandEnabled READ customTransferCommandEnabled WRITE setCustomTransferCommandEnabled + NOTIFY customTransferCommandEnabledChanged) + Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) + Q_PROPERTY(QString loggerFileName READ loggerFileName WRITE setLoggerFileName NOTIFY loggerFileNameChanged) + Q_PROPERTY(int loggerVerbosity READ loggerVerbosity WRITE setLoggerVerbosity NOTIFY loggerVerbosityChanged) + Q_PROPERTY(int maximumConcurrentTransfers READ maximumConcurrentTransfers WRITE setMaximumConcurrentTransfers + NOTIFY maximumConcurrentTransfersChanged) + Q_PROPERTY(bool networkProxyEnabled READ networkProxyEnabled WRITE setNetworkProxyEnabled + NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyHost READ networkProxyHost WRITE setNetworkProxyHost NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyPassword READ networkProxyPassword WRITE setNetworkProxyPassword + NOTIFY networkProxyChanged) + Q_PROPERTY(int networkProxyPort READ networkProxyPort WRITE setNetworkProxyPort NOTIFY networkProxyChanged) + Q_PROPERTY(int networkProxyType READ networkProxyType WRITE setNetworkProxyType NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyUsername READ networkProxyUsername WRITE setNetworkProxyUsername + NOTIFY networkProxyChanged) + Q_PROPERTY(bool restorePlaybackQueueOnStartup READ restorePlaybackQueueOnStartup + WRITE setRestorePlaybackQueueOnStartup NOTIFY restorePlaybackQueueOnStartupChanged) + Q_PROPERTY(QStringList searchHistory READ searchHistory WRITE setSearchHistory NOTIFY searchHistoryChanged) + Q_PROPERTY(int sleepTimerDuration READ sleepTimerDuration WRITE setSleepTimerDuration + NOTIFY sleepTimerDurationChanged) + Q_PROPERTY(bool startTransfersAutomatically READ startTransfersAutomatically WRITE setStartTransfersAutomatically + NOTIFY startTransfersAutomaticallyChanged) + Q_PROPERTY(QByteArray transfersHeaderViewState READ transfersHeaderViewState WRITE setTransfersHeaderViewState) + +public: + ~Settings(); + + static Settings* instance(); + + static QStringList categoryNames(); + static QList categories(); + static void setCategories(const QList &c); + + static bool clipboardMonitorEnabled(); + + static QString currentService(); + + static QString customTransferCommand(); + static bool customTransferCommandEnabled(); + + static QString defaultCategory(); + + static QString defaultDownloadFormat(const QString &service); + static QString defaultPlaybackFormat(const QString &service); + + static QString defaultSearchType(const QString &service); + + static QString downloadPath(); + static QString downloadPath(const QString &category); + + static QString loggerFileName(); + static int loggerVerbosity(); + + static int maximumConcurrentTransfers(); + + static bool networkProxyEnabled(); + static QString networkProxyHost(); + static QString networkProxyPassword(); + static int networkProxyPort(); + static int networkProxyType(); + static QString networkProxyUsername(); + + static bool restorePlaybackQueueOnStartup(); + + static QStringList searchHistory(); + + static int sleepTimerDuration(); + + static bool startTransfersAutomatically(); + + static QByteArray transfersHeaderViewState(); + + static QVariant value(const QString &key, const QVariant &defaultValue = QVariant()); + +public Q_SLOTS: + static void addCategory(const QString &name, const QString &path); + static void setDefaultCategory(const QString &category); + static void removeCategory(const QString &name); + + static void setClipboardMonitorEnabled(bool enabled); + + static void setCurrentService(const QString &service); + + static void setCustomTransferCommand(const QString &command); + static void setCustomTransferCommandEnabled(bool enabled); + + static void setDefaultDownloadFormat(const QString &service, const QString &format); + static void setDefaultPlaybackFormat(const QString &service, const QString &format); + + static void setDefaultSearchType(const QString &service, const QString &type); + + static void setDownloadPath(const QString &path); + + static void setLoggerFileName(const QString &fileName); + static void setLoggerVerbosity(int verbosity); + + static void setMaximumConcurrentTransfers(int maximum); + + static void setNetworkProxy(); + static void setNetworkProxyEnabled(bool enabled); + static void setNetworkProxyHost(const QString &host); + static void setNetworkProxyPassword(const QString &password); + static void setNetworkProxyPort(int port); + static void setNetworkProxyType(int type); + static void setNetworkProxyUsername(const QString &username); + + static void setRestorePlaybackQueueOnStartup(bool enabled); + + static void setSearchHistory(const QStringList &searches); + static void addSearch(const QString &query); + static void removeSearch(const QString &query); + + static void setSleepTimerDuration(int duration); + + static void setStartTransfersAutomatically(bool enabled); + + static void setTransfersHeaderViewState(const QByteArray &state); + + static void setValue(const QString &key, const QVariant &value); + +Q_SIGNALS: + void categoriesChanged(); + void defaultCategoryChanged(const QString &category); + void clipboardMonitorEnabledChanged(bool enabled); + void currentServiceChanged(const QString &service); + void customTransferCommandChanged(const QString &command); + void customTransferCommandEnabledChanged(bool enabled); + void defaultSearchTypeChanged(); + void downloadFormatsChanged(); + void downloadPathChanged(const QString &path); + void loggerFileNameChanged(const QString &fileName); + void loggerVerbosityChanged(int verbosity); + void maximumConcurrentTransfersChanged(int maximum); + void networkProxyChanged(); + void playbackFormatsChanged(); + void restorePlaybackQueueOnStartupChanged(bool enabled); + void searchHistoryChanged(); + void sleepTimerDurationChanged(int duration); + void startTransfersAutomaticallyChanged(bool enabled); + +private: + Settings(); + + static Settings *self; +}; + +#endif // SETTINGS_H diff --git a/app/src/maemo5/settingsdialog.cpp b/app/src/maemo5/settingsdialog.cpp index 401aac1..1cdb570 100644 --- a/app/src/maemo5/settingsdialog.cpp +++ b/app/src/maemo5/settingsdialog.cpp @@ -19,55 +19,72 @@ #include "concurrenttransfersmodel.h" #include "listview.h" #include "networkproxydialog.h" +#include "pluginconfigmodel.h" #include "pluginsettingsdialog.h" -#include "pluginsettingsmodel.h" #include "settings.h" #include "valueselector.h" -#include #include -#include -#include #include -#include #include +#include +#include +#include +#include +#include +#include SettingsDialog::SettingsDialog(QWidget *parent) : Dialog(parent), m_transfersModel(new ConcurrentTransfersModel(this)), - m_pluginModel(new PluginSettingsModel(this)), + m_pluginModel(new PluginConfigModel(this)), m_transfersSelector(new ValueSelector(tr("Maximum concurrent transfers"), this)), m_pluginView(new ListView(this)), m_scrollArea(new QScrollArea(this)), m_downloadPathSelector(new QMaemo5ValueButton(tr("Default download path"), this)), + m_restoreQueueCheckBox(new QCheckBox(tr("Restore playback queue on startup"), this)), m_clipboardCheckBox(new QCheckBox(tr("Monitor clipboard for URLs"), this)), m_transfersCheckBox(new QCheckBox(tr("Start transfers automatically"), this)), + m_sleepTimerSpinBox(new QSpinBox(this)), m_categoriesButton(new QPushButton(tr("Categories"), this)), m_proxyButton(new QPushButton(tr("Network proxy"), this)), m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this)), m_layout(new QHBoxLayout(this)) { setWindowTitle(tr("Settings")); + setMinimumHeight(360); - m_downloadPathSelector->setValueText(Settings::instance()->downloadPath()); + m_downloadPathSelector->setValueText(Settings::downloadPath()); m_transfersSelector->setModel(m_transfersModel); - m_transfersSelector->setValue(Settings::instance()->maximumConcurrentTransfers()); + m_transfersSelector->setValue(Settings::maximumConcurrentTransfers()); m_pluginView->setModel(m_pluginModel); m_pluginView->setFixedHeight(m_pluginModel->rowCount() > 0 ? m_pluginModel->rowCount() * m_pluginView->sizeHintForRow(0) : 0); - m_clipboardCheckBox->setChecked(Settings::instance()->clipboardMonitorEnabled()); - m_transfersCheckBox->setChecked(Settings::instance()->startTransfersAutomatically()); + m_restoreQueueCheckBox->setChecked(Settings::restorePlaybackQueueOnStartup()); + m_clipboardCheckBox->setChecked(Settings::clipboardMonitorEnabled()); + m_transfersCheckBox->setChecked(Settings::startTransfersAutomatically()); + m_sleepTimerSpinBox->setRange(1, 1000); + m_sleepTimerSpinBox->setSuffix(tr(" minutes")); + m_sleepTimerSpinBox->setValue(Settings::sleepTimerDuration()); + + const QString midColor = palette().color(QPalette::Mid).name(); QWidget *scrollWidget = new QWidget(m_scrollArea); QVBoxLayout *vbox = new QVBoxLayout(scrollWidget); - vbox->addWidget(new QLabel(tr("Media/content"), this)); + vbox->addWidget(new QLabel(QString("

%2

").arg(midColor).arg(tr("Media/content")), this), + 0, Qt::AlignHCenter); vbox->addWidget(m_downloadPathSelector); vbox->addWidget(m_clipboardCheckBox); - vbox->addWidget(new QLabel(tr("Transfers"), this)); + vbox->addWidget(m_restoreQueueCheckBox); + vbox->addWidget(new QLabel(tr("Sleep timer duration"), this)); + vbox->addWidget(m_sleepTimerSpinBox); + vbox->addWidget(new QLabel(QString("

%2

").arg(midColor).arg(tr("Transfers")), this), + 0, Qt::AlignHCenter); vbox->addWidget(m_transfersCheckBox); vbox->addWidget(m_transfersSelector); vbox->addWidget(m_proxyButton); vbox->addWidget(m_categoriesButton); - vbox->addWidget(new QLabel(tr("Plugins"), this)); + vbox->addWidget(new QLabel(QString("

%2

").arg(midColor).arg(tr("Plugins")), this), + 0, Qt::AlignHCenter); vbox->addWidget(m_pluginView); vbox->setContentsMargins(0, 0, 0, 0); m_scrollArea->setWidget(scrollWidget); @@ -77,8 +94,14 @@ SettingsDialog::SettingsDialog(QWidget *parent) : m_layout->addWidget(m_buttonBox); connect(m_downloadPathSelector, SIGNAL(clicked()), this, SLOT(showFileDialog())); - connect(m_clipboardCheckBox, SIGNAL(toggled(bool)), Settings::instance(), SLOT(setClipboardMonitorEnabled(bool))); - connect(m_transfersCheckBox, SIGNAL(toggled(bool)), Settings::instance(), SLOT(setStartTransfersAutomatically(bool))); + connect(m_restoreQueueCheckBox, SIGNAL(toggled(bool)), + Settings::instance(), SLOT(setRestorePlaybackQueueOnStartup(bool))); + connect(m_clipboardCheckBox, SIGNAL(toggled(bool)), + Settings::instance(), SLOT(setClipboardMonitorEnabled(bool))); + connect(m_transfersCheckBox, SIGNAL(toggled(bool)), + Settings::instance(), SLOT(setStartTransfersAutomatically(bool))); + connect(m_sleepTimerSpinBox, SIGNAL(valueChanged(int)), + Settings::instance(), SLOT(setSleepTimerDuration(int))); connect(m_proxyButton, SIGNAL(clicked()), this, SLOT(showNetworkProxyDialog())); connect(m_categoriesButton, SIGNAL(clicked()), this, SLOT(showCategoriesDialog())); connect(m_pluginView, SIGNAL(activated(QModelIndex)), this, SLOT(showPluginDialog(QModelIndex))); @@ -87,28 +110,32 @@ SettingsDialog::SettingsDialog(QWidget *parent) : } void SettingsDialog::showCategoriesDialog() { - CategoriesDialog *dialog = new CategoriesDialog(this); - dialog->open(); + CategoriesDialog(this).exec(); } void SettingsDialog::showFileDialog() { - QString path = QFileDialog::getExistingDirectory(this, tr("Default download path"), - Settings::instance()->downloadPath()); + const QString path = QFileDialog::getExistingDirectory(this, tr("Default download path"), + Settings::downloadPath()); if (!path.isEmpty()) { m_downloadPathSelector->setValueText(path); - Settings::instance()->setDownloadPath(path); + Settings::setDownloadPath(path); } } void SettingsDialog::showNetworkProxyDialog() { - NetworkProxyDialog *dialog = new NetworkProxyDialog(this); - dialog->open(); + NetworkProxyDialog(this).exec(); } void SettingsDialog::showPluginDialog(const QModelIndex &index) { - PluginSettingsDialog *dialog = new PluginSettingsDialog(index.data(PluginSettingsModel::NameRole).toString(), - index.data(PluginSettingsModel::ValueRole).toString(), - this); - dialog->open(); + const QVariantList settings = index.data(PluginConfigModel::SettingsRole).toList(); + + if (settings.isEmpty()) { + QMaemo5InformationBox::information(this, tr("No settings for this plugin")); + } + else { + PluginSettingsDialog dialog(index.data(PluginConfigModel::IdRole).toString(), settings, this); + dialog.setWindowTitle(index.data(PluginConfigModel::DisplayNameRole).toString()); + dialog.exec(); + } } diff --git a/app/src/maemo5/settingsdialog.h b/app/src/maemo5/settingsdialog.h index 12d0b19..95a977b 100644 --- a/app/src/maemo5/settingsdialog.h +++ b/app/src/maemo5/settingsdialog.h @@ -20,7 +20,7 @@ #include "dialog.h" class ConcurrentTransfersModel; -class PluginSettingsModel; +class PluginConfigModel; class ListView; class ValueSelector; class QPushButton; @@ -30,6 +30,7 @@ class QDialogButtonBox; class QHBoxLayout; class QModelIndex; class QScrollArea; +class QSpinBox; class SettingsDialog : public Dialog { @@ -46,14 +47,16 @@ private Q_SLOTS: private: ConcurrentTransfersModel *m_transfersModel; - PluginSettingsModel *m_pluginModel; + PluginConfigModel *m_pluginModel; ValueSelector *m_transfersSelector; ListView *m_pluginView; QScrollArea *m_scrollArea; QMaemo5ValueButton *m_downloadPathSelector; + QCheckBox *m_restoreQueueCheckBox; QCheckBox *m_clipboardCheckBox; QCheckBox *m_transfersCheckBox; + QSpinBox *m_sleepTimerSpinBox; QPushButton *m_categoriesButton; QPushButton *m_proxyButton; QDialogButtonBox *m_buttonBox; diff --git a/app/src/maemo5/soundcloud/soundcloudaccountswindow.cpp b/app/src/maemo5/soundcloud/soundcloudaccountswindow.cpp index fedeb38..c07f0e7 100644 --- a/app/src/maemo5/soundcloud/soundcloudaccountswindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudaccountswindow.cpp @@ -54,6 +54,7 @@ SoundCloudAccountsWindow::SoundCloudAccountsWindow(StackedWindow *parent) : m_layout = new QVBoxLayout(centralWidget()); m_layout->addWidget(m_label); m_layout->addWidget(m_view); + m_layout->setContentsMargins(0, 11, 0, 0); connect(m_model, SIGNAL(countChanged(int)), this, SLOT(onModelCountChanged(int))); connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(selectAccount(QModelIndex))); @@ -66,10 +67,10 @@ SoundCloudAccountsWindow::SoundCloudAccountsWindow(StackedWindow *parent) : void SoundCloudAccountsWindow::initAuthRequest() { if (!m_authRequest) { m_authRequest = new QSoundCloud::AuthenticationRequest(this); - m_authRequest->setClientId(SoundCloud::instance()->clientId()); - m_authRequest->setClientSecret(SoundCloud::instance()->clientSecret()); - m_authRequest->setRedirectUri(SoundCloud::instance()->redirectUri()); - m_authRequest->setScopes(SoundCloud::instance()->scopes()); + m_authRequest->setClientId(SoundCloud::clientId()); + m_authRequest->setClientSecret(SoundCloud::clientSecret()); + m_authRequest->setRedirectUri(SoundCloud::redirectUri()); + m_authRequest->setScopes(SoundCloud::scopes()); connect(m_authRequest, SIGNAL(finished()), this, SLOT(onAuthRequestFinished())); } } @@ -77,11 +78,11 @@ void SoundCloudAccountsWindow::initAuthRequest() { void SoundCloudAccountsWindow::initUserRequest() { if (!m_userRequest) { m_userRequest = new QSoundCloud::ResourcesRequest(this); - m_userRequest->setClientId(SoundCloud::instance()->clientId()); - m_userRequest->setClientSecret(SoundCloud::instance()->clientSecret()); + m_userRequest->setClientId(SoundCloud::clientId()); + m_userRequest->setClientSecret(SoundCloud::clientSecret()); if (m_authRequest) { - QVariantMap token = m_authRequest->result().toMap(); + const QVariantMap token = m_authRequest->result().toMap(); m_userRequest->setAccessToken(token.value("access_token").toString()); m_userRequest->setRefreshToken(token.value("refresh_token").toString()); } @@ -92,7 +93,7 @@ void SoundCloudAccountsWindow::initUserRequest() { void SoundCloudAccountsWindow::removeAccount() { if (m_view->currentIndex().isValid()) { - QString username = m_view->currentIndex().data(SoundCloudAccountModel::UsernameRole).toString(); + const QString username = m_view->currentIndex().data(SoundCloudAccountModel::UsernameRole).toString(); if (m_model->removeAccount(m_view->currentIndex().row())) { QMaemo5InformationBox::information(this, tr("Account '%1' removed. Please visit the SoundCloud website to \ @@ -116,19 +117,18 @@ void SoundCloudAccountsWindow::selectAccount(const QModelIndex &index) { } void SoundCloudAccountsWindow::showAuthDialog() { - SoundCloudAuthDialog *dialog = new SoundCloudAuthDialog(this); - dialog->open(); - connect(dialog, SIGNAL(codeReady(QString)), this, SLOT(submitCode(QString))); -} + SoundCloudAuthDialog dialog(this); + dialog.login(); -void SoundCloudAccountsWindow::submitCode(const QString &code) { - initAuthRequest(); - showProgressIndicator(); - m_authRequest->exchangeCodeForAccessToken(code); + if (dialog.exec() == QDialog::Accepted) { + initAuthRequest(); + showProgressIndicator(); + m_authRequest->exchangeCodeForAccessToken(dialog.code()); + } } void SoundCloudAccountsWindow::onAuthRequestFinished() { - QVariantMap result = m_authRequest->result().toMap(); + const QVariantMap result = m_authRequest->result().toMap(); if (m_authRequest->status() == QSoundCloud::AuthenticationRequest::Ready) { if (result.contains("access_token")) { @@ -147,7 +147,7 @@ void SoundCloudAccountsWindow::onAuthRequestFinished() { void SoundCloudAccountsWindow::onUserRequestFinished() { if (m_userRequest->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap user = m_userRequest->result().toMap(); + const QVariantMap user = m_userRequest->result().toMap(); if (!user.isEmpty()) { m_view->setEnabled(true); @@ -156,7 +156,7 @@ void SoundCloudAccountsWindow::onUserRequestFinished() { if (m_model->addAccount(user.value("id").toString(), user.value("username").toString(), m_userRequest->accessToken(), m_userRequest->refreshToken(), - SoundCloud::instance()->scopes().join(" "))) { + SoundCloud::scopes().join(" "))) { QMaemo5InformationBox::information(this, tr("You are signed in to account '%1'") .arg(user.value("username").toString())); } diff --git a/app/src/maemo5/soundcloud/soundcloudaccountswindow.h b/app/src/maemo5/soundcloud/soundcloudaccountswindow.h index 0416e54..c2f592e 100644 --- a/app/src/maemo5/soundcloud/soundcloudaccountswindow.h +++ b/app/src/maemo5/soundcloud/soundcloudaccountswindow.h @@ -47,7 +47,6 @@ private Q_SLOTS: void selectAccount(const QModelIndex &index); void showAuthDialog(); - void submitCode(const QString &code); void onAuthRequestFinished(); void onUserRequestFinished(); diff --git a/app/src/maemo5/soundcloud/soundcloudartistswindow.cpp b/app/src/maemo5/soundcloud/soundcloudartistswindow.cpp index 255c832..62314f9 100644 --- a/app/src/maemo5/soundcloud/soundcloudartistswindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudartistswindow.cpp @@ -19,7 +19,6 @@ #include "imagecache.h" #include "listview.h" #include "nowplayingaction.h" -#include "settings.h" #include "soundcloudartistwindow.h" #include #include @@ -35,7 +34,7 @@ SoundCloudArtistsWindow::SoundCloudArtistsWindow(StackedWindow *parent) : m_view(new ListView(this)), m_reloadAction(new QAction(tr("Reload"), this)), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No artists found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Artists")); setCentralWidget(new QWidget); diff --git a/app/src/maemo5/soundcloud/soundcloudartistwindow.cpp b/app/src/maemo5/soundcloud/soundcloudartistwindow.cpp index 1845092..c32fbe3 100644 --- a/app/src/maemo5/soundcloud/soundcloudartistwindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudartistwindow.cpp @@ -21,7 +21,6 @@ #include "navdelegate.h" #include "nowplayingaction.h" #include "resources.h" -#include "settings.h" #include "soundcloud.h" #include "soundcloudartistswindow.h" #include "soundcloudplaylistswindow.h" @@ -81,7 +80,7 @@ SoundCloudArtistWindow::SoundCloudArtistWindow(SoundCloudArtist *artist, Stacked this, SLOT(onArtistUpdateStatusChanged(QSoundCloud::ResourcesRequest::Status))); connect(m_artist, SIGNAL(followedChanged()), this, SLOT(onArtistFollowedChanged())); - if ((!m_artist->isFollowed()) && (!SoundCloud::instance()->userId().isEmpty())) { + if ((!m_artist->isFollowed()) && (!SoundCloud::userId().isEmpty())) { m_artist->checkIfFollowed(); } } @@ -130,8 +129,8 @@ void SoundCloudArtistWindow::loadBaseUi() { void SoundCloudArtistWindow::loadArtistUi() { setWindowTitle(m_artist->name()); - m_followButton->setEnabled((!SoundCloud::instance()->userId().isEmpty()) - && (m_artist->id() != SoundCloud::instance()->userId())); + m_followButton->setEnabled((!SoundCloud::userId().isEmpty()) + && (m_artist->id() != SoundCloud::userId())); m_followButton->setText(m_artist->isFollowed() ? tr("Unfollow") : tr("Follow")); m_titleLabel->setText(m_titleLabel->fontMetrics().elidedText(m_artist->name(), Qt::ElideRight, 250)); m_statsLabel->setText(tr("%1 %2\n%3 %4").arg(Utils::formatLargeNumber(m_artist->followersCount())) @@ -183,7 +182,7 @@ void SoundCloudArtistWindow::showTracks() { } void SoundCloudArtistWindow::showResource(const QUrl &url) { - QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); if (resource.value("service") != Resources::SOUNDCLOUD) { QDesktopServices::openUrl(url); @@ -262,7 +261,7 @@ void SoundCloudArtistWindow::onArtistStatusChanged(QSoundCloud::ResourcesRequest this, SLOT(onArtistUpdateStatusChanged(QSoundCloud::ResourcesRequest::Status))); connect(m_artist, SIGNAL(followedChanged()), this, SLOT(onArtistFollowedChanged())); - if ((!m_artist->isFollowed()) && (!SoundCloud::instance()->userId().isEmpty())) { + if ((!m_artist->isFollowed()) && (!SoundCloud::userId().isEmpty())) { m_artist->checkIfFollowed(); } } diff --git a/app/src/maemo5/soundcloud/soundcloudauthdialog.cpp b/app/src/maemo5/soundcloud/soundcloudauthdialog.cpp index 53d56cd..0ef1dab 100644 --- a/app/src/maemo5/soundcloud/soundcloudauthdialog.cpp +++ b/app/src/maemo5/soundcloud/soundcloudauthdialog.cpp @@ -15,12 +15,10 @@ */ #include "soundcloudauthdialog.h" +#include "logger.h" #include "soundcloud.h" #include "webview.h" #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudAuthDialog::SoundCloudAuthDialog(QWidget *parent) : Dialog(parent), @@ -36,17 +34,24 @@ SoundCloudAuthDialog::SoundCloudAuthDialog(QWidget *parent) : connect(m_view, SIGNAL(loadFinished(bool)), this, SLOT(hideProgressIndicator())); } -void SoundCloudAuthDialog::showEvent(QShowEvent *e) { - Dialog::showEvent(e); - m_view->setUrl(SoundCloud::instance()->authUrl()); +QString SoundCloudAuthDialog::code() const { + return m_code; +} + +void SoundCloudAuthDialog::setCode(const QString &code) { + m_code = code; +} + +void SoundCloudAuthDialog::login() { + setCode(QString()); + m_view->setUrl(SoundCloud::authUrl()); } void SoundCloudAuthDialog::onWebViewUrlChanged(const QUrl &url) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudAuthDialog::onWebViewUrlChanged" << url; -#endif + Logger::log("SoundCloudAuthDialog::onWebViewUrlChanged(): " + url.toString()); + if (url.hasQueryItem("code")) { - emit codeReady(url.queryItemValue("code")); + setCode(url.queryItemValue("code")); accept(); } } diff --git a/app/src/maemo5/soundcloud/soundcloudauthdialog.h b/app/src/maemo5/soundcloud/soundcloudauthdialog.h index 7324c51..b4082f7 100644 --- a/app/src/maemo5/soundcloud/soundcloudauthdialog.h +++ b/app/src/maemo5/soundcloud/soundcloudauthdialog.h @@ -26,22 +26,27 @@ class QVBoxLayout; class SoundCloudAuthDialog : public Dialog { Q_OBJECT + + Q_PROPERTY(QString code READ code) public: explicit SoundCloudAuthDialog(QWidget *parent = 0); + + QString code() const; -protected: - void showEvent(QShowEvent *e); +public Q_SLOTS: + void login(); private Q_SLOTS: void onWebViewUrlChanged(const QUrl &url); -Q_SIGNALS: - void codeReady(const QString &code); - private: + void setCode(const QString &code); + WebView *m_view; QVBoxLayout *m_layout; + + QString m_code; }; #endif // SOUNDCLOUDAUTHDIALOG_H diff --git a/app/src/maemo5/soundcloud/soundclouddownloaddialog.cpp b/app/src/maemo5/soundcloud/soundclouddownloaddialog.cpp index e617525..a8a624f 100644 --- a/app/src/maemo5/soundcloud/soundclouddownloaddialog.cpp +++ b/app/src/maemo5/soundcloud/soundclouddownloaddialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -18,76 +18,108 @@ #include "categorynamemodel.h" #include "resources.h" #include "settings.h" -#include "transfers.h" #include "valueselector.h" +#include +#include +#include +#include #include #include -#include +#include #include -SoundCloudDownloadDialog::SoundCloudDownloadDialog(const QString &resourceId, const QString &title, QWidget *parent) : +SoundCloudDownloadDialog::SoundCloudDownloadDialog(QWidget *parent) : Dialog(parent), - m_id(resourceId), - m_title(title), m_streamModel(new SoundCloudStreamModel(this)), m_categoryModel(new CategoryNameModel(this)), + m_scrollArea(new QScrollArea(this)), + m_commandCheckBox(new QCheckBox(tr("Override global custom command"), this)), + m_commandEdit(new QLineEdit(this)), m_streamSelector(new ValueSelector(tr("Audio format"), this)), m_categorySelector(new ValueSelector(tr("Category"), this)), m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Vertical, this)), - m_layout(new QGridLayout(this)) + m_layout(new QHBoxLayout(this)) { - setWindowTitle(tr("Download track")); + setWindowTitle(tr("Download")); + setMinimumHeight(360); m_streamSelector->setModel(m_streamModel); m_categorySelector->setModel(m_categoryModel); - m_categorySelector->setValue(Settings::instance()->defaultCategory()); + m_categorySelector->setValue(Settings::defaultCategory()); m_categorySelector->setEnabled(m_categoryModel->rowCount() > 0); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - - m_layout->addWidget(m_streamSelector, 0, 0); - m_layout->addWidget(m_categorySelector, 1, 0); - m_layout->addWidget(m_buttonBox, 1, 1); - m_layout->setColumnStretch(0, 1); + + QWidget *scrollWidget = new QWidget(m_scrollArea); + QVBoxLayout *vbox = new QVBoxLayout(scrollWidget); + vbox->addWidget(m_streamSelector); + vbox->addWidget(m_categorySelector); + vbox->addWidget(new QLabel(tr("Custom command (%f for filename)"), this)); + vbox->addWidget(m_commandEdit); + vbox->addWidget(m_commandCheckBox); + vbox->setContentsMargins(0, 0, 0, 0); + m_scrollArea->setWidget(scrollWidget); + m_scrollArea->setWidgetResizable(true); + + m_layout->addWidget(m_scrollArea); + m_layout->addWidget(m_buttonBox, Qt::AlignBottom); + m_layout->setStretch(0, 1); connect(m_streamModel, SIGNAL(statusChanged(QSoundCloud::StreamsRequest::Status)), this, SLOT(onStreamModelStatusChanged(QSoundCloud::StreamsRequest::Status))); - connect(m_categorySelector, SIGNAL(valueChanged(QVariant)), this, SLOT(onCategoryChanged())); - connect(m_streamSelector, SIGNAL(valueChanged(QVariant)), this, SLOT(onStreamChanged())); - connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(addDownload())); + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } -void SoundCloudDownloadDialog::showEvent(QShowEvent *e) { - Dialog::showEvent(e); - m_streamModel->get(m_id); +QString SoundCloudDownloadDialog::trackId() const { + return m_trackId; +} + +QString SoundCloudDownloadDialog::streamId() const { + return m_streamSelector->currentValue().toMap().value("id").toString(); +} + +QString SoundCloudDownloadDialog::category() const { + return m_categorySelector->valueText(); } -void SoundCloudDownloadDialog::onCategoryChanged() { - Settings::instance()->setDefaultCategory(m_categorySelector->valueText()); +QString SoundCloudDownloadDialog::customCommand() const { + return m_commandEdit->text(); } -void SoundCloudDownloadDialog::onStreamChanged() { - Settings::instance()->setDefaultDownloadFormat(Resources::SOUNDCLOUD, m_streamSelector->valueText()); +bool SoundCloudDownloadDialog::customCommandOverrideEnabled() const { + return m_commandCheckBox->isChecked(); +} + +void SoundCloudDownloadDialog::accept() { + Settings::setDefaultDownloadFormat(Resources::SOUNDCLOUD, m_streamSelector->valueText()); + Settings::setDefaultCategory(category()); + Dialog::accept(); +} + +void SoundCloudDownloadDialog::get(const QString &trackId) { + m_trackId = trackId; + m_streamModel->get(trackId); } void SoundCloudDownloadDialog::onStreamModelStatusChanged(QSoundCloud::StreamsRequest::Status status) { switch (status) { case QSoundCloud::StreamsRequest::Loading: + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); showProgressIndicator(); return; case QSoundCloud::StreamsRequest::Ready: if (m_streamModel->rowCount() > 0) { m_streamSelector->setCurrentIndex(qMax(0, m_streamModel->match("name", - Settings::instance()->defaultDownloadFormat(Resources::SOUNDCLOUD)))); + Settings::defaultDownloadFormat(Resources::SOUNDCLOUD)))); } else { - QMessageBox::critical(this, tr("Error"), tr("No streams available for '%1'").arg(m_title)); + QMessageBox::critical(this, tr("Error"), tr("No streams available")); } break; case QSoundCloud::StreamsRequest::Failed: - QMessageBox::critical(this, tr("Error"), tr("No streams available for '%1'").arg(m_title)); + QMessageBox::critical(this, tr("Error"), tr("No streams available")); break; default: break; @@ -96,10 +128,3 @@ void SoundCloudDownloadDialog::onStreamModelStatusChanged(QSoundCloud::StreamsRe m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_streamModel->rowCount() > 0); hideProgressIndicator(); } - -void SoundCloudDownloadDialog::addDownload() { - QString streamId = m_streamSelector->currentValue().toMap().value("id").toString(); - QString category = m_categorySelector->valueText(); - Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, m_id, streamId, QUrl(), m_title, category); - accept(); -} diff --git a/app/src/maemo5/soundcloud/soundclouddownloaddialog.h b/app/src/maemo5/soundcloud/soundclouddownloaddialog.h index d5bd2ee..2cc4c19 100644 --- a/app/src/maemo5/soundcloud/soundclouddownloaddialog.h +++ b/app/src/maemo5/soundcloud/soundclouddownloaddialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -22,36 +22,55 @@ class CategoryNameModel; class ValueSelector; +class QScrollArea; +class QCheckBox; +class QLineEdit; class QDialogButtonBox; -class QGridLayout; +class QHBoxLayout; class SoundCloudDownloadDialog : public Dialog { Q_OBJECT + + Q_PROPERTY(QString trackId READ trackId) + Q_PROPERTY(QString streamId READ streamId) + Q_PROPERTY(QString category READ category) + Q_PROPERTY(QString customCommand READ customCommand) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled) public: - explicit SoundCloudDownloadDialog(const QString &resourceId, const QString &title, QWidget *parent = 0); - -protected: - void showEvent(QShowEvent *e); - + explicit SoundCloudDownloadDialog(QWidget *parent = 0); + + QString trackId() const; + + QString streamId() const; + + QString category() const; + + QString customCommand() const; + bool customCommandOverrideEnabled() const; + +public Q_SLOTS: + virtual void accept(); + + void get(const QString &trackId); + private Q_SLOTS: - void onCategoryChanged(); - void onStreamChanged(); void onStreamModelStatusChanged(QSoundCloud::StreamsRequest::Status status); - void addDownload(); - private: - QString m_id; - QString m_title; SoundCloudStreamModel *m_streamModel; CategoryNameModel *m_categoryModel; + QScrollArea *m_scrollArea; + QCheckBox *m_commandCheckBox; + QLineEdit *m_commandEdit; ValueSelector *m_streamSelector; ValueSelector *m_categorySelector; QDialogButtonBox *m_buttonBox; - QGridLayout *m_layout; + QHBoxLayout *m_layout; + + QString m_trackId; }; #endif // SOUNDCLOUDDOWNLOADDIALOG_H diff --git a/app/src/maemo5/soundcloud/soundcloudplaylistswindow.cpp b/app/src/maemo5/soundcloud/soundcloudplaylistswindow.cpp index 6513289..6074eaf 100644 --- a/app/src/maemo5/soundcloud/soundcloudplaylistswindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudplaylistswindow.cpp @@ -19,7 +19,6 @@ #include "listview.h" #include "nowplayingaction.h" #include "playlistdelegate.h" -#include "settings.h" #include "soundcloudplaylistwindow.h" #include #include @@ -34,7 +33,7 @@ SoundCloudPlaylistsWindow::SoundCloudPlaylistsWindow(StackedWindow *parent) : m_view(new ListView(this)), m_reloadAction(new QAction(tr("Reload"), this)), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No sets found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Sets")); setCentralWidget(new QWidget); diff --git a/app/src/maemo5/soundcloud/soundcloudplaylistwindow.cpp b/app/src/maemo5/soundcloud/soundcloudplaylistwindow.cpp index 10fd2da..45f0bab 100644 --- a/app/src/maemo5/soundcloud/soundcloudplaylistwindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudplaylistwindow.cpp @@ -23,7 +23,6 @@ #include "nowplayingaction.h" #include "nowplayingwindow.h" #include "resources.h" -#include "settings.h" #include "soundcloud.h" #include "soundcloudartist.h" #include "soundcloudartistwindow.h" @@ -33,6 +32,7 @@ #include "soundcloudtrackwindow.h" #include "textbrowser.h" #include "trackdelegate.h" +#include "transfers.h" #include "utils.h" #include #include @@ -62,14 +62,9 @@ SoundCloudPlaylistWindow::SoundCloudPlaylistWindow(const QString &id, StackedWin m_dateLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), m_noTracksLabel(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)), m_reloadAction(new QAction(tr("Reload"), this)), - m_queuePlaylistAction(new QAction(tr("Queue"), this)), - m_contextMenu(new QMenu(this)), - m_queueAction(new QAction(tr("Queue"), this)), - m_downloadAction(new QAction(tr("Download"), this)), - m_shareAction(new QAction(tr("Copy URL"), this)), - m_favouriteAction(0) + m_queuePlaylistAction(new QAction(tr("Queue"), this)) { loadBaseUi(); connect(m_playlist, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), @@ -99,12 +94,7 @@ SoundCloudPlaylistWindow::SoundCloudPlaylistWindow(SoundCloudPlaylist *playlist, m_noTracksLabel(new QLabel(QString("

%2

") .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), m_reloadAction(new QAction(tr("Reload"), this)), - m_queuePlaylistAction(new QAction(tr("Queue"), this)), - m_contextMenu(new QMenu(this)), - m_queueAction(new QAction(tr("Queue"), this)), - m_downloadAction(new QAction(tr("Download"), this)), - m_shareAction(new QAction(tr("Copy URL"), this)), - m_favouriteAction(0) + m_queuePlaylistAction(new QAction(tr("Queue"), this)) { loadBaseUi(); loadPlaylistUi(); @@ -140,10 +130,6 @@ void SoundCloudPlaylistWindow::loadBaseUi() { m_reloadAction->setEnabled(false); - m_contextMenu->addAction(m_queueAction); - m_contextMenu->addAction(m_downloadAction); - m_contextMenu->addAction(m_shareAction); - QWidget *scrollWidget = new QWidget(m_scrollArea); QGridLayout *grid = new QGridLayout(scrollWidget); grid->addWidget(m_thumbnail, 0, 0, 1, 2, Qt::AlignLeft); @@ -182,15 +168,6 @@ void SoundCloudPlaylistWindow::loadBaseUi() { connect(m_queuePlaylistAction, SIGNAL(triggered()), this, SLOT(queuePlaylist())); connect(m_avatar, SIGNAL(clicked()), this, SLOT(showArtist())); connect(m_descriptionLabel, SIGNAL(anchorClicked(QUrl)), this, SLOT(showResource(QUrl))); - connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueTrack())); - connect(m_downloadAction, SIGNAL(triggered()), this, SLOT(downloadTrack())); - connect(m_shareAction, SIGNAL(triggered()), this, SLOT(shareTrack())); - - if (!SoundCloud::instance()->userId().isEmpty()) { - m_favouriteAction = new QAction(this); - m_contextMenu->addAction(m_favouriteAction); - connect(m_favouriteAction, SIGNAL(triggered()), this, SLOT(setTrackFavourite())); - } } void SoundCloudPlaylistWindow::loadPlaylistUi() { @@ -250,13 +227,23 @@ void SoundCloudPlaylistWindow::queuePlaylist() { } } -void SoundCloudPlaylistWindow::downloadTrack() { - if ((!isBusy()) && (m_view->currentIndex().isValid())) { - QString id = m_view->currentIndex().data(SoundCloudTrackModel::IdRole).toString(); - QString title = m_view->currentIndex().data(SoundCloudTrackModel::TitleRole).toString(); +void SoundCloudPlaylistWindow::downloadTrack(const QModelIndex &index) { + if (isBusy()) { + return; + } - SoundCloudDownloadDialog *dialog = new SoundCloudDownloadDialog(id, title, this); - dialog->open(); + if (index.isValid()) { + const QString id = index.data(SoundCloudTrackModel::IdRole).toString(); + const QString title = index.data(SoundCloudTrackModel::TitleRole).toString(); + + SoundCloudDownloadDialog dialog(this); + dialog.get(id); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, id, dialog.streamId(), QUrl(), title, + dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } } } @@ -272,23 +259,23 @@ void SoundCloudPlaylistWindow::playTrack(const QModelIndex &index) { } } -void SoundCloudPlaylistWindow::queueTrack() { +void SoundCloudPlaylistWindow::queueTrack(const QModelIndex &index) { if (isBusy()) { return; } - if (SoundCloudTrack *track = m_model->get(m_view->currentIndex().row())) { + if (SoundCloudTrack *track = m_model->get(index.row())) { AudioPlayer::instance()->addTrack(track); QMaemo5InformationBox::information(this, tr("'%1' added to playback queue").arg(track->title())); } } -void SoundCloudPlaylistWindow::setTrackFavourite() { +void SoundCloudPlaylistWindow::setTrackFavourite(const QModelIndex &index) { if (isBusy()) { return; } - if (SoundCloudTrack *track = m_model->get(m_view->currentIndex().row())) { + if (SoundCloudTrack *track = m_model->get(index.row())) { connect(track, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), this, SLOT(onTrackUpdateStatusChanged(QSoundCloud::ResourcesRequest::Status))); @@ -301,8 +288,8 @@ void SoundCloudPlaylistWindow::setTrackFavourite() { } } -void SoundCloudPlaylistWindow::shareTrack() { - if (const SoundCloudTrack *track = m_model->get(m_view->currentIndex().row())) { +void SoundCloudPlaylistWindow::shareTrack(const QModelIndex &index) { + if (const SoundCloudTrack *track = m_model->get(index.row())) { Clipboard::instance()->setText(track->url().toString()); QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } @@ -325,18 +312,67 @@ void SoundCloudPlaylistWindow::showArtist() { } void SoundCloudPlaylistWindow::showContextMenu(const QPoint &pos) { - if ((!isBusy()) && (m_view->currentIndex().isValid())) { - if (m_favouriteAction) { - m_favouriteAction->setText(m_view->currentIndex().data(SoundCloudTrackModel::FavouriteRole).toBool() - ? tr("Unfavourite") : tr("Favourite")); + if (isBusy()) { + return; + } + + const QModelIndex index = m_view->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (!SoundCloud::userId().isEmpty()) { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *favouriteAction = menu.addAction(index.data(SoundCloudTrackModel::FavouriteRole).toBool() + ? tr("Unfavourite") : tr("Favourite")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueTrack(index); + } + else if (action == downloadAction) { + downloadTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } + else if (action == favouriteAction) { + setTrackFavourite(index); + } + } + else { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; } - m_contextMenu->popup(pos, m_queueAction); + if (action == queueAction) { + queueTrack(index); + } + else if (action == downloadAction) { + downloadTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } } } void SoundCloudPlaylistWindow::showResource(const QUrl &url) { - QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); if (resource.value("service") != Resources::SOUNDCLOUD) { QDesktopServices::openUrl(url); diff --git a/app/src/maemo5/soundcloud/soundcloudplaylistwindow.h b/app/src/maemo5/soundcloud/soundcloudplaylistwindow.h index dedd6c0..a24a278 100644 --- a/app/src/maemo5/soundcloud/soundcloudplaylistwindow.h +++ b/app/src/maemo5/soundcloud/soundcloudplaylistwindow.h @@ -53,11 +53,11 @@ private Q_SLOTS: void playPlaylist(); void queuePlaylist(); - void downloadTrack(); + void downloadTrack(const QModelIndex &index); void playTrack(const QModelIndex &index); - void queueTrack(); - void setTrackFavourite(); - void shareTrack(); + void queueTrack(const QModelIndex &index); + void setTrackFavourite(const QModelIndex &index); + void shareTrack(const QModelIndex &index); void showTrack(const QModelIndex &index); void showArtist(); @@ -91,11 +91,6 @@ private Q_SLOTS: QLabel *m_noTracksLabel; QAction *m_reloadAction; QAction *m_queuePlaylistAction; - QMenu *m_contextMenu; - QAction *m_queueAction; - QAction *m_downloadAction; - QAction *m_shareAction; - QAction *m_favouriteAction; QHBoxLayout *m_layout; }; diff --git a/app/src/maemo5/soundcloud/soundcloudsearchdialog.cpp b/app/src/maemo5/soundcloud/soundcloudsearchdialog.cpp index be887da..8f35060 100644 --- a/app/src/maemo5/soundcloud/soundcloudsearchdialog.cpp +++ b/app/src/maemo5/soundcloud/soundcloudsearchdialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -15,11 +15,10 @@ */ #include "soundcloudsearchdialog.h" -#include "mainwindow.h" +#include "soundcloudsearchtypemodel.h" #include "resources.h" #include "searchhistorydialog.h" #include "settings.h" -#include "soundcloudsearchtypemodel.h" #include "valueselector.h" #include #include @@ -40,7 +39,7 @@ SoundCloudSearchDialog::SoundCloudSearchDialog(QWidget *parent) : m_typeSelector->setModel(m_typeModel); m_typeSelector->setCurrentIndex(qMax(0, m_typeModel->match("name", - Settings::instance()->defaultSearchType(Resources::SOUNDCLOUD)))); + Settings::defaultSearchType(Resources::SOUNDCLOUD)))); m_searchEdit->setPlaceholderText(tr("Search")); m_searchEdit->setFocus(Qt::OtherFocusReason); @@ -55,39 +54,42 @@ SoundCloudSearchDialog::SoundCloudSearchDialog(QWidget *parent) : m_layout->addWidget(m_typeSelector, 1, 0); m_layout->addWidget(m_buttonBox, 0, 1, 2, 1, Qt::AlignBottom); - connect(m_typeSelector, SIGNAL(valueChanged(QVariant)), this, SLOT(onSearchTypeChanged())); - connect(m_searchEdit, SIGNAL(textChanged(QString)), this, SLOT(onSearchTextChanged(QString))); + connect(m_searchEdit, SIGNAL(textChanged(QString)), this, SLOT(onQueryChanged(QString))); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(m_historyButton, SIGNAL(clicked()), this, SLOT(showHistoryDialog())); - connect(m_searchButton, SIGNAL(clicked()), this, SLOT(search())); + connect(m_searchButton, SIGNAL(clicked()), this, SLOT(accept())); } -void SoundCloudSearchDialog::showEvent(QShowEvent *e) { - Dialog::showEvent(e); - m_searchEdit->setFocus(Qt::OtherFocusReason); +QString SoundCloudSearchDialog::query() const { + return m_searchEdit->text(); } -void SoundCloudSearchDialog::search() { - if (!MainWindow::instance()->showResource(m_searchEdit->text())) { - QVariantMap type = m_typeSelector->currentValue().toMap(); - Settings::instance()->addSearch(m_searchEdit->text()); - MainWindow::instance()->search(Resources::SOUNDCLOUD, m_searchEdit->text(), type.value("type").toString(), - type.value("order").toString()); - } - - accept(); +void SoundCloudSearchDialog::setQuery(const QString &query) { + m_searchEdit->setText(query); } -void SoundCloudSearchDialog::showHistoryDialog() { - SearchHistoryDialog *dialog = new SearchHistoryDialog(this); - dialog->open(); - connect(dialog, SIGNAL(searchChosen(QString)), m_searchEdit, SLOT(setText(QString))); +QString SoundCloudSearchDialog::order() const { + return m_typeSelector->currentValue().toMap().value("order").toString(); } -void SoundCloudSearchDialog::onSearchTextChanged(const QString &text) { - m_searchButton->setEnabled(!text.isEmpty()); +QString SoundCloudSearchDialog::type() const { + return m_typeSelector->currentValue().toMap().value("type").toString(); +} + +void SoundCloudSearchDialog::accept() { + Settings::addSearch(m_searchEdit->text()); + Settings::setDefaultSearchType(Resources::SOUNDCLOUD, m_typeSelector->valueText()); + Dialog::accept(); +} + +void SoundCloudSearchDialog::showHistoryDialog() { + SearchHistoryDialog dialog(this); + + if (dialog.exec() == QDialog::Accepted) { + setQuery(dialog.query()); + } } -void SoundCloudSearchDialog::onSearchTypeChanged() { - Settings::instance()->setDefaultSearchType(Resources::SOUNDCLOUD, m_typeSelector->valueText()); +void SoundCloudSearchDialog::onQueryChanged(const QString &query) { + m_searchButton->setEnabled(!query.isEmpty()); } diff --git a/app/src/maemo5/soundcloud/soundcloudsearchdialog.h b/app/src/maemo5/soundcloud/soundcloudsearchdialog.h index 88d0756..71871de 100644 --- a/app/src/maemo5/soundcloud/soundcloudsearchdialog.h +++ b/app/src/maemo5/soundcloud/soundcloudsearchdialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -29,20 +29,29 @@ class QGridLayout; class SoundCloudSearchDialog : public Dialog { Q_OBJECT + + Q_PROPERTY(QString query READ query WRITE setQuery) + Q_PROPERTY(QString order READ order) + Q_PROPERTY(QString type READ type) public: explicit SoundCloudSearchDialog(QWidget *parent = 0); -protected: - void showEvent(QShowEvent *e); + QString query() const; + + QString type() const; + + QString order() const; + +public Q_SLOTS: + virtual void accept(); + void setQuery(const QString &query); + private Q_SLOTS: - void search(); + void onQueryChanged(const QString &query); void showHistoryDialog(); - - void onSearchTextChanged(const QString &text); - void onSearchTypeChanged(); private: SoundCloudSearchTypeModel *m_typeModel; @@ -55,4 +64,4 @@ private Q_SLOTS: QGridLayout *m_layout; }; -#endif // SOUNDCLOUDSEARCHDIALOG_H +#endif // SEARCHDIALOG_H diff --git a/app/src/maemo5/soundcloud/soundcloudtrackswindow.cpp b/app/src/maemo5/soundcloud/soundcloudtrackswindow.cpp index 51af9fb..ff45dec 100644 --- a/app/src/maemo5/soundcloud/soundcloudtrackswindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudtrackswindow.cpp @@ -21,11 +21,12 @@ #include "listview.h" #include "nowplayingaction.h" #include "nowplayingwindow.h" -#include "settings.h" +#include "resources.h" #include "soundcloud.h" #include "soundclouddownloaddialog.h" #include "soundcloudtrackwindow.h" #include "trackdelegate.h" +#include "transfers.h" #include #include #include @@ -44,13 +45,8 @@ SoundCloudTracksWindow::SoundCloudTracksWindow(StackedWindow *parent) : SoundCloudTrackModel::DurationStringRole, SoundCloudTrackModel::ThumbnailUrlRole, SoundCloudTrackModel::TitleRole, this)), m_reloadAction(new QAction(tr("Reload"), this)), - m_contextMenu(new QMenu(this)), - m_queueAction(new QAction(tr("Queue"), this)), - m_downloadAction(new QAction(tr("Download"), this)), - m_shareAction(new QAction(tr("Copy URL"), this)), - m_favouriteAction(0), m_label(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)) + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)) { setWindowTitle(tr("Tracks")); setCentralWidget(new QWidget); @@ -59,11 +55,7 @@ SoundCloudTracksWindow::SoundCloudTracksWindow(StackedWindow *parent) : m_view->setItemDelegate(m_delegate); m_view->setContextMenuPolicy(Qt::CustomContextMenu); - m_reloadAction->setEnabled(false); - - m_contextMenu->addAction(m_queueAction); - m_contextMenu->addAction(m_downloadAction); - m_contextMenu->addAction(m_shareAction); + m_reloadAction->setEnabled(false); m_label->hide(); @@ -82,15 +74,6 @@ SoundCloudTracksWindow::SoundCloudTracksWindow(StackedWindow *parent) : connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(m_delegate, SIGNAL(thumbnailClicked(QModelIndex)), this, SLOT(playTrack(QModelIndex))); connect(m_reloadAction, SIGNAL(triggered()), m_model, SLOT(reload())); - connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueTrack())); - connect(m_downloadAction, SIGNAL(triggered()), this, SLOT(downloadTrack())); - connect(m_shareAction, SIGNAL(triggered()), this, SLOT(shareTrack())); - - if (!SoundCloud::instance()->userId().isEmpty()) { - m_favouriteAction = new QAction(this); - m_contextMenu->addAction(m_favouriteAction); - connect(m_favouriteAction, SIGNAL(triggered()), this, SLOT(setTrackFavourite())); - } } SoundCloudTracksWindow::~SoundCloudTracksWindow() { @@ -102,17 +85,23 @@ void SoundCloudTracksWindow::get(const QString &resourcePath, const QVariantMap m_model->get(resourcePath, filters); } -void SoundCloudTracksWindow::downloadTrack() { +void SoundCloudTracksWindow::downloadTrack(const QModelIndex &index) { if (isBusy()) { return; } - - if (m_view->currentIndex().isValid()) { - QString id = m_view->currentIndex().data(SoundCloudTrackModel::IdRole).toString(); - QString title = m_view->currentIndex().data(SoundCloudTrackModel::TitleRole).toString(); - SoundCloudDownloadDialog *dialog = new SoundCloudDownloadDialog(id, title, this); - dialog->open(); + if (index.isValid()) { + const QString id = index.data(SoundCloudTrackModel::IdRole).toString(); + const QString title = index.data(SoundCloudTrackModel::TitleRole).toString(); + + SoundCloudDownloadDialog dialog(this); + dialog.get(id); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, id, dialog.streamId(), QUrl(), title, + dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } } } @@ -128,23 +117,23 @@ void SoundCloudTracksWindow::playTrack(const QModelIndex &index) { } } -void SoundCloudTracksWindow::queueTrack() { +void SoundCloudTracksWindow::queueTrack(const QModelIndex &index) { if (isBusy()) { return; } - if (SoundCloudTrack *track = m_model->get(m_view->currentIndex().row())) { + if (SoundCloudTrack *track = m_model->get(index.row())) { AudioPlayer::instance()->addTrack(track); QMaemo5InformationBox::information(this, tr("'%1' added to playback queue").arg(track->title())); } } -void SoundCloudTracksWindow::setTrackFavourite() { +void SoundCloudTracksWindow::setTrackFavourite(const QModelIndex &index) { if (isBusy()) { return; } - if (SoundCloudTrack *track = m_model->get(m_view->currentIndex().row())) { + if (SoundCloudTrack *track = m_model->get(index.row())) { connect(track, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), this, SLOT(onTrackUpdateStatusChanged(QSoundCloud::ResourcesRequest::Status))); @@ -157,8 +146,8 @@ void SoundCloudTracksWindow::setTrackFavourite() { } } -void SoundCloudTracksWindow::shareTrack() { - if (const SoundCloudTrack *track = m_model->get(m_view->currentIndex().row())) { +void SoundCloudTracksWindow::shareTrack(const QModelIndex &index) { + if (const SoundCloudTrack *track = m_model->get(index.row())) { Clipboard::instance()->setText(track->url().toString()); QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } @@ -176,13 +165,62 @@ void SoundCloudTracksWindow::showTrack(const QModelIndex &index) { } void SoundCloudTracksWindow::showContextMenu(const QPoint &pos) { - if ((!isBusy()) && (m_view->currentIndex().isValid())) { - if (m_favouriteAction) { - m_favouriteAction->setText(m_view->currentIndex().data(SoundCloudTrackModel::FavouriteRole).toBool() - ? tr("Unfavourite") : tr("Favourite")); + if (isBusy()) { + return; + } + + const QModelIndex index = m_view->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (!SoundCloud::userId().isEmpty()) { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *favouriteAction = menu.addAction(index.data(SoundCloudTrackModel::FavouriteRole).toBool() + ? tr("Unfavourite") : tr("Favourite")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueTrack(index); + } + else if (action == downloadAction) { + downloadTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } + else if (action == favouriteAction) { + setTrackFavourite(index); + } + } + else { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; } - m_contextMenu->popup(pos, m_queueAction); + if (action == queueAction) { + queueTrack(index); + } + else if (action == downloadAction) { + downloadTrack(index); + } + else if (action == shareAction) { + shareTrack(index); + } } } diff --git a/app/src/maemo5/soundcloud/soundcloudtrackswindow.h b/app/src/maemo5/soundcloud/soundcloudtrackswindow.h index b06da90..83ba36f 100644 --- a/app/src/maemo5/soundcloud/soundcloudtrackswindow.h +++ b/app/src/maemo5/soundcloud/soundcloudtrackswindow.h @@ -26,7 +26,6 @@ class NowPlayingAction; class ListView; class QLabel; class QVBoxLayout; -class QMenu; class SoundCloudTracksWindow : public StackedWindow { @@ -40,11 +39,11 @@ public Q_SLOTS: void get(const QString &resourcePath, const QVariantMap &filters = QVariantMap()); private Q_SLOTS: - void downloadTrack(); + void downloadTrack(const QModelIndex &index); void playTrack(const QModelIndex &index); - void queueTrack(); - void setTrackFavourite(); - void shareTrack(); + void queueTrack(const QModelIndex &index); + void setTrackFavourite(const QModelIndex &index); + void shareTrack(const QModelIndex &index); void showTrack(const QModelIndex &index); void showContextMenu(const QPoint &pos); @@ -61,11 +60,6 @@ private Q_SLOTS: ListView *m_view; TrackDelegate *m_delegate; QAction *m_reloadAction; - QMenu *m_contextMenu; - QAction *m_queueAction; - QAction *m_downloadAction; - QAction *m_shareAction; - QAction *m_favouriteAction; QLabel *m_label; QVBoxLayout *m_layout; }; diff --git a/app/src/maemo5/soundcloud/soundcloudtrackwindow.cpp b/app/src/maemo5/soundcloud/soundcloudtrackwindow.cpp index 1305f84..84bb92e 100644 --- a/app/src/maemo5/soundcloud/soundcloudtrackwindow.cpp +++ b/app/src/maemo5/soundcloud/soundcloudtrackwindow.cpp @@ -25,7 +25,6 @@ #include "nowplayingaction.h" #include "nowplayingwindow.h" #include "resources.h" -#include "settings.h" #include "soundcloud.h" #include "soundcloudartist.h" #include "soundcloudartistwindow.h" @@ -35,6 +34,7 @@ #include "soundcloudplaylistwindow.h" #include "textbrowser.h" #include "trackdelegate.h" +#include "transfers.h" #include "utils.h" #include #include @@ -70,25 +70,19 @@ SoundCloudTrackWindow::SoundCloudTrackWindow(const QString &id, StackedWindow *p m_descriptionLabel(new TextBrowser(this)), m_dateLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), - m_noTracksLabel(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), - m_noCommentsLabel(0), + m_noResultsLabel(new QLabel(QString("

%2

") + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)), m_reloadAction(new QAction(tr("Reload"), this)), m_queueAction(new QAction(tr("Queue"), this)), m_downloadAction(new QAction(tr("Download"), this)), m_shareAction(new QAction(tr("Copy URL"), this)), m_favouriteAction(0), - m_commentAction(0), - m_contextMenu(new QMenu(this)), - m_relatedQueueAction(new QAction(tr("Queue"), this)), - m_relatedDownloadAction(new QAction(tr("Download"), this)), - m_relatedShareAction(new QAction(tr("Copy URL"), this)), - m_relatedFavouriteAction(0) + m_commentAction(0) { loadBaseUi(); connect(m_track, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), this, SLOT(onTrackStatusChanged(QSoundCloud::ResourcesRequest::Status))); - + m_track->loadTrack(id); } @@ -107,7 +101,6 @@ SoundCloudTrackWindow::SoundCloudTrackWindow(SoundCloudTrack *track, StackedWind m_relatedDelegate(new TrackDelegate(m_cache, SoundCloudTrackModel::ArtistRole, SoundCloudTrackModel::DateRole, SoundCloudTrackModel::DurationStringRole, SoundCloudTrackModel::ThumbnailUrlRole, SoundCloudTrackModel::TitleRole, this)), - m_commentDelegate(0), m_scrollArea(new QScrollArea(this)), m_tabBar(new QTabBar(this)), m_stack(new QStackedWidget(this)), @@ -115,27 +108,21 @@ SoundCloudTrackWindow::SoundCloudTrackWindow(SoundCloudTrack *track, StackedWind m_descriptionLabel(new TextBrowser(this)), m_dateLabel(new QLabel(this)), m_artistLabel(new QLabel(this)), - m_noTracksLabel(new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No tracks found")), this)), - m_noCommentsLabel(0), + m_noResultsLabel(new QLabel(QString("

%2

") + .arg(palette().color(QPalette::Mid).name()).arg(tr("No results")), this)), m_reloadAction(new QAction(tr("Reload"), this)), m_queueAction(new QAction(tr("Queue"), this)), m_downloadAction(new QAction(tr("Download"), this)), m_shareAction(new QAction(tr("Copy URL"), this)), m_favouriteAction(0), - m_commentAction(0), - m_contextMenu(new QMenu(this)), - m_relatedQueueAction(new QAction(tr("Queue"), this)), - m_relatedDownloadAction(new QAction(tr("Download"), this)), - m_relatedShareAction(new QAction(tr("Copy URL"), this)), - m_relatedFavouriteAction(0) + m_commentAction(0) { loadBaseUi(); loadTrackUi(); getRelatedTracks(); connect(m_artist, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), this, SLOT(onArtistStatusChanged(QSoundCloud::ResourcesRequest::Status))); - + m_artist->loadArtist(track->artistId()); } @@ -163,10 +150,6 @@ void SoundCloudTrackWindow::loadBaseUi() { m_reloadAction->setEnabled(false); - m_contextMenu->addAction(m_relatedQueueAction); - m_contextMenu->addAction(m_relatedDownloadAction); - m_contextMenu->addAction(m_relatedShareAction); - QWidget *scrollWidget = new QWidget(m_scrollArea); QGridLayout *grid = new QGridLayout(scrollWidget); grid->addWidget(m_thumbnail, 0, 0, 1, 2, Qt::AlignLeft); @@ -189,7 +172,7 @@ void SoundCloudTrackWindow::loadBaseUi() { m_tabBar->setStyleSheet("QTabBar::tab { height: 40px; }"); m_stack->addWidget(m_relatedView); - m_stack->addWidget(m_noTracksLabel); + m_stack->addWidget(m_noResultsLabel); m_layout = new QGridLayout(centralWidget()); m_layout->addWidget(m_scrollArea, 0, 0, 2, 1); @@ -210,8 +193,6 @@ void SoundCloudTrackWindow::loadBaseUi() { connect(m_cache, SIGNAL(imageReady()), this, SLOT(onImageReady())); connect(m_relatedView, SIGNAL(activated(QModelIndex)), this, SLOT(showRelatedTrack(QModelIndex))); connect(m_relatedView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - connect(m_relatedView, SIGNAL(scrollingStarted()), m_nowPlayingAction, SLOT(hide())); - connect(m_relatedView, SIGNAL(scrollingStopped()), m_nowPlayingAction, SLOT(poke())); connect(m_relatedDelegate, SIGNAL(thumbnailClicked(QModelIndex)), this, SLOT(playRelatedTrack(QModelIndex))); connect(m_tabBar, SIGNAL(currentChanged(int)), this, SLOT(onTabIndexChanged(int))); connect(m_reloadAction, SIGNAL(triggered()), this, SLOT(reload())); @@ -221,19 +202,13 @@ void SoundCloudTrackWindow::loadBaseUi() { connect(m_queueAction, SIGNAL(triggered()), this, SLOT(queueTrack())); connect(m_downloadAction, SIGNAL(triggered()), this, SLOT(downloadTrack())); connect(m_shareAction, SIGNAL(triggered()), this, SLOT(shareTrack())); - connect(m_relatedQueueAction, SIGNAL(triggered()), this, SLOT(queueRelatedTrack())); - connect(m_relatedDownloadAction, SIGNAL(triggered()), this, SLOT(downloadRelatedTrack())); - connect(m_relatedShareAction, SIGNAL(triggered()), this, SLOT(shareRelatedTrack())); - if (!SoundCloud::instance()->userId().isEmpty()) { + if (!SoundCloud::userId().isEmpty()) { m_favouriteAction = new QAction(tr("Favourite"), this); - m_relatedFavouriteAction = new QAction(this); m_commentAction = new QAction(tr("Add comment"), this); menuBar()->insertAction(m_nowPlayingAction, m_favouriteAction); menuBar()->insertAction(m_nowPlayingAction, m_commentAction); - m_contextMenu->addAction(m_relatedFavouriteAction); connect(m_favouriteAction, SIGNAL(triggered()), this, SLOT(setTrackFavourite())); - connect(m_relatedFavouriteAction, SIGNAL(triggered()), this, SLOT(setRelatedTrackFavourite())); connect(m_commentAction, SIGNAL(triggered()), this, SLOT(addComment())); } } @@ -268,8 +243,7 @@ void SoundCloudTrackWindow::addComment() { return; } - SoundCloudCommentDialog *dialog = new SoundCloudCommentDialog(m_track->id(), this); - dialog->open(); + SoundCloudCommentDialog(m_track->id(), this).exec(); } void SoundCloudTrackWindow::downloadTrack() { @@ -277,8 +251,14 @@ void SoundCloudTrackWindow::downloadTrack() { return; } - SoundCloudDownloadDialog *dialog = new SoundCloudDownloadDialog(m_track->id(), m_track->title(), this); - dialog->open(); + SoundCloudDownloadDialog dialog(this); + dialog.get(m_track->id()); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, m_track->id(), dialog.streamId(), + QUrl(), m_track->title(), dialog.category(), + dialog.customCommand(), dialog.customCommandOverrideEnabled()); + } } void SoundCloudTrackWindow::playTrack() { @@ -321,13 +301,23 @@ void SoundCloudTrackWindow::shareTrack() { QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } -void SoundCloudTrackWindow::downloadRelatedTrack() { - if ((!isBusy()) && (m_relatedView->currentIndex().isValid())) { - QString id = m_relatedView->currentIndex().data(SoundCloudTrackModel::IdRole).toString(); - QString title = m_relatedView->currentIndex().data(SoundCloudTrackModel::TitleRole).toString(); +void SoundCloudTrackWindow::downloadRelatedTrack(const QModelIndex &index) { + if (isBusy()) { + return; + } - SoundCloudDownloadDialog *dialog = new SoundCloudDownloadDialog(id, title, this); - dialog->open(); + if (index.isValid()) { + const QString id = index.data(SoundCloudTrackModel::IdRole).toString(); + const QString title = index.data(SoundCloudTrackModel::TitleRole).toString(); + + SoundCloudDownloadDialog dialog(this); + dialog.get(id); + + if (dialog.exec() == QDialog::Accepted) { + Transfers::instance()->addDownloadTransfer(Resources::SOUNDCLOUD, id, dialog.streamId(), QUrl(), title, + dialog.category(), dialog.customCommand(), + dialog.customCommandOverrideEnabled()); + } } } @@ -343,23 +333,23 @@ void SoundCloudTrackWindow::playRelatedTrack(const QModelIndex &index) { } } -void SoundCloudTrackWindow::queueRelatedTrack() { +void SoundCloudTrackWindow::queueRelatedTrack(const QModelIndex &index) { if (isBusy()) { return; } - if (SoundCloudTrack *track = m_relatedModel->get(m_relatedView->currentIndex().row())) { + if (SoundCloudTrack *track = m_relatedModel->get(index.row())) { AudioPlayer::instance()->addTrack(track); QMaemo5InformationBox::information(this, tr("'%1' added to playback queue").arg(track->title())); } } -void SoundCloudTrackWindow::setRelatedTrackFavourite() { +void SoundCloudTrackWindow::setRelatedTrackFavourite(const QModelIndex &index) { if (isBusy()) { return; } - if (SoundCloudTrack *track = m_relatedModel->get(m_relatedView->currentIndex().row())) { + if (SoundCloudTrack *track = m_relatedModel->get(index.row())) { connect(track, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), this, SLOT(onTrackUpdateStatusChanged(QSoundCloud::ResourcesRequest::Status))); @@ -372,8 +362,8 @@ void SoundCloudTrackWindow::setRelatedTrackFavourite() { } } -void SoundCloudTrackWindow::shareRelatedTrack() { - if (const SoundCloudTrack *track = m_relatedModel->get(m_relatedView->currentIndex().row())) { +void SoundCloudTrackWindow::shareRelatedTrack(const QModelIndex &index) { + if (const SoundCloudTrack *track = m_relatedModel->get(index.row())) { Clipboard::instance()->setText(track->url().toString()); QMaemo5InformationBox::information(this, tr("URL copied to clipboard")); } @@ -402,13 +392,62 @@ void SoundCloudTrackWindow::reload() { } void SoundCloudTrackWindow::showContextMenu(const QPoint &pos) { - if ((!isBusy()) && (m_relatedView->currentIndex().isValid())) { - if (m_relatedFavouriteAction) { - m_relatedFavouriteAction->setText(m_relatedView->currentIndex().data(SoundCloudTrackModel::FavouriteRole).toBool() - ? tr("Unfavourite") : tr("Favourite")); + if (isBusy()) { + return; + } + + const QModelIndex index = m_relatedView->currentIndex(); + + if (!index.isValid()) { + return; + } + + if (!SoundCloud::userId().isEmpty()) { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *favouriteAction = menu.addAction(index.data(SoundCloudTrackModel::FavouriteRole).toBool() + ? tr("Unfavourite") : tr("Favourite")); + QAction *action = menu.exec(pos); + + if (!action) { + return; } - m_contextMenu->popup(pos, m_relatedQueueAction); + if (action == queueAction) { + queueRelatedTrack(index); + } + else if (action == downloadAction) { + downloadRelatedTrack(index); + } + else if (action == shareAction) { + shareRelatedTrack(index); + } + else if (action == favouriteAction) { + setRelatedTrackFavourite(index); + } + } + else { + QMenu menu(this); + QAction *queueAction = menu.addAction(tr("Queue")); + QAction *downloadAction = menu.addAction(tr("Download")); + QAction *shareAction = menu.addAction(tr("Copy URL")); + QAction *action = menu.exec(pos); + + if (!action) { + return; + } + + if (action == queueAction) { + queueRelatedTrack(index); + } + else if (action == downloadAction) { + downloadRelatedTrack(index); + } + else if (action == shareAction) { + shareRelatedTrack(index); + } } } @@ -442,10 +481,7 @@ void SoundCloudTrackWindow::showComments() { m_commentView->setUniformItemSizes(false); m_commentView->setModel(m_commentModel); m_commentView->setItemDelegate(m_commentDelegate); - m_noCommentsLabel = new QLabel(QString("

%2

") - .arg(palette().color(QPalette::Mid).name()).arg(tr("No comments found")), this); m_stack->addWidget(m_commentView); - m_stack->addWidget(m_noCommentsLabel); connect(m_commentDelegate, SIGNAL(thumbnailClicked(QModelIndex)), this, SLOT(showArtist(QModelIndex))); connect(m_commentModel, SIGNAL(statusChanged(QSoundCloud::ResourcesRequest::Status)), @@ -457,7 +493,7 @@ void SoundCloudTrackWindow::showComments() { } if ((m_commentModel->rowCount() == 0) && (m_commentModel->status() != QSoundCloud::ResourcesRequest::Loading)) { - m_stack->setCurrentWidget(m_noCommentsLabel); + m_stack->setCurrentWidget(m_noResultsLabel); } else { m_stack->setCurrentWidget(m_commentView); @@ -466,7 +502,7 @@ void SoundCloudTrackWindow::showComments() { void SoundCloudTrackWindow::showRelatedTracks() { if ((m_relatedModel->rowCount() == 0) && (m_relatedModel->status() != QSoundCloud::ResourcesRequest::Loading)) { - m_stack->setCurrentWidget(m_noTracksLabel); + m_stack->setCurrentWidget(m_noResultsLabel); } else { m_stack->setCurrentWidget(m_relatedView); @@ -474,7 +510,7 @@ void SoundCloudTrackWindow::showRelatedTracks() { } void SoundCloudTrackWindow::showResource(const QUrl &url) { - QVariantMap resource = Resources::getResourceFromUrl(url.toString()); + const QVariantMap resource = Resources::getResourceFromUrl(url.toString()); if (resource.value("service") != Resources::SOUNDCLOUD) { QDesktopServices::openUrl(url); diff --git a/app/src/maemo5/soundcloud/soundcloudtrackwindow.h b/app/src/maemo5/soundcloud/soundcloudtrackwindow.h index fad4b9e..d6c212a 100644 --- a/app/src/maemo5/soundcloud/soundcloudtrackwindow.h +++ b/app/src/maemo5/soundcloud/soundcloudtrackwindow.h @@ -60,11 +60,11 @@ private Q_SLOTS: void setTrackFavourite(); void shareTrack(); - void downloadRelatedTrack(); + void downloadRelatedTrack(const QModelIndex &index); void playRelatedTrack(const QModelIndex &index); - void queueRelatedTrack(); - void setRelatedTrackFavourite(); - void shareRelatedTrack(); + void queueRelatedTrack(const QModelIndex &index); + void setRelatedTrackFavourite(const QModelIndex &index); + void shareRelatedTrack(const QModelIndex &index); void showRelatedTrack(const QModelIndex &index); void reload(); @@ -106,19 +106,13 @@ private Q_SLOTS: TextBrowser *m_descriptionLabel; QLabel *m_dateLabel; QLabel *m_artistLabel; - QLabel *m_noTracksLabel; - QLabel *m_noCommentsLabel; + QLabel *m_noResultsLabel; QAction *m_reloadAction; QAction *m_queueAction; QAction *m_downloadAction; QAction *m_shareAction; QAction *m_favouriteAction; QAction *m_commentAction; - QMenu *m_contextMenu; - QAction *m_relatedQueueAction; - QAction *m_relatedDownloadAction; - QAction *m_relatedShareAction; - QAction *m_relatedFavouriteAction; QGridLayout *m_layout; }; diff --git a/app/src/maemo5/soundcloud/soundcloudview.cpp b/app/src/maemo5/soundcloud/soundcloudview.cpp index 99b52f8..e2ab554 100644 --- a/app/src/maemo5/soundcloud/soundcloudview.cpp +++ b/app/src/maemo5/soundcloud/soundcloudview.cpp @@ -19,7 +19,6 @@ #include "listview.h" #include "navdelegate.h" #include "resources.h" -#include "settings.h" #include "soundcloud.h" #include "soundcloudaccountswindow.h" #include "soundcloudartist.h" @@ -61,9 +60,7 @@ SoundCloudView::SoundCloudView(QWidget *parent) : this, SLOT(onTrackUnfavourited(SoundCloudTrack*))); } -void SoundCloudView::search(const QString &query, const QString &type, const QString &order) { - Q_UNUSED(order); - +void SoundCloudView::search(const QString &query, const QString &type, const QString &) { QVariantMap filters; filters["q"] = query; filters["limit"] = MAX_RESULTS; @@ -139,8 +136,18 @@ void SoundCloudView::showPlaylists() { } void SoundCloudView::showSearchDialog() { - SoundCloudSearchDialog *dialog = new SoundCloudSearchDialog(StackedWindow::currentWindow()); - dialog->open(); + SoundCloudSearchDialog dialog(StackedWindow::currentWindow()); + + if (dialog.exec() == QDialog::Accepted) { + const QVariantMap resource = Resources::getResourceFromUrl(dialog.query()); + + if (resource.value("service") == Resources::SOUNDCLOUD) { + showResource(resource.value("type").toString(), resource.value("id").toString()); + } + else { + search(dialog.query(), dialog.type(), dialog.order()); + } + } } void SoundCloudView::showTracks() { diff --git a/app/src/maemo5/stackedwindow.cpp b/app/src/maemo5/stackedwindow.cpp index ceb3604..3eeee9a 100644 --- a/app/src/maemo5/stackedwindow.cpp +++ b/app/src/maemo5/stackedwindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -55,7 +55,10 @@ bool StackedWindow::isBusy() const { void StackedWindow::showProgressIndicator() { m_progressCount++; - setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + + if (isVisible()) { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + } } void StackedWindow::hideProgressIndicator() { @@ -67,3 +70,11 @@ void StackedWindow::hideProgressIndicator() { } } } + +void StackedWindow::showEvent(QShowEvent *e) { + QMainWindow::showEvent(e); + + if (m_progressCount > 0) { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + } +} diff --git a/app/src/maemo5/stackedwindow.h b/app/src/maemo5/stackedwindow.h index 6609609..f0d30e5 100644 --- a/app/src/maemo5/stackedwindow.h +++ b/app/src/maemo5/stackedwindow.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -40,6 +40,8 @@ public Q_SLOTS: void hideProgressIndicator(); protected: + virtual void showEvent(QShowEvent *e); + static QList stack; private: diff --git a/app/src/maemo5/textbrowser.cpp b/app/src/maemo5/textbrowser.cpp index 083042a..6f062f3 100644 --- a/app/src/maemo5/textbrowser.cpp +++ b/app/src/maemo5/textbrowser.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as diff --git a/app/src/maemo5/textbrowser.h b/app/src/maemo5/textbrowser.h index 8b126bd..3c81b9b 100644 --- a/app/src/maemo5/textbrowser.h +++ b/app/src/maemo5/textbrowser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as diff --git a/app/src/maemo5/trackdelegate.cpp b/app/src/maemo5/trackdelegate.cpp index a709aa8..9d80e98 100644 --- a/app/src/maemo5/trackdelegate.cpp +++ b/app/src/maemo5/trackdelegate.cpp @@ -96,11 +96,10 @@ void TrackDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, textRect.setTop(textRect.top() + 8); textRect.setBottom(textRect.bottom() - 8); - QFontMetrics fm = painter->fontMetrics(); - - QString duration = index.data(m_durationRole).toString(); - QString title = fm.elidedText(index.data(m_titleRole).toString(), Qt::ElideRight, - textRect.width() - fm.width(duration) - 8); + const QFontMetrics fm = painter->fontMetrics(); + const QString duration = index.data(m_durationRole).toString(); + const QString title = fm.elidedText(index.data(m_titleRole).toString(), Qt::ElideRight, + textRect.width() - fm.width(duration) - 8); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, title); painter->drawText(textRect, Qt::AlignRight | Qt::AlignTop, duration); diff --git a/app/src/maemo5/transfer.cpp b/app/src/maemo5/transfer.cpp new file mode 100644 index 0000000..a94812d --- /dev/null +++ b/app/src/maemo5/transfer.cpp @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "transfer.h" +#include "definitions.h" +#include "logger.h" +#include "settings.h" +#include "utils.h" +#include +#include +#include +#include + +Transfer::Transfer(QObject *parent) : + QObject(parent), + m_nam(0), + m_reply(0), + m_process(0), + m_ownNetworkAccessManager(false), + m_canceled(false), + m_category(tr("Default")), + m_customCommandOverrideEnabled(false), + m_priority(NormalPriority), + m_progress(0), + m_size(0), + m_bytesTransferred(0), + m_redirects(0), + m_status(Paused), + m_transferType(Download), + m_metadataSet(false) +{ +} + +void Transfer::setNetworkAccessManager(QNetworkAccessManager *manager) { + if ((m_nam) && (m_ownNetworkAccessManager)) { + delete m_nam; + } + + m_nam = manager; + m_ownNetworkAccessManager = false; +} + +qint64 Transfer::bytesTransferred() const { + return m_bytesTransferred; +} + +QString Transfer::category() const { + return m_category; +} + +void Transfer::setCategory(const QString &c) { + if (c != category()) { + m_category = c; + emit categoryChanged(); + } +} + +QString Transfer::customCommand() const { + return m_customCommand; +} + +void Transfer::setCustomCommand(const QString &c) { + if (c != customCommand()) { + m_customCommand = c; + emit customCommandChanged(); + } +} + +bool Transfer::customCommandOverrideEnabled() const { + return m_customCommandOverrideEnabled; +} + +void Transfer::setCustomCommandOverrideEnabled(bool enabled) { + if (enabled != customCommandOverrideEnabled()) { + m_customCommandOverrideEnabled = enabled; + emit customCommandOverrideEnabledChanged(); + } +} + +QString Transfer::downloadPath() const { + return m_downloadPath; +} + +void Transfer::setDownloadPath(const QString &path) { + if (path != downloadPath()) { + m_downloadPath = path.endsWith("/") ? path : path + "/"; + emit downloadPathChanged(); + + if (!fileName().isEmpty()) { + m_file.setFileName(downloadPath() + fileName()); + m_bytesTransferred = m_file.size(); + + if ((m_size > 0) && (m_bytesTransferred > 0)) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } +} + +QString Transfer::errorString() const { + return m_errorString; +} + +void Transfer::setErrorString(const QString &es) { + m_errorString = es; +} + +QString Transfer::fileName() const { + return m_fileName; +} + +void Transfer::setFileName(const QString &name) { + if (name != fileName()) { + m_fileName = name; + + switch (transferType()) { + case Transfer::Download: + m_fileName.replace(ILLEGAL_FILENAME_CHARS_RE, "_"); + break; + default: + break; + } + + emit fileNameChanged(); + + if (!downloadPath().isEmpty()) { + m_file.setFileName(downloadPath() + fileName()); + m_bytesTransferred = m_file.size(); + + if ((m_size > 0) && (m_bytesTransferred > 0)) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } +} + +QString Transfer::id() const { + return m_id; +} + +void Transfer::setId(const QString &i) { + if (i != id()) { + m_id = i; + emit idChanged(); + } +} + +Transfer::Priority Transfer::priority() const { + return m_priority; +} + +void Transfer::setPriority(Priority p) { + if (p != priority()) { + m_priority = p; + emit priorityChanged(); + } +} + +QString Transfer::priorityString() const { + switch (priority()) { + case HighPriority: + return tr("High"); + case NormalPriority: + return tr("Normal"); + case LowPriority: + return tr("Low"); + default: + return QString(); + } +} + +int Transfer::progress() const { + return m_progress; +} + +void Transfer::setProgress(int p) { + if (p != progress()) { + m_progress = p; + emit progressChanged(); + } +} + +QString Transfer::progressString() const { + return tr("%1 of %2 (%3%)").arg(Utils::formatBytes(bytesTransferred())).arg(Utils::formatBytes(size())) + .arg(progress()); +} + +QString Transfer::service() const { + return m_service; +} + +void Transfer::setService(const QString &s) { + if (s != service()) { + m_service = s; + emit serviceChanged(); + } +} + +qint64 Transfer::size() const { + return m_size; +} + +void Transfer::setSize(qint64 s) { + if (s != size()) { + m_size = s; + emit sizeChanged(); + + if ((m_size > 0) && (m_bytesTransferred > 0)) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } +} + +Transfer::Status Transfer::status() const { + return m_status; +} + +void Transfer::setStatus(Status s) { + if (s != status()) { + m_status = s; + Logger::log(QString("Transfer::setStatus(). ID: %1, Status: %2").arg(id()).arg(statusString()), + Logger::LowVerbosity); + emit statusChanged(); + } +} + +QString Transfer::statusString() const { + switch (status()) { + case Paused: + return tr("Paused"); + case Canceled: + return tr("Canceled"); + case Failed: + return tr("Failed: %1").arg(errorString()); + case Completed: + return tr("Completed"); + case Queued: + return tr("Queued"); + case Connecting: + return tr("Connecting"); + case Downloading: + return tr("Downloading"); + case Uploading: + return tr("Uploading"); + case ExecutingCustomCommand: + return tr("Executing custom command"); + default: + return QString(); + } +} + +QString Transfer::streamId() const { + return m_streamId; +} + +void Transfer::setStreamId(const QString &si) { + if (si != streamId()) { + m_streamId = si; + emit streamIdChanged(); + } +} + +QUrl Transfer::streamUrl() const { + return m_streamUrl; +} + +void Transfer::setStreamUrl(const QUrl &url) { + if (url != streamUrl()) { + m_streamUrl = url; + emit streamUrlChanged(); + } +} + +QString Transfer::title() const { + return m_title; +} + +void Transfer::setTitle(const QString &t) { + if (t != title()) { + m_title = t; + emit titleChanged(); + } +} + +QString Transfer::trackId() const { + return m_trackId; +} + +void Transfer::setTrackId(const QString &i) { + if (i != trackId()) { + m_trackId = i; + emit trackIdChanged(); + } +} + +Transfer::TransferType Transfer::transferType() const { + return m_transferType; +} + +void Transfer::setTransferType(TransferType type) { + if (type != transferType()) { + m_transferType = type; + emit transferTypeChanged(); + } +} + +QUrl Transfer::url() const { + return m_reply ? m_reply->url() : QUrl(); +} + +void Transfer::queue() { + switch (status()) { + case Canceled: + case Completed: + case Queued: + case Connecting: + case Downloading: + case Uploading: + case ExecutingCustomCommand: + return; + default: + break; + } + + setStatus(Queued); +} + +void Transfer::start() { + switch (status()) { + case Canceled: + case Completed: + case Connecting: + case Downloading: + case Uploading: + case ExecutingCustomCommand: + return; + default: + break; + } + + switch (transferType()) { + case Upload: + return; + default: + break; + } + + setStatus(Connecting); + + if (streamUrl().isEmpty()) { + listStreams(); + } + else { + startDownload(streamUrl()); + } +} + +void Transfer::pause() { + switch (status()) { + case Paused: + case Canceled: + case Completed: + case Connecting: + case ExecutingCustomCommand: + return; + default: + break; + } + + if ((m_reply) && (m_reply->isRunning())) { + m_canceled = false; + m_reply->abort(); + } + else { + setStatus(Paused); + } +} + +void Transfer::cancel() { + switch (status()) { + case Canceled: + case Completed: + case ExecutingCustomCommand: + return; + default: + break; + } + + if ((m_reply) && (m_reply->isRunning())) { + m_canceled = true; + m_reply->abort(); + } + else { + m_file.remove(); + QDir().rmdir(downloadPath()); + setStatus(Canceled); + } +} + +void Transfer::startDownload(const QUrl &u) { + Logger::log("Transfer::startDownload(). URL: " + u.toString(), Logger::LowVerbosity); + QDir().mkpath(downloadPath()); + + if (!m_file.open(m_file.exists() ? QFile::Append : QFile::WriteOnly)) { + setErrorString(m_file.errorString()); + setStatus(Failed); + return; + } + + if (!m_nam) { + m_nam = new QNetworkAccessManager(this); + m_ownNetworkAccessManager = true; + } + + QNetworkRequest request(u); + request.setRawHeader("User-Agent", USER_AGENT); + + if (m_bytesTransferred > 0) { + request.setRawHeader("Range", "bytes=" + QByteArray::number(m_bytesTransferred) + "-"); + } + + setStatus(Downloading); + + m_redirects = 0; + m_reply = m_nam->get(request); + connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); + connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); + connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +void Transfer::followRedirect(const QUrl &u) { + Logger::log("Transfer::followRedirect(). URL: " + u.toString(), Logger::LowVerbosity); + QDir().mkpath(downloadPath()); + + if (!m_file.open(m_file.exists() ? QFile::Append : QFile::WriteOnly)) { + setErrorString(m_file.errorString()); + setStatus(Failed); + return; + } + + m_redirects++; + + if (!m_nam) { + m_nam = new QNetworkAccessManager(this); + m_ownNetworkAccessManager = true; + } + + QNetworkRequest request(u); + request.setRawHeader("User-Agent", USER_AGENT); + + if (m_bytesTransferred > 0) { + request.setRawHeader("Range", "bytes=" + QByteArray::number(m_bytesTransferred) + "-"); + } + + m_reply = m_nam->get(request); + connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); + connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); + connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +bool Transfer::executeCustomCommands() { + Logger::log("Transfer::executeCustomCommands()", Logger::LowVerbosity); + m_commands.clear(); + QString command = customCommand(); + const QString defaultCommand = Settings::customTransferCommand(); + const bool defaultEnabled = (!defaultCommand.isEmpty()) && (Settings::customTransferCommandEnabled()); + + if (!command.isEmpty()) { + const QString workingDirectory = downloadPath(); + command.replace("%f", fileName()); + m_commands << Command(workingDirectory, command); + Logger::log(QString("Transfer::executeCustomCommands(): Adding custom command: Working directory: %1, Command: %2") + .arg(workingDirectory).arg(command), Logger::LowVerbosity); + } + + if ((defaultEnabled) && ((command.isEmpty()) || (!customCommandOverrideEnabled()))) { + const QString workingDirectory = downloadPath(); + command = defaultCommand; + command.replace("%f", fileName()); + m_commands << Command(workingDirectory, command); + Logger::log(QString("Transfer::executeCustomCommands(): Adding custom command: Working directory: %1, Command: %2") + .arg(workingDirectory).arg(command), Logger::LowVerbosity); + } + + if (!m_commands.isEmpty()) { + setStatus(ExecutingCustomCommand); + executeCustomCommand(m_commands.takeFirst()); + return true; + } + + return false; +} + +void Transfer::executeCustomCommand(const Command &command) { + if (!m_process) { + m_process = new QProcess(this); + connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onCustomCommandFinished(int))); + connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onCustomCommandError())); + } + + Logger::log(QString("Transfer::executeCustomCommand(): Working directory: %1, Command: %2") + .arg(command.workingDirectory).arg(command.command), Logger::LowVerbosity); + + if (QDir(command.workingDirectory).exists()) { + m_process->setWorkingDirectory(command.workingDirectory); + } + + m_process->start(command.command); +} + +void Transfer::moveDownloadedFiles() { + Logger::log("Transfer::moveDownloadedFiles()", Logger::LowVerbosity); + QDir destDir(Settings::downloadPath(category())); + + if (!destDir.mkpath(destDir.path())) { + setErrorString(tr("Cannot make download path %1").arg(destDir.path())); + setStatus(Failed); + return; + } + + QDir downDir(downloadPath()); + + foreach (const QFileInfo &info, downDir.entryInfoList(QDir::Files)) { + int i = 0; + QString suffix = info.suffix(); + + if (suffix.isEmpty()) { + Logger::log("Transfer::moveDownloadedFiles(). Using default suffix .mp3", Logger::MediumVerbosity); + suffix = "mp3"; + } + + QString newFileName = QString("%1/%2.%3").arg(destDir.path()).arg(info.completeBaseName()).arg(suffix); + + while ((destDir.exists(newFileName)) && (i < 100)) { + i++; + newFileName = (i == 1 ? QString("%1(%2)%3").arg(newFileName.left(newFileName.lastIndexOf('.'))) + .arg(i).arg(newFileName.mid(newFileName.lastIndexOf('.'))) + : QString("%1(%2)%3").arg(newFileName.left(newFileName.lastIndexOf('('))) + .arg(i).arg(newFileName.mid(newFileName.lastIndexOf('.')))); + } + + if (!destDir.rename(info.absoluteFilePath(), newFileName)) { + setErrorString(tr("Cannot rename downloaded file to %1").arg(newFileName)); + setStatus(Failed); + return; + } + } + + downDir.rmdir(downDir.path()); + setErrorString(QString()); + setStatus(Completed); +} + +void Transfer::onReplyMetaDataChanged() { + if ((m_metadataSet) || (m_reply->error() != QNetworkReply::NoError) || (!m_reply->rawHeader("Location").isEmpty())) { + return; + } + + qint64 bytes = m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); + + if (bytes <= 0) { + bytes = m_reply->rawHeader("Content-Length").toLongLong(); + } + + if (bytes > 0) { + setSize(bytes + bytesTransferred()); + } + + m_metadataSet = true; +} + +void Transfer::onReplyReadyRead() { + if (!m_metadataSet) { + return; + } + + const qint64 bytes = m_reply->bytesAvailable(); + + if (bytes < DOWNLOAD_BUFFER_SIZE) { + return; + } + + if (m_file.write(m_reply->read(bytes)) == -1) { + m_reply->deleteLater(); + m_reply = 0; + setErrorString(tr("Cannot write to file - %1").arg(m_file.errorString())); + setStatus(Failed); + return; + } + + m_bytesTransferred += bytes; + emit bytesTransferredChanged(); + + if (m_size > 0) { + setProgress(m_bytesTransferred * 100 / m_size); + } +} + +void Transfer::onReplyFinished() { + const QString redirect = QString::fromUtf8(m_reply->rawHeader("Location")); + + if (!redirect.isEmpty()) { + m_file.close(); + m_reply->deleteLater(); + m_reply = 0; + + if (m_redirects < MAX_REDIRECTS) { + followRedirect(redirect); + } + else { + setErrorString(tr("Maximum redirects reached")); + setStatus(Failed); + } + + return; + } + + const QNetworkReply::NetworkError error = m_reply->error(); + const QString errorString = m_reply->errorString(); + + if ((m_reply->isOpen()) && (error == QNetworkReply::NoError) && (m_file.isOpen())) { + const qint64 bytes = m_reply->bytesAvailable(); + + if ((bytes > 0) && (m_metadataSet)) { + m_file.write(m_reply->read(bytes)); + m_bytesTransferred += bytes; + + if (m_size > 0) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } + + m_file.close(); + m_reply->deleteLater(); + m_reply = 0; + + switch (error) { + case QNetworkReply::NoError: + break; + case QNetworkReply::OperationCanceledError: + setErrorString(QString()); + + if (m_canceled) { + m_file.remove(); + QDir().rmdir(downloadPath()); + setStatus(Canceled); + } + else { + setStatus(Paused); + } + + return; + default: + setErrorString(errorString); + setStatus(Failed); + return; + } + + if (!executeCustomCommands()) { + moveDownloadedFiles(); + } +} + +void Transfer::onCustomCommandFinished(int exitCode) { + if (exitCode != 0) { + Logger::log("Transfer::onCustomCommandFinished(): Error: " + m_process->readAllStandardError()); + } + + if (!m_commands.isEmpty()) { + executeCustomCommand(m_commands.takeFirst()); + } + else { + moveDownloadedFiles(); + } +} + +void Transfer::onCustomCommandError() { + Logger::log("Transfer::onCustomCommandError(): " + m_process->errorString()); + + if (!m_commands.isEmpty()) { + executeCustomCommand(m_commands.takeFirst()); + } + else { + moveDownloadedFiles(); + } +} diff --git a/app/src/maemo5/transfer.h b/app/src/maemo5/transfer.h new file mode 100644 index 0000000..b3cf3ae --- /dev/null +++ b/app/src/maemo5/transfer.h @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRANSFER_H +#define TRANSFER_H + +#include +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QProcess; + +struct Command +{ + Command(const QString &dir, const QString &com) : + workingDirectory(dir), + command(com) + { + } + + QString workingDirectory; + QString command; +}; + +typedef QList CommandList; + +class Transfer : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qint64 bytesTransferred READ bytesTransferred NOTIFY bytesTransferredChanged) + Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) + Q_PROPERTY(QString customCommand READ customCommand WRITE setCustomCommand NOTIFY customCommandChanged) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled WRITE setCustomCommandOverrideEnabled + NOTIFY customCommandOverrideEnabledChanged) + Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged) + Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) + Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) + Q_PROPERTY(QString priorityString READ priorityString NOTIFY priorityChanged) + Q_PROPERTY(int progress READ progress NOTIFY progressChanged) + Q_PROPERTY(QString progressString READ progressString NOTIFY progressChanged) + Q_PROPERTY(QString service READ service NOTIFY serviceChanged) + Q_PROPERTY(qint64 size READ size WRITE setSize NOTIFY sizeChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString statusString READ statusString NOTIFY statusChanged) + Q_PROPERTY(QString streamId READ streamId WRITE setStreamId NOTIFY streamIdChanged) + Q_PROPERTY(QUrl streamUrl READ streamUrl WRITE setStreamUrl NOTIFY streamUrlChanged) + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QString trackId READ trackId WRITE setTrackId NOTIFY trackIdChanged) + Q_PROPERTY(TransferType transferType READ transferType WRITE setTransferType NOTIFY transferTypeChanged) + Q_PROPERTY(QUrl url READ url NOTIFY statusChanged) + + Q_ENUMS(Priority Status TransferType) + +public: + enum Priority { + HighPriority = 0, + NormalPriority, + LowPriority + }; + + enum Status { + Paused = 0, + Canceled, + Failed, + Completed, + Queued, + Connecting, + Downloading, + Uploading, + ExecutingCustomCommand, + Unknown + }; + + enum TransferType { + Download = 0, + Upload + }; + + explicit Transfer(QObject *parent = 0); + + void setNetworkAccessManager(QNetworkAccessManager *manager); + + qint64 bytesTransferred() const; + + QString category() const; + void setCategory(const QString &c); + + QString customCommand() const; + void setCustomCommand(const QString &c); + bool customCommandOverrideEnabled() const; + void setCustomCommandOverrideEnabled(bool enabled); + + QString downloadPath() const; + void setDownloadPath(const QString &path); + + QString errorString() const; + + QString fileName() const; + void setFileName(const QString &fn); + + QString id() const; + void setId(const QString &i); + + Priority priority() const; + void setPriority(Priority p); + QString priorityString() const; + + int progress() const; + QString progressString() const; + + QString service() const; + + qint64 size() const; + void setSize(qint64 s); + + Status status() const; + QString statusString() const; + + QString streamId() const; + void setStreamId(const QString &si); + QUrl streamUrl() const; + void setStreamUrl(const QUrl &url); + + QString title() const; + void setTitle(const QString &title); + + QString trackId() const; + void setTrackId(const QString &i); + + TransferType transferType() const; + void setTransferType(TransferType type); + + QUrl url() const; + +public Q_SLOTS: + void queue(); + void start(); + void pause(); + void cancel(); + +protected: + virtual void listStreams() = 0; + + void setErrorString(const QString &es); + + void setProgress(int p); + + void setService(const QString &s); + + void setStatus(Status s); + + void startDownload(const QUrl &u); + void followRedirect(const QUrl &u); + + bool executeCustomCommands(); + void executeCustomCommand(const Command &command); + + void moveDownloadedFiles(); + +private Q_SLOTS: + void onReplyMetaDataChanged(); + void onReplyReadyRead(); + void onReplyFinished(); + void onCustomCommandFinished(int exitCode); + void onCustomCommandError(); + +Q_SIGNALS: + void bytesTransferredChanged(); + void categoryChanged(); + void customCommandChanged(); + void customCommandOverrideEnabledChanged(); + void downloadPathChanged(); + void fileNameChanged(); + void idChanged(); + void priorityChanged(); + void progressChanged(); + void serviceChanged(); + void sizeChanged(); + void statusChanged(); + void streamIdChanged(); + void streamUrlChanged(); + void titleChanged(); + void trackIdChanged(); + void transferTypeChanged(); + +private: + QPointer m_nam; + QNetworkReply *m_reply; + QProcess *m_process; + + QFile m_file; + + bool m_ownNetworkAccessManager; + bool m_canceled; + + QString m_category; + + QString m_customCommand; + bool m_customCommandOverrideEnabled; + + QString m_downloadPath; + + QString m_errorString; + + QString m_fileName; + + QString m_id; + + Priority m_priority; + + int m_progress; + + QString m_service; + + qint64 m_size; + qint64 m_bytesTransferred; + + int m_redirects; + + Status m_status; + + QString m_streamId; + QUrl m_streamUrl; + + QString m_title; + + QString m_trackId; + + TransferType m_transferType; + + CommandList m_commands; + + bool m_metadataSet; +}; + +#endif // TRANSFER_H diff --git a/app/src/maemo5/transferswindow.cpp b/app/src/maemo5/transferswindow.cpp index ceb3d2a..8e7f414 100644 --- a/app/src/maemo5/transferswindow.cpp +++ b/app/src/maemo5/transferswindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -15,6 +15,7 @@ */ #include "transferswindow.h" +#include "customcommanddialog.h" #include "settings.h" #include "transfermodel.h" #include "transfers.h" @@ -33,22 +34,18 @@ TransfersWindow::TransfersWindow(StackedWindow *parent) : m_startAction(new QAction(tr("Start all transfers"), this)), m_pauseAction(new QAction(tr("Pause all transfers"), this)), m_contextMenu(new QMenu(this)), - m_transferStartAction(m_contextMenu->addAction(QIcon::fromTheme("media-playback-start"), tr("Start"), - this, SLOT(startCurrentTransfer()))), - m_transferPauseAction(m_contextMenu->addAction(QIcon::fromTheme("media-playback-pause"), tr("Pause"), - this, SLOT(pauseCurrentTransfer()))), + m_transferCommandAction(m_contextMenu->addAction(tr("Set custom command"), + this, SLOT(setCurrentTransferCustomCommand()))), + m_transferStartAction(m_contextMenu->addAction(tr("Start"), this, SLOT(startCurrentTransfer()))), + m_transferPauseAction(m_contextMenu->addAction(tr("Pause"), this, SLOT(pauseCurrentTransfer()))), m_transferCategoryMenu(m_contextMenu->addMenu(tr("Category"))), m_transferCategoryGroup(new QActionGroup(this)), m_transferPriorityMenu(m_contextMenu->addMenu(tr("Priority"))), m_transferPriorityGroup(new QActionGroup(this)), - m_transferHighPriorityAction(m_transferPriorityMenu->addAction(tr("High"), - this, SLOT(setCurrentTransferPriority()))), - m_transferNormalPriorityAction(m_transferPriorityMenu->addAction(tr("Normal"), - this, SLOT(setCurrentTransferPriority()))), - m_transferLowPriorityAction(m_transferPriorityMenu->addAction(tr("Low"), - this, SLOT(setCurrentTransferPriority()))), - m_transferRemoveAction(m_contextMenu->addAction(QIcon::fromTheme("edit-delete"), tr("Remove"), - this, SLOT(removeCurrentTransfer()))), + m_transferHighPriorityAction(m_transferPriorityMenu->addAction(tr("High"), this, SLOT(setCurrentTransferPriority()))), + m_transferNormalPriorityAction(m_transferPriorityMenu->addAction(tr("Normal"), this, SLOT(setCurrentTransferPriority()))), + m_transferLowPriorityAction(m_transferPriorityMenu->addAction(tr("Low"), this, SLOT(setCurrentTransferPriority()))), + m_transferRemoveAction(m_contextMenu->addAction(tr("Remove"), this, SLOT(removeCurrentTransfer()))), m_label(new QLabel(QString("

%2

") .arg(palette().color(QPalette::Mid).name()).arg(tr("No transfers queued")), this)) { @@ -66,12 +63,14 @@ TransfersWindow::TransfersWindow(StackedWindow *parent) : m_view->setColumnHidden(1, true); QHeaderView *header = m_view->header(); - QFontMetrics fm = header->fontMetrics(); - header->resizeSection(0, fm.width("A long transfer name") + 10); - header->resizeSection(1, fm.width("A long category name") + 10); - header->resizeSection(2, fm.width(m_model->headerData(2).toString()) + 10); - header->resizeSection(3, fm.width("999.99MB of 999.99MB (99.99%)") + 10); - header->setSectionHidden(1, true); + + if (!header->restoreState(Settings::transfersHeaderViewState())) { + const QFontMetrics fm = header->fontMetrics(); + header->resizeSection(0, fm.width("A long transfer name") + 10); + header->resizeSection(2, fm.width(m_model->headerData(2).toString()) + 10); + header->resizeSection(3, fm.width("999.99MB of 999.99MB (99.99%)") + 10); + header->setSectionHidden(1, true); + } m_transferCategoryGroup->setExclusive(true); m_transferPriorityGroup->setExclusive(true); @@ -103,29 +102,28 @@ TransfersWindow::TransfersWindow(StackedWindow *parent) : setCategoryMenuActions(); } +void TransfersWindow::closeEvent(QCloseEvent *e) { + Settings::setTransfersHeaderViewState(m_view->header()->saveState()); + StackedWindow::closeEvent(e); +} + void TransfersWindow::onCountChanged(int count) { if (count > 0) { m_label->hide(); m_view->show(); - m_startAction->setEnabled(true); - m_pauseAction->setEnabled(true); } else { m_view->hide(); m_label->show(); - m_startAction->setEnabled(false); - m_pauseAction->setEnabled(false); } } void TransfersWindow::setCategoryMenuActions() { m_transferCategoryMenu->clear(); - - QStringList categories = Settings::instance()->categoryNames(); - + const QStringList categories = Settings::categoryNames(); m_transferCategoryMenu->setEnabled(!categories.isEmpty()); - foreach (QString category, categories) { + foreach (const QString &category, categories) { QAction *transferAction = m_transferCategoryMenu->addAction(category, this, SLOT(setCurrentTransferCategory())); transferAction->setCheckable(true); transferAction->setActionGroup(m_transferCategoryGroup); @@ -133,7 +131,7 @@ void TransfersWindow::setCategoryMenuActions() { } void TransfersWindow::setTransferMenuActions() { - QModelIndex index = m_view->currentIndex(); + const QModelIndex index = m_view->currentIndex(); if (!index.isValid()) { return; @@ -161,7 +159,7 @@ void TransfersWindow::setTransferMenuActions() { m_transferNormalPriorityAction->setChecked(true); } - QString category = index.data(TransferModel::CategoryRole).toString(); + const QString category = index.data(TransferModel::CategoryRole).toString(); foreach (QAction *action, m_transferCategoryMenu->actions()) { if (action->text() == category) { @@ -176,51 +174,76 @@ void TransfersWindow::setTransferMenuActions() { } void TransfersWindow::showContextMenu(const QPoint &pos) { - m_contextMenu->popup(m_view->mapToGlobal(pos), m_startAction); + m_contextMenu->popup(m_view->mapToGlobal(pos), m_transferCommandAction); } void TransfersWindow::startCurrentTransfer() { - if (m_view->currentIndex().isValid()) { - if (Transfer *transfer = Transfers::instance()->get(m_view->currentIndex().row())) { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { + if (Transfer *transfer = Transfers::instance()->get(index.row())) { transfer->queue(); } } } void TransfersWindow::pauseCurrentTransfer() { - if (m_view->currentIndex().isValid()) { - if (Transfer *transfer = Transfers::instance()->get(m_view->currentIndex().row())) { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { + if (Transfer *transfer = Transfers::instance()->get(index.row())) { transfer->pause(); } } } void TransfersWindow::removeCurrentTransfer() { - if (m_view->currentIndex().isValid()) { - if (Transfer *transfer = Transfers::instance()->get(m_view->currentIndex().row())) { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { + if (Transfer *transfer = Transfers::instance()->get(index.row())) { transfer->cancel(); } } } +void TransfersWindow::setCurrentTransferCustomCommand() { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { + CustomCommandDialog dialog(this); + dialog.setCommand(index.data(TransferModel::CustomCommandRole).toString()); + dialog.setOverrideEnabled(index.data(TransferModel::CustomCommandOverrideEnabledRole).toBool()); + + if (dialog.exec() == QDialog::Accepted) { + m_model->setData(index, dialog.command(), TransferModel::CustomCommandRole); + m_model->setData(index, dialog.overrideEnabled(), TransferModel::CustomCommandOverrideEnabledRole); + } + } +} + void TransfersWindow::setCurrentTransferCategory() { - if (m_view->currentIndex().isValid()) { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { if (QAction *action = qobject_cast(sender())) { - m_model->setData(m_view->currentIndex(), action->text(), TransferModel::CategoryRole); + m_model->setData(index, action->text(), TransferModel::CategoryRole); } } } void TransfersWindow::setCurrentTransferPriority() { - if (m_view->currentIndex().isValid()) { + const QModelIndex index = m_view->currentIndex(); + + if (index.isValid()) { if (m_transferPriorityGroup->checkedAction() == m_transferHighPriorityAction) { - m_model->setData(m_view->currentIndex(), Transfer::HighPriority, TransferModel::PriorityRole); + m_model->setData(index, Transfer::HighPriority, TransferModel::PriorityRole); } else if (m_transferPriorityGroup->checkedAction() == m_transferLowPriorityAction) { - m_model->setData(m_view->currentIndex(), Transfer::LowPriority, TransferModel::PriorityRole); + m_model->setData(index, Transfer::LowPriority, TransferModel::PriorityRole); } else { - m_model->setData(m_view->currentIndex(), Transfer::NormalPriority, TransferModel::PriorityRole); + m_model->setData(index, Transfer::NormalPriority, TransferModel::PriorityRole); } } } diff --git a/app/src/maemo5/transferswindow.h b/app/src/maemo5/transferswindow.h index 025d9af..8395651 100644 --- a/app/src/maemo5/transferswindow.h +++ b/app/src/maemo5/transferswindow.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -33,7 +33,10 @@ class TransfersWindow : public StackedWindow public: explicit TransfersWindow(StackedWindow *parent = 0); - + +protected: + virtual void closeEvent(QCloseEvent *e); + private Q_SLOTS: void onCountChanged(int count); void setCategoryMenuActions(); @@ -42,6 +45,7 @@ private Q_SLOTS: void startCurrentTransfer(); void pauseCurrentTransfer(); void removeCurrentTransfer(); + void setCurrentTransferCustomCommand(); void setCurrentTransferCategory(); void setCurrentTransferPriority(); @@ -52,6 +56,7 @@ private Q_SLOTS: QAction *m_startAction; QAction *m_pauseAction; QMenu *m_contextMenu; + QAction *m_transferCommandAction; QAction *m_transferStartAction; QAction *m_transferPauseAction; QMenu *m_transferCategoryMenu; diff --git a/app/src/maemo5/webview.cpp b/app/src/maemo5/webview.cpp index 1989199..be6503d 100644 --- a/app/src/maemo5/webview.cpp +++ b/app/src/maemo5/webview.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2015 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "webview.h" #include diff --git a/app/src/maemo5/webview.h b/app/src/maemo5/webview.h index 32cd6c6..f579895 100644 --- a/app/src/maemo5/webview.h +++ b/app/src/maemo5/webview.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2015 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + #ifndef WEBVIEW_H #define WEBVIEW_H diff --git a/app/src/plugins/externalresourcesrequest.cpp b/app/src/plugins/externalresourcesrequest.cpp new file mode 100644 index 0000000..06e7aeb --- /dev/null +++ b/app/src/plugins/externalresourcesrequest.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "externalresourcesrequest.h" +#include "json.h" +#include "logger.h" +#include + +ExternalResourcesRequest::ExternalResourcesRequest(const QString &id, const QString &fileName, QObject *parent) : + ResourcesRequest(parent), + m_process(0), + m_fileName(fileName), + m_id(id) +{ +} + +QString ExternalResourcesRequest::fileName() const { + return m_fileName; +} + +QString ExternalResourcesRequest::id() const { + return m_id; +} + +QString ExternalResourcesRequest::errorString() const { + return m_errorString; +} + +void ExternalResourcesRequest::setErrorString(const QString &e) { + m_errorString = e; +} + +QVariant ExternalResourcesRequest::result() const { + return m_result; +} + +void ExternalResourcesRequest::setResult(const QVariant &r) { + m_result = r; +} + +ResourcesRequest::Status ExternalResourcesRequest::status() const { + return m_status; +} + +void ExternalResourcesRequest::setStatus(ResourcesRequest::Status s) { + if (s != status()) { + m_status = s; + emit statusChanged(s); + } +} + +QProcess* ExternalResourcesRequest::process() { + if (!m_process) { + m_process = new QProcess(this); + connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onRequestError())); + connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onRequestFinished(int))); + } + + return m_process; +} + +bool ExternalResourcesRequest::cancel() { + return false; +} + +bool ExternalResourcesRequest::del(const QString &sourceType, const QString &sourceId, const QString &destinationType, + const QString &destinationId) { + if (status() == Loading) { + return false; + } + + setStatus(Loading); + const QString command = QString("\"%1\" del \"%2\" \"%3\" \"%4\" \"%5\"").arg(fileName()).arg(sourceType) + .arg(sourceId).arg(destinationType) + .arg(destinationId); + QProcess *pr = process(); + pr->start(command); + + switch (pr->state()) { + case QProcess::Starting: + case QProcess::Running: + Logger::log("ExternalResourcesRequest::del(). Invoking command: " + command, Logger::MediumVerbosity); + return true; + default: + Logger::log("ExternalResourcesRequest::del(). Error invoking command: " + command); + return false; + } +} + +bool ExternalResourcesRequest::get(const QString &resourceType, const QString &resourceId) { + if (status() == Loading) { + return false; + } + + setStatus(Loading); + const QString command = QString("\"%1\" get \"%2\" \"%3\"").arg(fileName()).arg(resourceType).arg(resourceId); + QProcess *pr = process(); + pr->start(command); + + switch (pr->state()) { + case QProcess::Starting: + case QProcess::Running: + Logger::log("ExternalResourcesRequest::get(). Invoking command: " + command, Logger::MediumVerbosity); + return true; + default: + Logger::log("ExternalResourcesRequest::get(). Error invoking command: " + command); + return false; + }; +} + +bool ExternalResourcesRequest::insert(const QString &sourceType, const QString &sourceId, + const QString &destinationType, const QString &destinationId) { + if (status() == Loading) { + return false; + } + + setStatus(Loading); + const QString command = QString("\"%1\" insert \"%2\" \"%3\" \"%4\" \"%5\"").arg(fileName()).arg(sourceType) + .arg(sourceId).arg(destinationType) + .arg(destinationId); + QProcess *pr = process(); + pr->start(command); + + switch (pr->state()) { + case QProcess::Starting: + case QProcess::Running: + Logger::log("ExternalResourcesRequest::insert(). Invoking command: " + command, Logger::MediumVerbosity); + return true; + default: + Logger::log("ExternalResourcesRequest::insert(). Error invoking command: " + command); + return false; + } +} + +bool ExternalResourcesRequest::list(const QString &resourceType, const QString &resourceId) { + if (status() == Loading) { + return false; + } + + setStatus(Loading); + const QString command = QString("\"%1\" list \"%2\" \"%3\"").arg(fileName()).arg(resourceType).arg(resourceId); + QProcess *pr = process(); + pr->start(command); + + switch (pr->state()) { + case QProcess::Starting: + case QProcess::Running: + Logger::log("ExternalResourcesRequest::list(). Invoking command: " + command, Logger::MediumVerbosity); + return true; + default: + Logger::log("ExternalResourcesRequest::list(). Error invoking command: " + command); + return false; + } +} + +bool ExternalResourcesRequest::search(const QString &resourceType, const QString &query, const QString &order) { + if (status() == Loading) { + return false; + } + + setStatus(Loading); + const QString command = QString("\"%1\" search \"%2\" \"%3\" \"%4\"").arg(fileName()).arg(resourceType).arg(query) + .arg(order); + QProcess *pr = process(); + pr->start(command); + + switch (pr->state()) { + case QProcess::Starting: + case QProcess::Running: + Logger::log("ExternalResourcesRequest::search(). Invoking command: " + command, Logger::MediumVerbosity); + return true; + default: + Logger::log("ExternalResourcesRequest::search(). Error invoking command: " + command); + return false; + } +} + +void ExternalResourcesRequest::onRequestError() { + const QString errorString = m_process->errorString(); + Logger::log("ExternalResourcesRequest::onRequestError(): " + errorString); + setErrorString(errorString); + setResult(QVariant()); + setStatus(Failed); + emit finished(); +} + +void ExternalResourcesRequest::onRequestFinished(int exitCode) { + const QVariant result = QtJson::Json::parse(QString::fromUtf8(m_process->readAllStandardOutput())); + setResult(result); + + if (exitCode == 0) { + Logger::log("ExternalResourcesRequest::onRequestFinished(). Exit code 0", Logger::MediumVerbosity); + setErrorString(QString()); + setStatus(Ready); + } + else { + if (result.type() == QVariant::Map) { + const QString errorString = result.toMap().value("error").toString(); + + if (!errorString.isEmpty()) { + setErrorString(errorString); + } + else { + setErrorString(tr("Unknown error")); + } + } + else { + setErrorString(m_process->errorString()); + } + + Logger::log(QString("ExternalResourcesRequest::onRequestFinished(). Exit code: %1, Error: %2") + .arg(exitCode).arg(errorString())); + setStatus(Failed); + } + + emit finished(); +} diff --git a/app/src/plugins/externalresourcesrequest.h b/app/src/plugins/externalresourcesrequest.h new file mode 100644 index 0000000..448ffed --- /dev/null +++ b/app/src/plugins/externalresourcesrequest.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EXTERNALRESOURCESREQUEST_H +#define EXTERNALRESOURCESREQUEST_H + +#include "resourcesrequest.h" + +class QProcess; + +class ExternalResourcesRequest : public ResourcesRequest +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName) + Q_PROPERTY(QString id READ id) + +public: + explicit ExternalResourcesRequest(const QString &id, const QString &fileName, QObject *parent = 0); + + QString fileName() const; + + QString id() const; + + virtual QString errorString() const; + + virtual QVariant result() const; + + virtual Status status() const; + +public Q_SLOTS: + virtual bool cancel(); + virtual bool del(const QString &sourceType, const QString &sourceId, const QString &destinationType, + const QString &destinationId); + virtual bool get(const QString &resourceType, const QString &resourceId); + virtual bool insert(const QString &sourceType, const QString &sourceId, const QString &destinationType, + const QString &destinationId); + virtual bool list(const QString &resourceType, const QString &resourceId); + virtual bool search(const QString &resourceType, const QString &query, const QString &order); + +private Q_SLOTS: + void onRequestError(); + void onRequestFinished(int exitCode); + +private: + void setErrorString(const QString &e); + + void setResult(const QVariant &r); + + void setStatus(Status s); + + QProcess* process(); + + QProcess *m_process; + + QString m_fileName; + QString m_id; + + QString m_errorString; + + QVariant m_result; + + Status m_status; +}; + +#endif // EXTERNALSERVICEPLUGIN_H diff --git a/app/src/plugins/externalserviceplugin.cpp b/app/src/plugins/externalserviceplugin.cpp new file mode 100644 index 0000000..746c00b --- /dev/null +++ b/app/src/plugins/externalserviceplugin.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "externalserviceplugin.h" +#include "externalresourcesrequest.h" + +ExternalServicePlugin::ExternalServicePlugin(QObject *parent) : + QObject(parent), + ServicePlugin() +{ +} + +ExternalServicePlugin::ExternalServicePlugin(const QString &id, const QString &fileName, QObject *parent) : + QObject(parent), + ServicePlugin(), + m_fileName(fileName), + m_id(id) +{ +} + +QString ExternalServicePlugin::fileName() const { + return m_fileName; +} + +void ExternalServicePlugin::setFileName(const QString &fileName) { + m_fileName = fileName; +} + +QString ExternalServicePlugin::id() const { + return m_id; +} + +void ExternalServicePlugin::setId(const QString &id) { + m_id = id; +} + +ResourcesRequest* ExternalServicePlugin::createRequest(QObject *parent) { + return new ExternalResourcesRequest(id(), fileName(), parent); +} diff --git a/app/src/plugins/externalserviceplugin.h b/app/src/plugins/externalserviceplugin.h new file mode 100644 index 0000000..bbbb7bb --- /dev/null +++ b/app/src/plugins/externalserviceplugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef EXTERNALSERVICEPLUGIN_H +#define EXTERNALSERVICEPLUGIN_H + +#include "serviceplugin.h" + +class ExternalServicePlugin : public QObject, public ServicePlugin +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(QString id READ id WRITE setId) + + Q_INTERFACES(ServicePlugin) + +public: + explicit ExternalServicePlugin(QObject *parent = 0); + explicit ExternalServicePlugin(const QString &id, const QString &fileName, QObject *parent = 0); + + QString fileName() const; + void setFileName(const QString &fileName); + + QString id() const; + void setId(const QString &id); + + virtual ResourcesRequest* createRequest(QObject *parent = 0); + +private: + QString m_fileName; + QString m_id; +}; + +#endif // EXTERNALSERVICEPLUGIN_H diff --git a/app/src/plugins/javascriptresourcesrequest.cpp b/app/src/plugins/javascriptresourcesrequest.cpp new file mode 100644 index 0000000..3178014 --- /dev/null +++ b/app/src/plugins/javascriptresourcesrequest.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "javascriptresourcesrequest.h" +#include "logger.h" +#include "pluginsettings.h" +#include +#include + +JavaScriptResourcesRequest::JavaScriptResourcesRequest(const QString &id, const QString &fileName, QObject *parent) : + ResourcesRequest(parent), + m_global(0), + m_engine(0), + m_fileName(fileName), + m_id(id), + m_evaluated(false) +{ +} + +QString JavaScriptResourcesRequest::fileName() const { + return m_fileName; +} + +QString JavaScriptResourcesRequest::id() const { + return m_id; +} + +QString JavaScriptResourcesRequest::errorString() const { + return m_errorString; +} + +void JavaScriptResourcesRequest::setErrorString(const QString &e) { + m_errorString = e; +} + +QVariant JavaScriptResourcesRequest::result() const { + return m_result; +} + +void JavaScriptResourcesRequest::setResult(const QVariant &r) { + m_result = r; +} + +ResourcesRequest::Status JavaScriptResourcesRequest::status() const { + return m_status; +} + +void JavaScriptResourcesRequest::setStatus(ResourcesRequest::Status s) { + if (s != status()) { + m_status = s; + emit statusChanged(s); + } +} + +void JavaScriptResourcesRequest::initEngine() { + if (m_evaluated) { + return; + } + + if (!m_engine) { + m_engine = new QScriptEngine(this); + } + + QFile file(fileName()); + + if (file.open(QFile::ReadOnly)) { + const QScriptValue result = m_engine->evaluate(file.readAll(), fileName()); + file.close(); + + if (result.isError()) { + Logger::log("JavaScriptResourcesRequest::initEngine(): Error evaluating JavaScript file: " + + result.toString()); + return; + } + + Logger::log("JavaScriptResourcesRequest::initEngine(): JavaScript file evaluated OK", Logger::MediumVerbosity); + m_evaluated = true; + m_global = new JavaScriptResourcesRequestGlobalObject(m_engine); + + connect(m_global, SIGNAL(error(QString)), this, SLOT(onRequestError(QString))); + connect(m_global, SIGNAL(finished(QVariant)), this, SLOT(onRequestFinished(QVariant))); + + m_engine->installTranslatorFunctions(); + m_engine->globalObject().setProperty("settings", m_engine->newQObject(new PluginSettings(id(), m_engine))); + } + else { + Logger::log("JavaScriptResourcesRequest::initEngine(): Error reading JavaScript file: " + + file.errorString()); + } +} + +bool JavaScriptResourcesRequest::cancel() { + if (!m_engine) { + return false; + } + + return m_engine->globalObject().property("cancel").call(QScriptValue()).toBool(); +} + +bool JavaScriptResourcesRequest::del(const QString &sourceType, const QString &sourceId, const QString &destinationType, + const QString &destinationId) { + if (status() == Loading) { + return false; + } + + initEngine(); + QScriptValue func = m_engine->globalObject().property("del"); + + if (func.isFunction()) { + const QScriptValue result = func.call(QScriptValue(), QScriptValueList() << sourceType << sourceId + << destinationType << destinationId); + + if (result.isError()) { + const QString errorString = result.toString(); + Logger::log("JavaScriptResourcesRequest::del(). Error calling del(): " + errorString); + setErrorString(errorString); + setStatus(Failed); + emit finished(); + return false; + } + + if (result.toBool()) { + setErrorString(QString()); + setStatus(Loading); + return true; + } + } + else { + Logger::log("JavaScriptResourcesRequest::del(). del() function not defined"); + setErrorString(tr("del() function not defined")); + setStatus(Failed); + emit finished(); + } + + return false; +} + +bool JavaScriptResourcesRequest::get(const QString &resourceType, const QString &resourceId) { + if (status() == Loading) { + return false; + } + + initEngine(); + QScriptValue func = m_engine->globalObject().property("get"); + + if (func.isFunction()) { + const QScriptValue result = func.call(QScriptValue(), QScriptValueList() << resourceType << resourceId); + + if (result.isError()) { + const QString errorString = result.toString(); + Logger::log("JavaScriptResourcesRequest::get(). Error calling get(): " + errorString); + setErrorString(errorString); + setStatus(Failed); + emit finished(); + return false; + } + + if (result.toBool()) { + setErrorString(QString()); + setStatus(Loading); + return true; + } + } + else { + Logger::log("JavaScriptResourcesRequest::get(). get() function not defined"); + setErrorString(tr("get() function not defined")); + setStatus(Failed); + emit finished(); + } + + return false; +} + +bool JavaScriptResourcesRequest::insert(const QString &sourceType, const QString &sourceId, + const QString &destinationType, const QString &destinationId) { + if (status() == Loading) { + return false; + } + + initEngine(); + QScriptValue func = m_engine->globalObject().property("insert"); + + if (func.isFunction()) { + const QScriptValue result = func.call(QScriptValue(), QScriptValueList() << sourceType << sourceId + << destinationType << destinationId); + + if (result.isError()) { + const QString errorString = result.toString(); + Logger::log("JavaScriptResourcesRequest::insert(). Error calling insert(): " + errorString); + setErrorString(errorString); + setStatus(Failed); + emit finished(); + return false; + } + + if (result.toBool()) { + setErrorString(QString()); + setStatus(Loading); + return true; + } + } + else { + Logger::log("JavaScriptResourcesRequest::insert(). insert() function not defined"); + setErrorString(tr("insert() function not defined")); + setStatus(Failed); + emit finished(); + } + + return false; +} + +bool JavaScriptResourcesRequest::list(const QString &resourceType, const QString &resourceId) { + if (status() == Loading) { + return false; + } + + initEngine(); + QScriptValue func = m_engine->globalObject().property("list"); + + if (func.isFunction()) { + const QScriptValue result = func.call(QScriptValue(), QScriptValueList() << resourceType << resourceId); + + if (result.isError()) { + const QString errorString = result.toString(); + Logger::log("JavaScriptResourcesRequest::list(). Error calling list(): " + errorString); + setErrorString(errorString); + setStatus(Failed); + emit finished(); + return false; + } + + if (result.toBool()) { + setErrorString(QString()); + setStatus(Loading); + return true; + } + } + else { + Logger::log("JavaScriptResourcesRequest::list(). list() function not defined"); + setErrorString(tr("list() function not defined")); + setStatus(Failed); + emit finished(); + } + + return false; +} + +bool JavaScriptResourcesRequest::search(const QString &resourceType, const QString &query, const QString &order) { + if (status() == Loading) { + return false; + } + + initEngine(); + QScriptValue func = m_engine->globalObject().property("search"); + + if (func.isFunction()) { + const QScriptValue result = func.call(QScriptValue(), QScriptValueList() << resourceType << query << order); + + if (result.isError()) { + const QString errorString = result.toString(); + Logger::log("JavaScriptResourcesRequest::search(). Error calling search(): " + errorString); + setErrorString(errorString); + setStatus(Failed); + emit finished(); + return false; + } + + if (result.toBool()) { + setErrorString(QString()); + setStatus(Loading); + return true; + } + } + else { + Logger::log("JavaScriptResourcesRequest::search(). search() function not defined"); + setErrorString(tr("search() function not defined")); + setStatus(Failed); + emit finished(); + } + + return false; +} + +void JavaScriptResourcesRequest::onRequestError(const QString &errorString) { + Logger::log("JavaScriptResourcesRequest::onRequestError(): " + errorString); + setErrorString(errorString); + setResult(QVariant()); + setStatus(Failed); + emit finished(); +} + +void JavaScriptResourcesRequest::onRequestFinished(const QVariant &result) { + Logger::log("JavaScriptResourcesRequest::onRequestFinished()", Logger::MediumVerbosity); + setErrorString(QString()); + setResult(result); + setStatus(Ready); + emit finished(); +} diff --git a/app/src/plugins/javascriptresourcesrequest.h b/app/src/plugins/javascriptresourcesrequest.h new file mode 100644 index 0000000..0a2d919 --- /dev/null +++ b/app/src/plugins/javascriptresourcesrequest.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef JAVASCRIPTRESOURCESREQUEST_H +#define JAVASCRIPTRESOURCESREQUEST_H + +#include "resourcesrequest.h" +#include "javascriptresourcesrequestglobalobject.h" + +class QScriptEngine; + +class JavaScriptResourcesRequest : public ResourcesRequest +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName) + Q_PROPERTY(QString id READ id) + +public: + explicit JavaScriptResourcesRequest(const QString &id, const QString &fileName, QObject *parent = 0); + + QString fileName() const; + + QString id() const; + + virtual QString errorString() const; + + virtual QVariant result() const; + + virtual Status status() const; + +public Q_SLOTS: + virtual bool cancel(); + virtual bool del(const QString &sourceType, const QString &sourceId, const QString &destinationType, + const QString &destinationId); + virtual bool get(const QString &resourceType, const QString &resourceId); + virtual bool insert(const QString &sourceType, const QString &sourceId, const QString &destinationType, + const QString &destinationId); + virtual bool list(const QString &resourceType, const QString &resourceId); + virtual bool search(const QString &resourceType, const QString &query, const QString &order); + +private Q_SLOTS: + void onRequestError(const QString &errorString); + void onRequestFinished(const QVariant &result); + +private: + void setErrorString(const QString &e); + + void setResult(const QVariant &r); + + void setStatus(Status s); + + void initEngine(); + + JavaScriptResourcesRequestGlobalObject *m_global; + QScriptEngine *m_engine; + + QString m_fileName; + QString m_id; + + QString m_errorString; + + QVariant m_result; + + Status m_status; + + bool m_evaluated; +}; + +#endif // JAVASCRIPTSERVICEPLUGIN_H diff --git a/app/src/plugins/javascriptresourcesrequestglobalobject.cpp b/app/src/plugins/javascriptresourcesrequestglobalobject.cpp new file mode 100644 index 0000000..b4dde46 --- /dev/null +++ b/app/src/plugins/javascriptresourcesrequestglobalobject.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "javascriptresourcesrequestglobalobject.h" +#include "logger.h" +#include "xmlhttprequest.h" +#include +#include +#include + +JavaScriptResourcesRequestGlobalObject::JavaScriptResourcesRequestGlobalObject(QScriptEngine *engine) : + QObject(engine), + m_nam(0), + m_engine(engine) +{ + QScriptValue oldGlobal = engine->globalObject(); + QScriptValue thisGlobal = engine->newQObject(this, QScriptEngine::QtOwnership, + QScriptEngine::ExcludeChildObjects + | QScriptEngine::ExcludeDeleteLater); + + thisGlobal.setProperty("XMLHttpRequest", engine->newQMetaObject(&XMLHttpRequest::staticMetaObject, + engine->newFunction(newXMLHttpRequest))); + + QScriptValueIterator iterator(oldGlobal); + + while (iterator.hasNext()) { + iterator.next(); + thisGlobal.setProperty(iterator.name(), iterator.value()); + } + + engine->setGlobalObject(thisGlobal); +} + +QScriptValue JavaScriptResourcesRequestGlobalObject::newXMLHttpRequest(QScriptContext *context, QScriptEngine *engine) { + XMLHttpRequest *request; + + if (JavaScriptResourcesRequestGlobalObject *obj = + qobject_cast(engine->globalObject().toQObject())) { + request = new XMLHttpRequest(obj->networkAccessManager(), context->argument(0).toQObject()); + } + else { + request = new XMLHttpRequest(context->argument(0).toQObject()); + } + + return engine->newQObject(request, QScriptEngine::ScriptOwnership); +} + +QNetworkAccessManager* JavaScriptResourcesRequestGlobalObject::networkAccessManager() { + return m_nam ? m_nam : m_nam = new QNetworkAccessManager(this); +} + +QString JavaScriptResourcesRequestGlobalObject::atob(const QString &ascii) const { + return QString::fromUtf8(QByteArray::fromBase64(ascii.toUtf8())); +} + +QString JavaScriptResourcesRequestGlobalObject::btoa(const QString &binary) const { + return QString::fromUtf8(binary.toUtf8().toBase64()); +} + +void JavaScriptResourcesRequestGlobalObject::clearInterval(int timerId) { + if (m_intervals.contains(timerId)) { + m_intervals.remove(timerId); + killTimer(timerId); + } +} + +void JavaScriptResourcesRequestGlobalObject::clearTimeout(int timerId) { + if (m_timeouts.contains(timerId)) { + m_timeouts.remove(timerId); + killTimer(timerId); + } +} + +void JavaScriptResourcesRequestGlobalObject::setInterval(const QScriptValue &function, int msecs) { + if ((function.isFunction()) || (function.isString())) { + m_intervals[startTimer(msecs)] = function; + } +} + +void JavaScriptResourcesRequestGlobalObject::setTimeout(const QScriptValue &function, int msecs) { + if ((function.isFunction()) || (function.isString())) { + m_timeouts[startTimer(msecs)] = function; + } +} + +bool JavaScriptResourcesRequestGlobalObject::callFunction(QScriptValue function) const { + if (function.isFunction()) { + const QScriptValue result = function.call(QScriptValue()); + + if (result.isError()) { + Logger::log("JavaScriptResourcesRequestGlobalObject::callFunction(). Error: " + result.toString()); + return false; + } + + return true; + } + + if (!m_engine.isNull()) { + const QScriptValue result = m_engine->globalObject().property(function.toString()).call(QScriptValue()); + + if (result.isError()) { + Logger::log("JavaScriptResourcesRequestGlobalObject::callFunction(). Error: " + result.toString()); + return false; + } + + return true; + } + + return false; +} + +void JavaScriptResourcesRequestGlobalObject::timerEvent(QTimerEvent *event) { + if (m_intervals.contains(event->timerId())) { + if (!callFunction(m_intervals.value(event->timerId()))) { + clearInterval(event->timerId()); + } + + event->accept(); + return; + } + + if (m_timeouts.contains(event->timerId())) { + callFunction(m_timeouts.value(event->timerId())); + clearTimeout(event->timerId()); + event->accept(); + } +} diff --git a/app/src/plugins/javascriptresourcesrequestglobalobject.h b/app/src/plugins/javascriptresourcesrequestglobalobject.h new file mode 100644 index 0000000..b1f6217 --- /dev/null +++ b/app/src/plugins/javascriptresourcesrequestglobalobject.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef JAVASCRIPTRESOURCESREQUESTGLOBALOBJECT_H +#define JAVASCRIPTRESOURCESREQUESTGLOBALOBJECT_H + +#include +#include +#include + +class QNetworkAccessManager; + +class JavaScriptResourcesRequestGlobalObject : public QObject +{ + Q_OBJECT + +public: + explicit JavaScriptResourcesRequestGlobalObject(QScriptEngine *engine); + +public Q_SLOTS: + QString atob(const QString &ascii) const; + QString btoa(const QString &binary) const; + + void clearInterval(int timerId); + void clearTimeout(int timerId); + + void setInterval(const QScriptValue &function, int msecs); + void setTimeout(const QScriptValue &function, int msecs); + +Q_SIGNALS: + void error(const QString &errorString); + void finished(const QVariant &result); + +private: + static QScriptValue newXMLHttpRequest(QScriptContext *context, QScriptEngine *engine); + + QNetworkAccessManager* networkAccessManager(); + + bool callFunction(QScriptValue function) const; + + virtual void timerEvent(QTimerEvent *event); + + QNetworkAccessManager *m_nam; + + QPointer m_engine; + + QHash m_intervals; + QHash m_timeouts; +}; + +#endif // JAVASCRIPTRESOURCESREQUESTGLOBALOBJECT_H diff --git a/app/src/plugins/javascriptserviceplugin.cpp b/app/src/plugins/javascriptserviceplugin.cpp new file mode 100644 index 0000000..43f3aae --- /dev/null +++ b/app/src/plugins/javascriptserviceplugin.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "javascriptserviceplugin.h" +#include "javascriptresourcesrequest.h" + +JavaScriptServicePlugin::JavaScriptServicePlugin(QObject *parent) : + QObject(parent), + ServicePlugin() +{ +} + +JavaScriptServicePlugin::JavaScriptServicePlugin(const QString &id, const QString &fileName, QObject *parent) : + QObject(parent), + ServicePlugin(), + m_fileName(fileName), + m_id(id) +{ +} + +QString JavaScriptServicePlugin::fileName() const { + return m_fileName; +} + +void JavaScriptServicePlugin::setFileName(const QString &fileName) { + m_fileName = fileName; +} + +QString JavaScriptServicePlugin::id() const { + return m_id; +} + +void JavaScriptServicePlugin::setId(const QString &id) { + m_id = id; +} + +ResourcesRequest* JavaScriptServicePlugin::createRequest(QObject *parent) { + return new JavaScriptResourcesRequest(id(), fileName(), parent); +} diff --git a/app/src/plugins/javascriptserviceplugin.h b/app/src/plugins/javascriptserviceplugin.h new file mode 100644 index 0000000..2529f55 --- /dev/null +++ b/app/src/plugins/javascriptserviceplugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef JAVASCRIPTSERVICEPLUGIN_H +#define JAVASCRIPTSERVICEPLUGIN_H + +#include "serviceplugin.h" + +class JavaScriptServicePlugin : public QObject, public ServicePlugin +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(QString id READ id WRITE setId) + + Q_INTERFACES(ServicePlugin) + +public: + explicit JavaScriptServicePlugin(QObject *parent = 0); + explicit JavaScriptServicePlugin(const QString &id, const QString &fileName, QObject *parent = 0); + + QString fileName() const; + void setFileName(const QString &fileName); + + QString id() const; + void setId(const QString &id); + + virtual ResourcesRequest* createRequest(QObject *parent = 0); + +private: + QString m_fileName; + QString m_id; +}; + +#endif // JAVASCRIPTSERVICEPLUGIN_H diff --git a/app/src/plugins/pluginartist.cpp b/app/src/plugins/pluginartist.cpp index de87e06..4084a35 100644 --- a/app/src/plugins/pluginartist.cpp +++ b/app/src/plugins/pluginartist.cpp @@ -15,73 +15,195 @@ */ #include "pluginartist.h" +#include "pluginmanager.h" #include "resources.h" PluginArtist::PluginArtist(QObject *parent) : MKArtist(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginArtist::PluginArtist(const QString &service, const QString &id, QObject *parent) : MKArtist(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadArtist(service, id); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginArtist::PluginArtist(const QString &service, const QVariantMap &artist, QObject *parent) : MKArtist(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadArtist(service, artist); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } -PluginArtist::PluginArtist(PluginArtist *artist, QObject *parent) : +PluginArtist::PluginArtist(const PluginArtist *artist, QObject *parent) : MKArtist(artist, parent), - m_request(new ResourcesRequest(this)) + m_request(0), + m_actions(artist->actions()), + m_playlistsId(artist->playlistsId()), + m_tracksId(artist->tracksId()) { } +QVariantList PluginArtist::actions() const { + return m_actions; +} + +void PluginArtist::setActions(const QVariantList &a) { + m_actions = a; + emit changed(); + emit actionsChanged(); +} + +QString PluginArtist::playlistsId() const { + return m_playlistsId; +} + +void PluginArtist::setPlaylistsId(const QString &i) { + if (i != playlistsId()) { + m_playlistsId = i; + emit changed(); + emit playlistsIdChanged(); + } +} + +QString PluginArtist::tracksId() const { + return m_tracksId; +} + +void PluginArtist::setTracksId(const QString &i) { + if (i != tracksId()) { + m_tracksId = i; + emit changed(); + emit tracksIdChanged(); + } +} + QString PluginArtist::errorString() const { return m_request->errorString(); } ResourcesRequest::Status PluginArtist::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } void PluginArtist::loadArtist(const QString &service, const QString &id) { + setService(service); + setId(id); + if (status() == ResourcesRequest::Loading) { return; } - m_request->setService(service); - m_request->get(Resources::ARTIST, id); - - emit statusChanged(status()); + if (ResourcesRequest *r = request()) { + r->get(Resources::ARTIST, id); + emit changed(); + emit statusChanged(status()); + } } void PluginArtist::loadArtist(const QString &service, const QVariantMap &artist) { setService(service); - setDescription(artist.value("description").toString()); - setId(artist.value("id").toString()); - setLargeThumbnailUrl(artist.value("largeThumbnailUrl").toString()); - setName(artist.value("name").toString()); - setThumbnailUrl(artist.value("thumbnailUrl").toString()); + + if (artist.contains("actions")) { + setActions(artist.value("actions").toList()); + } + + if (artist.contains("description")) { + setDescription(artist.value("description").toString()); + } + + if (artist.contains("id")) { + setId(artist.value("id").toString()); + } + + if (artist.contains("largeThumbnailUrl")) { + setLargeThumbnailUrl(artist.value("largeThumbnailUrl").toString()); + } + + if (artist.contains("name")) { + setName(artist.value("name").toString()); + } + + if (artist.contains("playlistsId")) { + setPlaylistsId(artist.value("playlistsId").toString()); + } + + if (artist.contains("thumbnailUrl")) { + setThumbnailUrl(artist.value("thumbnailUrl").toString()); + } + + if (artist.contains("tracksId")) { + setTracksId(artist.value("tracksId").toString()); + } + + if (artist.contains("url")) { + setUrl(artist.value("url").toString()); + } } void PluginArtist::loadArtist(PluginArtist *artist) { MKArtist::loadArtist(artist); + setActions(artist->actions()); + setPlaylistsId(artist->playlistsId()); + setTracksId(artist->tracksId()); +} + +void PluginArtist::cancel() { + if (status() == ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginArtist::del(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->del(Resources::ARTIST, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginArtist::insert(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->insert(Resources::ARTIST, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +ResourcesRequest* PluginArtist::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; } void PluginArtist::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - loadArtist(m_request->service(), m_request->result().toMap()); + const QVariantMap result = m_request->result().toMap(); + + if (!result.isEmpty()) { + loadArtist(service(), result); + } } + emit changed(); emit statusChanged(status()); } diff --git a/app/src/plugins/pluginartist.h b/app/src/plugins/pluginartist.h index 38eff5e..641de66 100644 --- a/app/src/plugins/pluginartist.h +++ b/app/src/plugins/pluginartist.h @@ -24,6 +24,9 @@ class PluginArtist : public MKArtist { Q_OBJECT + Q_PROPERTY(QVariantList actions READ actions NOTIFY actionsChanged) + Q_PROPERTY(QString playlistsId READ playlistsId NOTIFY playlistsIdChanged) + Q_PROPERTY(QString tracksId READ tracksId NOTIFY tracksIdChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) Q_PROPERTY(ResourcesRequest::Status status READ status NOTIFY statusChanged) @@ -31,24 +34,50 @@ class PluginArtist : public MKArtist explicit PluginArtist(QObject *parent = 0); explicit PluginArtist(const QString &service, const QString &id, QObject *parent = 0); explicit PluginArtist(const QString &service, const QVariantMap &artist, QObject *parent = 0); - explicit PluginArtist(PluginArtist *artist, QObject *parent = 0); + explicit PluginArtist(const PluginArtist *artist, QObject *parent = 0); + + QVariantList actions() const; + + QString playlistsId() const; + QString tracksId() const; QString errorString() const; - + ResourcesRequest::Status status() const; Q_INVOKABLE void loadArtist(const QString &service, const QString &id); Q_INVOKABLE void loadArtist(const QString &service, const QVariantMap &artist); Q_INVOKABLE void loadArtist(PluginArtist *artist); - + +public Q_SLOTS: + void cancel(); + void del(const QString &resourceType, const QString &resourceId); + void insert(const QString &resourceType, const QString &resourceId); + +protected: + void setActions(const QVariantList &a); + + void setPlaylistsId(const QString &i); + void setTracksId(const QString &i); + private Q_SLOTS: void onRequestFinished(); Q_SIGNALS: + void actionsChanged(); + void playlistsIdChanged(); + void tracksIdChanged(); void statusChanged(ResourcesRequest::Status s); private: + ResourcesRequest* request(); + ResourcesRequest *m_request; + + QVariantList m_actions; + + QString m_playlistsId; + QString m_tracksId; }; #endif // PLUGINARTIST_H diff --git a/app/src/plugins/pluginartistmodel.cpp b/app/src/plugins/pluginartistmodel.cpp index 6cff3d1..d706fa5 100644 --- a/app/src/plugins/pluginartistmodel.cpp +++ b/app/src/plugins/pluginartistmodel.cpp @@ -15,39 +15,55 @@ */ #include "pluginartistmodel.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" PluginArtistModel::PluginArtistModel(QObject *parent) : QAbstractListModel(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { + m_roles[ActionsRole] = "actions"; m_roles[DescriptionRole] = "description"; + m_roles[ErrorStringRole] = "errorString"; m_roles[IdRole] = "id"; m_roles[LargeThumbnailUrlRole] = "largeThumbnailUrl"; m_roles[NameRole] = "name"; + m_roles[PlaylistsIdRole] = "playlistsId"; m_roles[ServiceRole] = "service"; + m_roles[StatusRole] = "status"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; + m_roles[TracksIdRole] = "tracksId"; + m_roles[UrlRole] = "url"; #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - connect(m_request, SIGNAL(serviceChanged()), this, SIGNAL(serviceChanged())); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); +} + +QString PluginArtistModel::errorString() const { + return m_request ? m_request->errorString() : QString(); } QString PluginArtistModel::service() const { - return m_request->service(); + return m_service; } void PluginArtistModel::setService(const QString &s) { - m_request->setService(s); -} + if (s != service()) { + m_service = s; + emit serviceChanged(); -QString PluginArtistModel::errorString() const { - return m_request->errorString(); + clear(); + + if (m_request) { + m_request->deleteLater(); + m_request = 0; + } + } } ResourcesRequest::Status PluginArtistModel::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } #if QT_VERSION >=0x050000 @@ -56,25 +72,57 @@ QHash PluginArtistModel::roleNames() const { } #endif -int PluginArtistModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int PluginArtistModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int PluginArtistModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 2; } -bool PluginArtistModel::canFetchMore(const QModelIndex &) const { - return (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); +bool PluginArtistModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); } -void PluginArtistModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void PluginArtistModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } + + if (ResourcesRequest *r = request()) { + r->list(Resources::ARTIST, m_next); + emit statusChanged(status()); + } +} + +QVariant PluginArtistModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } - m_request->list(Resources::ARTIST, m_next); - emit statusChanged(status()); + switch (section) { + case 0: + return tr("Name"); + case 1: + return tr("Description"); + default: + return QVariant(); + } } QVariant PluginArtistModel::data(const QModelIndex &index, int role) const { - if (PluginArtist *artist = get(index.row())) { + if (const PluginArtist *artist = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return artist->name(); + case 1: + return artist->description(); + default: + return QVariant(); + } + } + return artist->property(m_roles[role]); } @@ -84,7 +132,7 @@ QVariant PluginArtistModel::data(const QModelIndex &index, int role) const { QMap PluginArtistModel::itemData(const QModelIndex &index) const { QMap map; - if (PluginArtist *artist = get(index.row())) { + if (const PluginArtist *artist = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -97,7 +145,7 @@ QMap PluginArtistModel::itemData(const QModelIndex &index) const } QVariant PluginArtistModel::data(int row, const QByteArray &role) const { - if (PluginArtist *artist = get(row)) { + if (const PluginArtist *artist = get(row)) { return artist->property(role); } @@ -107,8 +155,8 @@ QVariant PluginArtistModel::data(int row, const QByteArray &role) const { QVariantMap PluginArtistModel::itemData(int row) const { QVariantMap map; - if (PluginArtist *artist = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const PluginArtist *artist = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = artist->property(role); } } @@ -124,16 +172,20 @@ PluginArtist* PluginArtistModel::get(int row) const { return 0; } -void PluginArtistModel::list(const QString &id) { +void PluginArtistModel::list(const QString &resourceId) { if (status() == ResourcesRequest::Loading) { return; } + Logger::log("PluginArtistModel::list(). Resource ID: " + resourceId, Logger::MediumVerbosity); clear(); - m_id = id; + m_resourceId = resourceId; m_query = QString(); - m_request->list(Resources::ARTIST, id); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->list(Resources::ARTIST, resourceId); + emit statusChanged(status()); + } } void PluginArtistModel::search(const QString &query, const QString &order) { @@ -141,12 +193,17 @@ void PluginArtistModel::search(const QString &query, const QString &order) { return; } + Logger::log(QString("PluginArtistModel::search(). Query: %1, Order: %2").arg(query).arg(order), + Logger::MediumVerbosity); clear(); - m_id = QString(); + m_resourceId = QString(); m_query = query; m_order = order; - m_request->search(Resources::ARTIST, query, order); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->search(Resources::ARTIST, query, order); + emit statusChanged(status()); + } } void PluginArtistModel::clear() { @@ -165,20 +222,28 @@ void PluginArtistModel::cancel() { } void PluginArtistModel::reload() { - clear(); - - if (m_query.isEmpty()) { - m_request->list(Resources::ARTIST, m_id); - } - else { - m_request->search(Resources::ARTIST, m_query, m_order); + if (status() == ResourcesRequest::Loading) { + return; } - emit statusChanged(status()); + Logger::log("PluginArtistModel::reload(). Resource ID: " + m_resourceId, Logger::MediumVerbosity); + clear(); + + if (ResourcesRequest *r = request()) { + if (m_query.isEmpty()) { + r->list(Resources::ARTIST, m_resourceId); + } + else { + r->search(Resources::ARTIST, m_query, m_order); + } + + emit statusChanged(status()); + } } void PluginArtistModel::append(PluginArtist *artist) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(artist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << artist; endInsertRows(); } @@ -186,6 +251,7 @@ void PluginArtistModel::append(PluginArtist *artist) { void PluginArtistModel::insert(int row, PluginArtist *artist) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(artist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, artist); endInsertRows(); } @@ -202,18 +268,40 @@ void PluginArtistModel::remove(int row) { } } +ResourcesRequest* PluginArtistModel::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; +} + +void PluginArtistModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void PluginArtistModel::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_next = result.value("next").toString(); - QVariantList list = result.value("items").toList(); + const QVariantList list = result.value("items").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new PluginArtist(service(), item.toMap(), this); + foreach (const QVariant &item, list) { + PluginArtist *artist = new PluginArtist(service(), item.toMap(), this); + connect(artist, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << artist; } endInsertRows(); @@ -221,6 +309,9 @@ void PluginArtistModel::onRequestFinished() { } } + else { + Logger::log("PluginArtistModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/plugins/pluginartistmodel.h b/app/src/plugins/pluginartistmodel.h index 28404f7..664233c 100644 --- a/app/src/plugins/pluginartistmodel.h +++ b/app/src/plugins/pluginartistmodel.h @@ -17,7 +17,6 @@ #ifndef PLUGINARTISTMODEL_H #define PLUGINARTISTMODEL_H -#include "resourcesrequest.h" #include "pluginartist.h" #include @@ -33,31 +32,40 @@ class PluginArtistModel : public QAbstractListModel public: enum Roles { - DescriptionRole = Qt::UserRole + 1, + ActionsRole = Qt::UserRole + 1, + DescriptionRole, + ErrorStringRole, IdRole, LargeThumbnailUrlRole, NameRole, + PlaylistsIdRole, ServiceRole, - ThumbnailUrlRole + StatusRole, + ThumbnailUrlRole, + TracksIdRole, + UrlRole }; explicit PluginArtistModel(QObject *parent = 0); - QString service() const; - void setService(const QString &service); - QString errorString() const; + QString service() const; + void setService(const QString &s); + ResourcesRequest::Status status() const; #if QT_VERSION >= 0x050000 QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -66,7 +74,7 @@ class PluginArtistModel : public QAbstractListModel Q_INVOKABLE PluginArtist* get(int row) const; - Q_INVOKABLE void list(const QString &id = QString()); + Q_INVOKABLE void list(const QString &resourceId); Q_INVOKABLE void search(const QString &query, const QString &order); public Q_SLOTS: @@ -74,23 +82,26 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(PluginArtist *artist); - void insert(int row, PluginArtist *artist); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void serviceChanged(); void statusChanged(ResourcesRequest::Status s); private: + void append(PluginArtist *artist); + void insert(int row, PluginArtist *artist); + void remove(int row); + + ResourcesRequest* request(); + ResourcesRequest *m_request; - QString m_id; + QString m_resourceId; + QString m_service; QString m_query; QString m_order; QString m_next; diff --git a/app/src/plugins/plugincategorymodel.cpp b/app/src/plugins/plugincategorymodel.cpp index bb231ed..5753e84 100644 --- a/app/src/plugins/plugincategorymodel.cpp +++ b/app/src/plugins/plugincategorymodel.cpp @@ -1,94 +1,159 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ #include "plugincategorymodel.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" PluginCategoryModel::PluginCategoryModel(QObject *parent) : SelectionModel(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { - connect(m_request, SIGNAL(serviceChanged()), this, SIGNAL(serviceChanged())); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } -bool PluginCategoryModel::canFetchMore(const QModelIndex &) const { - return (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); +bool PluginCategoryModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); } -void PluginCategoryModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void PluginCategoryModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } - - m_request->list(Resources::CATEGORY, m_next); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->list(Resources::CATEGORY, m_next); + emit statusChanged(status()); + } +} + +QString PluginCategoryModel::errorString() const { + return m_request ? m_request->errorString() : QString(); } QString PluginCategoryModel::service() const { - return m_request->service(); + return m_service; } void PluginCategoryModel::setService(const QString &s) { - m_request->setService(s); -} + if (s != service()) { + m_service = s; + emit serviceChanged(); -QString PluginCategoryModel::errorString() const { - return m_request->errorString(); + clear(); + + if (m_request) { + m_request->deleteLater(); + m_request = 0; + } + } } ResourcesRequest::Status PluginCategoryModel::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } -void PluginCategoryModel::list(const QString &id) { +void PluginCategoryModel::list(const QString &resourceId) { if (status() == ResourcesRequest::Loading) { return; } + Logger::log("PluginCategoryModel::list(). Resource ID: " + resourceId, Logger::MediumVerbosity); clear(); - m_id = id; - m_next = QString(); - m_request->list(Resources::CATEGORY, id); - emit statusChanged(status()); + m_resourceId = resourceId; + m_query = QString(); + + if (ResourcesRequest *r = request()) { + r->list(Resources::CATEGORY, resourceId); + emit statusChanged(status()); + } +} + +void PluginCategoryModel::search(const QString &query, const QString &order) { + if (status() == ResourcesRequest::Loading) { + return; + } + + Logger::log(QString("PluginCategoryModel::search(). Query: %1, Order: %2").arg(query).arg(order), + Logger::MediumVerbosity); + clear(); + m_resourceId = QString(); + m_query = query; + m_order = order; + + if (ResourcesRequest *r = request()) { + r->search(Resources::CATEGORY, query, order); + emit statusChanged(status()); + } } void PluginCategoryModel::cancel() { - m_request->cancel(); + if (m_request) { + m_request->cancel(); + } } void PluginCategoryModel::reload() { + if (status() == ResourcesRequest::Loading) { + return; + } + + Logger::log("PluginCategoryModel::reload(). Resource ID: " + m_resourceId, Logger::MediumVerbosity); clear(); - m_next = QString(); - m_request->list(Resources::CATEGORY, m_id); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + if (m_query.isEmpty()) { + r->list(Resources::CATEGORY, m_resourceId); + } + else { + r->search(Resources::CATEGORY, m_query, m_order); + } + + emit statusChanged(status()); + } +} + +ResourcesRequest* PluginCategoryModel::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; } void PluginCategoryModel::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); m_next = QString(); - foreach (QVariant v, result.value("items").toList()) { - QVariantMap category = v.toMap(); - append(category.value("title").toString(), category.value("id").toString()); + foreach (const QVariant &v, result.value("items").toList()) { + const QVariantMap category = v.toMap(); + append(category.value("title").toString(), category); } m_next = result.value("next").toString(); } + else { + Logger::log("PluginCategoryModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/plugins/plugincategorymodel.h b/app/src/plugins/plugincategorymodel.h index 31256f7..2fbab98 100644 --- a/app/src/plugins/plugincategorymodel.h +++ b/app/src/plugins/plugincategorymodel.h @@ -1,17 +1,18 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef PLUGINCATEGORYMODEL_H @@ -34,15 +35,16 @@ class PluginCategoryModel : public SelectionModel bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + + QString errorString() const; QString service() const; - void setService(const QString &service); - - QString errorString() const; + void setService(const QString &s); ResourcesRequest::Status status() const; - Q_INVOKABLE void list(const QString &id = QString()); + Q_INVOKABLE void list(const QString &resourceId); + Q_INVOKABLE void search(const QString &query, const QString &order); public Q_SLOTS: void cancel(); @@ -56,9 +58,14 @@ private Q_SLOTS: void statusChanged(ResourcesRequest::Status s); private: - ResourcesRequest *m_request; + ResourcesRequest* request(); - QString m_id; + ResourcesRequest *m_request; + + QString m_service; + QString m_resourceId; + QString m_query; + QString m_order; QString m_next; }; diff --git a/app/src/plugins/plugincomment.cpp b/app/src/plugins/plugincomment.cpp index 2454f33..7bfe2b9 100644 --- a/app/src/plugins/plugincomment.cpp +++ b/app/src/plugins/plugincomment.cpp @@ -15,75 +15,167 @@ */ #include "plugincomment.h" +#include "pluginmanager.h" #include "resources.h" PluginComment::PluginComment(QObject *parent) : MKComment(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginComment::PluginComment(const QString &service, const QString &id, QObject *parent) : MKComment(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadComment(service, id); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginComment::PluginComment(const QString &service, const QVariantMap &comment, QObject *parent) : MKComment(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadComment(service, comment); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } -PluginComment::PluginComment(PluginComment *comment, QObject *parent) : +PluginComment::PluginComment(const PluginComment *comment, QObject *parent) : MKComment(comment, parent), - m_request(new ResourcesRequest(this)) + m_request(0), + m_actions(comment->actions()) { } +QVariantList PluginComment::actions() const { + return m_actions; +} + +void PluginComment::setActions(const QVariantList &a) { + m_actions = a; + emit changed(); + emit actionsChanged(); +} + QString PluginComment::errorString() const { - return m_request->errorString(); + return m_request ? m_request->errorString() : QString(); } ResourcesRequest::Status PluginComment::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } void PluginComment::loadComment(const QString &service, const QString &id) { + setService(service); + setId(id); + if (status() == ResourcesRequest::Loading) { return; } - - m_request->setService(service); - m_request->get(Resources::COMMENT, id); - emit statusChanged(status()); + if (ResourcesRequest *r = request()) { + r->get(Resources::COMMENT, id); + emit changed(); + emit statusChanged(status()); + } } void PluginComment::loadComment(const QString &service, const QVariantMap &comment) { setService(service); - setArtist(comment.value("artist").toString()); - setArtistId(comment.value("artistId").toString()); - setBody(comment.value("body").toString()); - setDate(comment.value("date").toString()); - setId(comment.value("id").toString()); - setThumbnailUrl(comment.value("thumbnailUrl").toString()); - setTrackId(comment.value("trackId").toString()); + + if (comment.contains("actions")) { + setActions(comment.value("actions").toList()); + } + + if (comment.contains("artist")) { + setArtist(comment.value("artist").toString()); + } + + if (comment.contains("artistId")) { + setArtistId(comment.value("artistId").toString()); + } + + if (comment.contains("body")) { + setBody(comment.value("body").toString()); + } + + if (comment.contains("date")) { + setDate(comment.value("date").toString()); + } + + if (comment.contains("id")) { + setId(comment.value("id").toString()); + } + + if (comment.contains("thumbnailUrl")) { + setThumbnailUrl(comment.value("thumbnailUrl").toString()); + } + + if (comment.contains("trackId")) { + setTrackId(comment.value("trackId").toString()); + } + + if (comment.contains("url")) { + setUrl(comment.value("url").toString()); + } } void PluginComment::loadComment(PluginComment *comment) { MKComment::loadComment(comment); + setActions(comment->actions()); +} + +void PluginComment::cancel() { + if (status() == ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginComment::del(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->del(Resources::COMMENT, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginComment::insert(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->insert(Resources::COMMENT, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +ResourcesRequest* PluginComment::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; } void PluginComment::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - loadComment(m_request->service(), m_request->result().toMap()); + const QVariantMap result = m_request->result().toMap(); + + if (!result.isEmpty()) { + loadComment(service(), result); + } } + emit changed(); emit statusChanged(status()); } diff --git a/app/src/plugins/plugincomment.h b/app/src/plugins/plugincomment.h index be7898f..debb670 100644 --- a/app/src/plugins/plugincomment.h +++ b/app/src/plugins/plugincomment.h @@ -24,6 +24,7 @@ class PluginComment : public MKComment { Q_OBJECT + Q_PROPERTY(QVariantList actions READ actions NOTIFY actionsChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) Q_PROPERTY(ResourcesRequest::Status status READ status NOTIFY statusChanged) @@ -31,7 +32,9 @@ class PluginComment : public MKComment explicit PluginComment(QObject *parent = 0); explicit PluginComment(const QString &service, const QString &id, QObject *parent = 0); explicit PluginComment(const QString &service, const QVariantMap &comment, QObject *parent = 0); - explicit PluginComment(PluginComment *comment, QObject *parent = 0); + explicit PluginComment(const PluginComment *comment, QObject *parent = 0); + + QVariantList actions() const; QString errorString() const; @@ -40,15 +43,28 @@ class PluginComment : public MKComment Q_INVOKABLE void loadComment(const QString &service, const QString &id); Q_INVOKABLE void loadComment(const QString &service, const QVariantMap &comment); Q_INVOKABLE void loadComment(PluginComment *comment); - + +public Q_SLOTS: + void cancel(); + void del(const QString &resourceType, const QString &resourceId); + void insert(const QString &resourceType, const QString &resourceId); + +protected: + void setActions(const QVariantList &a); + private Q_SLOTS: void onRequestFinished(); Q_SIGNALS: + void actionsChanged(); void statusChanged(ResourcesRequest::Status s); private: + ResourcesRequest* request(); + ResourcesRequest *m_request; + + QVariantList m_actions; }; #endif // PLUGINCOMMENT_H diff --git a/app/src/plugins/plugincommentmodel.cpp b/app/src/plugins/plugincommentmodel.cpp index 22e6f32..5805785 100644 --- a/app/src/plugins/plugincommentmodel.cpp +++ b/app/src/plugins/plugincommentmodel.cpp @@ -15,40 +15,55 @@ */ #include "plugincommentmodel.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" PluginCommentModel::PluginCommentModel(QObject *parent) : QAbstractListModel(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { + m_roles[ActionsRole] = "actions"; m_roles[ArtistRole] = "artist"; m_roles[ArtistIdRole] = "artistId"; m_roles[BodyRole] = "body"; m_roles[DateRole] = "date"; + m_roles[ErrorStringRole] = "errorString"; m_roles[IdRole] = "id"; + m_roles[ServiceRole] = "service"; + m_roles[StatusRole] = "status"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TrackIdRole] = "trackId"; + m_roles[UrlRole] = "url"; #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - connect(m_request, SIGNAL(serviceChanged()), this, SIGNAL(serviceChanged())); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); +} + +QString PluginCommentModel::errorString() const { + return m_request ? m_request->errorString() : QString(); } QString PluginCommentModel::service() const { - return m_request->service(); + return m_service; } void PluginCommentModel::setService(const QString &s) { - m_request->setService(s); -} + if (s != service()) { + m_service = s; + emit serviceChanged(); -QString PluginCommentModel::errorString() const { - return m_request->errorString(); + clear(); + + if (m_request) { + m_request->deleteLater(); + m_request = 0; + } + } } ResourcesRequest::Status PluginCommentModel::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } #if QT_VERSION >=0x050000 @@ -57,25 +72,61 @@ QHash PluginCommentModel::roleNames() const { } #endif -int PluginCommentModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int PluginCommentModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int PluginCommentModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 3; } -bool PluginCommentModel::canFetchMore(const QModelIndex &) const { - return (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); +bool PluginCommentModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); } -void PluginCommentModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void PluginCommentModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } + + if (ResourcesRequest *r = request()) { + r->list(Resources::COMMENT, m_next); + emit statusChanged(status()); + } +} + +QVariant PluginCommentModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } - m_request->list(Resources::COMMENT, m_next); - emit statusChanged(status()); + switch (section) { + case 0: + return tr("Artist"); + case 1: + return tr("Date"); + case 2: + return tr("Comment"); + default: + return QVariant(); + } } QVariant PluginCommentModel::data(const QModelIndex &index, int role) const { - if (PluginComment *comment = get(index.row())) { + if (const PluginComment *comment = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return comment->artist(); + case 1: + return comment->date(); + case 2: + return comment->body(); + default: + return QVariant(); + } + } + return comment->property(m_roles[role]); } @@ -85,7 +136,7 @@ QVariant PluginCommentModel::data(const QModelIndex &index, int role) const { QMap PluginCommentModel::itemData(const QModelIndex &index) const { QMap map; - if (PluginComment *comment = get(index.row())) { + if (const PluginComment *comment = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -98,7 +149,7 @@ QMap PluginCommentModel::itemData(const QModelIndex &index) const } QVariant PluginCommentModel::data(int row, const QByteArray &role) const { - if (PluginComment *comment = get(row)) { + if (const PluginComment *comment = get(row)) { return comment->property(role); } @@ -108,8 +159,8 @@ QVariant PluginCommentModel::data(int row, const QByteArray &role) const { QVariantMap PluginCommentModel::itemData(int row) const { QVariantMap map; - if (PluginComment *comment = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const PluginComment *comment = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = comment->property(role); } } @@ -125,16 +176,20 @@ PluginComment* PluginCommentModel::get(int row) const { return 0; } -void PluginCommentModel::list(const QString &id) { +void PluginCommentModel::list(const QString &resourceId) { if (status() == ResourcesRequest::Loading) { return; } + Logger::log("PluginCommentModel::list(). Resource ID: " + resourceId, Logger::MediumVerbosity); clear(); - m_id = id; + m_resourceId = resourceId; m_query = QString(); - m_request->list(Resources::COMMENT, id); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->list(Resources::COMMENT, resourceId); + emit statusChanged(status()); + } } void PluginCommentModel::search(const QString &query, const QString &order) { @@ -142,12 +197,17 @@ void PluginCommentModel::search(const QString &query, const QString &order) { return; } + Logger::log(QString("PluginCommentModel::search(). Query: %1, Order: %2").arg(query).arg(order), + Logger::MediumVerbosity); clear(); - m_id = QString(); + m_resourceId = QString(); m_query = query; m_order = order; - m_request->search(Resources::COMMENT, query, order); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->search(Resources::COMMENT, query, order); + emit statusChanged(status()); + } } void PluginCommentModel::clear() { @@ -162,24 +222,34 @@ void PluginCommentModel::clear() { } void PluginCommentModel::cancel() { - m_request->cancel(); + if (m_request) { + m_request->cancel(); + } } void PluginCommentModel::reload() { - clear(); - - if (m_query.isEmpty()) { - m_request->list(Resources::COMMENT, m_id); - } - else { - m_request->search(Resources::COMMENT, m_query, m_order); + if (status() == ResourcesRequest::Loading) { + return; } - emit statusChanged(status()); + Logger::log("PluginCommentModel::reload(). Resource ID: " + m_resourceId, Logger::MediumVerbosity); + clear(); + + if (ResourcesRequest *r = request()) { + if (m_query.isEmpty()) { + r->list(Resources::COMMENT, m_resourceId); + } + else { + r->search(Resources::COMMENT, m_query, m_order); + } + + emit statusChanged(status()); + } } void PluginCommentModel::append(PluginComment *comment) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(comment, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << comment; endInsertRows(); } @@ -187,6 +257,7 @@ void PluginCommentModel::append(PluginComment *comment) { void PluginCommentModel::insert(int row, PluginComment *comment) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(comment, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, comment); endInsertRows(); } @@ -203,18 +274,40 @@ void PluginCommentModel::remove(int row) { } } +ResourcesRequest* PluginCommentModel::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; +} + +void PluginCommentModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void PluginCommentModel::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_next = result.value("next").toString(); - QVariantList list = result.value("items").toList(); + const QVariantList list = result.value("items").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new PluginComment(service(), item.toMap(), this); + foreach (const QVariant &item, list) { + PluginComment *comment = new PluginComment(service(), item.toMap(), this); + connect(comment, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << comment; } endInsertRows(); @@ -222,6 +315,9 @@ void PluginCommentModel::onRequestFinished() { } } + else { + Logger::log("PluginCommentModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/plugins/plugincommentmodel.h b/app/src/plugins/plugincommentmodel.h index c9a9f8a..3de0cda 100644 --- a/app/src/plugins/plugincommentmodel.h +++ b/app/src/plugins/plugincommentmodel.h @@ -17,7 +17,6 @@ #ifndef PLUGINCOMMENTMODEL_H #define PLUGINCOMMENTMODEL_H -#include "resourcesrequest.h" #include "plugincomment.h" #include @@ -33,32 +32,40 @@ class PluginCommentModel : public QAbstractListModel public: enum Roles { - ArtistRole = Qt::UserRole + 1, + ActionsRole = Qt::UserRole + 1, + ArtistRole, ArtistIdRole, BodyRole, DateRole, + ErrorStringRole, IdRole, + ServiceRole, + StatusRole, ThumbnailUrlRole, - TrackIdRole + TrackIdRole, + UrlRole }; explicit PluginCommentModel(QObject *parent = 0); - QString service() const; - void setService(const QString &service); - QString errorString() const; + QString service() const; + void setService(const QString &s); + ResourcesRequest::Status status() const; #if QT_VERSION >= 0x050000 QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -67,7 +74,7 @@ class PluginCommentModel : public QAbstractListModel Q_INVOKABLE PluginComment* get(int row) const; - Q_INVOKABLE void list(const QString &id); + Q_INVOKABLE void list(const QString &resourceId); Q_INVOKABLE void search(const QString &query, const QString &order); public Q_SLOTS: @@ -75,23 +82,26 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(PluginComment *comment); - void insert(int row, PluginComment *comment); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void serviceChanged(); void statusChanged(ResourcesRequest::Status s); private: + void append(PluginComment *comment); + void insert(int row, PluginComment *comment); + void remove(int row); + + ResourcesRequest* request(); + ResourcesRequest *m_request; - QString m_id; + QString m_service; + QString m_resourceId; QString m_query; QString m_order; QString m_next; diff --git a/app/src/plugins/pluginconfigmodel.cpp b/app/src/plugins/pluginconfigmodel.cpp new file mode 100644 index 0000000..3541650 --- /dev/null +++ b/app/src/plugins/pluginconfigmodel.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pluginconfigmodel.h" + +PluginConfigModel::PluginConfigModel(QObject *parent) : + QAbstractListModel(parent) +{ + m_roles[DisplayNameRole] = "displayName"; + m_roles[FilePathRole] = "filePath"; + m_roles[IdRole] = "id"; + m_roles[PluginFilePathRole] = "pluginFilePath"; + m_roles[PluginTypeRole] = "pluginType"; + m_roles[SettingsRole] = "settings"; + m_roles[VersionRole] = "version"; +#if QT_VERSION < 0x050000 + setRoleNames(m_roles); +#endif + reload(); +} + +#if QT_VERSION >= 0x050000 +QHash PluginConfigModel::roleNames() const { + return m_roles; +} +#endif + +int PluginConfigModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int PluginConfigModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 3; +} + +QVariant PluginConfigModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } + + switch (section) { + case 0: + return tr("Name"); + case 1: + return tr("Type"); + case 2: + return tr("Version"); + default: + return QVariant(); + } +} + +QVariant PluginConfigModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + + const ServicePluginConfig *config = m_items.at(index.row()).config; + + if (!config) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case 0: + return config->displayName(); + case 1: + return config->pluginType(); + case 2: + return config->version(); + default: + break; + } + + return QVariant(); + case FilePathRole: + return config->filePath(); + case IdRole: + return config->id(); + case PluginFilePathRole: + return config->pluginFilePath(); + case PluginTypeRole: + return config->pluginType(); + case SettingsRole: + return config->settings(); + case VersionRole: + return config->version(); + default: + return QVariant(); + } +} + +QMap PluginConfigModel::itemData(const QModelIndex &index) const { + QMap map; + + foreach (const int &role, m_roles.keys()) { + map[role] = data(index, role); + } + + return map; +} + +QVariant PluginConfigModel::data(int row, const QByteArray &role) const { + return data(index(row), m_roles.key(role)); +} + +QVariantMap PluginConfigModel::itemData(int row) const { + QVariantMap map; + + foreach (const QByteArray &role, m_roles.values()) { + map[QString::fromUtf8(role)] = data(row, role); + } + + return map; +} + +QModelIndexList PluginConfigModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, + Qt::MatchFlags flags) const { + return QAbstractListModel::match(start, role, value, hits, flags); +} + +int PluginConfigModel::match(int start, const QByteArray &role, const QVariant &value, int flags) const { + const QModelIndexList idxs = match(index(start), m_roles.key(role), value, 1, Qt::MatchFlags(flags)); + return idxs.isEmpty() ? -1 : idxs.first().row(); +} + +void PluginConfigModel::clear() { + if (!m_items.isEmpty()) { + beginResetModel(); + m_items.clear(); + endResetModel(); + emit countChanged(0); + } +} + +void PluginConfigModel::reload() { + clear(); + beginResetModel(); + m_items = PluginManager::instance()->plugins(); + endResetModel(); + emit countChanged(rowCount()); +} diff --git a/app/src/plugins/pluginconfigmodel.h b/app/src/plugins/pluginconfigmodel.h new file mode 100644 index 0000000..4620441 --- /dev/null +++ b/app/src/plugins/pluginconfigmodel.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINCONFIGMODEL_H +#define PLUGINCONFIGMODEL_H + +#include "pluginmanager.h" +#include + +class PluginConfigModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + enum Roles { + DisplayNameRole = Qt::DisplayRole, + FilePathRole = Qt::UserRole + 1, + IdRole, + PluginFilePathRole, + PluginTypeRole, + SettingsRole, + VersionRole + }; + + explicit PluginConfigModel(QObject *parent = 0); + +#if QT_VERSION >= 0x050000 + QHash roleNames() const; +#endif + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + + QVariant data(const QModelIndex &index, int role = DisplayNameRole) const; + QMap itemData(const QModelIndex &index) const; + + Q_INVOKABLE virtual QVariant data(int row, const QByteArray &role) const; + Q_INVOKABLE virtual QVariantMap itemData(int row) const; + + QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, + Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap)) const; + Q_INVOKABLE virtual int match(int start, const QByteArray &role, const QVariant &value, + int flags = Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap)) const; + +public Q_SLOTS: + void clear(); + void reload(); + +Q_SIGNALS: + void countChanged(int count); + +protected: + ServicePluginList m_items; + QHash m_roles; +}; + +#endif // PLUGINCONFIGMODEL_H diff --git a/app/src/plugins/pluginmanager.cpp b/app/src/plugins/pluginmanager.cpp new file mode 100644 index 0000000..3ef44c2 --- /dev/null +++ b/app/src/plugins/pluginmanager.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "pluginmanager.h" +#include "definitions.h" +#include "externalserviceplugin.h" +#include "javascriptserviceplugin.h" +#include "logger.h" +#include +#include +#include + +static bool displayNameLessThan(const ServicePluginPair &pair, const ServicePluginPair &other) { + return QString::localeAwareCompare(pair.config->displayName(), other.config->displayName()) < 0; +} + +PluginManager* PluginManager::self = 0; + +PluginManager::PluginManager(QObject *parent) : + QObject(parent), + m_lastLoaded(QDateTime::fromTime_t(0)) +{ +} + +PluginManager::~PluginManager() { + self = 0; +} + +PluginManager* PluginManager::instance() { + return self ? self : self = new PluginManager; +} + +ServicePluginList PluginManager::plugins() const { + return m_plugins; +} + +ServicePluginConfig* PluginManager::getConfigForService(const QString &service) const { + foreach (const ServicePluginPair &pair, m_plugins) { + if (pair.config->id() == service) { + Logger::log("PluginManager::getConfigForService(). PluginFound: " + service, Logger::HighVerbosity); + return pair.config; + } + } + + Logger::log("PluginManager::getConfigForService(). No Plugin found", Logger::HighVerbosity); + return 0; +} + +ServicePluginConfig* PluginManager::getConfigByFilePath(const QString &filePath) const { + foreach (const ServicePluginPair &pair, m_plugins) { + if (pair.config->filePath() == filePath) { + Logger::log("PluginManager::getConfigByFilePath(). PluginFound: " + pair.config->id(), + Logger::HighVerbosity); + return pair.config; + } + } + + Logger::log("PluginManager::getConfigByFilePath(). No Plugin found", Logger::HighVerbosity); + return 0; +} + +ServicePlugin* PluginManager::getPluginForService(const QString &service) const { + foreach (const ServicePluginPair &pair, m_plugins) { + if (pair.config->id() == service) { + Logger::log("PluginManager::getPluginForService(). PluginFound: " + service, Logger::HighVerbosity); + return pair.plugin; + } + } + + Logger::log("PluginManager::getPluginForService(). No Plugin found", Logger::HighVerbosity); + return 0; +} + +ResourcesRequest* PluginManager::createRequestForService(const QString &service, QObject *parent) const { + if (ServicePlugin *plugin = getPluginForService(service)) { + return plugin->createRequest(parent); + } + + return 0; +} + +bool PluginManager::resourceTypeIsSupported(const QString &service, const QString &resourceType, + const QString &method) const { + if (ServicePluginConfig *config = getConfigForService(service)) { + return config->resourceTypeIsSupported(resourceType, method); + } + + return false; +} + +int PluginManager::load() { + Logger::log("PluginManager::load(): Loading plugins modified since " + + m_lastLoaded.toString(Qt::ISODate), Logger::LowVerbosity); + int count = 0; + QDir dir; + + foreach (const QString &path, PLUGIN_PATHS) { + dir.setPath(path); + + foreach (const QFileInfo &info, dir.entryInfoList(QStringList() << "*.json", QDir::Files, QDir::Time)) { + if (info.lastModified() > m_lastLoaded) { + ServicePluginConfig *config = getConfigByFilePath(info.absoluteFilePath()); + + if (!config) { + config = new ServicePluginConfig(this); + + if (config->load(info.absoluteFilePath())) { + if (config->pluginType() == "qt") { + QPluginLoader loader(config->pluginFilePath()); + QObject *obj = loader.instance(); + + if (obj) { + if (ServicePlugin *plugin = qobject_cast(obj)) { + m_plugins << ServicePluginPair(config, plugin); + ++count; + Logger::log("PluginManager::load(). Qt Plugin loaded: " + config->id(), + Logger::MediumVerbosity); + } + else { + loader.unload(); + Logger::log("PluginManager::load(). Error loading Qt plugin: " + + config->id()); + } + } + else { + Logger::log("PluginManager::load(). Qt plugin is NULL: " + config->id()); + } + } + else if (config->pluginType() == "js") { + JavaScriptServicePlugin *js = + new JavaScriptServicePlugin(config->id(), config->pluginFilePath(), this); + m_plugins << ServicePluginPair(config, js); + ++count; + Logger::log("PluginManager::load(). JavaScript plugin loaded: " + config->id(), + Logger::MediumVerbosity); + } + else { + ExternalServicePlugin *ext = + new ExternalServicePlugin(config->id(), config->pluginFilePath(), this); + m_plugins << ServicePluginPair(config, ext); + ++count; + Logger::log("PluginManager::load(). External plugin loaded: " + config->id(), + Logger::MediumVerbosity); + } + } + else { + delete config; + } + } + } + else { + break; + } + } + } + + Logger::log(QString("PluginManager::load() %1 plugins loaded").arg(count), Logger::LowVerbosity); + + if (count > 0) { + qSort(m_plugins.begin(), m_plugins.end(), displayNameLessThan); + emit loaded(count); + } + + m_lastLoaded = QDateTime::currentDateTime(); + return count; +} diff --git a/app/src/plugins/pluginmanager.h b/app/src/plugins/pluginmanager.h new file mode 100644 index 0000000..dae372a --- /dev/null +++ b/app/src/plugins/pluginmanager.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include "serviceplugin.h" +#include "servicepluginconfig.h" +#include + +struct ServicePluginPair +{ + ServicePluginPair(ServicePluginConfig *c, ServicePlugin* p) : + config(c), + plugin(p) + { + } + + ServicePluginConfig *config; + ServicePlugin *plugin; +}; + +typedef QList ServicePluginList; + +class PluginManager : public QObject +{ + Q_OBJECT + +public: + explicit PluginManager(QObject *parent = 0); + ~PluginManager(); + + static PluginManager* instance(); + + ServicePluginList plugins() const; + + ServicePluginConfig* getConfigForService(const QString &service) const; + + ServicePlugin* getPluginForService(const QString &service) const; + +public Q_SLOTS: + ResourcesRequest* createRequestForService(const QString &service, QObject *parent = 0) const; + + bool resourceTypeIsSupported(const QString &service, const QString &resourceType, + const QString &method = QString("list")) const; + + int load(); + +Q_SIGNALS: + void loaded(int count); + +private: + ServicePluginConfig* getConfigByFilePath(const QString &filePath) const; + + static PluginManager *self; + + QDateTime m_lastLoaded; + + ServicePluginList m_plugins; +}; + +#endif // PLUGINMANAGER_H diff --git a/app/src/plugins/pluginnavmodel.h b/app/src/plugins/pluginnavmodel.h index 16fc707..3b040f2 100644 --- a/app/src/plugins/pluginnavmodel.h +++ b/app/src/plugins/pluginnavmodel.h @@ -1,24 +1,25 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef PLUGINNAVMODEL_H #define PLUGINNAVMODEL_H #include "selectionmodel.h" -#include "resourcesplugins.h" +#include "pluginmanager.h" class PluginNavModel : public SelectionModel { @@ -48,17 +49,21 @@ class PluginNavModel : public SelectionModel public Q_SLOTS: inline void reload() { clear(); - ResourcesPlugin plugin = ResourcesPlugins::instance()->getPluginFromName(service()); + const ServicePluginConfig *config = PluginManager::instance()->getConfigForService(service()); + + if (!config) { + return; + } - if (!plugin.searchResources.isEmpty()) { + if (!config->searchResources().isEmpty()) { append(tr("Search"), ""); } - foreach (ListResource resource, plugin.listResources.values()) { - QString name = resource.value("name").toString(); + foreach (const ListResource &resource, config->listResources()) { + const QString label = resource.label(); - if (!name.isEmpty()) { - append(name, resource); + if (!label.isEmpty()) { + append(label, resource); } } } diff --git a/app/src/plugins/pluginplaylist.cpp b/app/src/plugins/pluginplaylist.cpp index b593f40..bd16767 100644 --- a/app/src/plugins/pluginplaylist.cpp +++ b/app/src/plugins/pluginplaylist.cpp @@ -15,85 +15,206 @@ */ #include "pluginplaylist.h" +#include "pluginmanager.h" #include "resources.h" PluginPlaylist::PluginPlaylist(QObject *parent) : MKPlaylist(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginPlaylist::PluginPlaylist(const QString &service, const QString &id, QObject *parent) : MKPlaylist(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadPlaylist(service, id); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginPlaylist::PluginPlaylist(const QString &service, const QVariantMap &playlist, QObject *parent) : MKPlaylist(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadPlaylist(service, playlist); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } -PluginPlaylist::PluginPlaylist(PluginPlaylist *playlist, QObject *parent) : +PluginPlaylist::PluginPlaylist(const PluginPlaylist *playlist, QObject *parent) : MKPlaylist(playlist, parent), - m_request(new ResourcesRequest(this)) + m_request(0), + m_actions(playlist->actions()), + m_tracksId(playlist->tracksId()) { } +QVariantList PluginPlaylist::actions() const { + return m_actions; +} + +void PluginPlaylist::setActions(const QVariantList &a) { + m_actions = a; + emit changed(); + emit actionsChanged(); +} + +QString PluginPlaylist::tracksId() const { + return m_tracksId; +} + +void PluginPlaylist::setTracksId(const QString &i) { + if (i != tracksId()) { + m_tracksId = i; + emit changed(); + emit tracksIdChanged(); + } +} + QString PluginPlaylist::errorString() const { - return m_request->errorString(); + return m_request ? m_request->errorString() : QString(); } ResourcesRequest::Status PluginPlaylist::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } void PluginPlaylist::loadPlaylist(const QString &service, const QString &id) { + setService(service); + setId(id); + if (status() == ResourcesRequest::Loading) { return; } - m_request->setService(service); - m_request->get(Resources::PLAYLIST, id); - - emit statusChanged(status()); + if (ResourcesRequest *r = request()) { + r->get(Resources::PLAYLIST, id); + emit changed(); + emit statusChanged(status()); + } } void PluginPlaylist::loadPlaylist(const QString &service, const QVariantMap &playlist) { setService(service); - setArtist(playlist.value("artist").toString()); - setArtistId(playlist.value("artistId").toString()); - setDate(playlist.value("date").toString()); - setDescription(playlist.value("description").toString()); - setGenre(playlist.value("genre").toString()); - setId(playlist.value("id").toString()); - setLargeThumbnailUrl(playlist.value("largeThumbnailUrl").toString()); - setThumbnailUrl(playlist.value("thumbnailUrl").toString()); - setTitle(playlist.value("title").toString()); - setTrackCount(playlist.value("trackCount").toInt()); - if (playlist.value("duration").type() == QVariant::String) { - setDurationString(playlist.value("duration").toString()); + if (playlist.contains("actions")) { + setActions(playlist.value("actions").toList()); + } + + if (playlist.contains("artist")) { + setArtist(playlist.value("artist").toString()); + } + + if (playlist.contains("artistId")) { + setArtistId(playlist.value("artistId").toString()); + } + + if (playlist.contains("date")) { + setDate(playlist.value("date").toString()); + } + + if (playlist.contains("description")) { + setDescription(playlist.value("description").toString()); + } + + if (playlist.contains("duration")) { + if (playlist.value("duration").type() == QVariant::String) { + setDurationString(playlist.value("duration").toString()); + } + else { + setDuration(playlist.value("duration").toLongLong()); + } + } + + if (playlist.contains("genre")) { + setGenre(playlist.value("genre").toString()); + } + + if (playlist.contains("id")) { + setId(playlist.value("id").toString()); + } + + if (playlist.contains("largeThumbnailUrl")) { + setLargeThumbnailUrl(playlist.value("largeThumbnailUrl").toString()); + } + + if (playlist.contains("thumbnailUrl")) { + setThumbnailUrl(playlist.value("thumbnailUrl").toString()); + } + + if (playlist.contains("title")) { + setTitle(playlist.value("title").toString()); + } + + if (playlist.contains("trackCount")) { + setTrackCount(playlist.value("trackCount").toInt()); + } + + if (playlist.contains("tracksId")) { + setTracksId(playlist.value("tracksId").toString()); } - else { - setDuration(playlist.value("duration").toLongLong()); + + if (playlist.contains("url")) { + setUrl(playlist.value("url").toString()); } } void PluginPlaylist::loadPlaylist(PluginPlaylist *playlist) { MKPlaylist::loadPlaylist(playlist); + setActions(playlist->actions()); + setTracksId(playlist->tracksId()); +} + +void PluginPlaylist::cancel() { + if (status() == ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginPlaylist::del(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->del(Resources::PLAYLIST, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginPlaylist::insert(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->insert(Resources::PLAYLIST, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +ResourcesRequest* PluginPlaylist::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; } void PluginPlaylist::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - loadPlaylist(m_request->service(), m_request->result().toMap()); + const QVariantMap result = m_request->result().toMap(); + + if (!result.isEmpty()) { + loadPlaylist(service(), result); + } } + emit changed(); emit statusChanged(status()); } diff --git a/app/src/plugins/pluginplaylist.h b/app/src/plugins/pluginplaylist.h index b77fffa..79a162a 100644 --- a/app/src/plugins/pluginplaylist.h +++ b/app/src/plugins/pluginplaylist.h @@ -24,6 +24,8 @@ class PluginPlaylist : public MKPlaylist { Q_OBJECT + Q_PROPERTY(QVariantList actions READ actions NOTIFY actionsChanged) + Q_PROPERTY(QString tracksId READ tracksId NOTIFY tracksIdChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) Q_PROPERTY(ResourcesRequest::Status status READ status NOTIFY statusChanged) @@ -31,7 +33,11 @@ class PluginPlaylist : public MKPlaylist explicit PluginPlaylist(QObject *parent = 0); explicit PluginPlaylist(const QString &service, const QString &id, QObject *parent = 0); explicit PluginPlaylist(const QString &service, const QVariantMap &playlist, QObject *parent = 0); - explicit PluginPlaylist(PluginPlaylist *playlist, QObject *parent = 0); + explicit PluginPlaylist(const PluginPlaylist *playlist, QObject *parent = 0); + + QVariantList actions() const; + + QString tracksId() const; QString errorString() const; @@ -40,15 +46,33 @@ class PluginPlaylist : public MKPlaylist Q_INVOKABLE void loadPlaylist(const QString &service, const QString &id); Q_INVOKABLE void loadPlaylist(const QString &service, const QVariantMap &playlist); Q_INVOKABLE void loadPlaylist(PluginPlaylist *playlist); - + +public Q_SLOTS: + void cancel(); + void del(const QString &resourceType, const QString &resourceId); + void insert(const QString &resourceType, const QString &resourceId); + +protected: + void setActions(const QVariantList &a); + + void setTracksId(const QString &i); + private Q_SLOTS: void onRequestFinished(); Q_SIGNALS: + void actionsChanged(); + void tracksIdChanged(); void statusChanged(ResourcesRequest::Status s); private: + ResourcesRequest* request(); + ResourcesRequest *m_request; + + QVariantList m_actions; + + QString m_tracksId; }; #endif // PLUGINPLAYLIST_H diff --git a/app/src/plugins/pluginplaylistmodel.cpp b/app/src/plugins/pluginplaylistmodel.cpp index ffafc27..f479a8f 100644 --- a/app/src/plugins/pluginplaylistmodel.cpp +++ b/app/src/plugins/pluginplaylistmodel.cpp @@ -15,46 +15,61 @@ */ #include "pluginplaylistmodel.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" PluginPlaylistModel::PluginPlaylistModel(QObject *parent) : QAbstractListModel(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { + m_roles[ActionsRole] = "actions"; m_roles[ArtistRole] = "artist"; m_roles[ArtistIdRole] = "artistId"; m_roles[DateRole] = "date"; m_roles[DescriptionRole] = "description"; m_roles[DurationRole] = "duration"; m_roles[DurationStringRole] = "durationString"; + m_roles[ErrorStringRole] = "errorString"; m_roles[GenreRole] = "genre"; m_roles[IdRole] = "id"; m_roles[LargeThumbnailUrlRole] = "largeThumbnailUrl"; m_roles[ServiceRole] = "service"; + m_roles[StatusRole] = "status"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TitleRole] = "title"; m_roles[TrackCountRole] = "trackCount"; + m_roles[TracksIdRole] = "tracksId"; + m_roles[UrlRole] = "url"; #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - connect(m_request, SIGNAL(serviceChanged()), this, SIGNAL(serviceChanged())); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); +} + +QString PluginPlaylistModel::errorString() const { + return m_request ? m_request->errorString() : QString(); } QString PluginPlaylistModel::service() const { - return m_request->service(); + return m_service; } void PluginPlaylistModel::setService(const QString &s) { - m_request->setService(s); -} + if (s != service()) { + m_service = s; + emit serviceChanged(); -QString PluginPlaylistModel::errorString() const { - return m_request->errorString(); + clear(); + + if (m_request) { + m_request->deleteLater(); + m_request = 0; + } + } } ResourcesRequest::Status PluginPlaylistModel::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } #if QT_VERSION >=0x050000 @@ -63,25 +78,65 @@ QHash PluginPlaylistModel::roleNames() const { } #endif -int PluginPlaylistModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int PluginPlaylistModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int PluginPlaylistModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 4; } -bool PluginPlaylistModel::canFetchMore(const QModelIndex &) const { - return (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); +bool PluginPlaylistModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); } -void PluginPlaylistModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void PluginPlaylistModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } + + if (ResourcesRequest *r = request()) { + r->list(Resources::PLAYLIST, m_next); + emit statusChanged(status()); + } +} + +QVariant PluginPlaylistModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } - m_request->list(Resources::PLAYLIST, m_next); - emit statusChanged(status()); + switch (section) { + case 0: + return tr("Title"); + case 1: + return tr("Artist"); + case 2: + return tr("Tracks"); + case 3: + return tr("Duration"); + default: + return QVariant(); + } } QVariant PluginPlaylistModel::data(const QModelIndex &index, int role) const { - if (PluginPlaylist *playlist = get(index.row())) { + if (const PluginPlaylist *playlist = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return playlist->title(); + case 1: + return playlist->artist(); + case 2: + return playlist->trackCount(); + case 3: + return playlist->durationString(); + default: + return QVariant(); + } + } + return playlist->property(m_roles[role]); } @@ -91,7 +146,7 @@ QVariant PluginPlaylistModel::data(const QModelIndex &index, int role) const { QMap PluginPlaylistModel::itemData(const QModelIndex &index) const { QMap map; - if (PluginPlaylist *playlist = get(index.row())) { + if (const PluginPlaylist *playlist = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -104,7 +159,7 @@ QMap PluginPlaylistModel::itemData(const QModelIndex &index) cons } QVariant PluginPlaylistModel::data(int row, const QByteArray &role) const { - if (PluginPlaylist *playlist = get(row)) { + if (const PluginPlaylist *playlist = get(row)) { return playlist->property(role); } @@ -114,8 +169,8 @@ QVariant PluginPlaylistModel::data(int row, const QByteArray &role) const { QVariantMap PluginPlaylistModel::itemData(int row) const { QVariantMap map; - if (PluginPlaylist *playlist = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const PluginPlaylist *playlist = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = playlist->property(role); } } @@ -131,16 +186,20 @@ PluginPlaylist* PluginPlaylistModel::get(int row) const { return 0; } -void PluginPlaylistModel::list(const QString &id) { +void PluginPlaylistModel::list(const QString &resourceId) { if (status() == ResourcesRequest::Loading) { return; } + Logger::log("PluginPlaylistModel::list(). Resource ID: " + resourceId, Logger::MediumVerbosity); clear(); - m_id = id; + m_resourceId = resourceId; m_query = QString(); - m_request->list(Resources::PLAYLIST, id); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->list(Resources::PLAYLIST, resourceId); + emit statusChanged(status()); + } } void PluginPlaylistModel::search(const QString &query, const QString &order) { @@ -148,12 +207,17 @@ void PluginPlaylistModel::search(const QString &query, const QString &order) { return; } + Logger::log(QString("PluginPlaylistModel::search(). Query: %1, Order: %2").arg(query).arg(order), + Logger::MediumVerbosity); clear(); - m_id = QString(); + m_resourceId = QString(); m_query = query; m_order = order; - m_request->search(Resources::PLAYLIST, query, order); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->search(Resources::PLAYLIST, query, order); + emit statusChanged(status()); + } } void PluginPlaylistModel::clear() { @@ -172,20 +236,28 @@ void PluginPlaylistModel::cancel() { } void PluginPlaylistModel::reload() { - clear(); - - if (m_query.isEmpty()) { - m_request->list(Resources::PLAYLIST, m_id); - } - else { - m_request->search(Resources::PLAYLIST, m_query, m_order); + if (status() == ResourcesRequest::Loading) { + return; } - emit statusChanged(status()); + Logger::log("PluginPlaylistModel::reload(). Resource ID: " + m_resourceId, Logger::MediumVerbosity); + clear(); + + if (ResourcesRequest *r = request()) { + if (m_query.isEmpty()) { + r->list(Resources::PLAYLIST, m_resourceId); + } + else { + r->search(Resources::PLAYLIST, m_query, m_order); + } + + emit statusChanged(status()); + } } void PluginPlaylistModel::append(PluginPlaylist *playlist) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(playlist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << playlist; endInsertRows(); } @@ -193,6 +265,7 @@ void PluginPlaylistModel::append(PluginPlaylist *playlist) { void PluginPlaylistModel::insert(int row, PluginPlaylist *playlist) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(playlist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, playlist); endInsertRows(); } @@ -209,24 +282,49 @@ void PluginPlaylistModel::remove(int row) { } } +ResourcesRequest* PluginPlaylistModel::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; +} + +void PluginPlaylistModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void PluginPlaylistModel::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_next = result.value("next").toString(); - QVariantList list = result.value("items").toList(); + const QVariantList list = result.value("items").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new PluginPlaylist(service(), item.toMap(), this); + foreach (const QVariant &item, list) { + PluginPlaylist *playlist = new PluginPlaylist(service(), item.toMap(), this); + connect(playlist, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << playlist; } endInsertRows(); emit countChanged(rowCount()); } } + else { + Logger::log("PluginPlaylistModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/plugins/pluginplaylistmodel.h b/app/src/plugins/pluginplaylistmodel.h index 15b1ab3..153983a 100644 --- a/app/src/plugins/pluginplaylistmodel.h +++ b/app/src/plugins/pluginplaylistmodel.h @@ -33,38 +33,46 @@ class PluginPlaylistModel : public QAbstractListModel public: enum Roles { - ArtistRole = Qt::UserRole + 1, + ActionsRole = Qt::UserRole + 1, + ArtistRole, ArtistIdRole, DateRole, DescriptionRole, DurationRole, DurationStringRole, + ErrorStringRole, GenreRole, IdRole, LargeThumbnailUrlRole, ServiceRole, + StatusRole, ThumbnailUrlRole, TitleRole, - TrackCountRole + TrackCountRole, + TracksIdRole, + UrlRole }; explicit PluginPlaylistModel(QObject *parent = 0); - QString service() const; - void setService(const QString &service); - QString errorString() const; + QString service() const; + void setService(const QString &s); + ResourcesRequest::Status status() const; #if QT_VERSION >= 0x050000 QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -73,7 +81,7 @@ class PluginPlaylistModel : public QAbstractListModel Q_INVOKABLE PluginPlaylist* get(int row) const; - Q_INVOKABLE void list(const QString &id = QString()); + Q_INVOKABLE void list(const QString &resourceId); Q_INVOKABLE void search(const QString &query, const QString &order); public Q_SLOTS: @@ -81,23 +89,26 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(PluginPlaylist *playlist); - void insert(int row, PluginPlaylist *playlist); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void serviceChanged(); void statusChanged(ResourcesRequest::Status s); private: + void append(PluginPlaylist *playlist); + void insert(int row, PluginPlaylist *playlist); + void remove(int row); + + ResourcesRequest* request(); + ResourcesRequest *m_request; - QString m_id; + QString m_service; + QString m_resourceId; QString m_query; QString m_order; QString m_next; diff --git a/app/src/plugins/pluginsearchtypemodel.h b/app/src/plugins/pluginsearchtypemodel.h index c9a0a23..60fcd8c 100644 --- a/app/src/plugins/pluginsearchtypemodel.h +++ b/app/src/plugins/pluginsearchtypemodel.h @@ -1,24 +1,25 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef PLUGINSEARCHTYPEMODEL_H #define PLUGINSEARCHTYPEMODEL_H #include "selectionmodel.h" -#include "resourcesplugins.h" +#include "pluginmanager.h" class PluginSearchTypeModel : public SelectionModel { @@ -48,10 +49,18 @@ class PluginSearchTypeModel : public SelectionModel public Q_SLOTS: inline void reload() { clear(); - ResourcesPlugin plugin = ResourcesPlugins::instance()->getPluginFromName(service()); + const ServicePluginConfig *config = PluginManager::instance()->getConfigForService(service()); + + if (!config) { + return; + } - foreach (SearchResource resource, plugin.searchResources.values()) { - append(resource.value("name").toString(), resource); + foreach (const SearchResource &resource, config->searchResources()) { + const QString label = resource.label(); + + if (!label.isEmpty()) { + append(label, resource); + } } } diff --git a/app/src/plugins/pluginsettings.cpp b/app/src/plugins/pluginsettings.cpp new file mode 100644 index 0000000..baf090e --- /dev/null +++ b/app/src/plugins/pluginsettings.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pluginsettings.h" +#include "definitions.h" +#include + +PluginSettings::PluginSettings(QObject *parent) : + QObject(parent) +{ +} + +PluginSettings::PluginSettings(const QString &pluginId, QObject *parent) : + QObject(parent), + m_pluginId(pluginId) +{ +} + +QString PluginSettings::pluginId() const { + return m_pluginId; +} + +void PluginSettings::setPluginId(const QString &id) { + if (id != pluginId()) { + m_pluginId = id; + emit changed(); + } +} + +bool PluginSettings::remove(const QString &key) { + if ((!pluginId().isEmpty()) && (key != value(key))) { + QSettings settings(PLUGIN_CONFIG_PATH + pluginId(), QSettings::IniFormat); + + if (settings.contains(key)) { + settings.remove(key); + emit changed(); + return true; + } + } + + return false; +} + +QVariant PluginSettings::value(const QString &key, const QVariant &defaultValue) const { + if (pluginId().isEmpty()) { + return QVariant(); + } + + const QVariant v = QSettings(PLUGIN_CONFIG_PATH + pluginId(), QSettings::IniFormat).value(key, defaultValue); + + if (v.type() == QVariant::String) { + if ((v == "true") || (v == "false")) { + /* It seems that the type of a boolean value is QVariant::String when read from the file, which causes + problems in the scripting environment, so we convert it to a boolean. + */ + return v.toBool(); + } + } + + return v; +} + +void PluginSettings::setValue(const QString &key, const QVariant &value) { + if ((!pluginId().isEmpty()) && (key != this->value(key))) { + QSettings(PLUGIN_CONFIG_PATH + pluginId(), QSettings::IniFormat).setValue(key, value); + emit changed(); + } +} diff --git a/app/src/plugins/pluginsettings.h b/app/src/plugins/pluginsettings.h new file mode 100644 index 0000000..6196cdb --- /dev/null +++ b/app/src/plugins/pluginsettings.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINSETTINGS_H +#define PLUGINSETTINGS_H + +#include +#include + +class PluginSettings : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString pluginId READ pluginId WRITE setPluginId NOTIFY changed) + +public: + explicit PluginSettings(QObject *parent = 0); + explicit PluginSettings(const QString &pluginId, QObject *parent = 0); + + QString pluginId() const; + void setPluginId(const QString &id); + +public Q_SLOTS: + bool remove(const QString &key); + + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + void setValue(const QString &key, const QVariant &value); + +Q_SIGNALS: + void changed(); + +private: + QString m_pluginId; +}; + +#endif // PLUGINSETTINGS_H diff --git a/app/src/plugins/pluginstreammodel.cpp b/app/src/plugins/pluginstreammodel.cpp index 87e2ce7..ffd496a 100644 --- a/app/src/plugins/pluginstreammodel.cpp +++ b/app/src/plugins/pluginstreammodel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -15,60 +15,164 @@ */ #include "pluginstreammodel.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" +#include PluginStreamModel::PluginStreamModel(QObject *parent) : SelectionModel(parent), - m_request(new ResourcesRequest(this)) + m_request(0), + m_soundcloudRequest(0), + m_status(ResourcesRequest::Null) { - connect(m_request, SIGNAL(serviceChanged()), this, SIGNAL(serviceChanged())); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); +} + +QString PluginStreamModel::errorString() const { + return m_request ? m_request->errorString() : QString(); +} + +void PluginStreamModel::setErrorString(const QString &e) { + m_errorString = e; } QString PluginStreamModel::service() const { - return m_request->service(); + return m_service; } void PluginStreamModel::setService(const QString &s) { - m_request->setService(s); -} + if (s != service()) { + m_service = s; + emit serviceChanged(); -QString PluginStreamModel::errorString() const { - return m_request->errorString(); + clear(); + + if (m_request) { + m_request->deleteLater(); + m_request = 0; + } + } } ResourcesRequest::Status PluginStreamModel::status() const { - return m_request->status(); + return m_status; } -void PluginStreamModel::list(const QString &id) { +void PluginStreamModel::setStatus(ResourcesRequest::Status s) { + if (s != status()) { + m_status = s; + emit statusChanged(s); + } +} + +void PluginStreamModel::list(const QString &resourceId) { if (status() == ResourcesRequest::Loading) { return; } + Logger::log("PluginStreamModel::list(). Resource ID: " + resourceId, Logger::MediumVerbosity); clear(); - m_id = id; - m_request->list(Resources::STREAM, id); - emit statusChanged(status()); + m_resourceId = resourceId; + + if (ResourcesRequest *r = request()) { + r->list(Resources::STREAM, resourceId); + setStatus(r->status()); + } } void PluginStreamModel::cancel() { - m_request->cancel(); + if (m_request) { + m_request->cancel(); + } } void PluginStreamModel::reload() { + if (status() == ResourcesRequest::Loading) { + return; + } + + Logger::log("PluginStreamModel::reload(). Resource ID: " + m_resourceId, Logger::MediumVerbosity); clear(); - m_request->list(Resources::STREAM, m_id); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->list(Resources::STREAM, m_resourceId); + setStatus(r->status()); + } +} + +ResourcesRequest* PluginStreamModel::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; +} + +QSoundCloud::StreamsRequest* PluginStreamModel::soundcloudRequest() { + if (!m_soundcloudRequest) { + m_soundcloudRequest = new QSoundCloud::StreamsRequest(this); + connect(m_soundcloudRequest, SIGNAL(finished()), this, SLOT(onSoundCloudRequestFinished())); + } + + return m_soundcloudRequest; } void PluginStreamModel::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - foreach (QVariant v, m_request->result().toMap().value("items").toList()) { - QVariantMap stream = v.toMap(); + const QVariantMap result = m_request->result().toMap(); + + if (result.contains("items")) { + foreach (const QVariant &v, result.value("items").toList()) { + const QVariantMap stream = v.toMap(); + append(stream.value("description").toString(), stream); + } + + setErrorString(QString()); + setStatus(ResourcesRequest::Ready); + } + else if (result.contains("service")) { + const QString service = result.value("service").toString(); + + if (service == Resources::SOUNDCLOUD) { + soundcloudRequest()->get(result.value("id").toString()); + } + else { + setErrorString(tr("Attempted redirect to unsupported service '%1'").arg(service)); + setStatus(ResourcesRequest::Failed); + Logger::log("PluginStreamModel::onRequestFinished(). Error: " + errorString()); + } + } + else { + setErrorString(tr("Unknown error")); + setStatus(ResourcesRequest::Failed); + Logger::log("PluginStreamModel::onRequestFinished(). Error: " + errorString()); + } + } + else { + setErrorString(m_request->errorString()); + setStatus(ResourcesRequest::Failed); + Logger::log("PluginStreamModel::onRequestFinished(). Error: " + errorString()); + } +} + +void PluginStreamModel::onSoundCloudRequestFinished() { + if (m_soundcloudRequest->status() == QSoundCloud::StreamsRequest::Ready) { + foreach (const QVariant &v, m_soundcloudRequest->result().toList()) { + const QVariantMap stream = v.toMap(); append(stream.value("description").toString(), stream); } + + setErrorString(QString()); + setStatus(ResourcesRequest::Ready); + } + else { + setErrorString(m_soundcloudRequest->errorString()); + setStatus(ResourcesRequest::Failed); + Logger::log("PluginStreamModel::onSoundCloudRequestFinished(). Error: " + errorString()); } - emit statusChanged(status()); } diff --git a/app/src/plugins/pluginstreammodel.h b/app/src/plugins/pluginstreammodel.h index 57bb53f..ece9388 100644 --- a/app/src/plugins/pluginstreammodel.h +++ b/app/src/plugins/pluginstreammodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -20,6 +20,10 @@ #include "selectionmodel.h" #include "resourcesrequest.h" +namespace QSoundCloud { + class StreamsRequest; +} + class PluginStreamModel : public SelectionModel { Q_OBJECT @@ -30,15 +34,15 @@ class PluginStreamModel : public SelectionModel public: explicit PluginStreamModel(QObject *parent = 0); + + QString errorString() const; QString service() const; - void setService(const QString &service); - - QString errorString() const; + void setService(const QString &s); ResourcesRequest::Status status() const; - Q_INVOKABLE void list(const QString &id); + Q_INVOKABLE void list(const QString &resourceId); public Q_SLOTS: void cancel(); @@ -46,15 +50,28 @@ public Q_SLOTS: private Q_SLOTS: void onRequestFinished(); + void onSoundCloudRequestFinished(); Q_SIGNALS: void serviceChanged(); void statusChanged(ResourcesRequest::Status s); private: + void setErrorString(const QString &e); + + void setStatus(ResourcesRequest::Status s); + + ResourcesRequest* request(); + QSoundCloud::StreamsRequest* soundcloudRequest(); + ResourcesRequest *m_request; + QSoundCloud::StreamsRequest *m_soundcloudRequest; + + QString m_errorString; + QString m_service; + QString m_resourceId; - QString m_id; + ResourcesRequest::Status m_status; }; #endif // PLUGINSTREAMMODEL_H diff --git a/app/src/plugins/plugintrack.cpp b/app/src/plugins/plugintrack.cpp index 623edc6..610157d 100644 --- a/app/src/plugins/plugintrack.cpp +++ b/app/src/plugins/plugintrack.cpp @@ -15,96 +15,245 @@ */ #include "plugintrack.h" +#include "pluginmanager.h" #include "resources.h" PluginTrack::PluginTrack(QObject *parent) : MKTrack(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginTrack::PluginTrack(const QString &service, const QString &id, QObject *parent) : MKTrack(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadTrack(service, id); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } PluginTrack::PluginTrack(const QString &service, const QVariantMap &track, QObject *parent) : MKTrack(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { loadTrack(service, track); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); } -PluginTrack::PluginTrack(PluginTrack *track, QObject *parent) : +PluginTrack::PluginTrack(const PluginTrack *track, QObject *parent) : MKTrack(track, parent), - m_request(new ResourcesRequest(this)) + m_request(0), + m_actions(track->actions()), + m_commentsId(track->commentsId()), + m_relatedTracksId(track->relatedTracksId()) { } +QVariantList PluginTrack::actions() const { + return m_actions; +} + +void PluginTrack::setActions(const QVariantList &a) { + m_actions = a; + emit changed(); + emit actionsChanged(); +} + +QString PluginTrack::commentsId() const { + return m_commentsId; +} + +void PluginTrack::setCommentsId(const QString &i) { + if (i != commentsId()) { + m_commentsId = i; + emit changed(); + emit commentsIdChanged(); + } +} + +QString PluginTrack::relatedTracksId() const { + return m_relatedTracksId; +} + +void PluginTrack::setRelatedTracksId(const QString &i) { + if (i != relatedTracksId()) { + m_relatedTracksId = i; + emit changed(); + emit relatedTracksIdChanged(); + } +} + QString PluginTrack::errorString() const { - return m_request->errorString(); + return m_request ? m_request->errorString() : QString(); } ResourcesRequest::Status PluginTrack::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } void PluginTrack::loadTrack(const QString &service, const QString &id) { + setService(service); + setId(id); + if (status() == ResourcesRequest::Loading) { return; } - m_request->setService(service); - m_request->get(Resources::TRACK, id); - - emit statusChanged(status()); + if (ResourcesRequest *r = request()) { + r->get(Resources::TRACK, id); + emit changed(); + emit statusChanged(status()); + } } void PluginTrack::loadTrack(const QString &service, const QVariantMap &track) { setService(service); - setArtist(track.value("artist").toString()); - setArtistId(track.value("artistId").toString()); - setDate(track.value("date").toString()); - setDescription(track.value("description").toString()); - setDownloadable(track.value("downloadable", true).toBool()); - setFormat(track.value("format").toString()); - setGenre(track.value("genre").toString()); - setId(track.value("id").toString()); - setLargeThumbnailUrl(track.value("largeThumbnailUrl").toString()); - setPlayCount(track.value("playCount").toLongLong()); - setStreamUrl(track.value("streamUrl").toString()); - setThumbnailUrl(track.value("thumbnailUrl").toString()); - setTitle(track.value("title").toString()); - setUrl(track.value("url").toString()); - - if (track.value("duration").type() == QVariant::String) { - setDurationString(track.value("duration").toString()); + + if (track.contains("actions")) { + setActions(track.value("actions").toList()); + } + + if (track.contains("artist")) { + setArtist(track.value("artist").toString()); + } + + if (track.contains("artistId")) { + setArtistId(track.value("artistId").toString()); } - else { - setDuration(track.value("duration").toLongLong()); + + if (track.contains("commentsId")) { + setCommentsId(track.value("commentsId").toString()); + } + + if (track.contains("date")) { + setDate(track.value("date").toString()); + } + + if (track.contains("description")) { + setDescription(track.value("description").toString()); } - if (track.value("size").type() == QVariant::String) { - setSizeString(track.value("size").toString()); + if (track.contains("downloadable")) { + setDownloadable(track.value("downloadable").toBool()); } - else { - setSize(track.value("size").toLongLong()); + + if (track.contains("duration")) { + if (track.value("duration").type() == QVariant::String) { + setDurationString(track.value("duration").toString()); + } + else { + setDuration(track.value("duration").toLongLong()); + } + } + + if (track.contains("format")) { + setFormat(track.value("format").toString()); + } + + if (track.contains("genre")) { + setGenre(track.value("genre").toString()); + } + + if (track.contains("id")) { + setId(track.value("id").toString()); + } + + if (track.contains("largeThumbnailUrl")) { + setLargeThumbnailUrl(track.value("largeThumbnailUrl").toString()); + } + + if (track.contains("playCount")) { + setPlayCount(track.value("playCount").toLongLong()); + } + + if (track.contains("relatedTracksId")) { + setRelatedTracksId(track.value("relatedTracksId").toString()); + } + + if (track.contains("size")) { + if (track.value("size").type() == QVariant::String) { + setSizeString(track.value("size").toString()); + } + else { + setSize(track.value("size").toLongLong()); + } + } + + if (track.contains("streamUrl")) { + setStreamUrl(track.value("streamUrl").toString()); + } + + if (track.contains("thumbnailUrl")) { + setThumbnailUrl(track.value("thumbnailUrl").toString()); + } + + if (track.contains("title")) { + setTitle(track.value("title").toString()); + } + + if (track.contains("url")) { + setUrl(track.value("url").toString()); } } void PluginTrack::loadTrack(PluginTrack *track) { MKTrack::loadTrack(track); + setActions(track->actions()); + setCommentsId(track->commentsId()); + setRelatedTracksId(track->relatedTracksId()); +} + +void PluginTrack::cancel() { + if (status() == ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginTrack::del(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->del(Resources::TRACK, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +void PluginTrack::insert(const QString &resourceType, const QString &resourceId) { + if (status() == ResourcesRequest::Loading) { + return; + } + + if (ResourcesRequest *r = request()) { + r->insert(Resources::TRACK, id(), resourceType, resourceId); + emit changed(); + emit statusChanged(status()); + } +} + +ResourcesRequest* PluginTrack::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; } void PluginTrack::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - loadTrack(m_request->service(), m_request->result().toMap()); + const QVariantMap result = m_request->result().toMap(); + + if (!result.isEmpty()) { + loadTrack(service(), result); + } } + emit changed(); emit statusChanged(status()); } diff --git a/app/src/plugins/plugintrack.h b/app/src/plugins/plugintrack.h index 33138d2..97bfb7f 100644 --- a/app/src/plugins/plugintrack.h +++ b/app/src/plugins/plugintrack.h @@ -24,6 +24,9 @@ class PluginTrack : public MKTrack { Q_OBJECT + Q_PROPERTY(QVariantList actions READ actions NOTIFY actionsChanged) + Q_PROPERTY(QString commentsId READ commentsId NOTIFY commentsIdChanged) + Q_PROPERTY(QString relatedTracksId READ relatedTracksId NOTIFY relatedTracksIdChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) Q_PROPERTY(ResourcesRequest::Status status READ status NOTIFY statusChanged) @@ -31,7 +34,12 @@ class PluginTrack : public MKTrack explicit PluginTrack(QObject *parent = 0); explicit PluginTrack(const QString &service, const QString &id, QObject *parent = 0); explicit PluginTrack(const QString &service, const QVariantMap &track, QObject *parent = 0); - explicit PluginTrack(PluginTrack *track, QObject *parent = 0); + explicit PluginTrack(const PluginTrack *track, QObject *parent = 0); + + QVariantList actions() const; + + QString commentsId() const; + QString relatedTracksId() const; QString errorString() const; @@ -40,15 +48,36 @@ class PluginTrack : public MKTrack Q_INVOKABLE void loadTrack(const QString &service, const QString &id); Q_INVOKABLE void loadTrack(const QString &service, const QVariantMap &track); Q_INVOKABLE void loadTrack(PluginTrack *track); - + +public Q_SLOTS: + void cancel(); + void del(const QString &resourceType, const QString &resourceId); + void insert(const QString &resourceType, const QString &resourceId); + +protected: + void setActions(const QVariantList &a); + + void setCommentsId(const QString &i); + void setRelatedTracksId(const QString &i); + private Q_SLOTS: void onRequestFinished(); Q_SIGNALS: + void actionsChanged(); + void commentsIdChanged(); + void relatedTracksIdChanged(); void statusChanged(ResourcesRequest::Status s); private: + ResourcesRequest* request(); + ResourcesRequest *m_request; + + QVariantList m_actions; + + QString m_commentsId; + QString m_relatedTracksId; }; #endif // PLUGINTRACK_H diff --git a/app/src/plugins/plugintrackmodel.cpp b/app/src/plugins/plugintrackmodel.cpp index da1235b..e963314 100644 --- a/app/src/plugins/plugintrackmodel.cpp +++ b/app/src/plugins/plugintrackmodel.cpp @@ -15,27 +15,34 @@ */ #include "plugintrackmodel.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" PluginTrackModel::PluginTrackModel(QObject *parent) : QAbstractListModel(parent), - m_request(new ResourcesRequest(this)) + m_request(0) { + m_roles[ActionsRole] = "actions"; m_roles[ArtistRole] = "artist"; m_roles[ArtistIdRole] = "artistId"; m_roles[DateRole] = "date"; + m_roles[CommentsIdRole] = "commentsId"; m_roles[DescriptionRole] = "description"; m_roles[DownloadableRole] = "downloadable"; m_roles[DurationRole] = "duration"; m_roles[DurationStringRole] = "durationString"; + m_roles[ErrorStringRole] = "errorString"; m_roles[FormatRole] = "format"; m_roles[GenreRole] = "genre"; m_roles[IdRole] = "id"; m_roles[LargeThumbnailUrlRole] = "largeThumbnailUrl"; m_roles[PlayCountRole] = "playCount"; + m_roles[RelatedTracksIdRole] = "relatedTracksId"; m_roles[ServiceRole] = "service"; m_roles[SizeRole] = "size"; m_roles[SizeStringRole] = "sizeString"; + m_roles[StatusRole] = "status"; m_roles[StreamUrlRole] = "streamUrl"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TitleRole] = "title"; @@ -43,24 +50,32 @@ PluginTrackModel::PluginTrackModel(QObject *parent) : #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - connect(m_request, SIGNAL(serviceChanged()), this, SIGNAL(serviceChanged())); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); +} + +QString PluginTrackModel::errorString() const { + return m_request ? m_request->errorString() : QString(); } QString PluginTrackModel::service() const { - return m_request->service(); + return m_service; } void PluginTrackModel::setService(const QString &s) { - m_request->setService(s); -} + if (s != service()) { + m_service = s; + emit serviceChanged(); -QString PluginTrackModel::errorString() const { - return m_request->errorString(); + clear(); + + if (m_request) { + m_request->deleteLater(); + m_request = 0; + } + } } ResourcesRequest::Status PluginTrackModel::status() const { - return m_request->status(); + return m_request ? m_request->status() : ResourcesRequest::Null; } #if QT_VERSION >=0x050000 @@ -69,25 +84,65 @@ QHash PluginTrackModel::roleNames() const { } #endif -int PluginTrackModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int PluginTrackModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int PluginTrackModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 4; } -bool PluginTrackModel::canFetchMore(const QModelIndex &) const { - return (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); +bool PluginTrackModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != ResourcesRequest::Loading) && (!m_next.isEmpty()); } -void PluginTrackModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void PluginTrackModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } + + if (ResourcesRequest *r = request()) { + r->list(Resources::TRACK, m_next); + emit statusChanged(status()); + } +} + +QVariant PluginTrackModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } - m_request->list(Resources::TRACK, m_next); - emit statusChanged(status()); + switch (section) { + case 0: + return tr("Title"); + case 1: + return tr("Artist"); + case 2: + return tr("Genre"); + case 3: + return tr("Duration"); + default: + return QVariant(); + } } QVariant PluginTrackModel::data(const QModelIndex &index, int role) const { - if (PluginTrack *track = get(index.row())) { + if (const PluginTrack *track = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return track->title(); + case 1: + return track->artist(); + case 2: + return track->genre(); + case 3: + return track->durationString(); + default: + return QVariant(); + } + } + return track->property(m_roles[role]); } @@ -97,7 +152,7 @@ QVariant PluginTrackModel::data(const QModelIndex &index, int role) const { QMap PluginTrackModel::itemData(const QModelIndex &index) const { QMap map; - if (PluginTrack *track = get(index.row())) { + if (const PluginTrack *track = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -110,7 +165,7 @@ QMap PluginTrackModel::itemData(const QModelIndex &index) const { } QVariant PluginTrackModel::data(int row, const QByteArray &role) const { - if (PluginTrack *track = get(row)) { + if (const PluginTrack *track = get(row)) { return track->property(role); } @@ -120,8 +175,8 @@ QVariant PluginTrackModel::data(int row, const QByteArray &role) const { QVariantMap PluginTrackModel::itemData(int row) const { QVariantMap map; - if (PluginTrack *track = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const PluginTrack *track = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = track->property(role); } } @@ -137,16 +192,20 @@ PluginTrack* PluginTrackModel::get(int row) const { return 0; } -void PluginTrackModel::list(const QString &id) { +void PluginTrackModel::list(const QString &resourceId) { if (status() == ResourcesRequest::Loading) { return; } + Logger::log("PluginTrackModel::list(). Resource ID: " + resourceId, Logger::MediumVerbosity); clear(); - m_id = id; + m_resourceId = resourceId; m_query = QString(); - m_request->list(Resources::TRACK, id); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->list(Resources::TRACK, resourceId); + emit statusChanged(status()); + } } void PluginTrackModel::search(const QString &query, const QString &order) { @@ -154,12 +213,17 @@ void PluginTrackModel::search(const QString &query, const QString &order) { return; } + Logger::log(QString("PluginTrackModel::search(). Query: %1, Order: %2").arg(query).arg(order), + Logger::MediumVerbosity); clear(); - m_id = QString(); + m_resourceId = QString(); m_query = query; m_order = order; - m_request->search(Resources::TRACK, query, order); - emit statusChanged(status()); + + if (ResourcesRequest *r = request()) { + r->search(Resources::TRACK, query, order); + emit statusChanged(status()); + } } void PluginTrackModel::clear() { @@ -174,24 +238,34 @@ void PluginTrackModel::clear() { } void PluginTrackModel::cancel() { - m_request->cancel(); + if (m_request) { + m_request->cancel(); + } } void PluginTrackModel::reload() { - clear(); - - if (m_query.isEmpty()) { - m_request->list(Resources::TRACK, m_id); - } - else { - m_request->search(Resources::TRACK, m_query, m_order); + if (status() == ResourcesRequest::Loading) { + return; } - emit statusChanged(status()); + Logger::log("PluginTrackModel::reload(). Resource ID: " + m_resourceId, Logger::MediumVerbosity); + clear(); + + if (ResourcesRequest *r = request()) { + if (m_query.isEmpty()) { + r->list(Resources::TRACK, m_resourceId); + } + else { + r->search(Resources::TRACK, m_query, m_order); + } + + emit statusChanged(status()); + } } void PluginTrackModel::append(PluginTrack *track) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << track; endInsertRows(); } @@ -199,6 +273,7 @@ void PluginTrackModel::append(PluginTrack *track) { void PluginTrackModel::insert(int row, PluginTrack *track) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, track); endInsertRows(); } @@ -215,18 +290,40 @@ void PluginTrackModel::remove(int row) { } } +ResourcesRequest* PluginTrackModel::request() { + if (!m_request) { + m_request = PluginManager::instance()->createRequestForService(service(), this); + + if (m_request) { + connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); + } + } + + return m_request; +} + +void PluginTrackModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void PluginTrackModel::onRequestFinished() { if (m_request->status() == ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_next = result.value("next").toString(); - QVariantList list = result.value("items").toList(); + const QVariantList list = result.value("items").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new PluginTrack(service(), item.toMap(), this); + foreach (const QVariant &item, list) { + PluginTrack *track = new PluginTrack(service(), item.toMap(), this); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << track; } endInsertRows(); @@ -234,6 +331,9 @@ void PluginTrackModel::onRequestFinished() { } } + else { + Logger::log("PluginTrackModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/plugins/plugintrackmodel.h b/app/src/plugins/plugintrackmodel.h index e44dce3..1fa4fe3 100644 --- a/app/src/plugins/plugintrackmodel.h +++ b/app/src/plugins/plugintrackmodel.h @@ -17,7 +17,6 @@ #ifndef PLUGINTRACKMODEL_H #define PLUGINTRACKMODEL_H -#include "resourcesrequest.h" #include "plugintrack.h" #include @@ -33,21 +32,26 @@ class PluginTrackModel : public QAbstractListModel public: enum Roles { - ArtistRole = Qt::UserRole + 1, + ActionsRole = Qt::UserRole + 1, + ArtistRole, ArtistIdRole, + CommentsIdRole, DateRole, DescriptionRole, DownloadableRole, DurationRole, DurationStringRole, + ErrorStringRole, FormatRole, GenreRole, IdRole, LargeThumbnailUrlRole, PlayCountRole, + RelatedTracksIdRole, ServiceRole, SizeRole, SizeStringRole, + StatusRole, StreamUrlRole, ThumbnailUrlRole, TitleRole, @@ -56,21 +60,24 @@ class PluginTrackModel : public QAbstractListModel explicit PluginTrackModel(QObject *parent = 0); - QString service() const; - void setService(const QString &service); - QString errorString() const; + QString service() const; + void setService(const QString &service); + ResourcesRequest::Status status() const; #if QT_VERSION >= 0x050000 QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -79,7 +86,7 @@ class PluginTrackModel : public QAbstractListModel Q_INVOKABLE PluginTrack* get(int row) const; - Q_INVOKABLE void list(const QString &id = QString()); + Q_INVOKABLE void list(const QString &resourceId); Q_INVOKABLE void search(const QString &query, const QString &order); public Q_SLOTS: @@ -87,23 +94,26 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(PluginTrack *track); - void insert(int row, PluginTrack *track); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void serviceChanged(); void statusChanged(ResourcesRequest::Status s); private: + void append(PluginTrack *track); + void insert(int row, PluginTrack *track); + void remove(int row); + + ResourcesRequest* request(); + ResourcesRequest *m_request; - QString m_id; + QString m_service; + QString m_resourceId; QString m_query; QString m_order; QString m_next; diff --git a/app/src/plugins/plugintransfer.cpp b/app/src/plugins/plugintransfer.cpp index 465d420..99bef74 100644 --- a/app/src/plugins/plugintransfer.cpp +++ b/app/src/plugins/plugintransfer.cpp @@ -15,38 +15,127 @@ */ #include "plugintransfer.h" +#include "logger.h" +#include "pluginmanager.h" #include "resources.h" #include "resourcesrequest.h" +#include +#include PluginTransfer::PluginTransfer(const QString &service, QObject *parent) : Transfer(parent), - m_streamsRequest(0) + m_streamsRequest(0), + m_soundcloudStreamsRequest(0) { setService(service); } void PluginTransfer::listStreams() { + if (ResourcesRequest *r = streamsRequest()) { + Logger::log("PluginTransfer::listStreams(). ID: " + trackId(), Logger::MediumVerbosity); + r->list(Resources::STREAM, trackId()); + } + else { + setErrorString(tr("No streams plugin found for service '%1'").arg(service())); + Logger::log("PluginTransfer::listStreams(). Error: " + errorString()); + setStatus(Failed); + } +} + +ResourcesRequest* PluginTransfer::streamsRequest() { if (!m_streamsRequest) { - m_streamsRequest = new ResourcesRequest(this); - connect(m_streamsRequest, SIGNAL(finished()), this, SLOT(onStreamsRequestFinished())); + m_streamsRequest = PluginManager::instance()->createRequestForService(service(), this); + + if (m_streamsRequest) { + connect(m_streamsRequest, SIGNAL(finished()), this, SLOT(onStreamsRequestFinished())); + } + } + + return m_streamsRequest; +} + +QSoundCloud::StreamsRequest* PluginTransfer::soundcloudStreamsRequest() { + if (!m_soundcloudStreamsRequest) { + m_soundcloudStreamsRequest = new QSoundCloud::StreamsRequest(this); + connect(m_soundcloudStreamsRequest, SIGNAL(finished()), this, SLOT(onSoundCloudStreamsRequestFinished())); } - m_streamsRequest->setService(service()); - m_streamsRequest->list(Resources::STREAM, resourceId()); + return m_soundcloudStreamsRequest; } void PluginTransfer::onStreamsRequestFinished() { if (m_streamsRequest->status() == ResourcesRequest::Ready) { - QVariantList list = m_streamsRequest->result().toMap().value("items").toList(); + const QVariantMap result = m_streamsRequest->result().toMap(); - foreach (QVariant v, list) { - QVariantMap stream = v.toMap(); + if (result.contains("items")) { + const QVariantList list = result.value("items").toList(); + + foreach (const QVariant &v, list) { + const QVariantMap stream = v.toMap(); + + if (stream.value("id") == streamId()) { + const QFileInfo info(downloadPath() + fileName()); + QString suffix = info.suffix(); + + if (suffix.isEmpty()) { + suffix = stream.value("ext").toString(); + + if (!suffix.isEmpty()) { + if (!suffix.startsWith(".")) { + suffix.prepend("."); + } + + setFileName(info.completeBaseName() + suffix); + } + } + + startDownload(stream.value("url").toString()); + return; + } + } + } + else if (result.contains("service")) { + const QString service = result.value("service").toString(); + + if (service == Resources::SOUNDCLOUD) { + soundcloudStreamsRequest()->get(result.value("id").toString()); + } + else { + setErrorString(tr("Attempted redirect to unsupported service '%1'").arg(service)); + Logger::log("PluginTransfer::onStreamsRequestFinished(). Error: " + errorString()); + setStatus(Failed); + } + + return; + } + } + + setErrorString(tr("No stream URL found")); + Logger::log("PluginTransfer::onStreamsRequestFinished(). Error: " + errorString()); + setStatus(Failed); +} + +void PluginTransfer::onSoundCloudStreamsRequestFinished() { + if (m_soundcloudStreamsRequest->status() == QSoundCloud::StreamsRequest::Ready) { + const QVariantList list = m_soundcloudStreamsRequest->result().toList(); + foreach (const QVariant &v, list) { + const QVariantMap stream = v.toMap(); + if (stream.value("id") == streamId()) { - QString ext = stream.value("ext").toString(); + const QFileInfo info(downloadPath() + fileName()); + QString suffix = info.suffix(); - if (!ext.isEmpty()) { - setFileExtension(ext); + if (suffix.isEmpty()) { + suffix = stream.value("ext").toString(); + + if (!suffix.isEmpty()) { + if (!suffix.startsWith(".")) { + suffix.prepend("."); + } + + setFileName(info.completeBaseName() + suffix); + } } startDownload(stream.value("url").toString()); @@ -56,5 +145,6 @@ void PluginTransfer::onStreamsRequestFinished() { } setErrorString(tr("No stream URL found")); + Logger::log("PluginTransfer::onSoundCloudStreamsRequestFinished(). Error: " + errorString()); setStatus(Failed); -} \ No newline at end of file +} diff --git a/app/src/plugins/plugintransfer.h b/app/src/plugins/plugintransfer.h index 1c90a2c..0babd99 100644 --- a/app/src/plugins/plugintransfer.h +++ b/app/src/plugins/plugintransfer.h @@ -21,21 +21,29 @@ class ResourcesRequest; +namespace QSoundCloud { + class StreamsRequest; +} + class PluginTransfer : public Transfer { Q_OBJECT public: explicit PluginTransfer(const QString &service, QObject *parent = 0); - -private: - void listStreams(); private Q_SLOTS: void onStreamsRequestFinished(); + void onSoundCloudStreamsRequestFinished(); private: + void listStreams(); + + ResourcesRequest* streamsRequest(); + QSoundCloud::StreamsRequest* soundcloudStreamsRequest(); + ResourcesRequest *m_streamsRequest; + QSoundCloud::StreamsRequest *m_soundcloudStreamsRequest; }; #endif // PLUGINTRANSFER_H diff --git a/app/src/plugins/resourcesplugins.cpp b/app/src/plugins/resourcesplugins.cpp deleted file mode 100644 index d08848d..0000000 --- a/app/src/plugins/resourcesplugins.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "resourcesplugins.h" -#include "definitions.h" -#include -#include -#include -#include -#ifdef MUSIKLOUD_DEBUG -#include -#endif - -ResourcesPlugins* ResourcesPlugins::self = 0; - -ResourcesPlugins::ResourcesPlugins(QObject *parent) : - QObject(parent) -{ - if (!self) { - self = this; - } -} - -ResourcesPlugins::~ResourcesPlugins() { - if (self == this) { - self = 0; - } -} - -ResourcesPlugins* ResourcesPlugins::instance() { - return self; -} - -ResourcesPlugin ResourcesPlugins::getPluginFromName(const QString &name) const { - return m_plugins.value(name); -} - -QList ResourcesPlugins::plugins() const { - return m_plugins.values(); -} - -QStringList ResourcesPlugins::pluginNames() const { - return m_plugins.keys(); -} - -bool ResourcesPlugins::resourceTypeIsSupported(const QString &pluginName, const QString &resource, - const QString &method) const { - if (method == "list") { - return getPluginFromName(pluginName).listResources.contains(resource); - } - - if (method == "search") { - return getPluginFromName(pluginName).searchResources.contains(resource); - } - - if (method == "get") { - return getPluginFromName(pluginName).regExps.contains(resource); - } - - return false; -} - -void ResourcesPlugins::load() { - m_plugins.clear(); - QDir dir; - - foreach (QString path, PLUGIN_PATHS) { - dir.setPath(path); - - foreach (QString fileName, dir.entryList(QStringList() << "*.plugin", QDir::Files)) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesPlugins::load: Plugin found:" << fileName; -#endif - QDomDocument doc; - QFile file(dir.absoluteFilePath(fileName)); - - if (!file.open(QIODevice::ReadOnly)) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesPlugins::load: File error:" << file.errorString(); -#endif - continue; - } - - if (!doc.setContent(&file)) { - file.close(); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesPlugins::load: XML error"; -#endif - continue; - } - - QDomElement docElem = doc.documentElement(); - QString name = docElem.attribute("name"); - QString command = docElem.attribute("exec"); - QDomNodeList resources = docElem.elementsByTagName("resource"); - - if ((!name.isEmpty()) && (!command.isEmpty()) && (!resources.isEmpty())) { - ResourcesPlugin plugin; - plugin.name = name; - plugin.command = command; - - if (docElem.hasAttribute("settings")) { - QString settings = docElem.attribute("settings"); - plugin.settings = settings.startsWith('/') ? settings : path + settings; - } - - for (int i = 0; i < resources.size(); i++) { - QDomElement resourceElem = resources.at(i).toElement(); - QString method = resourceElem.attribute("method"); - - if (method == "list") { - ListResource listResource(resourceElem.attribute("name"), resourceElem.attribute("type"), - resourceElem.attribute("id")); - plugin.listResources.insert(resourceElem.attribute("type"), listResource); - } - else if (method == "search") { - SearchResource searchResource(resourceElem.attribute("name"), resourceElem.attribute("type"), - resourceElem.attribute("order")); - plugin.searchResources.insert(resourceElem.attribute("type"), searchResource); - } - else if (method == "get") { - plugin.regExps[resourceElem.attribute("type")] = QRegExp(resourceElem.attribute("regexp")); - } - } - - m_plugins[name] = plugin; - } - } - } -} diff --git a/app/src/plugins/resourcesplugins.h b/app/src/plugins/resourcesplugins.h deleted file mode 100644 index c43837d..0000000 --- a/app/src/plugins/resourcesplugins.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef RESOURCESPLUGINS_H -#define RESOURCESPLUGINS_H - -#include "resources.h" -#include -#include -#include -#include - -struct ResourcesPlugin { - QString name; - QString command; - QString settings; - QMultiMap listResources; - QMultiMap searchResources; - QMap regExps; -}; - -class ResourcesPlugins : public QObject -{ - Q_OBJECT - -public: - explicit ResourcesPlugins(QObject *parent = 0); - ~ResourcesPlugins(); - - static ResourcesPlugins* instance(); - - ResourcesPlugin getPluginFromName(const QString &name) const; - - QList plugins() const; - - QStringList pluginNames() const; - - Q_INVOKABLE bool resourceTypeIsSupported(const QString &pluginName, const QString &resourceType, - const QString &method = QString("list")) const; - -public Q_SLOTS: - void load(); - -private: - static ResourcesPlugins *self; - - QMap m_plugins; -}; - -#endif // RESOURCESPLUGINS_H diff --git a/app/src/plugins/resourcesrequest.cpp b/app/src/plugins/resourcesrequest.cpp deleted file mode 100644 index e50c4bc..0000000 --- a/app/src/plugins/resourcesrequest.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "resourcesrequest.h" -#include "json.h" -#include "resourcesplugins.h" -#include -#ifdef MUSIKLOUD_DEBUG -#include -#endif - -ResourcesRequest::ResourcesRequest(QObject *parent) : - QObject(parent), - m_process(new QProcess(this)), - m_status(Null), - m_error(NoError) -{ - connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished(int))); - connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onProcessError())); -} - -QString ResourcesRequest::service() const { - return m_service; -} - -void ResourcesRequest::setService(const QString &s) { - if (s != service()) { - m_service = s; - emit serviceChanged(); - } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesRequest::setService" << s; -#endif -} - -ResourcesRequest::Status ResourcesRequest::status() const { - return m_status; -} - -void ResourcesRequest::setStatus(Status s) { - if (s != status()) { - m_status = s; - emit statusChanged(s); - } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesRequest::setStatus" << s; -#endif -} - -QVariant ResourcesRequest::result() const { - return m_result; -} - -void ResourcesRequest::setResult(const QVariant &r) { - m_result = r; -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesRequest::setResult" << r; -#endif -} - -ResourcesRequest::Error ResourcesRequest::error() const { - return m_error; -} - -void ResourcesRequest::setError(Error e) { - m_error = e; -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesRequest::setError" << e; -#endif -} - -QString ResourcesRequest::errorString() const { - return m_errorString; -} - -void ResourcesRequest::setErrorString(const QString &es) { - m_errorString = es; -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesRequest::setErrorString" << es; -#endif -} - -void ResourcesRequest::list(const QString &resourceType, const QString &id) { - if (status() == Loading) { - return; - } - - ResourcesPlugin plugin = ResourcesPlugins::instance()->getPluginFromName(service()); - - if (plugin.command.isEmpty()) { - setStatus(Failed); - setError(PluginError); - setErrorString(tr("No plugin found for %1").arg(service())); - emit finished(); - return; - } - - setStatus(Loading); - QStringList args = QStringList() << "-m" << "list" << "-r" << resourceType; - - if (!id.isEmpty()) { - args << "-i" << id; - } - - start(plugin.command, args); -} - -void ResourcesRequest::search(const QString &resourceType, const QString &query, const QString &order) { - if (status() == Loading) { - return; - } - - ResourcesPlugin plugin = ResourcesPlugins::instance()->getPluginFromName(service()); - - if (plugin.command.isEmpty()) { - setStatus(Failed); - setError(PluginError); - setErrorString(tr("No plugin found for %1").arg(service())); - emit finished(); - return; - } - - setStatus(Loading); - start(plugin.command, QStringList() << "-m" << "search" << "-r" << resourceType << "-q" << query << "-o" << order); -} - -void ResourcesRequest::get(const QString &resourceType, const QString &id) { - if (status() == Loading) { - return; - } - - ResourcesPlugin plugin = ResourcesPlugins::instance()->getPluginFromName(service()); - - if (plugin.command.isEmpty()) { - setStatus(Failed); - setError(PluginError); - setErrorString(tr("No plugin found for %1").arg(service())); - emit finished(); - return; - } - - setStatus(Loading); - start(plugin.command, QStringList() << "-m" << "get" << "-r" << resourceType << "-i" << id); -} - -void ResourcesRequest::start(const QString &program, const QStringList &args) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "ResourcesRequest::start" << program << args; -#endif - m_process->start(program, args); -} - -void ResourcesRequest::cancel() { - m_process->kill(); -} - -void ResourcesRequest::onProcessFinished(int exitCode) { - bool ok; - setResult(QtJson::Json::parse(QString::fromUtf8(m_process->readAllStandardOutput()), ok)); - - if (exitCode == 0) { - if (ok) { - setStatus(Ready); - setError(NoError); - setErrorString(QString()); - } - else { - setStatus(Failed); - setError(ParseError); - setErrorString(tr("Unable to parse response")); - } - } - else { - setStatus(Failed); - setError(ProcessError); - - if (result().type() == QVariant::Map) { - QVariantMap map = result().toMap(); - - if (map.contains("error")) { - setErrorString(map.value("error").toString()); - } - else { - setErrorString(m_process->errorString()); - } - } - else { - setErrorString(m_process->errorString()); - } - } - - emit finished(); -} - -void ResourcesRequest::onProcessError() { - setStatus(Failed); - setError(ProcessError); - setErrorString(m_process->errorString()); - emit finished(); -} diff --git a/app/src/plugins/resourcesrequest.h b/app/src/plugins/resourcesrequest.h index 72b11d4..bd36655 100644 --- a/app/src/plugins/resourcesrequest.h +++ b/app/src/plugins/resourcesrequest.h @@ -1,39 +1,37 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ - + #ifndef RESOURCESREQUEST_H #define RESOURCESREQUEST_H #include -#include #include -class QProcess; +class QNetworkAccessManager; class ResourcesRequest : public QObject { Q_OBJECT - - Q_PROPERTY(QString service READ service WRITE setService NOTIFY serviceChanged) - Q_PROPERTY(Status status READ status NOTIFY statusChanged) - Q_PROPERTY(QVariant result READ result NOTIFY finished) - Q_PROPERTY(Error error READ error NOTIFY finished) + Q_PROPERTY(QString errorString READ errorString NOTIFY finished) + Q_PROPERTY(QVariant result READ result NOTIFY finished) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) - Q_ENUMS(Status Error) + Q_ENUMS(Status) public: enum Status { @@ -44,66 +42,25 @@ class ResourcesRequest : public QObject Failed }; - enum Error { - NoError = 0, - ProcessError, - PluginError, - ParseError, - UnknownError - }; - - explicit ResourcesRequest(QObject *parent = 0); - - QString service() const; - void setService(const QString &s); - - Status status() const; - - QVariant result() const; - - Error error() const; - QString errorString() const; - - Q_INVOKABLE void list(const QString &resourceType, const QString &id = QString()); - - Q_INVOKABLE void search(const QString &resourceType, const QString &query, const QString &order); + explicit ResourcesRequest(QObject *parent = 0) : QObject(parent) {} + + virtual QString errorString() const { return QString(); } + + virtual QVariant result() const { return QVariant(); } - Q_INVOKABLE void get(const QString &resourceType, const QString &id); + virtual Status status() const { return Null; } public Q_SLOTS: - void cancel(); - -private: - void start(const QString &program, const QStringList &args); - - void setStatus(Status s); - - void setResult(const QVariant &r); - - void setError(Error e); - - void setErrorString(const QString &es); - -private Q_SLOTS: - void onProcessFinished(int exitCode); - void onProcessError(); - + virtual bool cancel() { return false; } + virtual bool del(const QString &, const QString &, const QString &, const QString &) { return false; } + virtual bool get(const QString &, const QString &) { return false; } + virtual bool insert(const QString &, const QString &, const QString &, const QString &) { return false; } + virtual bool list(const QString &, const QString &) { return false; } + virtual bool search(const QString &, const QString &, const QString &) { return false; } + Q_SIGNALS: - void serviceChanged(); - void statusChanged(Status s); + void statusChanged(ResourcesRequest::Status s); void finished(); - -private: - QProcess *m_process; - - QString m_service; - - Status m_status; - - QVariant m_result; - - Error m_error; - QString m_errorString; }; #endif // RESOURCESREQUEST_H diff --git a/app/src/maemo5/plugins/pluginsettingsslider.h b/app/src/plugins/serviceplugin.h similarity index 52% rename from app/src/maemo5/plugins/pluginsettingsslider.h rename to app/src/plugins/serviceplugin.h index 41326b4..2b04b97 100644 --- a/app/src/maemo5/plugins/pluginsettingsslider.h +++ b/app/src/plugins/serviceplugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -15,33 +15,20 @@ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLUGINSETTINGSSLIDER_H -#define PLUGINSETTINGSSLIDER_H +#ifndef SERVICEPLUGIN_H +#define SERVICEPLUGIN_H -#include -#include +#include "resourcesrequest.h" -class PluginSettingsSlider : public QSlider +class ServicePlugin { - Q_OBJECT public: - explicit PluginSettingsSlider(QWidget *parent = 0); - - inline QString key() const { return m_key; } - inline QVariant defaultValue() const { return m_default; } + virtual ~ServicePlugin() {} -public Q_SLOTS: - void setKey(const QString &key); - void setDefaultValue(const QVariant &value); - void load(); - -private Q_SLOTS: - void onReleased(); - -private: - QString m_key; - QVariant m_default; + virtual ResourcesRequest* createRequest(QObject *parent = 0) = 0; }; -#endif // PLUGINSETTINGSSLIDER_H +Q_DECLARE_INTERFACE(ServicePlugin, "org.musikloud2.ServicePlugin") + +#endif // SERVICEPLUGIN_H diff --git a/app/src/plugins/servicepluginconfig.cpp b/app/src/plugins/servicepluginconfig.cpp new file mode 100644 index 0000000..8422182 --- /dev/null +++ b/app/src/plugins/servicepluginconfig.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "servicepluginconfig.h" +#include "definitions.h" +#include "json.h" +#include "logger.h" +#include + +ServicePluginConfig::ServicePluginConfig(QObject *parent) : + QObject(parent), + m_version(1) +{ +} + +QString ServicePluginConfig::displayName() const { + return m_displayName; +} + +QString ServicePluginConfig::filePath() const { + return m_filePath; +} + +QString ServicePluginConfig::id() const { + return m_id; +} + +QString ServicePluginConfig::pluginFilePath() const { + return m_pluginFilePath; +} + +QString ServicePluginConfig::pluginType() const { + return m_pluginType; +} + +QVariantList ServicePluginConfig::settings() const { + return m_settings; +} + +int ServicePluginConfig::version() const { + return m_version; +} + +bool ServicePluginConfig::load(const QString &filePath) { + m_filePath = filePath; + QFile file(filePath); + + if ((!file.exists()) || (!file.open(QFile::ReadOnly))) { + Logger::log("ServicePluginConfig::load(): Unable to open config file: " + filePath); + return false; + } + + bool ok; + const QVariant v = QtJson::Json::parse(QString::fromUtf8(file.readAll()), ok); + file.close(); + + if (!ok) { + Logger::log("ServicePluginConfig::load(): Error parsing config file: " + filePath); + return false; + } + + const QVariantMap config = v.toMap(); + + if (!config.contains("name")) { + Logger::log("ServicePluginConfig::load(): 'name' parameter is missing"); + return false; + } + + if (!config.contains("resources")) { + Logger::log("ServicePluginConfig::load(): 'resources' parameter is missing"); + return false; + } + + Logger::log("ServicePluginConfig::load(): Config file loaded: " + filePath, Logger::MediumVerbosity); + const int slash = filePath.lastIndexOf("/"); + const QString fileName = filePath.mid(slash + 1); + const int dot = fileName.lastIndexOf("."); + m_displayName = config.value("name").toString(); + m_id = fileName.left(dot); + m_pluginType = config.value("type").toString(); + m_settings = config.value("settings").toList(); + m_version = qMax(1, config.value("version").toInt()); + + if (m_pluginType == "qt") { + m_pluginFilePath = filePath.left(slash + 1) + LIB_PREFIX + m_id + LIB_SUFFIX; + } + else { + m_pluginFilePath = filePath.left(slash + 1) + m_id + "." + m_pluginType; + } + + m_getResources.clear(); + m_listResources.clear(); + m_searchResources.clear(); + + foreach (const QVariant &v, config.value("resources").toList()) { + const QVariantMap resource = v.toMap(); + const QString method = resource.value("method").toString(); + + if (method == "get") { + m_getResources << GetResource(resource); + } + else if (method == "list") { + m_listResources << ListResource(resource); + } + else if (method == "search") { + m_searchResources << SearchResource(resource); + } + } + + emit changed(); + return true; +} + +QList ServicePluginConfig::getResources() const { + return m_getResources; +} + +QList ServicePluginConfig::listResources() const { + return m_listResources; +} + +QList ServicePluginConfig::searchResources() const { + return m_searchResources; +} + +bool ServicePluginConfig::resourceTypeIsSupported(const QString &resourceType, const QString &method) const { + if (method == "get") { + foreach (const GetResource &resource, m_getResources) { + if (resource.type() == resourceType) { + return true; + } + } + } + else if (method == "list") { + foreach (const ListResource &resource, m_listResources) { + if (resource.type() == resourceType) { + return true; + } + } + } + else if (method == "search") { + foreach (const SearchResource &resource, m_searchResources) { + if (resource.type() == resourceType) { + return true; + } + } + } + + return false; +} diff --git a/app/src/plugins/servicepluginconfig.h b/app/src/plugins/servicepluginconfig.h new file mode 100644 index 0000000..b06075d --- /dev/null +++ b/app/src/plugins/servicepluginconfig.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef SERVICEPLUGINCONFIG_H +#define SERVICEPLUGINCONFIG_H + +#include "resources.h" +#include + +class ServicePluginConfig : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString displayName READ displayName NOTIFY changed) + Q_PROPERTY(QString filePath READ filePath NOTIFY changed) + Q_PROPERTY(QString id READ id NOTIFY changed) + Q_PROPERTY(QString pluginFilePath READ pluginFilePath NOTIFY changed) + Q_PROPERTY(QString pluginType READ pluginType NOTIFY changed) + Q_PROPERTY(QList getResources READ getResources NOTIFY changed) + Q_PROPERTY(QList listResources READ listResources NOTIFY changed) + Q_PROPERTY(QList searchResources READ searchResources NOTIFY changed) + Q_PROPERTY(QVariantList settings READ settings NOTIFY changed) + Q_PROPERTY(int version READ version NOTIFY changed) + +public: + explicit ServicePluginConfig(QObject *parent = 0); + + QString displayName() const; + + QString filePath() const; + + QString id() const; + + QString pluginFilePath() const; + + QString pluginType() const; + + QList getResources() const; + QList listResources() const; + QList searchResources() const; + + QVariantList settings() const; + + int version() const; + +public Q_SLOTS: + bool resourceTypeIsSupported(const QString &resourceType, const QString &method = QString("list")) const; + + bool load(const QString &filePath); + +Q_SIGNALS: + void changed(); + +private: + QString m_displayName; + QString m_filePath; + QString m_id; + QString m_pluginFilePath; + QString m_pluginType; + + QList m_getResources; + QList m_listResources; + QList m_searchResources; + QVariantList m_settings; + + int m_version; +}; + +#endif // SERVICEPLUGINCONFIG_H diff --git a/app/src/plugins/xmlhttprequest.cpp b/app/src/plugins/xmlhttprequest.cpp new file mode 100644 index 0000000..1baaf9d --- /dev/null +++ b/app/src/plugins/xmlhttprequest.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xmlhttprequest.h" +#include "definitions.h" +#include "logger.h" +#include +#include +#include + +XMLHttpRequest::XMLHttpRequest(QObject *parent) : + QObject(parent), + m_nam(0), + m_reply(0), + m_readyState(UNSENT), + m_status(0), + m_redirects(0) +{ +} + +XMLHttpRequest::XMLHttpRequest(QNetworkAccessManager *manager, QObject *parent) : + QObject(parent), + m_nam(manager), + m_reply(0), + m_readyState(UNSENT), + m_status(0), + m_redirects(0) +{ +} + +QNetworkAccessManager* XMLHttpRequest::networkAccessManager() { + return m_nam ? m_nam : m_nam = new QNetworkAccessManager(this); +} + +int XMLHttpRequest::readyState() const { + return m_readyState; +} + +void XMLHttpRequest::setReadyState(int state) { + Logger::log("XMLHttpRequest::setReadyState(): readyState: " + QString::number(state), Logger::MediumVerbosity); + + if (state != readyState()) { + m_readyState = state; + m_onReadyStateChange.call(QScriptValue()); + } +} + +QString XMLHttpRequest::responseText() const { + return readyState() == DONE ? QString::fromUtf8(m_response) : QString(); +} + +QString XMLHttpRequest::responseXML() const { + return readyState() == DONE ? QString::fromUtf8(m_response) : QString(); +} + +int XMLHttpRequest::status() const { + return m_status; +} + +void XMLHttpRequest::setStatus(int s) { + m_status = s; +} + +QString XMLHttpRequest::statusText() const { + return m_statusText; +} + +void XMLHttpRequest::setStatusText(const QString &text) { + m_statusText = text; +} + +QScriptValue XMLHttpRequest::onReadyStateChange() const { + return m_onReadyStateChange; +} + +void XMLHttpRequest::setOnReadyStateChange(const QScriptValue &function) { + m_onReadyStateChange = function; +} + +void XMLHttpRequest::setRequestHeader(const QString &name, const QString &value) { + m_request.setRawHeader(name.toUtf8(), value.toUtf8()); +} + +QString XMLHttpRequest::getResponseHeader(const QString &name) const { + return m_responseHeaders.value(name); +} + +QString XMLHttpRequest::getAllResponseHeaders() const { + if (m_responseHeaders.isEmpty()) { + return QString(); + } + + QString headerString; + QMapIterator iterator(m_responseHeaders); + + while (iterator.hasNext()) { + iterator.next(); + headerString.append(iterator.key()); + headerString.append(": "); + headerString.append(iterator.value()); + headerString.append("\n"); + } + + return headerString; +} + +void XMLHttpRequest::open(const QString &method, const QString &url, const QString &username, const QString &password) { + Logger::log(QString("XMLHttpRequest::open(). Method: %1, URL: %2").arg(method).arg(url), Logger::MediumVerbosity); + + switch (readyState()) { + case OPENED: + case HEADERS_RECEIVED: + case LOADING: + return; + default: + break; + } + + m_method = method.toUtf8(); + m_request.setUrl(QUrl::fromUserInput(url)); + + if ((!username.isEmpty()) || (!password.isEmpty())) { + m_request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username).arg(password).toUtf8() + .toBase64()); + } + + setReadyState(OPENED); +} + +void XMLHttpRequest::send(const QString &body) { + Logger::log("XMLHttpRequest::send(): Body: " + body, Logger::MediumVerbosity); + + switch (readyState()) { + case HEADERS_RECEIVED: + case LOADING: + return; + default: + break; + } + + m_redirects = 0; + QBuffer *buffer = new QBuffer; + buffer->setData(body.toUtf8()); + buffer->open(QBuffer::ReadOnly); + m_reply = networkAccessManager()->sendCustomRequest(m_request, m_method, buffer); + buffer->setParent(m_reply); + connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); + connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); + connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +void XMLHttpRequest::abort() { + Logger::log("XMLHttpRequest::abort()", Logger::MediumVerbosity); + + if ((m_reply) && (m_reply->isRunning())) { + m_reply->abort(); + } +} + +void XMLHttpRequest::followRedirect(const QUrl &url) { + Logger::log("XMLHttpRequest::followRedirect(): URL: " + url.toString(), Logger::MediumVerbosity); + m_redirects++; + m_response = QByteArray(); + QNetworkRequest request(m_request); + request.setUrl(url); + m_reply = networkAccessManager()->get(request); + setReadyState(OPENED); + connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); + connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); + connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +void XMLHttpRequest::reset() { + setReadyState(UNSENT); + setStatus(0); + setStatusText(QString()); + m_request = QNetworkRequest(); + m_response = QByteArray(); + m_responseHeaders.clear(); +} + +void XMLHttpRequest::onReplyMetaDataChanged() { + if (readyState() > OPENED) { + return; + } + + setReadyState(HEADERS_RECEIVED); + + foreach (const QByteArray &header, m_reply->rawHeaderList()) { + m_responseHeaders[QString::fromUtf8(header)] = QString::fromUtf8(m_reply->rawHeader(header)); + } + + setReadyState(LOADING); +} + +void XMLHttpRequest::onReplyReadyRead() { + m_response += m_reply->readAll(); +} + +void XMLHttpRequest::onReplyFinished() { + const QString redirect = QString::fromUtf8(m_reply->rawHeader("Location")); + + if (!redirect.isEmpty()) { + Logger::log("XMLHttpRequest::onReplyFinished(): Redirect: " + redirect, Logger::MediumVerbosity); + + if (m_redirects < MAX_REDIRECTS) { + QUrl url(redirect); + + if (url.scheme().isEmpty()) { + url.setScheme(m_reply->url().scheme()); + } + + if (url.authority().isEmpty()) { + url.setAuthority(m_reply->url().authority()); + } + + m_reply->deleteLater(); + m_reply = 0; + followRedirect(url); + return; + } + } + + if (m_reply->bytesAvailable() > 0) { + m_response += m_reply->readAll(); + } + + setStatus(m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + setStatusText(m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()); + setReadyState(DONE); + m_reply->deleteLater(); + m_reply = 0; +} diff --git a/app/src/plugins/xmlhttprequest.h b/app/src/plugins/xmlhttprequest.h new file mode 100644 index 0000000..22e6042 --- /dev/null +++ b/app/src/plugins/xmlhttprequest.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef XMLHTTPREQUEST_H +#define XMLHTTPREQUEST_H + +#include +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +class XMLHttpRequest : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int readyState READ readyState) + Q_PROPERTY(QString responseText READ responseText) + Q_PROPERTY(QString responseXML READ responseXML) + Q_PROPERTY(int status READ status) + Q_PROPERTY(QString statusText READ statusText) + Q_PROPERTY(QScriptValue onreadystatechange READ onReadyStateChange WRITE setOnReadyStateChange) + + Q_ENUMS(ReadyState) + +public: + enum ReadyState { + UNSENT = 0, + OPENED, + HEADERS_RECEIVED, + LOADING, + DONE + }; + + explicit XMLHttpRequest(QObject *parent = 0); + explicit XMLHttpRequest(QNetworkAccessManager *manager, QObject *parent = 0); + + int readyState() const; + + QString responseText() const; + QString responseXML() const; + + int status() const; + QString statusText() const; + + QScriptValue onReadyStateChange() const; + void setOnReadyStateChange(const QScriptValue &function); + +public Q_SLOTS: + void setRequestHeader(const QString &name, const QString &value); + + QString getResponseHeader(const QString &name) const; + QString getAllResponseHeaders() const; + + void open(const QString &method, const QString &url, const QString &username = QString(), + const QString &password = QString()); + void send(const QString &body = QString()); + void abort(); + +private Q_SLOTS: + void onReplyMetaDataChanged(); + void onReplyReadyRead(); + void onReplyFinished(); + +private: + QNetworkAccessManager* networkAccessManager(); + + void setReadyState(int state); + + void setStatus(int s); + void setStatusText(const QString &text); + + void followRedirect(const QUrl &redirect); + + void reset(); + + QPointer m_nam; + QNetworkReply *m_reply; + + QNetworkRequest m_request; + + int m_readyState; + int m_status; + int m_redirects; + + QString m_statusText; + + QByteArray m_method; + QByteArray m_response; + + QMap m_responseHeaders; + + QScriptValue m_onReadyStateChange; +}; + +Q_DECLARE_METATYPE(XMLHttpRequest*) + +#endif // XMLHTTPREQUEST_H diff --git a/app/src/soundcloud/soundcloud.cpp b/app/src/soundcloud/soundcloud.cpp index 0046cf2..53e2887 100644 --- a/app/src/soundcloud/soundcloud.cpp +++ b/app/src/soundcloud/soundcloud.cpp @@ -16,49 +16,50 @@ #include "soundcloud.h" #include "database.h" +#include "logger.h" #include #include #include #if QT_VERSION >= 0x050000 #include #endif -#ifdef MUSIKLOUD_DEBUG -#include -#endif -static const QString CLIENT_ID("9b7cb759c6d41b14af05855f94bc743c"); -static const QString CLIENT_SECRET("f83369b46fe992306a90ef579d57fac4"); -static const QString REDIRECT_URI("http://marxoft.co.uk/projects/musikloud2"); -static const QStringList SCOPES = QStringList() << QSoundCloud::NON_EXPIRING_SCOPE; +const QString SoundCloud::CLIENT_ID("9b7cb759c6d41b14af05855f94bc743c"); +const QString SoundCloud::CLIENT_SECRET("f83369b46fe992306a90ef579d57fac4"); +const QString SoundCloud::REDIRECT_URI("http://marxoft.co.uk/projects/musikloud2"); +const QStringList SoundCloud::SCOPES = QStringList() << QSoundCloud::NON_EXPIRING_SCOPE; + +const QRegExp SoundCloud::URL_REGEXP("http(s|)://(api\\.|)soundcloud\\.com/[\\w-_/]+"); SoundCloud::FollowingCache SoundCloud::followingCache; SoundCloud* SoundCloud::self = 0; -SoundCloud::SoundCloud(QObject *parent) : - QObject(parent) +SoundCloud::SoundCloud() : + QObject() { - if (!self) { - self = this; - } } SoundCloud::~SoundCloud() { - if (self == this) { - self = 0; - } + self = 0; } SoundCloud* SoundCloud::instance() { - return self; + return self ? self : self = new SoundCloud; +} + +void SoundCloud::init() { + if (accessToken().isEmpty()) { + setUserId(QString()); + } } QString SoundCloud::getErrorString(const QVariantMap &error) { if (error.contains("errors")) { - QVariantList errors = error.value("errors").toList(); + const QVariantList errors = error.value("errors").toList(); if (!errors.isEmpty()) { - QVariantMap em = errors.first().toMap(); + const QVariantMap em = errors.first().toMap(); if (em.contains("error_message")) { return em.value("error_message").toString(); @@ -69,7 +70,7 @@ QString SoundCloud::getErrorString(const QVariantMap &error) { return tr("Unknown error"); } -QUrl SoundCloud::authUrl() const { +QUrl SoundCloud::authUrl() { QUrl url(QSoundCloud::AUTH_URL); #if QT_VERSION >= 0x050000 QUrlQuery query(url); @@ -78,7 +79,7 @@ QUrl SoundCloud::authUrl() const { query.addQueryItem("response_type", "code"); query.addQueryItem("display", "popup"); - QStringList s = scopes(); + const QStringList s = scopes(); if (!s.isEmpty()) { query.addQueryItem("scope", s.join(" ")); @@ -91,7 +92,7 @@ QUrl SoundCloud::authUrl() const { url.addQueryItem("response_type", "code"); url.addQueryItem("display", "popup"); - QStringList s = scopes(); + const QStringList s = scopes(); if (!s.isEmpty()) { url.addQueryItem("scope", s.join(" ")); @@ -100,7 +101,7 @@ QUrl SoundCloud::authUrl() const { return url; } -QString SoundCloud::userId() const { +QString SoundCloud::userId() { return QSettings().value("SoundCloud/userId").toString(); } @@ -110,11 +111,14 @@ void SoundCloud::setUserId(const QString &id) { followingCache.ids.clear(); followingCache.nextHref = QString(); followingCache.loaded = false; - emit userIdChanged(); + + if (self) { + emit self->userIdChanged(id); + } } } -QString SoundCloud::accessToken() const { +QString SoundCloud::accessToken() { if (userId().isEmpty()) { return QString(); } @@ -123,7 +127,7 @@ QString SoundCloud::accessToken() const { .arg(userId())); if (query.lastError().isValid()) { - qDebug() << "SoundCloud::accessToken: database error:" << query.lastError().text(); + Logger::log("SoundCloud::accessToken: database error: " + query.lastError().text()); return QString(); } @@ -135,21 +139,19 @@ QString SoundCloud::accessToken() const { } void SoundCloud::setAccessToken(const QString &token) { + Logger::log("SoundCloud::setAccessToken(). Token: " + token, Logger::MediumVerbosity); QSqlQuery query = getDatabase().exec(QString("UPDATE soundcloudAccounts SET accessToken = '%1' WHERE userId = '%2'") .arg(token).arg(userId())); if (query.lastError().isValid()) { - qDebug() << "SoundCloud::setAccessToken: database error:" << query.lastError().text(); + Logger::log("SoundCloud::setAccessToken: database error: " + query.lastError().text()); } - else { - emit accessTokenChanged(); + else if (self) { + emit self->accessTokenChanged(token); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloud::setAccessToken" << token; -#endif } -QString SoundCloud::refreshToken() const { +QString SoundCloud::refreshToken() { if (userId().isEmpty()) { return QString(); } @@ -158,7 +160,7 @@ QString SoundCloud::refreshToken() const { .arg(userId())); if (query.lastError().isValid()) { - qDebug() << "SoundCloud::refreshToken: database error:" << query.lastError().text(); + Logger::log("SoundCloud::refreshToken(): database error: " + query.lastError().text()); return QString(); } @@ -170,77 +172,83 @@ QString SoundCloud::refreshToken() const { } void SoundCloud::setRefreshToken(const QString &token) { + Logger::log("SoundCloud::setRefreshToken(). Token: " + token, Logger::MediumVerbosity); QSqlQuery query = getDatabase().exec(QString("UPDATE soundcloudAccounts SET refreshToken = '%1' WHERE userId = '%2'") .arg(token).arg(userId())); if (query.lastError().isValid()) { - qDebug() << "SoundCloud::setRefreshToken: database error:" << query.lastError().text(); + Logger::log("SoundCloud::setRefreshToken(): database error: " + query.lastError().text()); } - else { - emit accessTokenChanged(); + else if (self) { + emit self->accessTokenChanged(token); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloud::setRefreshToken" << token; -#endif } -QString SoundCloud::clientId() const { +QString SoundCloud::clientId() { return QSettings().value("SoundCloud/clientId", CLIENT_ID).toString(); } void SoundCloud::setClientId(const QString &id) { + Logger::log("SoundCloud::setClientId(). ID: " + id, Logger::MediumVerbosity); + if (id != clientId()) { QSettings().setValue("SoundCloud/clientId", id); - emit clientIdChanged(); + + if (self) { + emit self->clientIdChanged(id); + } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloud::setClientId" << id; -#endif } -QString SoundCloud::clientSecret() const { +QString SoundCloud::clientSecret() { return QSettings().value("SoundCloud/clientSecret", CLIENT_SECRET).toString(); } void SoundCloud::setClientSecret(const QString &secret) { + Logger::log("SoundCloud::setClientSecret(). Secret: " + secret, Logger::MediumVerbosity); + if (secret != clientSecret()) { QSettings().setValue("SoundCloud/clientSecret", secret); - emit clientSecretChanged(); + + if (self) { + emit self->clientSecretChanged(secret); + } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloud::setClientSecret" << secret; -#endif } -QString SoundCloud::redirectUri() const { +QString SoundCloud::redirectUri() { return QSettings().value("SoundCloud/redirectUri", REDIRECT_URI).toString(); } void SoundCloud::setRedirectUri(const QString &uri) { + Logger::log("SoundCloud::setRedirectUri(). URI: " + uri, Logger::MediumVerbosity); + if (uri != redirectUri()) { QSettings().setValue("SoundCloud/redirectUri", uri); - emit redirectUriChanged(); + + if (self) { + emit self->redirectUriChanged(uri); + } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloud::setRedirectUri" << uri; -#endif } -QStringList SoundCloud::scopes() const { +QStringList SoundCloud::scopes() { return QSettings().value("SoundCloud/scopes", SCOPES).toStringList(); } void SoundCloud::setScopes(const QStringList &s) { + Logger::log("SoundCloud::setScopes(). Scopes: " + s.join(", "), Logger::MediumVerbosity); + if (s != scopes()) { QSettings().setValue("SoundCloud/scopes", s); - emit scopesChanged(); + + if (self) { + emit self->scopesChanged(s); + } } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloud::setScopes" << s; -#endif } -bool SoundCloud::hasScope(const QString &scope) const { +bool SoundCloud::hasScope(const QString &scope) { if (userId().isEmpty()) { return false; } @@ -249,7 +257,7 @@ bool SoundCloud::hasScope(const QString &scope) const { .arg(userId())); if (query.lastError().isValid()) { - qDebug() << "SoundCloud::hasScope: database error:" << query.lastError().text(); + Logger::log("SoundCloud::hasScope(): database error: " + query.lastError().text()); return false; } diff --git a/app/src/soundcloud/soundcloud.h b/app/src/soundcloud/soundcloud.h index 0cbd87d..3a87d0a 100644 --- a/app/src/soundcloud/soundcloud.h +++ b/app/src/soundcloud/soundcloud.h @@ -18,6 +18,7 @@ #define SOUNDCLOUD_H #include +#include #include #include #include @@ -42,57 +43,60 @@ class SoundCloud : public QObject Q_PROPERTY(QString WILDCARD_SCOPE READ wildcardScope CONSTANT) public: - explicit SoundCloud(QObject *parent = 0); ~SoundCloud(); + + static const QRegExp URL_REGEXP; static SoundCloud* instance(); Q_INVOKABLE static QString getErrorString(const QVariantMap &error); - Q_INVOKABLE QUrl authUrl() const; + Q_INVOKABLE static QUrl authUrl(); - QString userId() const; + static QString userId(); - QString accessToken() const; + static QString accessToken(); - QString refreshToken() const; + static QString refreshToken(); - QString clientId() const; + static QString clientId(); - QString clientSecret() const; + static QString clientSecret(); - QString redirectUri() const; + static QString redirectUri(); - QStringList scopes() const; + static QStringList scopes(); - Q_INVOKABLE bool hasScope(const QString &scope) const; + Q_INVOKABLE static bool hasScope(const QString &scope); static QString nonexpiringScope(); static QString wildcardScope(); -public Q_SLOTS: - void setUserId(const QString &id); +public Q_SLOTS: + static void init(); + + static void setUserId(const QString &id); - void setAccessToken(const QString &token); + static void setAccessToken(const QString &token); - void setRefreshToken(const QString &token); + static void setRefreshToken(const QString &token); - void setClientId(const QString &id); + static void setClientId(const QString &id); - void setClientSecret(const QString &secret); + static void setClientSecret(const QString &secret); - void setRedirectUri(const QString &uri); + static void setRedirectUri(const QString &uri); - void setScopes(const QStringList &s); + static void setScopes(const QStringList &s); Q_SIGNALS: - void userIdChanged(); - void accessTokenChanged(); - void refreshTokenChanged(); - void clientIdChanged(); - void clientSecretChanged(); - void redirectUriChanged(); - void scopesChanged(); + void userIdChanged(const QString &id); + void accessTokenChanged(const QString &token); + void refreshTokenChanged(const QString &token); + void clientIdChanged(const QString &id); + void clientSecretChanged(const QString &secret); + void redirectUriChanged(const QString &uri); + void scopesChanged(const QStringList &scopes); void artistFollowed(SoundCloudArtist *artist); void artistUnfollowed(SoundCloudArtist *artist); @@ -103,6 +107,8 @@ public Q_SLOTS: void trackUnfavourited(SoundCloudTrack *track); private: + SoundCloud(); + struct FollowingCache { QStringList ids; QString nextHref; @@ -116,10 +122,16 @@ public Q_SLOTS: filters["linked_partitioning"] = true; } }; - + static FollowingCache followingCache; static SoundCloud *self; + static const QString CLIENT_ID; + static const QString CLIENT_SECRET; + static const QString REDIRECT_URI; + + static const QStringList SCOPES; + friend class SoundCloudArtist; friend class SoundCloudComment; friend class SoundCloudPlaylist; diff --git a/app/src/soundcloud/soundcloudaccountmodel.cpp b/app/src/soundcloud/soundcloudaccountmodel.cpp index b326b07..71ce0a7 100644 --- a/app/src/soundcloud/soundcloudaccountmodel.cpp +++ b/app/src/soundcloud/soundcloudaccountmodel.cpp @@ -15,6 +15,7 @@ */ #include "soundcloudaccountmodel.h" +#include "logger.h" #include "soundcloud.h" #include #include @@ -51,7 +52,7 @@ QHash SoundCloudAccountModel::roleNames() const { QVariant SoundCloudAccountModel::data(const QModelIndex &idx, int role) const { if (role == ActiveRole) { - return SoundCloud::instance()->userId() == data(idx, UserIdRole); + return SoundCloud::userId() == data(idx, UserIdRole); } if (role >= UserIdRole) { @@ -67,7 +68,7 @@ QVariant SoundCloudAccountModel::data(int row, const QByteArray &role) const { bool SoundCloudAccountModel::addAccount(const QString &userId, const QString &username, const QString &accessToken, const QString &refreshToken, const QString &scopes) { - + Logger::log(QString("SoundCloudAccountModel::addAccount(). User ID: %1, Username: %2, Access token: %3, Refresh token: %4, Scopes: %5").arg(userId).arg(username).arg(accessToken).arg(refreshToken).arg(scopes), Logger::LowVerbosity); QSqlField userIdField("userId", QVariant::String); userIdField.setValue(userId); @@ -89,9 +90,22 @@ bool SoundCloudAccountModel::addAccount(const QString &userId, const QString &us record.append(accessTokenField); record.append(refreshTokenField); record.append(scopesField); + + const int count = rowCount(); + + for (int i = 0; i < count; i++) { + if (data(index(i, 0)) == userId) { + if (setRecord(i, record)) { + SoundCloud::setUserId(userId); + return true; + } + + return false; + } + } if (insertRecord(-1, record)) { - SoundCloud::instance()->setUserId(userId); + SoundCloud::setUserId(userId); const int count = rowCount(); emit dataChanged(index(0, 0), index(count - 1, columnCount() - 1)); emit countChanged(count); @@ -102,15 +116,17 @@ bool SoundCloudAccountModel::addAccount(const QString &userId, const QString &us } bool SoundCloudAccountModel::removeAccount(int row) { - QString userId = data(index(row, 0)).toString(); + const QString userId = data(index(row, 0)).toString(); + Logger::log(QString("SoundCloudAccountModel::removeAccount(). Row: %1, User ID: %2").arg(row).arg(userId), + Logger::MediumVerbosity); if (removeRows(row, 1)) { - if (userId == SoundCloud::instance()->userId()) { + if (userId == SoundCloud::userId()) { if (rowCount() > 0) { selectAccount(0); } else { - SoundCloud::instance()->setUserId(QString()); + SoundCloud::setUserId(QString()); } } @@ -122,10 +138,12 @@ bool SoundCloudAccountModel::removeAccount(int row) { } bool SoundCloudAccountModel::selectAccount(int row) { - QString userId = data(index(row, 0)).toString(); + const QString userId = data(index(row, 0)).toString(); + Logger::log(QString("SoundCloudAccountModel::selectAccount(). Row: %1, User ID: %2").arg(row).arg(userId), + Logger::MediumVerbosity); if (!userId.isEmpty()) { - SoundCloud::instance()->setUserId(userId); + SoundCloud::setUserId(userId); emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); return true; } diff --git a/app/src/soundcloud/soundcloudaccountmodel.h b/app/src/soundcloud/soundcloudaccountmodel.h index a0875c6..2e6ebba 100644 --- a/app/src/soundcloud/soundcloudaccountmodel.h +++ b/app/src/soundcloud/soundcloudaccountmodel.h @@ -54,7 +54,7 @@ class SoundCloudAccountModel : public QSqlTableModel Q_INVOKABLE bool selectAccount(int row); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); private: QHash m_roles; diff --git a/app/src/soundcloud/soundcloudactivity.cpp b/app/src/soundcloud/soundcloudactivity.cpp deleted file mode 100644 index 6e20243..0000000 --- a/app/src/soundcloud/soundcloudactivity.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "soundcloudactivity.h" -#include "soundcloud.h" -#include "utils.h" -#include - -SoundCloudActivity::SoundCloudActivity(QObject *parent) : - QObject(parent), - m_request(0) -{ -} - -SoundCloudActivity::SoundCloudActivity(const QString &id, QObject *parent) : - QObject(parent), - m_request(0) -{ - loadActivity(id); -} - -SoundCloudActivity::SoundCloudActivity(const QVariantMap &activity, QObject *parent) : - QObject(parent), - m_request(0) -{ - loadActivity(activity); -} - -SoundCloudActivity::SoundCloudActivity(SoundCloudActivity *activity, QObject *parent) : - QObject(parent), - m_request(0), - m_type(activity->activityType()), - m_typeString(activity->activityTypeString()), - m_artist(activity->artist()), - m_artistId(activity->artistId()), - m_artistThumbnailUrl(activity->artistThumbnailUrl()), - m_date(activity->date()), - m_description(activity->description()), - m_id(activity->id()), - m_originId(activity->originId()), - m_originThumbnailUrl(activity->originThumbnailUrl()), - m_title(activity->title()) -{ -} - -QString SoundCloudActivity::activityType() const { - return m_type; -} - -void SoundCloudActivity::setActivityType(const QString &t) { - if (t != activityType()) { - m_type = t; - - if (t == "track") { - m_typeString = tr("New track"); - } - else if (t == "playlist") { - m_typeString = tr("New playlist"); - } - else if (t == "track-repost") { - m_typeString = tr("Track repost"); - } - else if (t == "favoriting") { - m_typeString = tr("Track favouriting"); - } - else if (t == "track-sharing") { - m_typeString = tr("Track sharing"); - } - else if (t == "comment") { - m_typeString = tr("Comment"); - } - else { - m_typeString = tr("Unknown"); - } - - emit activityTypeChanged(); - } -} - -QString SoundCloudActivity::activityTypeString() const { - return m_typeString; -} - -QString SoundCloudActivity::artist() const { - return m_artist; -} - -void SoundCloudActivity::setArtist(const QString &a) { - if (a != artist()) { - m_artist = a; - emit artistChanged(); - } -} - -QString SoundCloudActivity::artistId() const { - return m_artistId; -} - -void SoundCloudActivity::setArtistId(const QString &i) { - if (i != artistId()) { - m_artistId = i; - emit artistIdChanged(); - } -} - -QUrl SoundCloudActivity::artistThumbnailUrl() const { - return m_artistThumbnailUrl; -} - -void SoundCloudActivity::setArtistThumbnailUrl(const QUrl &u) { - if (u != artistThumbnailUrl()) { - m_artistThumbnailUrl = u; - emit artistThumbnailUrlChanged(); - } -} - -QString SoundCloudActivity::date() const { - return m_date; -} - -void SoundCloudActivity::setDate(const QString &d) { - if (d != date()) { - m_date = d; - emit dateChanged(); - } -} - -QString SoundCloudActivity::description() const { - return m_description; -} - -void SoundCloudActivity::setDescription(const QString &d) { - if (d != description()) { - m_description = d; - emit descriptionChanged(); - } -} - -QString SoundCloudActivity::errorString() const { - return m_request ? SoundCloud::getErrorString(m_request->result().toMap()) : QString(); -} - -QString SoundCloudActivity::id() const { - return m_id; -} - -void SoundCloudActivity::setId(const QString &i) { - if (i != id()) { - m_id = i; - emit idChanged(); - } -} - -QString SoundCloudActivity::originId() const { - return m_originId; -} - -void SoundCloudActivity::setOriginId(const QString &i) { - if (i != originId()) { - m_originId = i; - emit originIdChanged(); - } -} - -QUrl SoundCloudActivity::originThumbnailUrl() const { - return m_originThumbnailUrl; -} - -void SoundCloudActivity::setOriginThumbnailUrl(const QUrl &u) { - if (u != originThumbnailUrl()) { - m_originThumbnailUrl = u; - emit originThumbnailUrlChanged(); - } -} - -QSoundCloud::ResourcesRequest::Status SoundCloudActivity::status() const { - return m_request ? m_request->status() : QSoundCloud::ResourcesRequest::Null; -} - -QString SoundCloudActivity::title() const { - return m_title; -} - -void SoundCloudActivity::setTitle(const QString &t) { - if (t != title()) { - m_title = t; - emit titleChanged(); - } -} - -void SoundCloudActivity::loadActivity(const QString &id) { - if (status() == QSoundCloud::ResourcesRequest::Loading) { - return; - } - - initRequest(); - m_request->get("/me/activities/" + id); - connect(m_request, SIGNAL(finished()), this, SLOT(onActivityRequestFinished())); - emit statusChanged(status()); -} - -void SoundCloudActivity::loadActivity(const QVariantMap &activity) { - setActivityType(activity.value("type").toString()); - - if (activityType() == "comment") { - loadCommentActivity(activity); - } - else if (activityType() == "playlist") { - loadPlaylistActivity(activity); - } - else if (activityType() == "track") { - loadTrackActivity(activity); - } - else if (activityType() == "favoriting") { - loadTrackFavouritingActivity(activity); - } - else if (activityType() == "track-repost") { - loadTrackRepostActivity(activity); - } - else { - loadTrackSharingActivity(activity); - } -} - -void SoundCloudActivity::loadCommentActivity(const QVariantMap &activity) { - QVariantMap origin = activity.value("origin").toMap(); - QVariantMap track = origin.value("track").toMap(); - QVariantMap user = origin.value("user").toMap(); - - setArtist(user.value("username").toString()); - setArtistId(user.value("id").toString()); - setArtistThumbnailUrl(user.value("avatar_url").toString()); - setDate(QDateTime::fromString(activity.value("created_at").toString(), - "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); - setDescription(origin.value("body").toString()); - setId(activity.value("id").toString()); - setOriginId(track.value("id").toString()); - setOriginThumbnailUrl(track.value("artwork_url").toString()); - setTitle(track.value("title").toString()); -} - -void SoundCloudActivity::loadPlaylistActivity(const QVariantMap &activity) { - QVariantMap playlist = activity.value("origin").toMap(); - QVariantMap user = playlist.value("user").toMap(); - - setArtist(user.value("username").toString()); - setArtistId(user.value("id").toString()); - setArtistThumbnailUrl(user.value("avatar_url").toString()); - setDate(QDateTime::fromString(activity.value("created_at").toString(), - "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); - setDescription(playlist.value("description").toString()); - setId(activity.value("id").toString()); - setOriginId(playlist.value("id").toString()); - setOriginThumbnailUrl(playlist.value("artwork_url").toString()); - setTitle(playlist.value("title").toString()); -} - -void SoundCloudActivity::loadTrackActivity(const QVariantMap &activity) { - QVariantMap track = activity.value("origin").toMap(); - QVariantMap user = track.value("user").toMap(); - - setArtist(user.value("username").toString()); - setArtistId(user.value("id").toString()); - setArtistThumbnailUrl(user.value("avatar_url").toString()); - setDate(QDateTime::fromString(activity.value("created_at").toString(), - "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); - setDescription(track.value("description").toString()); - setId(activity.value("id").toString()); - setOriginId(track.value("id").toString()); - setOriginThumbnailUrl(track.value("artwork_url").toString()); - setTitle(track.value("title").toString()); -} - -void SoundCloudActivity::loadTrackFavouritingActivity(const QVariantMap &activity) { - QVariantMap origin = activity.value("origin").toMap(); - QVariantMap track = origin.value("track").toMap(); - QVariantMap user = origin.value("user").toMap(); - - setArtist(user.value("username").toString()); - setArtistId(user.value("id").toString()); - setArtistThumbnailUrl(user.value("avatar_url").toString()); - setDate(QDateTime::fromString(activity.value("created_at").toString(), - "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); - setDescription(QString("%1 %2").arg(tr("Favourited by")).arg(artist())); - setId(activity.value("id").toString()); - setOriginId(track.value("id").toString()); - setOriginThumbnailUrl(track.value("artwork_url").toString()); - setTitle(track.value("title").toString()); -} - -void SoundCloudActivity::loadTrackRepostActivity(const QVariantMap &activity) { - QVariantMap track = activity.value("origin").toMap(); - QVariantMap user = track.value("user").toMap(); - - setArtist(user.value("username").toString()); - setArtistId(user.value("id").toString()); - setArtistThumbnailUrl(user.value("avatar_url").toString()); - setDate(QDateTime::fromString(activity.value("created_at").toString(), - "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); - setDescription(track.value("description").toString()); - setId(activity.value("id").toString()); - setOriginId(track.value("id").toString()); - setOriginThumbnailUrl(track.value("artwork_url").toString()); - setTitle(track.value("title").toString()); -} - -void SoundCloudActivity::loadTrackSharingActivity(const QVariantMap &activity) { - QVariantMap track = activity.value("origin").toMap().value("track").toMap(); - QVariantMap user = track.value("user").toMap(); - - setArtist(user.value("username").toString()); - setArtistId(user.value("id").toString()); - setArtistThumbnailUrl(user.value("avatar_url").toString()); - setDate(QDateTime::fromString(activity.value("created_at").toString(), - "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); - setDescription(track.value("description").toString()); - setId(activity.value("id").toString()); - setOriginId(track.value("id").toString()); - setOriginThumbnailUrl(track.value("artwork_url").toString()); - setTitle(track.value("title").toString()); -} - -void SoundCloudActivity::loadActivity(SoundCloudActivity *activity) { - setActivityType(activity->activityType()); - setArtist(activity->artist()); - setArtistId(activity->artistId()); - setArtistThumbnailUrl(activity->artistThumbnailUrl()); - setDate(activity->date()); - setDescription(activity->description()); - setId(activity->id()); - setOriginId(activity->originId()); - setOriginThumbnailUrl(activity->originThumbnailUrl()); - setTitle(activity->title()); -} - -void SoundCloudActivity::initRequest() { - if (!m_request) { - m_request = new QSoundCloud::ResourcesRequest(this); - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); - - connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); - connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); - } -} - -void SoundCloudActivity::onActivityRequestFinished() { - if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - loadActivity(m_request->result().toMap()); - } - - disconnect(m_request, SIGNAL(finished()), this, SLOT(onActivityRequestFinished())); - emit statusChanged(status()); -} diff --git a/app/src/soundcloud/soundcloudactivity.h b/app/src/soundcloud/soundcloudactivity.h deleted file mode 100644 index fbcaeab..0000000 --- a/app/src/soundcloud/soundcloudactivity.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef SOUNDCLOUDACTIVITY_H -#define SOUNDCLOUDACTIVITY_H - -#include -#include - -class SoundCloudActivity : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString activityType READ activityType NOTIFY activityTypeChanged) - Q_PROPERTY(QString activityTypeString READ activityTypeString NOTIFY activityTypeChanged) - Q_PROPERTY(QString artist READ artist NOTIFY artistChanged) - Q_PROPERTY(QString artistId READ artistId NOTIFY artistIdChanged) - Q_PROPERTY(QUrl artistThumbnailUrl READ artistThumbnailUrl NOTIFY artistThumbnailUrlChanged) - Q_PROPERTY(QString date READ date NOTIFY dateChanged) - Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) - Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) - Q_PROPERTY(QString id READ id NOTIFY idChanged) - Q_PROPERTY(QString originId READ originId NOTIFY originIdChanged) - Q_PROPERTY(QUrl originThumbnailUrl READ originThumbnailUrl NOTIFY originThumbnailUrlChanged) - Q_PROPERTY(QSoundCloud::ResourcesRequest::Status status READ status NOTIFY statusChanged) - Q_PROPERTY(QString title READ title NOTIFY titleChanged) - -public: - explicit SoundCloudActivity(QObject *parent = 0); - explicit SoundCloudActivity(const QString &id, QObject *parent = 0); - explicit SoundCloudActivity(const QVariantMap &activity, QObject *parent = 0); - explicit SoundCloudActivity(SoundCloudActivity *activity, QObject *parent = 0); - - QString activityType() const; - QString activityTypeString() const; - - QString artist() const; - QString artistId() const; - QUrl artistThumbnailUrl() const; - - QString date() const; - - QString description() const; - - QString errorString() const; - - QString id() const; - - QString originId() const; - QUrl originThumbnailUrl() const; - - QSoundCloud::ResourcesRequest::Status status() const; - - QString title() const; - - Q_INVOKABLE void loadActivity(const QString &id); - Q_INVOKABLE void loadActivity(const QVariantMap &activity); - Q_INVOKABLE void loadActivity(SoundCloudActivity *activity); - -private: - void initRequest(); - - void setActivityType(const QString &t); - - void setArtist(const QString &a); - void setArtistId(const QString &i); - void setArtistThumbnailUrl(const QUrl &u); - - void setDate(const QString &d); - - void setDescription(const QString &d); - - void setId(const QString &i); - - void setOriginId(const QString &i); - void setOriginThumbnailUrl(const QUrl &u); - - void setTitle(const QString &t); - - void loadCommentActivity(const QVariantMap &activity); - void loadPlaylistActivity(const QVariantMap &activity); - void loadTrackActivity(const QVariantMap &activity); - void loadTrackFavouritingActivity(const QVariantMap &activity); - void loadTrackRepostActivity(const QVariantMap &activity); - void loadTrackSharingActivity(const QVariantMap &activity); - -private Q_SLOTS: - void onActivityRequestFinished(); - -Q_SIGNALS: - void activityTypeChanged(); - - void artistChanged(); - void artistIdChanged(); - void artistThumbnailUrlChanged(); - - void dateChanged(); - - void descriptionChanged(); - - void idChanged(); - - void originIdChanged(); - void originThumbnailUrlChanged(); - - void statusChanged(QSoundCloud::ResourcesRequest::Status s); - - void titleChanged(); - -private: - QSoundCloud::ResourcesRequest *m_request; - - QString m_type; - QString m_typeString; - - QString m_artist; - QString m_artistId; - QUrl m_artistThumbnailUrl; - - QString m_date; - - QString m_description; - - QString m_id; - - QString m_originId; - QUrl m_originThumbnailUrl; - - QString m_title; -}; - -#endif // SOUNDCLOUDACTIVITY_H diff --git a/app/src/soundcloud/soundcloudactivitymodel.cpp b/app/src/soundcloud/soundcloudactivitymodel.cpp deleted file mode 100644 index e473bec..0000000 --- a/app/src/soundcloud/soundcloudactivitymodel.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "soundcloudactivitymodel.h" -#include "soundcloud.h" -#include - -SoundCloudActivityModel::SoundCloudActivityModel(QObject *parent) : - QAbstractListModel(parent), - m_request(new QSoundCloud::ResourcesRequest(this)) -{ - m_roles[ActivityTypeRole] = "activityType"; - m_roles[ActivityTypeStringRole] = "activityTypeString"; - m_roles[ArtistRole] = "artist"; - m_roles[ArtistIdRole] = "artistId"; - m_roles[DateRole] = "date"; - m_roles[DescriptionRole] = "description"; - m_roles[IdRole] = "id"; - m_roles[OriginIdRole] = "originId"; - m_roles[ThumbnailUrlRole] = "thumbnailUrl"; - m_roles[TitleRole] = "title"; -#if QT_VERSION < 0x050000 - setRoleNames(m_roles); -#endif - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); - - connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); - connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); - connect(m_request, SIGNAL(finished()), this, SLOT(onRequestFinished())); -} - -QString SoundCloudActivityModel::errorString() const { - return SoundCloud::getErrorString(m_request->result().toMap()); -} - -QSoundCloud::ResourcesRequest::Status SoundCloudActivityModel::status() const { - return m_request->status(); -} - -#if QT_VERSION >=0x050000 -QHash SoundCloudActivityModel::roleNames() const { - return m_roles; -} -#endif - -int SoundCloudActivityModel::rowCount(const QModelIndex &) const { - return m_items.size(); -} - -bool SoundCloudActivityModel::canFetchMore(const QModelIndex &) const { - return (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); -} - -void SoundCloudActivityModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { - return; - } - - m_request->get(m_nextHref); - emit statusChanged(status()); -} - -QVariant SoundCloudActivityModel::data(const QModelIndex &index, int role) const { - if (SoundCloudActivity *activity = get(index.row())) { - return activity->property(m_roles[role]); - } - - return QVariant(); -} - -QMap SoundCloudActivityModel::itemData(const QModelIndex &index) const { - QMap map; - - if (SoundCloudActivity *activity = get(index.row())) { - QHashIterator iterator(m_roles); - - while (iterator.hasNext()) { - iterator.next(); - map[iterator.key()] = activity->property(iterator.value()); - } - } - - return map; -} - -QVariant SoundCloudActivityModel::data(int row, const QByteArray &role) const { - if (SoundCloudActivity *activity = get(row)) { - return activity->property(role); - } - - return QVariant(); -} - -QVariantMap SoundCloudActivityModel::itemData(int row) const { - QVariantMap map; - - if (SoundCloudActivity *activity = get(row)) { - foreach (QByteArray role, m_roles.values()) { - map[role] = activity->property(role); - } - } - - return map; -} - -SoundCloudActivity* SoundCloudActivityModel::get(int row) const { - if ((row >= 0) && (row < m_items.size())) { - return m_items.at(row); - } - - return 0; -} - -void SoundCloudActivityModel::get(const QString &resourcePath, const QVariantMap &filters) { - if (status() == QSoundCloud::ResourcesRequest::Loading) { - return; - } - - clear(); - m_resourcePath = resourcePath; - m_filters = filters; - m_filters["linked_partitioning"] = true; - m_request->get(m_resourcePath, m_filters); - emit statusChanged(status()); -} - -void SoundCloudActivityModel::clear() { - if (!m_items.isEmpty()) { - beginResetModel(); - qDeleteAll(m_items); - m_items.clear(); - m_nextHref = QString(); - endResetModel(); - emit countChanged(rowCount()); - } -} - -void SoundCloudActivityModel::cancel() { - m_request->cancel(); -} - -void SoundCloudActivityModel::reload() { - clear(); - m_request->get(m_resourcePath, m_filters); - emit statusChanged(status()); -} - -void SoundCloudActivityModel::append(SoundCloudActivity *activity) { - beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); - m_items << activity; - endInsertRows(); -} - -void SoundCloudActivityModel::insert(int row, SoundCloudActivity *activity) { - if ((row >= 0) && (row < m_items.size())) { - beginInsertRows(QModelIndex(), row, row); - m_items.insert(row, activity); - endInsertRows(); - } - else { - append(activity); - } -} - -void SoundCloudActivityModel::remove(int row) { - if ((row >= 0) && (row < m_items.size())) { - beginRemoveRows(QModelIndex(), row, row); - m_items.takeAt(row)->deleteLater(); - endRemoveRows(); - } -} - -void SoundCloudActivityModel::onRequestFinished() { - if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); - - if (!result.isEmpty()) { - m_nextHref = result.value("next_href").toString().section(QSoundCloud::API_URL, -1); - QVariantList list = result.value("collection").toList(); - - beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - - foreach (QVariant item, list) { - m_items << new SoundCloudActivity(item.toMap(), this); - } - - endInsertRows(); - emit countChanged(rowCount()); - } - } - - emit statusChanged(status()); -} diff --git a/app/src/soundcloud/soundcloudactivitymodel.h b/app/src/soundcloud/soundcloudactivitymodel.h deleted file mode 100644 index 81ecec0..0000000 --- a/app/src/soundcloud/soundcloudactivitymodel.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef SOUNDCLOUDACTIVITYMODEL_H -#define SOUNDCLOUDACTIVITYMODEL_H - -#include "soundcloudactivity.h" -#include - -class SoundCloudActivityModel : public QAbstractListModel -{ - Q_OBJECT - - Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY statusChanged) - Q_PROPERTY(int count READ rowCount NOTIFY countChanged) - Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) - Q_PROPERTY(QSoundCloud::ResourcesRequest::Status status READ status NOTIFY statusChanged) - -public: - enum Roles { - ActivityTypeRole = Qt::UserRole + 1, - ActivityTypeStringRole, - ArtistRole, - ArtistIdRole, - DateRole, - DescriptionRole, - IdRole, - OriginIdRole, - ThumbnailUrlRole, - TitleRole - }; - - explicit SoundCloudActivityModel(QObject *parent = 0); - - QString errorString() const; - - QSoundCloud::ResourcesRequest::Status status() const; - -#if QT_VERSION >= 0x050000 - QHash roleNames() const; -#endif - int rowCount(const QModelIndex &parent = QModelIndex()) const; - - bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; - Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); - - QVariant data(const QModelIndex &index, int role) const; - QMap itemData(const QModelIndex &index) const; - - Q_INVOKABLE QVariant data(int row, const QByteArray &role) const; - Q_INVOKABLE QVariantMap itemData(int row) const; - - Q_INVOKABLE SoundCloudActivity* get(int row) const; - - Q_INVOKABLE void get(const QString &resourcePath, const QVariantMap &filters = QVariantMap()); - -public Q_SLOTS: - void clear(); - void cancel(); - void reload(); - -private: - void append(SoundCloudActivity *activity); - void insert(int row, SoundCloudActivity *activity); - void remove(int row); - -private Q_SLOTS: - void onRequestFinished(); - -Q_SIGNALS: - void countChanged(int c); - void statusChanged(QSoundCloud::ResourcesRequest::Status s); - -private: - QSoundCloud::ResourcesRequest *m_request; - - QString m_resourcePath; - QVariantMap m_filters; - QString m_nextHref; - - QList m_items; - - QHash m_roles; -}; - -#endif // SOUNDCLOUDACTIVITYMODEL_H diff --git a/app/src/soundcloud/soundcloudartist.cpp b/app/src/soundcloud/soundcloudartist.cpp index bca0cc6..76ced25 100644 --- a/app/src/soundcloud/soundcloudartist.cpp +++ b/app/src/soundcloud/soundcloudartist.cpp @@ -16,11 +16,9 @@ #include "soundcloudartist.h" #include "definitions.h" +#include "logger.h" #include "resources.h" #include "soundcloud.h" -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudArtist::SoundCloudArtist(QObject *parent) : MKArtist(parent), @@ -72,7 +70,7 @@ SoundCloudArtist::SoundCloudArtist(const QVariantMap &artist, QObject *parent) : this, SLOT(onArtistUpdated(SoundCloudArtist*))); } -SoundCloudArtist::SoundCloudArtist(SoundCloudArtist *artist, QObject *parent) : +SoundCloudArtist::SoundCloudArtist(const SoundCloudArtist *artist, QObject *parent) : MKArtist(artist, parent), m_request(0), m_followed(artist->isFollowed()), @@ -100,6 +98,7 @@ bool SoundCloudArtist::isFollowed() const { void SoundCloudArtist::setFollowed(bool s) { if (s != isFollowed()) { m_followed = s; + emit changed(); emit followedChanged(); } } @@ -111,6 +110,7 @@ qint64 SoundCloudArtist::followersCount() const { void SoundCloudArtist::setFollowersCount(qint64 c) { if (c != followersCount()) { m_followersCount = c; + emit changed(); emit followersCountChanged(); } } @@ -122,6 +122,7 @@ bool SoundCloudArtist::isOnline() const { void SoundCloudArtist::setOnline(bool o) { if (o != isOnline()) { m_online = o; + emit changed(); emit onlineChanged(); } } @@ -133,6 +134,7 @@ qint64 SoundCloudArtist::playlistCount() const { void SoundCloudArtist::setPlaylistCount(qint64 c) { if (c != playlistCount()) { m_playlistCount = c; + emit changed(); emit playlistCountChanged(); } } @@ -148,6 +150,7 @@ qint64 SoundCloudArtist::trackCount() const { void SoundCloudArtist::setTrackCount(qint64 c) { if (c != trackCount()) { m_trackCount = c; + emit changed(); emit trackCountChanged(); } } @@ -159,6 +162,7 @@ QString SoundCloudArtist::websiteTitle() const { void SoundCloudArtist::setWebsiteTitle(const QString &t) { if (t != websiteTitle()) { m_websiteTitle = t; + emit changed(); emit websiteTitleChanged(); } } @@ -170,6 +174,7 @@ QUrl SoundCloudArtist::websiteUrl() const { void SoundCloudArtist::setWebsiteUrl(const QUrl &u) { if (u != websiteUrl()) { m_websiteUrl = u; + emit changed(); emit websiteUrlChanged(); } } @@ -179,6 +184,7 @@ void SoundCloudArtist::loadArtist(const QString &id) { return; } + setId(id); initRequest(); if (id.startsWith("http")) { @@ -191,6 +197,7 @@ void SoundCloudArtist::loadArtist(const QString &id) { } connect(m_request, SIGNAL(finished()), this, SLOT(onArtistRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -200,14 +207,22 @@ void SoundCloudArtist::loadArtist(const QVariantMap &artist) { setDescription(artist.value("description").toString()); setFollowersCount(artist.value("followers_count").toLongLong()); setId(artist.value("id").toString()); - setLargeThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(LARGE_THUMBNAIL_SIZE)); setName(artist.value("username").toString()); setOnline(artist.value("online").toBool()); setPlaylistCount(artist.value("playlist_count").toLongLong()); - setThumbnailUrl(thumbnail); setTrackCount(artist.value("track_count").toLongLong()); + setUrl(artist.value("url").toString()); setWebsiteTitle(artist.value("website_title").toString()); setWebsiteUrl(artist.value("website_url").toString()); + + if (!thumbnail.isEmpty()) { + setLargeThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(LARGE_THUMBNAIL_SIZE)); + setThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(THUMBNAIL_SIZE)); + } + else { + setLargeThumbnailUrl(QString()); + setThumbnailUrl(QString()); + } } void SoundCloudArtist::loadArtist(SoundCloudArtist *artist) { @@ -232,13 +247,11 @@ void SoundCloudArtist::checkIfFollowed() { } if ((SoundCloud::followingCache.loaded) && (SoundCloud::followingCache.nextHref.isEmpty())) { -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudArtist::checkIfFollowed" << id() << "not found"; -#endif setFollowed(false); return; } + Logger::log("SoundCloudArtist::checkIfSubscribed(). ID: " + id(), Logger::MediumVerbosity); initRequest(); if (SoundCloud::followingCache.nextHref.isEmpty()) { @@ -249,6 +262,7 @@ void SoundCloudArtist::checkIfFollowed() { } connect(m_request, SIGNAL(finished()), this, SLOT(onFollowCheckRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -257,9 +271,11 @@ void SoundCloudArtist::follow() { return; } + Logger::log("SoundCloudArtist::follow(). ID: " + id(), Logger::MediumVerbosity); initRequest(); m_request->insert("/me/followings/" + id()); connect(m_request, SIGNAL(finished()), this, SLOT(onFollowRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -268,19 +284,29 @@ void SoundCloudArtist::unfollow() { return; } + Logger::log("SoundCloudArtist::unfollow(). ID: " + id(), Logger::MediumVerbosity); initRequest(); m_request->del("/me/followings/" + id()); connect(m_request, SIGNAL(finished()), this, SLOT(onUnfollowRequestFinished())); + emit changed(); emit statusChanged(status()); } +void SoundCloudArtist::cancel() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + void SoundCloudArtist::initRequest() { if (!m_request) { m_request = new QSoundCloud::ResourcesRequest(this); - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -293,30 +319,32 @@ void SoundCloudArtist::onArtistRequestFinished() { } disconnect(m_request, SIGNAL(finished()), this, SLOT(onArtistRequestFinished())); + emit changed(); emit statusChanged(status()); } void SoundCloudArtist::onFollowCheckRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); - QVariantList list = result.value("collection").toList(); + const QVariantMap result = m_request->result().toMap(); + const QVariantList list = result.value("collection").toList(); SoundCloud::followingCache.nextHref = result.value("next_href").toString().section('/', -1); SoundCloud::followingCache.loaded = true; - foreach (QVariant item, list) { - QVariantMap map = item.toMap(); + foreach (const QVariant &item, list) { + const QVariantMap map = item.toMap(); SoundCloud::followingCache.ids.append(map.value("id").toString()); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudArtist::onFollowCheckRequestFinished OK" << SoundCloud::followingCache.ids; -#endif + disconnect(m_request, SIGNAL(finished()), this, SLOT(onArtistRequestFinished())); + emit changed(); emit statusChanged(status()); checkIfFollowed(); return; } + Logger::log("SoundCloudArtist::onFollowCheckRequestFinished(). Error: " + errorString()); disconnect(m_request, SIGNAL(finished()), this, SLOT(onArtistRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -325,13 +353,16 @@ void SoundCloudArtist::onFollowRequestFinished() { setFollowed(true); setFollowersCount(followersCount() + 1); SoundCloud::followingCache.ids.append(id()); + Logger::log("SoundCloudArtist::onFollowRequestFinished(). Follow added. ID: " + id(), + Logger::MediumVerbosity); emit SoundCloud::instance()->artistFollowed(this); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudArtist::onFollowRequestFinished OK" << id(); -#endif + } + else { + Logger::log("SoundCloudArtist::onFollowRequestFinished(). Error: " + errorString()); } disconnect(m_request, SIGNAL(finished()), this, SLOT(onFollowRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -340,13 +371,16 @@ void SoundCloudArtist::onUnfollowRequestFinished() { setFollowed(false); setFollowersCount(followersCount() - 1); SoundCloud::followingCache.ids.removeOne(id()); + Logger::log("SoundCloudArtist::onUnfollowRequestFinished(). Follow removed. ID: " + id(), + Logger::MediumVerbosity); emit SoundCloud::instance()->artistUnfollowed(this); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudArtist::onUnfollowRequestFinished OK" << id(); -#endif + } + else { + Logger::log("SoundCloudArtist::onUnfollowRequestFinished(). Error: " + errorString()); } disconnect(m_request, SIGNAL(finished()), this, SLOT(onUnfollowRequestFinished())); + emit changed(); emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudartist.h b/app/src/soundcloud/soundcloudartist.h index d2656ca..2b732de 100644 --- a/app/src/soundcloud/soundcloudartist.h +++ b/app/src/soundcloud/soundcloudartist.h @@ -38,7 +38,7 @@ class SoundCloudArtist : public MKArtist explicit SoundCloudArtist(QObject *parent = 0); explicit SoundCloudArtist(const QString &id, QObject *parent = 0); explicit SoundCloudArtist(const QVariantMap &artist, QObject *parent = 0); - explicit SoundCloudArtist(SoundCloudArtist *artist, QObject *parent = 0); + explicit SoundCloudArtist(const SoundCloudArtist *artist, QObject *parent = 0); QString errorString() const; @@ -64,21 +64,7 @@ public Q_SLOTS: void checkIfFollowed(); void follow(); void unfollow(); - -private: - void setFollowed(bool s); - void setFollowersCount(qint64 c); - - void setOnline(bool o); - - void setPlaylistCount(qint64 c); - - void setTrackCount(qint64 c); - - void setWebsiteTitle(const QString &t); - void setWebsiteUrl(const QUrl &u); - - void initRequest(); + void cancel(); private Q_SLOTS: void onArtistRequestFinished(); @@ -102,7 +88,21 @@ private Q_SLOTS: void websiteTitleChanged(); void websiteUrlChanged(); -private: +private: + void setFollowed(bool s); + void setFollowersCount(qint64 c); + + void setOnline(bool o); + + void setPlaylistCount(qint64 c); + + void setTrackCount(qint64 c); + + void setWebsiteTitle(const QString &t); + void setWebsiteUrl(const QUrl &u); + + void initRequest(); + QSoundCloud::ResourcesRequest *m_request; bool m_followed; diff --git a/app/src/soundcloud/soundcloudartistmodel.cpp b/app/src/soundcloud/soundcloudartistmodel.cpp index 5bd0f45..415393a 100644 --- a/app/src/soundcloud/soundcloudartistmodel.cpp +++ b/app/src/soundcloud/soundcloudartistmodel.cpp @@ -15,17 +15,16 @@ */ #include "soundcloudartistmodel.h" +#include "logger.h" #include "soundcloud.h" #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudArtistModel::SoundCloudArtistModel(QObject *parent) : QAbstractListModel(parent), m_request(new QSoundCloud::ResourcesRequest(this)) { m_roles[DescriptionRole] = "description"; + m_roles[ErrorStringRole] = "errorString"; m_roles[FollowedRole] = "followed"; m_roles[FollowersCountRole] = "followersCount"; m_roles[IdRole] = "id"; @@ -33,17 +32,19 @@ SoundCloudArtistModel::SoundCloudArtistModel(QObject *parent) : m_roles[NameRole] = "name"; m_roles[OnlineRole] = "online"; m_roles[PlaylistCountRole] = "playlistCount"; + m_roles[StatusRole] = "status"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TrackCountRole] = "trackCount"; + m_roles[UrlRole] = "url"; m_roles[WebsiteTitleRole] = "websiteTitle"; m_roles[WebsiteUrlRole] = "websiteUrl"; #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -64,16 +65,20 @@ QHash SoundCloudArtistModel::roleNames() const { } #endif -int SoundCloudArtistModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int SoundCloudArtistModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int SoundCloudArtistModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 2; } -bool SoundCloudArtistModel::canFetchMore(const QModelIndex &) const { - return (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); +bool SoundCloudArtistModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); } -void SoundCloudArtistModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void SoundCloudArtistModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } @@ -81,8 +86,34 @@ void SoundCloudArtistModel::fetchMore(const QModelIndex &) { emit statusChanged(status()); } +QVariant SoundCloudArtistModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } + + switch (section) { + case 0: + return tr("Name"); + case 1: + return tr("Description"); + default: + return QVariant(); + } +} + QVariant SoundCloudArtistModel::data(const QModelIndex &index, int role) const { - if (SoundCloudArtist *artist = get(index.row())) { + if (const SoundCloudArtist *artist = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return artist->name(); + case 1: + return artist->description(); + default: + return QVariant(); + } + } + return artist->property(m_roles[role]); } @@ -92,7 +123,7 @@ QVariant SoundCloudArtistModel::data(const QModelIndex &index, int role) const { QMap SoundCloudArtistModel::itemData(const QModelIndex &index) const { QMap map; - if (SoundCloudArtist *artist = get(index.row())) { + if (const SoundCloudArtist *artist = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -105,7 +136,7 @@ QMap SoundCloudArtistModel::itemData(const QModelIndex &index) co } QVariant SoundCloudArtistModel::data(int row, const QByteArray &role) const { - if (SoundCloudArtist *artist = get(row)) { + if (const SoundCloudArtist *artist = get(row)) { return artist->property(role); } @@ -115,8 +146,8 @@ QVariant SoundCloudArtistModel::data(int row, const QByteArray &role) const { QVariantMap SoundCloudArtistModel::itemData(int row) const { QVariantMap map; - if (SoundCloudArtist *artist = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const SoundCloudArtist *artist = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = artist->property(role); } } @@ -137,6 +168,7 @@ void SoundCloudArtistModel::get(const QString &resourcePath, const QVariantMap & return; } + Logger::log("SoundCloudArtistModel::get(). Resource path: " + resourcePath, Logger::HighVerbosity); clear(); m_resourcePath = resourcePath; m_filters = filters; @@ -170,6 +202,11 @@ void SoundCloudArtistModel::cancel() { } void SoundCloudArtistModel::reload() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + return; + } + + Logger::log("SoundCloudArtistModel::reload(). Resource path: " + m_resourcePath, Logger::HighVerbosity); clear(); m_request->get(m_resourcePath, m_filters); emit statusChanged(status()); @@ -177,6 +214,7 @@ void SoundCloudArtistModel::reload() { void SoundCloudArtistModel::append(SoundCloudArtist *artist) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(artist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << artist; endInsertRows(); } @@ -184,6 +222,7 @@ void SoundCloudArtistModel::append(SoundCloudArtist *artist) { void SoundCloudArtistModel::insert(int row, SoundCloudArtist *artist) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(artist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, artist); endInsertRows(); } @@ -200,42 +239,49 @@ void SoundCloudArtistModel::remove(int row) { } } +void SoundCloudArtistModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void SoundCloudArtistModel::onRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_nextHref = result.value("next_href").toString().section(QSoundCloud::API_URL, -1); - QVariantList list = result.value("collection").toList(); + const QVariantList list = result.value("collection").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new SoundCloudArtist(item.toMap(), this); + foreach (const QVariant &item, list) { + SoundCloudArtist *artist = new SoundCloudArtist(item.toMap(), this); + connect(artist, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << artist; } endInsertRows(); emit countChanged(rowCount()); } } + else { + Logger::log("SoundCloudArtistModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } void SoundCloudArtistModel::onArtistFollowed(SoundCloudArtist *artist) { insert(0, new SoundCloudArtist(artist, this)); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudArtistModel::onArtistFollowed" << artist->id(); -#endif } void SoundCloudArtistModel::onArtistUnfollowed(SoundCloudArtist *artist) { - QModelIndexList list = match(index(0), IdRole, artist->id(), 1, Qt::MatchExactly); + const QModelIndexList list = match(index(0), IdRole, artist->id(), 1, Qt::MatchExactly); if (!list.isEmpty()) { remove(list.first().row()); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudArtistModel::onArtistUnfollowed" << artist->id(); -#endif } diff --git a/app/src/soundcloud/soundcloudartistmodel.h b/app/src/soundcloud/soundcloudartistmodel.h index 6308ba2..c036d1b 100644 --- a/app/src/soundcloud/soundcloudartistmodel.h +++ b/app/src/soundcloud/soundcloudartistmodel.h @@ -32,6 +32,7 @@ class SoundCloudArtistModel : public QAbstractListModel public: enum Roles { DescriptionRole = Qt::UserRole + 1, + ErrorStringRole, FollowedRole, FollowersCountRole, IdRole, @@ -39,8 +40,10 @@ class SoundCloudArtistModel : public QAbstractListModel NameRole, OnlineRole, PlaylistCountRole, + StatusRole, ThumbnailUrlRole, TrackCountRole, + UrlRole, WebsiteTitleRole, WebsiteUrlRole }; @@ -55,6 +58,7 @@ class SoundCloudArtistModel : public QAbstractListModel QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); @@ -62,6 +66,8 @@ class SoundCloudArtistModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + Q_INVOKABLE QVariant data(int row, const QByteArray &role) const; Q_INVOKABLE QVariantMap itemData(int row) const; @@ -80,13 +86,14 @@ public Q_SLOTS: void remove(int row); private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); void onArtistFollowed(SoundCloudArtist *artist); void onArtistUnfollowed(SoundCloudArtist *artist); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void statusChanged(QSoundCloud::ResourcesRequest::Status s); private: diff --git a/app/src/soundcloud/soundcloudcomment.cpp b/app/src/soundcloud/soundcloudcomment.cpp index 51cfee8..03b75c2 100644 --- a/app/src/soundcloud/soundcloudcomment.cpp +++ b/app/src/soundcloud/soundcloudcomment.cpp @@ -15,12 +15,11 @@ */ #include "soundcloudcomment.h" +#include "definitions.h" +#include "logger.h" #include "resources.h" #include "soundcloud.h" #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudComment::SoundCloudComment(QObject *parent) : MKComment(parent), @@ -45,7 +44,7 @@ SoundCloudComment::SoundCloudComment(const QVariantMap &comment, QObject *parent loadComment(comment); } -SoundCloudComment::SoundCloudComment(SoundCloudComment *comment, QObject *parent) : +SoundCloudComment::SoundCloudComment(const SoundCloudComment *comment, QObject *parent) : MKComment(comment, parent), m_request(0) { @@ -64,14 +63,17 @@ void SoundCloudComment::loadComment(const QString &id) { return; } + setId(id); initRequest(); m_request->get("/comments/" + id); connect(m_request, SIGNAL(finished()), this, SLOT(onCommentRequestFinished())); + emit changed(); emit statusChanged(status()); } void SoundCloudComment::loadComment(const QVariantMap &comment) { - QVariantMap user = comment.value("user").toMap(); + const QVariantMap user = comment.value("user").toMap(); + const QString thumbnail = user.value("avatar_url").toString(); setArtist(user.value("username").toString()); setArtistId(user.value("id").toString()); @@ -79,8 +81,15 @@ void SoundCloudComment::loadComment(const QVariantMap &comment) { setDate(QDateTime::fromString(comment.value("created_at").toString(), "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); setId(comment.value("id").toString()); - setThumbnailUrl(user.value("avatar_url").toString()); setTrackId(comment.value("track_id").toString()); + setUrl(comment.value("url").toString()); + + if (!thumbnail.isEmpty()) { + setThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(THUMBNAIL_SIZE)); + } + else { + setThumbnailUrl(QString()); + } } void SoundCloudComment::addComment() { @@ -88,12 +97,13 @@ void SoundCloudComment::addComment() { return; } + Logger::log("SoundCloudComment::addComment()", Logger::MediumVerbosity); initRequest(); - QVariantMap resource; resource["body"] = body(); m_request->insert(resource, QString("/tracks/%1/comments").arg(trackId())); connect(m_request, SIGNAL(finished()), this, SLOT(onAddCommentRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -102,13 +112,21 @@ void SoundCloudComment::addComment(const QVariantMap &comment) { addComment(); } +void SoundCloudComment::cancel() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + void SoundCloudComment::initRequest() { if (!m_request) { m_request = new QSoundCloud::ResourcesRequest(this); - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -121,18 +139,22 @@ void SoundCloudComment::onCommentRequestFinished() { } disconnect(m_request, SIGNAL(finished()), this, SLOT(onCommentRequestFinished())); + emit changed(); emit statusChanged(status()); } void SoundCloudComment::onAddCommentRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { loadComment(m_request->result().toMap()); + Logger::log("SoundCloudComment::onAddCommentRequestFinished(). Comment added. ID: " + id(), + Logger::MediumVerbosity); emit SoundCloud::instance()->commentAdded(this); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudComment::onAddCommentRequestFinished OK" << trackId(); -#endif + } + else { + Logger::log("SoundCloudComment::onAddCommentRequestFinished(). Error: " + errorString()); } disconnect(m_request, SIGNAL(finished()), this, SLOT(onAddCommentRequestFinished())); + emit changed(); emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudcomment.h b/app/src/soundcloud/soundcloudcomment.h index b4dc24d..6b7eedc 100644 --- a/app/src/soundcloud/soundcloudcomment.h +++ b/app/src/soundcloud/soundcloudcomment.h @@ -23,15 +23,18 @@ class SoundCloudComment : public MKComment { Q_OBJECT + + Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged); + Q_PROPERTY(QSoundCloud::ResourcesRequest::Status status READ status NOTIFY statusChanged) public: explicit SoundCloudComment(QObject *parent = 0); explicit SoundCloudComment(const QString &id, QObject *parent = 0); explicit SoundCloudComment(const QVariantMap &comment, QObject *parent = 0); - explicit SoundCloudComment(SoundCloudComment *comment, QObject *parent = 0); + explicit SoundCloudComment(const SoundCloudComment *comment, QObject *parent = 0); QString errorString() const; - + QSoundCloud::ResourcesRequest::Status status() const; Q_INVOKABLE void loadComment(const QString &id); @@ -40,6 +43,7 @@ class SoundCloudComment : public MKComment public Q_SLOTS: void addComment(); void addComment(const QVariantMap &comment); + void cancel(); private: void initRequest(); diff --git a/app/src/soundcloud/soundcloudcommentmodel.cpp b/app/src/soundcloud/soundcloudcommentmodel.cpp index 6ec2e98..a773693 100644 --- a/app/src/soundcloud/soundcloudcommentmodel.cpp +++ b/app/src/soundcloud/soundcloudcommentmodel.cpp @@ -15,11 +15,9 @@ */ #include "soundcloudcommentmodel.h" +#include "logger.h" #include "soundcloud.h" #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudCommentModel::SoundCloudCommentModel(QObject *parent) : QAbstractListModel(parent), @@ -29,16 +27,19 @@ SoundCloudCommentModel::SoundCloudCommentModel(QObject *parent) : m_roles[ArtistIdRole] = "artistId"; m_roles[BodyRole] = "body"; m_roles[DateRole] = "date"; + m_roles[ErrorStringRole] = "errorString"; m_roles[IdRole] = "id"; + m_roles[StatusRole] = "status"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TrackIdRole] = "trackId"; + m_roles[UrlRole] = "url"; #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -61,16 +62,20 @@ QHash SoundCloudCommentModel::roleNames() const { } #endif -int SoundCloudCommentModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int SoundCloudCommentModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int SoundCloudCommentModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 3; } -bool SoundCloudCommentModel::canFetchMore(const QModelIndex &) const { - return (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); +bool SoundCloudCommentModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); } -void SoundCloudCommentModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void SoundCloudCommentModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } @@ -78,9 +83,39 @@ void SoundCloudCommentModel::fetchMore(const QModelIndex &) { emit statusChanged(status()); } +QVariant SoundCloudCommentModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } + + switch (section) { + case 0: + return tr("Artist"); + case 1: + return tr("Date"); + case 2: + return tr("Comment"); + default: + return QVariant(); + } +} + QVariant SoundCloudCommentModel::data(const QModelIndex &index, int role) const { - if (SoundCloudComment *user = get(index.row())) { - return user->property(m_roles[role]); + if (const SoundCloudComment *comment = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return comment->artist(); + case 1: + return comment->date(); + case 2: + return comment->body(); + default: + return QVariant(); + } + } + + return comment->property(m_roles[role]); } return QVariant(); @@ -89,12 +124,12 @@ QVariant SoundCloudCommentModel::data(const QModelIndex &index, int role) const QMap SoundCloudCommentModel::itemData(const QModelIndex &index) const { QMap map; - if (SoundCloudComment *user = get(index.row())) { + if (const SoundCloudComment *comment = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { iterator.next(); - map[iterator.key()] = user->property(iterator.value()); + map[iterator.key()] = comment->property(iterator.value()); } } @@ -102,8 +137,8 @@ QMap SoundCloudCommentModel::itemData(const QModelIndex &index) c } QVariant SoundCloudCommentModel::data(int row, const QByteArray &role) const { - if (SoundCloudComment *user = get(row)) { - return user->property(role); + if (const SoundCloudComment *comment = get(row)) { + return comment->property(role); } return QVariant(); @@ -112,9 +147,9 @@ QVariant SoundCloudCommentModel::data(int row, const QByteArray &role) const { QVariantMap SoundCloudCommentModel::itemData(int row) const { QVariantMap map; - if (SoundCloudComment *user = get(row)) { - foreach (QByteArray role, m_roles.values()) { - map[role] = user->property(role); + if (const SoundCloudComment *comment = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { + map[role] = comment->property(role); } } @@ -134,6 +169,7 @@ void SoundCloudCommentModel::get(const QString &resourcePath, const QVariantMap return; } + Logger::log("SoundCloudCommentModel::get(). Resource path: " + resourcePath, Logger::HighVerbosity); clear(); m_resourcePath = resourcePath; m_filters = filters; @@ -158,25 +194,32 @@ void SoundCloudCommentModel::cancel() { } void SoundCloudCommentModel::reload() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + return; + } + + Logger::log("SoundCloudCommentModel::reload(). Resource path: " + m_resourcePath, Logger::HighVerbosity); clear(); m_request->get(m_resourcePath, m_filters); emit statusChanged(status()); } -void SoundCloudCommentModel::append(SoundCloudComment *user) { +void SoundCloudCommentModel::append(SoundCloudComment *comment) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); - m_items << user; + connect(comment, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << comment; endInsertRows(); } -void SoundCloudCommentModel::insert(int row, SoundCloudComment *user) { +void SoundCloudCommentModel::insert(int row, SoundCloudComment *comment) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); - m_items.insert(row, user); + connect(comment, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items.insert(row, comment); endInsertRows(); } else { - append(user); + append(comment); } } @@ -188,24 +231,37 @@ void SoundCloudCommentModel::remove(int row) { } } +void SoundCloudCommentModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void SoundCloudCommentModel::onRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_nextHref = result.value("next_href").toString().section(QSoundCloud::API_URL, -1); - QVariantList list = result.value("collection").toList(); + const QVariantList list = result.value("collection").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new SoundCloudComment(item.toMap(), this); + foreach (const QVariant &item, list) { + SoundCloudComment *comment = new SoundCloudComment(item.toMap(), this); + connect(comment, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << comment; } endInsertRows(); emit countChanged(rowCount()); } } + else { + Logger::log("SoundCloudCommentModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } @@ -214,7 +270,4 @@ void SoundCloudCommentModel::onCommentAdded(SoundCloudComment *comment) { if (comment->trackId() == m_resourcePath.section('/', 1, 1)) { insert(0, new SoundCloudComment(comment, this)); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudCommentModel::onCommentAdded" << comment->trackId(); -#endif } diff --git a/app/src/soundcloud/soundcloudcommentmodel.h b/app/src/soundcloud/soundcloudcommentmodel.h index 24ced3c..f86b874 100644 --- a/app/src/soundcloud/soundcloudcommentmodel.h +++ b/app/src/soundcloud/soundcloudcommentmodel.h @@ -35,9 +35,12 @@ class SoundCloudCommentModel : public QAbstractListModel ArtistIdRole, BodyRole, DateRole, + ErrorStringRole, IdRole, + StatusRole, ThumbnailUrlRole, - TrackIdRole + TrackIdRole, + UrlRole }; explicit SoundCloudCommentModel(QObject *parent = 0); @@ -50,10 +53,13 @@ class SoundCloudCommentModel : public QAbstractListModel QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -69,21 +75,21 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(SoundCloudComment *comment); - void insert(int row, SoundCloudComment *comment); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); void onCommentAdded(SoundCloudComment *comment); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void statusChanged(QSoundCloud::ResourcesRequest::Status s); private: + void append(SoundCloudComment *comment); + void insert(int row, SoundCloudComment *comment); + void remove(int row); + QSoundCloud::ResourcesRequest *m_request; QString m_resourcePath; diff --git a/app/src/soundcloud/soundcloudconnectionmodel.cpp b/app/src/soundcloud/soundcloudconnectionmodel.cpp index 452d335..5dacc9a 100644 --- a/app/src/soundcloud/soundcloudconnectionmodel.cpp +++ b/app/src/soundcloud/soundcloudconnectionmodel.cpp @@ -15,16 +15,17 @@ */ #include "soundcloudconnectionmodel.h" +#include "logger.h" #include "soundcloud.h" SoundCloudConnectionModel::SoundCloudConnectionModel(QObject *parent) : SelectionModel(parent), m_request(new QSoundCloud::ResourcesRequest(this)) { - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -44,6 +45,7 @@ void SoundCloudConnectionModel::get(const QString &resourcePath, const QVariantM return; } + Logger::log("SoundCloudConnectionModel::get(). Resource path: " + resourcePath, Logger::HighVerbosity); clear(); m_resourcePath = resourcePath; m_filters = filters; @@ -56,6 +58,11 @@ void SoundCloudConnectionModel::cancel() { } void SoundCloudConnectionModel::reload() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + return; + } + + Logger::log("SoundCloudConnectionModel::reload(). Resource path: " + m_resourcePath, Logger::HighVerbosity); clear(); m_request->get(m_resourcePath, m_filters); emit statusChanged(status()); @@ -63,11 +70,14 @@ void SoundCloudConnectionModel::reload() { void SoundCloudConnectionModel::onRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - foreach (QVariant v, m_request->result().toList()) { - QVariantMap connection = v.toMap(); + foreach (const QVariant &v, m_request->result().toList()) { + const QVariantMap connection = v.toMap(); append(connection.value("display_name").toString(), connection); } } + else { + Logger::log("SoundCloudConnectionModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudnavmodel.cpp b/app/src/soundcloud/soundcloudnavmodel.cpp index 5869817..df64787 100644 --- a/app/src/soundcloud/soundcloudnavmodel.cpp +++ b/app/src/soundcloud/soundcloudnavmodel.cpp @@ -21,11 +21,11 @@ SoundCloudNavModel::SoundCloudNavModel(QObject *parent) : QStringListModel(parent) { reload(); - connect(SoundCloud::instance(), SIGNAL(userIdChanged()), this, SLOT(reload())); + connect(SoundCloud::instance(), SIGNAL(userIdChanged(QString)), this, SLOT(reload())); } void SoundCloudNavModel::reload() { - if (SoundCloud::instance()->userId().isEmpty()) { + if (SoundCloud::userId().isEmpty()) { setStringList(QStringList() << tr("Accounts") << tr("Search")); } else { diff --git a/app/src/soundcloud/soundcloudplaylist.cpp b/app/src/soundcloud/soundcloudplaylist.cpp index 69b5f92..955311c 100644 --- a/app/src/soundcloud/soundcloudplaylist.cpp +++ b/app/src/soundcloud/soundcloudplaylist.cpp @@ -43,7 +43,7 @@ SoundCloudPlaylist::SoundCloudPlaylist(const QVariantMap &playlist, QObject *par loadPlaylist(playlist); } -SoundCloudPlaylist::SoundCloudPlaylist(SoundCloudPlaylist *playlist, QObject *parent) : +SoundCloudPlaylist::SoundCloudPlaylist(const SoundCloudPlaylist *playlist, QObject *parent) : MKPlaylist(playlist, parent), m_request(0), m_sharing(playlist->sharing()) @@ -61,6 +61,7 @@ QString SoundCloudPlaylist::sharing() const { void SoundCloudPlaylist::setSharing(const QString &s) { if (s != sharing()) { m_sharing = s; + emit changed(); emit sharingChanged(); } } @@ -74,6 +75,7 @@ void SoundCloudPlaylist::loadPlaylist(const QString &id) { return; } + setId(id); initRequest(); if (id.startsWith("http")) { @@ -86,11 +88,12 @@ void SoundCloudPlaylist::loadPlaylist(const QString &id) { } connect(m_request, SIGNAL(finished()), this, SLOT(onPlaylistRequestFinished())); + emit changed(); emit statusChanged(status()); } void SoundCloudPlaylist::loadPlaylist(const QVariantMap &playlist) { - QVariantMap user = playlist.value("user").toMap(); + const QVariantMap user = playlist.value("user").toMap(); const QString thumbnail = playlist.value("artwork_url").toString(); setArtist(user.value("username").toString()); @@ -101,11 +104,19 @@ void SoundCloudPlaylist::loadPlaylist(const QVariantMap &playlist) { setDuration(playlist.value("duration").toLongLong()); setGenre(playlist.value("genre").toString()); setId(playlist.value("id").toString()); - setLargeThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(LARGE_THUMBNAIL_SIZE)); setSharing(playlist.value("sharing").toString()); - setThumbnailUrl(thumbnail); setTitle(playlist.value("title").toString()); - setTrackCount(playlist.value("track_count").toInt()); + setTrackCount(playlist.value("track_count").toInt()); + setUrl(playlist.value("url").toString()); + + if (!thumbnail.isEmpty()) { + setLargeThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(LARGE_THUMBNAIL_SIZE)); + setThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(THUMBNAIL_SIZE)); + } + else { + setLargeThumbnailUrl(QString()); + setThumbnailUrl(QString()); + } } void SoundCloudPlaylist::loadPlaylist(SoundCloudPlaylist *playlist) { @@ -113,13 +124,21 @@ void SoundCloudPlaylist::loadPlaylist(SoundCloudPlaylist *playlist) { setSharing(playlist->sharing()); } +void SoundCloudPlaylist::cancel() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } +} + void SoundCloudPlaylist::initRequest() { if (!m_request) { m_request = new QSoundCloud::ResourcesRequest(this); - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -132,5 +151,6 @@ void SoundCloudPlaylist::onPlaylistRequestFinished() { } disconnect(m_request, SIGNAL(finished()), this, SLOT(onPlaylistRequestFinished())); + emit changed(); emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudplaylist.h b/app/src/soundcloud/soundcloudplaylist.h index 5f1cc2b..a4d26ff 100644 --- a/app/src/soundcloud/soundcloudplaylist.h +++ b/app/src/soundcloud/soundcloudplaylist.h @@ -32,7 +32,7 @@ class SoundCloudPlaylist : public MKPlaylist explicit SoundCloudPlaylist(QObject *parent = 0); explicit SoundCloudPlaylist(const QString &id, QObject *parent = 0); explicit SoundCloudPlaylist(const QVariantMap &playlist, QObject *parent = 0); - explicit SoundCloudPlaylist(SoundCloudPlaylist *playlist, QObject *parent = 0); + explicit SoundCloudPlaylist(const SoundCloudPlaylist *playlist, QObject *parent = 0); QString errorString() const; @@ -43,12 +43,10 @@ class SoundCloudPlaylist : public MKPlaylist Q_INVOKABLE void loadPlaylist(const QString &id); Q_INVOKABLE void loadPlaylist(const QVariantMap &playlist); Q_INVOKABLE void loadPlaylist(SoundCloudPlaylist *playlist); - -private: - void setSharing(const QString &s); - - void initRequest(); - + +public Q_SLOTS: + void cancel(); + private Q_SLOTS: void onPlaylistRequestFinished(); @@ -58,6 +56,10 @@ private Q_SLOTS: void statusChanged(QSoundCloud::ResourcesRequest::Status s); private: + void setSharing(const QString &s); + + void initRequest(); + QSoundCloud::ResourcesRequest *m_request; QString m_sharing; diff --git a/app/src/soundcloud/soundcloudplaylistmodel.cpp b/app/src/soundcloud/soundcloudplaylistmodel.cpp index 13ab588..f08a527 100644 --- a/app/src/soundcloud/soundcloudplaylistmodel.cpp +++ b/app/src/soundcloud/soundcloudplaylistmodel.cpp @@ -15,6 +15,7 @@ */ #include "soundcloudplaylistmodel.h" +#include "logger.h" #include "soundcloud.h" #include @@ -28,20 +29,23 @@ SoundCloudPlaylistModel::SoundCloudPlaylistModel(QObject *parent) : m_roles[DescriptionRole] = "description"; m_roles[DurationRole] = "duration"; m_roles[DurationStringRole] = "durationString"; + m_roles[ErrorStringRole] = "errorString"; m_roles[GenreRole] = "genre"; m_roles[IdRole] = "id"; m_roles[LargeThumbnailUrlRole] = "largeThumbnailUrl"; - m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[SharingRole] = "sharing"; + m_roles[StatusRole] = "status"; + m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TitleRole] = "title"; m_roles[TrackCountRole] = "trackCount"; + m_roles[UrlRole] = "url"; #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -62,16 +66,20 @@ QHash SoundCloudPlaylistModel::roleNames() const { } #endif -int SoundCloudPlaylistModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int SoundCloudPlaylistModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int SoundCloudPlaylistModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 4; } -bool SoundCloudPlaylistModel::canFetchMore(const QModelIndex &) const { - return (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); +bool SoundCloudPlaylistModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); } -void SoundCloudPlaylistModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void SoundCloudPlaylistModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } @@ -79,8 +87,42 @@ void SoundCloudPlaylistModel::fetchMore(const QModelIndex &) { emit statusChanged(status()); } +QVariant SoundCloudPlaylistModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } + + switch (section) { + case 0: + return tr("Title"); + case 1: + return tr("Artist"); + case 2: + return tr("Tracks"); + case 3: + return tr("Duration"); + default: + return QVariant(); + } +} + QVariant SoundCloudPlaylistModel::data(const QModelIndex &index, int role) const { - if (SoundCloudPlaylist *playlist = get(index.row())) { + if (const SoundCloudPlaylist *playlist = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return playlist->title(); + case 1: + return playlist->artist(); + case 2: + return playlist->trackCount(); + case 3: + return playlist->durationString(); + default: + return QVariant(); + } + } + return playlist->property(m_roles[role]); } @@ -90,7 +132,7 @@ QVariant SoundCloudPlaylistModel::data(const QModelIndex &index, int role) const QMap SoundCloudPlaylistModel::itemData(const QModelIndex &index) const { QMap map; - if (SoundCloudPlaylist *playlist = get(index.row())) { + if (const SoundCloudPlaylist *playlist = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -103,7 +145,7 @@ QMap SoundCloudPlaylistModel::itemData(const QModelIndex &index) } QVariant SoundCloudPlaylistModel::data(int row, const QByteArray &role) const { - if (SoundCloudPlaylist *playlist = get(row)) { + if (const SoundCloudPlaylist *playlist = get(row)) { return playlist->property(role); } @@ -113,8 +155,8 @@ QVariant SoundCloudPlaylistModel::data(int row, const QByteArray &role) const { QVariantMap SoundCloudPlaylistModel::itemData(int row) const { QVariantMap map; - if (SoundCloudPlaylist *playlist = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const SoundCloudPlaylist *playlist = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = playlist->property(role); } } @@ -135,6 +177,7 @@ void SoundCloudPlaylistModel::get(const QString &resourcePath, const QVariantMap return; } + Logger::log("SoundCloudPlaylistModel::get(). Resource path: " + resourcePath, Logger::HighVerbosity); clear(); m_resourcePath = resourcePath; m_filters = filters; @@ -160,6 +203,11 @@ void SoundCloudPlaylistModel::cancel() { } void SoundCloudPlaylistModel::reload() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + return; + } + + Logger::log("SoundCloudPlaylistModel::reload(). Resource path: " + m_resourcePath, Logger::HighVerbosity); clear(); m_request->get(m_resourcePath, m_filters); emit statusChanged(status()); @@ -167,6 +215,7 @@ void SoundCloudPlaylistModel::reload() { void SoundCloudPlaylistModel::append(SoundCloudPlaylist *playlist) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(playlist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << playlist; endInsertRows(); } @@ -174,6 +223,7 @@ void SoundCloudPlaylistModel::append(SoundCloudPlaylist *playlist) { void SoundCloudPlaylistModel::insert(int row, SoundCloudPlaylist *playlist) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(playlist, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, playlist); endInsertRows(); } @@ -190,24 +240,37 @@ void SoundCloudPlaylistModel::remove(int row) { } } +void SoundCloudPlaylistModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void SoundCloudPlaylistModel::onRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_nextHref = result.value("next_href").toString().section(QSoundCloud::API_URL, -1); - QVariantList list = result.value("collection").toList(); + const QVariantList list = result.value("collection").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new SoundCloudPlaylist(item.toMap(), this); + foreach (const QVariant &item, list) { + SoundCloudPlaylist *playlist = new SoundCloudPlaylist(item.toMap(), this); + connect(playlist, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << playlist; } endInsertRows(); emit countChanged(rowCount()); } } + else { + Logger::log("SoundCloudPlaylistModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudplaylistmodel.h b/app/src/soundcloud/soundcloudplaylistmodel.h index 106e07f..acc9a16 100644 --- a/app/src/soundcloud/soundcloudplaylistmodel.h +++ b/app/src/soundcloud/soundcloudplaylistmodel.h @@ -37,13 +37,16 @@ class SoundCloudPlaylistModel : public QAbstractListModel DescriptionRole, DurationRole, DurationStringRole, + ErrorStringRole, GenreRole, IdRole, LargeThumbnailUrlRole, SharingRole, + StatusRole, ThumbnailUrlRole, TitleRole, - TrackCountRole + TrackCountRole, + UrlRole }; explicit SoundCloudPlaylistModel(QObject *parent = 0); @@ -56,10 +59,13 @@ class SoundCloudPlaylistModel : public QAbstractListModel QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -75,19 +81,19 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(SoundCloudPlaylist *playlist); - void insert(int row, SoundCloudPlaylist *playlist); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void statusChanged(QSoundCloud::ResourcesRequest::Status s); private: + void append(SoundCloudPlaylist *playlist); + void insert(int row, SoundCloudPlaylist *playlist); + void remove(int row); + QSoundCloud::ResourcesRequest *m_request; QString m_resourcePath; diff --git a/app/src/soundcloud/soundcloudstreammodel.cpp b/app/src/soundcloud/soundcloudstreammodel.cpp index ac4a458..eb33165 100644 --- a/app/src/soundcloud/soundcloudstreammodel.cpp +++ b/app/src/soundcloud/soundcloudstreammodel.cpp @@ -15,16 +15,17 @@ */ #include "soundcloudstreammodel.h" +#include "logger.h" #include "soundcloud.h" SoundCloudStreamModel::SoundCloudStreamModel(QObject *parent) : SelectionModel(parent), m_request(new QSoundCloud::StreamsRequest(this)) { - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -44,6 +45,7 @@ void SoundCloudStreamModel::get(const QString &id) { return; } + Logger::log("SoundCloudStreamModel::get(). ID: " + id, Logger::MediumVerbosity); clear(); m_id = id; m_request->get(id); @@ -55,6 +57,11 @@ void SoundCloudStreamModel::cancel() { } void SoundCloudStreamModel::reload() { + if (status() == QSoundCloud::StreamsRequest::Loading) { + return; + } + + Logger::log("SoundCloudStreamModel::reload(). ID: " + m_id, Logger::MediumVerbosity); clear(); m_request->get(m_id); emit statusChanged(status()); @@ -62,11 +69,14 @@ void SoundCloudStreamModel::reload() { void SoundCloudStreamModel::onRequestFinished() { if (m_request->status() == QSoundCloud::StreamsRequest::Ready) { - foreach (QVariant v, m_request->result().toList()) { - QVariantMap stream = v.toMap(); + foreach (const QVariant &v, m_request->result().toList()) { + const QVariantMap stream = v.toMap(); append(stream.value("description").toString(), stream); } } + else { + Logger::log("SoundCloudStreamModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudtrack.cpp b/app/src/soundcloud/soundcloudtrack.cpp index c176e55..6908d52 100644 --- a/app/src/soundcloud/soundcloudtrack.cpp +++ b/app/src/soundcloud/soundcloudtrack.cpp @@ -16,21 +16,18 @@ #include "soundcloudtrack.h" #include "definitions.h" +#include "logger.h" #include "resources.h" #include "soundcloud.h" #include "utils.h" #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudTrack::SoundCloudTrack(QObject *parent) : MKTrack(parent), m_request(0), m_commentable(true), m_favourite(false), - m_favouriteCount(0), - m_streamable(true) + m_favouriteCount(0) { setService(Resources::SOUNDCLOUD); connect(SoundCloud::instance(), SIGNAL(trackFavourited(SoundCloudTrack*)), @@ -44,8 +41,7 @@ SoundCloudTrack::SoundCloudTrack(const QString &id, QObject *parent) : m_request(0), m_commentable(true), m_favourite(false), - m_favouriteCount(0), - m_streamable(true) + m_favouriteCount(0) { setService(Resources::SOUNDCLOUD); loadTrack(id); @@ -60,8 +56,7 @@ SoundCloudTrack::SoundCloudTrack(const QVariantMap &track, QObject *parent) : m_request(0), m_commentable(true), m_favourite(false), - m_favouriteCount(0), - m_streamable(true) + m_favouriteCount(0) { setService(Resources::SOUNDCLOUD); loadTrack(track); @@ -71,14 +66,13 @@ SoundCloudTrack::SoundCloudTrack(const QVariantMap &track, QObject *parent) : this, SLOT(onTrackUpdated(SoundCloudTrack*))); } -SoundCloudTrack::SoundCloudTrack(SoundCloudTrack *track, QObject *parent) : +SoundCloudTrack::SoundCloudTrack(const SoundCloudTrack *track, QObject *parent) : MKTrack(track, parent), m_request(0), m_commentable(track->isCommentable()), m_favourite(track->isFavourite()), m_favouriteCount(track->favouriteCount()), m_sharing(track->sharing()), - m_streamable(track->isStreamable()), m_waveformUrl(track->waveformUrl()) { connect(SoundCloud::instance(), SIGNAL(trackFavourited(SoundCloudTrack*)), @@ -94,6 +88,7 @@ bool SoundCloudTrack::isCommentable() const { void SoundCloudTrack::setCommentable(bool c) { if (c != isCommentable()) { m_commentable = c; + emit changed(); emit commentableChanged(); } } @@ -109,6 +104,7 @@ bool SoundCloudTrack::isFavourite() const { void SoundCloudTrack::setFavourite(bool f) { if (f != isFavourite()) { m_favourite = f; + emit changed(); emit favouriteChanged(); } } @@ -120,6 +116,7 @@ qint64 SoundCloudTrack::favouriteCount() const { void SoundCloudTrack::setFavouriteCount(qint64 c) { if (c != favouriteCount()) { m_favouriteCount = c; + emit changed(); emit favouriteCountChanged(); } } @@ -131,6 +128,7 @@ QString SoundCloudTrack::sharing() const { void SoundCloudTrack::setSharing(const QString &s) { if (s != sharing()) { m_sharing = s; + emit changed(); emit sharingChanged(); } } @@ -139,17 +137,6 @@ QSoundCloud::ResourcesRequest::Status SoundCloudTrack::status() const { return m_request ? m_request->status() : QSoundCloud::ResourcesRequest::Null; } -bool SoundCloudTrack::isStreamable() const { - return m_streamable; -} - -void SoundCloudTrack::setStreamable(bool s) { - if (s != isStreamable()) { - m_streamable = s; - emit streamableChanged(); - } -} - QUrl SoundCloudTrack::waveformUrl() const { return m_waveformUrl; } @@ -157,6 +144,7 @@ QUrl SoundCloudTrack::waveformUrl() const { void SoundCloudTrack::setWaveformUrl(const QUrl &u) { if (u != waveformUrl()) { m_waveformUrl = u; + emit changed(); emit waveformUrlChanged(); } } @@ -166,6 +154,7 @@ void SoundCloudTrack::loadTrack(const QString &id) { return; } + setId(id); initRequest(); if (id.startsWith("http")) { @@ -178,11 +167,12 @@ void SoundCloudTrack::loadTrack(const QString &id) { } connect(m_request, SIGNAL(finished()), this, SLOT(onTrackRequestFinished())); + emit changed(); emit statusChanged(status()); } void SoundCloudTrack::loadTrack(const QVariantMap &track) { - QVariantMap user = track.value("user").toMap(); + const QVariantMap user = track.value("user").toMap(); const QString thumbnail = track.value("artwork_url").toString(); setArtist(user.value("username").toString()); @@ -191,21 +181,26 @@ void SoundCloudTrack::loadTrack(const QVariantMap &track) { setDate(QDateTime::fromString(track.value("created_at").toString(), "yyyy/MM/dd HH:mm:ss +0000").toString("dd MMM yyyy")); setDescription(track.value("description").toString()); - setDownloadable(track.value("downloadable").toBool()); setDuration(track.value("duration").toLongLong()); setFavourite(track.value("user_favorite").toBool()); setFavouriteCount(track.value("favoritings_count").toLongLong()); setFormat(track.value("original_format").toString().toUpper()); setGenre(track.value("genre").toString()); setId(track.value("id").toString()); - setLargeThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(LARGE_THUMBNAIL_SIZE)); setPlayCount(track.value("playback_count").toLongLong()); setSize(track.value("original_content_size").toLongLong()); - setStreamable(track.value("streamable").toBool()); - setThumbnailUrl(thumbnail); setTitle(track.value("title").toString()); setUrl(track.value("permalink_url").toString()); setWaveformUrl(track.value("waveform_url").toString()); + + if (!thumbnail.isEmpty()) { + setLargeThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(LARGE_THUMBNAIL_SIZE)); + setThumbnailUrl(QString("%1-t%2x%2.jpg").arg(thumbnail.left(thumbnail.lastIndexOf('-'))).arg(THUMBNAIL_SIZE)); + } + else { + setLargeThumbnailUrl(QString()); + setThumbnailUrl(QString()); + } } void SoundCloudTrack::loadTrack(SoundCloudTrack *track) { @@ -213,7 +208,6 @@ void SoundCloudTrack::loadTrack(SoundCloudTrack *track) { setCommentable(track->isCommentable()); setFavourite(track->isFavourite()); setFavouriteCount(track->favouriteCount()); - setStreamable(track->isStreamable()); setWaveformUrl(track->waveformUrl()); } @@ -222,13 +216,12 @@ void SoundCloudTrack::favourite() { return; } + Logger::log("SoundCloudTrack::favourite(). ID: " + id(), Logger::MediumVerbosity); initRequest(); m_request->insert("/me/favorites/" + id()); connect(m_request, SIGNAL(finished()), this, SLOT(onFavouriteRequestFinished())); + emit changed(); emit statusChanged(status()); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudTrack::favourite" << id(); -#endif } void SoundCloudTrack::unfavourite() { @@ -236,22 +229,29 @@ void SoundCloudTrack::unfavourite() { return; } + Logger::log("SoundCloudTrack::unfavourite(). ID: " + id(), Logger::MediumVerbosity); initRequest(); m_request->del("/me/favorites/" + id()); connect(m_request, SIGNAL(finished()), this, SLOT(onUnfavouriteRequestFinished())); + emit changed(); emit statusChanged(status()); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudTrack::unfavourite" << id(); -#endif +} + +void SoundCloudTrack::cancel() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + m_request->cancel(); + emit changed(); + emit statusChanged(status()); + } } void SoundCloudTrack::initRequest() { if (!m_request) { m_request = new QSoundCloud::ResourcesRequest(this); - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -264,6 +264,7 @@ void SoundCloudTrack::onTrackRequestFinished() { } disconnect(m_request, SIGNAL(finished()), this, SLOT(onTrackRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -271,13 +272,16 @@ void SoundCloudTrack::onFavouriteRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { setFavourite(true); setFavouriteCount(favouriteCount() + 1); + Logger::log("SoundCloudTrack::onFavouriteRequestFinished(). Video favourited. ID: " + id(), + Logger::MediumVerbosity); emit SoundCloud::instance()->trackFavourited(this); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudTrack::onFavouriteRequestFinished OK" << id(); -#endif + } + else { + Logger::log("SoundCloudTrack::onFavouriteRequestFinished(). Error: " + errorString()); } disconnect(m_request, SIGNAL(finished()), this, SLOT(onFavouriteRequestFinished())); + emit changed(); emit statusChanged(status()); } @@ -285,13 +289,16 @@ void SoundCloudTrack::onUnfavouriteRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { setFavourite(false); setFavouriteCount(favouriteCount() - 1); + Logger::log("SoundCloudTrack::onUnfavouriteRequestFinished(). Video unfavourited. ID: " + id(), + Logger::MediumVerbosity); emit SoundCloud::instance()->trackUnfavourited(this); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudTrack::onUnfavouriteRequestFinished OK" << id(); -#endif + } + else { + Logger::log("SoundCloudTrack::onUnfavouriteRequestFinished(). Error: " + errorString()); } disconnect(m_request, SIGNAL(finished()), this, SLOT(onUnfavouriteRequestFinished())); + emit changed(); emit statusChanged(status()); } diff --git a/app/src/soundcloud/soundcloudtrack.h b/app/src/soundcloud/soundcloudtrack.h index dec7529..73550a4 100644 --- a/app/src/soundcloud/soundcloudtrack.h +++ b/app/src/soundcloud/soundcloudtrack.h @@ -30,17 +30,16 @@ class SoundCloudTrack : public MKTrack Q_PROPERTY(qint64 favouriteCount READ favouriteCount NOTIFY favouriteCountChanged) Q_PROPERTY(QString sharing READ sharing NOTIFY sharingChanged) Q_PROPERTY(QSoundCloud::ResourcesRequest::Status status READ status NOTIFY statusChanged) - Q_PROPERTY(bool streamable READ isStreamable NOTIFY streamableChanged) Q_PROPERTY(QUrl waveformUrl READ waveformUrl NOTIFY waveformUrlChanged) public: explicit SoundCloudTrack(QObject *parent = 0); explicit SoundCloudTrack(const QString &id, QObject *parent = 0); explicit SoundCloudTrack(const QVariantMap &track, QObject *parent = 0); - explicit SoundCloudTrack(SoundCloudTrack *track, QObject *parent = 0); + explicit SoundCloudTrack(const SoundCloudTrack *track, QObject *parent = 0); bool isCommentable() const; - + QString errorString() const; bool isFavourite() const; @@ -49,9 +48,7 @@ class SoundCloudTrack : public MKTrack QString sharing() const; QSoundCloud::ResourcesRequest::Status status() const; - - bool isStreamable() const; - + QUrl waveformUrl() const; Q_INVOKABLE void loadTrack(const QString &id); @@ -61,20 +58,7 @@ class SoundCloudTrack : public MKTrack public Q_SLOTS: void favourite(); void unfavourite(); - -private: - void initRequest(); - - void setCommentable(bool c); - - void setFavourite(bool f); - void setFavouriteCount(qint64 c); - - void setSharing(const QString &s); - - void setStreamable(bool s); - - void setWaveformUrl(const QUrl &u); + void cancel(); private Q_SLOTS: void onTrackRequestFinished(); @@ -91,12 +75,21 @@ private Q_SLOTS: void sharingChanged(); void statusChanged(QSoundCloud::ResourcesRequest::Status s); - - void streamableChanged(); - + void waveformUrlChanged(); private: + void initRequest(); + + void setCommentable(bool c); + + void setFavourite(bool f); + void setFavouriteCount(qint64 c); + + void setSharing(const QString &s); + + void setWaveformUrl(const QUrl &u); + QSoundCloud::ResourcesRequest *m_request; bool m_commentable; @@ -105,9 +98,7 @@ private Q_SLOTS: qint64 m_favouriteCount; QString m_sharing; - - bool m_streamable; - + QUrl m_waveformUrl; }; diff --git a/app/src/soundcloud/soundcloudtrackmodel.cpp b/app/src/soundcloud/soundcloudtrackmodel.cpp index 866e30d..69f8008 100644 --- a/app/src/soundcloud/soundcloudtrackmodel.cpp +++ b/app/src/soundcloud/soundcloudtrackmodel.cpp @@ -15,11 +15,9 @@ */ #include "soundcloudtrackmodel.h" +#include "logger.h" #include "soundcloud.h" #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif SoundCloudTrackModel::SoundCloudTrackModel(QObject *parent) : QAbstractListModel(parent), @@ -33,6 +31,7 @@ SoundCloudTrackModel::SoundCloudTrackModel(QObject *parent) : m_roles[DownloadableRole] = "downloadable"; m_roles[DurationRole] = "duration"; m_roles[DurationStringRole] = "durationString"; + m_roles[ErrorStringRole] = "errorString"; m_roles[FavouriteRole] = "favourited"; m_roles[FavouriteCountRole] = "favouriteCount"; m_roles[GenreRole] = "genre"; @@ -42,7 +41,7 @@ SoundCloudTrackModel::SoundCloudTrackModel(QObject *parent) : m_roles[SharingRole] = "sharing"; m_roles[SizeRole] = "size"; m_roles[SizeStringRole] = "sizeString"; - m_roles[StreamableRole] = "streamable"; + m_roles[StatusRole] = "status"; m_roles[ThumbnailUrlRole] = "thumbnailUrl"; m_roles[TitleRole] = "title"; m_roles[UrlRole] = "url"; @@ -50,10 +49,10 @@ SoundCloudTrackModel::SoundCloudTrackModel(QObject *parent) : #if QT_VERSION < 0x050000 setRoleNames(m_roles); #endif - m_request->setClientId(SoundCloud::instance()->clientId()); - m_request->setClientSecret(SoundCloud::instance()->clientSecret()); - m_request->setAccessToken(SoundCloud::instance()->accessToken()); - m_request->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_request->setClientId(SoundCloud::clientId()); + m_request->setClientSecret(SoundCloud::clientSecret()); + m_request->setAccessToken(SoundCloud::accessToken()); + m_request->setRefreshToken(SoundCloud::refreshToken()); connect(m_request, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_request, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); @@ -74,16 +73,20 @@ QHash SoundCloudTrackModel::roleNames() const { } #endif -int SoundCloudTrackModel::rowCount(const QModelIndex &) const { - return m_items.size(); +int SoundCloudTrackModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_items.size(); +} + +int SoundCloudTrackModel::columnCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : 4; } -bool SoundCloudTrackModel::canFetchMore(const QModelIndex &) const { - return (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); +bool SoundCloudTrackModel::canFetchMore(const QModelIndex &parent) const { + return (!parent.isValid()) && (status() != QSoundCloud::ResourcesRequest::Loading) && (!m_nextHref.isEmpty()); } -void SoundCloudTrackModel::fetchMore(const QModelIndex &) { - if (!canFetchMore()) { +void SoundCloudTrackModel::fetchMore(const QModelIndex &parent) { + if (!canFetchMore(parent)) { return; } @@ -91,8 +94,42 @@ void SoundCloudTrackModel::fetchMore(const QModelIndex &) { emit statusChanged(status()); } +QVariant SoundCloudTrackModel::headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { + return QVariant(); + } + + switch (section) { + case 0: + return tr("Title"); + case 1: + return tr("Artist"); + case 2: + return tr("Genre"); + case 3: + return tr("Duration"); + default: + return QVariant(); + } +} + QVariant SoundCloudTrackModel::data(const QModelIndex &index, int role) const { - if (SoundCloudTrack *track = get(index.row())) { + if (const SoundCloudTrack *track = get(index.row())) { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return track->title(); + case 1: + return track->artist(); + case 2: + return track->genre(); + case 3: + return track->durationString(); + default: + return QVariant(); + } + } + return track->property(m_roles[role]); } @@ -102,7 +139,7 @@ QVariant SoundCloudTrackModel::data(const QModelIndex &index, int role) const { QMap SoundCloudTrackModel::itemData(const QModelIndex &index) const { QMap map; - if (SoundCloudTrack *track = get(index.row())) { + if (const SoundCloudTrack *track = get(index.row())) { QHashIterator iterator(m_roles); while (iterator.hasNext()) { @@ -115,7 +152,7 @@ QMap SoundCloudTrackModel::itemData(const QModelIndex &index) con } QVariant SoundCloudTrackModel::data(int row, const QByteArray &role) const { - if (SoundCloudTrack *track = get(row)) { + if (const SoundCloudTrack *track = get(row)) { return track->property(role); } @@ -125,8 +162,8 @@ QVariant SoundCloudTrackModel::data(int row, const QByteArray &role) const { QVariantMap SoundCloudTrackModel::itemData(int row) const { QVariantMap map; - if (SoundCloudTrack *track = get(row)) { - foreach (QByteArray role, m_roles.values()) { + if (const SoundCloudTrack *track = get(row)) { + foreach (const QByteArray &role, m_roles.values()) { map[role] = track->property(role); } } @@ -147,6 +184,7 @@ void SoundCloudTrackModel::get(const QString &resourcePath, const QVariantMap &f return; } + Logger::log("SoundCloudTrackModel::get(). Resource path: " + resourcePath, Logger::HighVerbosity); clear(); m_resourcePath = resourcePath; m_filters = filters; @@ -184,6 +222,11 @@ void SoundCloudTrackModel::cancel() { } void SoundCloudTrackModel::reload() { + if (status() == QSoundCloud::ResourcesRequest::Loading) { + return; + } + + Logger::log("SoundCloudTrackModel::reload(). Resource path: " + m_resourcePath, Logger::HighVerbosity); clear(); m_request->get(m_resourcePath, m_filters); emit statusChanged(status()); @@ -191,6 +234,7 @@ void SoundCloudTrackModel::reload() { void SoundCloudTrackModel::append(SoundCloudTrack *track) { beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items << track; endInsertRows(); } @@ -198,6 +242,7 @@ void SoundCloudTrackModel::append(SoundCloudTrack *track) { void SoundCloudTrackModel::insert(int row, SoundCloudTrack *track) { if ((row >= 0) && (row < m_items.size())) { beginInsertRows(QModelIndex(), row, row); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); m_items.insert(row, track); endInsertRows(); } @@ -214,42 +259,49 @@ void SoundCloudTrackModel::remove(int row) { } } +void SoundCloudTrackModel::onItemChanged() { + const int row = m_items.indexOf(qobject_cast(sender())); + + if (row != -1) { + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } +} + void SoundCloudTrackModel::onRequestFinished() { if (m_request->status() == QSoundCloud::ResourcesRequest::Ready) { - QVariantMap result = m_request->result().toMap(); + const QVariantMap result = m_request->result().toMap(); if (!result.isEmpty()) { m_nextHref = result.value("next_href").toString().section(QSoundCloud::API_URL, -1); - QVariantList list = result.value(m_resourcePath.contains("/playlists/") ? "tracks" : "collection").toList(); + const QVariantList list = result.value(m_resourcePath.contains("/playlists/") ? "tracks" : "collection").toList(); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + list.size() - 1); - foreach (QVariant item, list) { - m_items << new SoundCloudTrack(item.toMap(), this); + foreach (const QVariant &item, list) { + SoundCloudTrack *track = new SoundCloudTrack(item.toMap(), this); + connect(track, SIGNAL(changed()), this, SLOT(onItemChanged())); + m_items << track; } endInsertRows(); emit countChanged(rowCount()); } } + else { + Logger::log("SoundCloudTrackModel::onRequestFinished(). Error: " + errorString()); + } emit statusChanged(status()); } void SoundCloudTrackModel::onTrackFavourited(SoundCloudTrack *track) { insert(0, new SoundCloudTrack(track, this)); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudTrackModel::onTrackFavourited" << track->id(); -#endif } void SoundCloudTrackModel::onTrackUnfavourited(SoundCloudTrack *track) { - QModelIndexList list = match(index(0), IdRole, track->id(), 1, Qt::MatchExactly); + const QModelIndexList list = match(index(0), IdRole, track->id(), 1, Qt::MatchExactly); if (!list.isEmpty()) { remove(list.first().row()); } -#ifdef MUSIKLOUD_DEBUG - qDebug() << "SoundCloudTrackModel::onTrackUnfavourited" << track->id(); -#endif } diff --git a/app/src/soundcloud/soundcloudtrackmodel.h b/app/src/soundcloud/soundcloudtrackmodel.h index 28f2949..a2fc2a1 100644 --- a/app/src/soundcloud/soundcloudtrackmodel.h +++ b/app/src/soundcloud/soundcloudtrackmodel.h @@ -41,6 +41,7 @@ class SoundCloudTrackModel : public QAbstractListModel DownloadableRole, DurationRole, DurationStringRole, + ErrorStringRole, FavouriteRole, FavouriteCountRole, GenreRole, @@ -50,7 +51,7 @@ class SoundCloudTrackModel : public QAbstractListModel SharingRole, SizeRole, SizeStringRole, - StreamableRole, + StatusRole, ThumbnailUrlRole, TitleRole, UrlRole, @@ -67,10 +68,13 @@ class SoundCloudTrackModel : public QAbstractListModel QHash roleNames() const; #endif int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; @@ -86,21 +90,22 @@ public Q_SLOTS: void cancel(); void reload(); -private: - void append(SoundCloudTrack *track); - void insert(int row, SoundCloudTrack *track); - void remove(int row); - private Q_SLOTS: + void onItemChanged(); void onRequestFinished(); + void onTrackFavourited(SoundCloudTrack *track); void onTrackUnfavourited(SoundCloudTrack *track); Q_SIGNALS: - void countChanged(int c); + void countChanged(int count); void statusChanged(QSoundCloud::ResourcesRequest::Status s); private: + void append(SoundCloudTrack *track); + void insert(int row, SoundCloudTrack *track); + void remove(int row); + QSoundCloud::ResourcesRequest *m_request; QString m_resourcePath; diff --git a/app/src/soundcloud/soundcloudtransfer.cpp b/app/src/soundcloud/soundcloudtransfer.cpp index eb91ab4..c0dd694 100644 --- a/app/src/soundcloud/soundcloudtransfer.cpp +++ b/app/src/soundcloud/soundcloudtransfer.cpp @@ -15,8 +15,10 @@ */ #include "soundcloudtransfer.h" +#include "logger.h" #include "soundcloud.h" #include +#include SoundCloudTransfer::SoundCloudTransfer(QObject *parent) : Transfer(parent), @@ -27,31 +29,40 @@ SoundCloudTransfer::SoundCloudTransfer(QObject *parent) : void SoundCloudTransfer::listStreams() { if (!m_streamsRequest) { m_streamsRequest = new QSoundCloud::StreamsRequest(this); - m_streamsRequest->setClientId(SoundCloud::instance()->clientId()); - m_streamsRequest->setClientSecret(SoundCloud::instance()->clientSecret()); - m_streamsRequest->setAccessToken(SoundCloud::instance()->accessToken()); - m_streamsRequest->setRefreshToken(SoundCloud::instance()->refreshToken()); + m_streamsRequest->setClientId(SoundCloud::clientId()); + m_streamsRequest->setClientSecret(SoundCloud::clientSecret()); + m_streamsRequest->setAccessToken(SoundCloud::accessToken()); + m_streamsRequest->setRefreshToken(SoundCloud::refreshToken()); connect(m_streamsRequest, SIGNAL(accessTokenChanged(QString)), SoundCloud::instance(), SLOT(setAccessToken(QString))); connect(m_streamsRequest, SIGNAL(refreshTokenChanged(QString)), SoundCloud::instance(), SLOT(setRefreshToken(QString))); connect(m_streamsRequest, SIGNAL(finished()), this, SLOT(onStreamsRequestFinished())); } - m_streamsRequest->get(resourceId()); + m_streamsRequest->get(trackId()); } void SoundCloudTransfer::onStreamsRequestFinished() { if (m_streamsRequest->status() == QSoundCloud::StreamsRequest::Ready) { - QVariantList list = m_streamsRequest->result().toList(); - - foreach (QVariant v, list) { - QVariantMap stream = v.toMap(); + const QVariantList list = m_streamsRequest->result().toList(); + foreach (const QVariant &v, list) { + const QVariantMap stream = v.toMap(); + if (stream.value("id") == streamId()) { - QString ext = stream.value("ext").toString(); + const QFileInfo info(downloadPath() + fileName()); + QString suffix = info.suffix(); - if (!ext.isEmpty()) { - setFileExtension(ext); + if (suffix.isEmpty()) { + suffix = stream.value("ext").toString(); + + if (!suffix.isEmpty()) { + if (!suffix.startsWith(".")) { + suffix.prepend("."); + } + + setFileName(info.completeBaseName() + suffix); + } } startDownload(stream.value("url").toString()); @@ -59,6 +70,9 @@ void SoundCloudTransfer::onStreamsRequestFinished() { } } } + else { + Logger::log("SoundCloudTransfer::onStreamsRequestFinished(). Error: " + m_streamsRequest->errorString()); + } setErrorString(tr("No stream URL found")); setStatus(Failed); diff --git a/app/src/symbian/database.h b/app/src/symbian/database.h new file mode 100644 index 0000000..a8da7cd --- /dev/null +++ b/app/src/symbian/database.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DATABASE_H +#define DATABASE_H + +#include "definitions.h" +#include "logger.h" +#include +#include +#include + +inline void initDatabase() { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName("musikloud2.db"); + + if (!db.isOpen()) { + db.open(); + } + + QSqlQuery query = db.exec("CREATE TABLE IF NOT EXISTS soundcloudAccounts (userId TEXT UNIQUE, username TEXT, \ + accessToken TEXT, refreshToken TEXT, scopes TEXT)"); + + if (query.lastError().isValid()) { + Logger::log("initDatabase: database error: " + query.lastError().text()); + } +} + +inline QSqlDatabase getDatabase() { + QSqlDatabase db = QSqlDatabase::database(); + + if (!db.isOpen()) { + db.open(); + } + + return db; +} + +#endif // DATABASE_H diff --git a/app/src/symbian/definitions.h b/app/src/symbian/definitions.h new file mode 100644 index 0000000..54e7e59 --- /dev/null +++ b/app/src/symbian/definitions.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +#include +#include +#include +#include + +// Home +static const QString ROOT_PATH("C:/"); +static const QString HOME_PATH("E:/"); +static const QString CARD_PATH("F:/"); + +// Plugins +static const QStringList PLUGIN_PATHS = QStringList() << QString(ROOT_PATH + "musikloud2/plugins/") + << QString(HOME_PATH + "musikloud2/plugins/") + << QString(CARD_PATH + "musikloud2/plugins/"); + +static const QString LIB_PREFIX; +static const QString LIB_SUFFIX(".qtplugin"); + +// Config +static const QString APP_CONFIG_PATH(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.config/MusiKloud2/"); +static const QString PLUGIN_CONFIG_PATH(APP_CONFIG_PATH + "plugins/"); + +// Downloads +static const QString DOWNLOAD_PATH(HOME_PATH + "musikloud2/"); +static const QRegExp ILLEGAL_FILENAME_CHARS_RE("[\"@&~=\\/:?#!|<>*^]"); + +// Content +static const int LARGE_THUMBNAIL_SIZE = 500; +static const int THUMBNAIL_SIZE = 80; + +// Network +static const int DOWNLOAD_BUFFER_SIZE = 512000; +static const int MAX_CONCURRENT_TRANSFERS = 4; +static const int MAX_REDIRECTS = 8; +static const int MAX_RESULTS = 20; +static const QByteArray USER_AGENT("Wget/1.13.4 (linux-gnu)"); + +// Version +static const QString VERSION_NUMBER("0.2.0"); + +#endif // DEFINITIONS_H diff --git a/app/src/symbian/logger.cpp b/app/src/symbian/logger.cpp new file mode 100644 index 0000000..fa73d02 --- /dev/null +++ b/app/src/symbian/logger.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "logger.h" +#include +#include +#include +#include + +QString Logger::fn; +int Logger::vb = 0; + +Logger::Logger(QObject *parent) : + QObject(parent) +{ +} + +QString Logger::fileName() { + return fn; +} + +void Logger::setFileName(const QString &f) { + fn = f; +} + +QString Logger::text() { + QString output; + + if (!fn.isEmpty()) { + QFile file(fn); + + if (file.open(QFile::ReadOnly | QFile::Text)) { + QTextStream stream(&file); + + while (!stream.atEnd()) { + output.append(stream.readLine()); + output.append("\n"); + } + + file.close(); + } + } + + return output; +} + +int Logger::verbosity() { + return vb; +} + +void Logger::setVerbosity(int v) { + vb = v; +} + +void Logger::clear() { + if (!fn.isEmpty()) { + QFile::remove(fn); + } +} + +void Logger::log(const QString &message, int minimumVerbosity) { + if (minimumVerbosity <= vb) { + const QString date = QDateTime::currentDateTime().toString(Qt::ISODate); + QString output = QString("%1: %2\n").arg(date).arg(message); + + if (!fn.isEmpty()) { + QFile file(fn); + + if (file.open(QFile::Append | QFile::Text)) { + QTextStream stream(&file); + stream << output; + file.close(); + return; + } + + output = tr("%1: Cannot write to log file '%2'. Error: %3").arg(date).arg(fn).arg(file.errorString()); + } + + qDebug() << output.toUtf8().constData(); + } +} + +void Logger::log(const QList &errors, int minimumVerbosity) { + foreach (const QDeclarativeError &error, errors) { + log(error.toString(), minimumVerbosity); + } +} diff --git a/app/src/symbian/logger.h b/app/src/symbian/logger.h new file mode 100644 index 0000000..a90133e --- /dev/null +++ b/app/src/symbian/logger.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include + +class Logger : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName WRITE setFileName) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(int verbosity READ verbosity WRITE setVerbosity) + + Q_ENUMS(Verbosity) + +public: + enum Verbosity { + NoVerbosity = 0, + LowestVerbosity, + LowVerbosity, + MediumVerbosity, + HighVerbosity, + HighestVerbosity + }; + + explicit Logger(QObject *parent = 0); + + static QString fileName(); + + static QString text(); + + static int verbosity(); + +public Q_SLOTS: + static void setFileName(const QString &f); + + static void setVerbosity(int v); + + static void clear(); + + static void log(const QString &message, int minimumVerbosity = LowestVerbosity); + static void log(const QList &errors, int minimumVerbosity = LowestVerbosity); + +private: + static QString fn; + static int vb; +}; + +#endif // LOGGER_H diff --git a/app/src/harmattan/main.cpp b/app/src/symbian/main.cpp similarity index 58% rename from app/src/harmattan/main.cpp rename to app/src/symbian/main.cpp index 8f45268..825b32a 100644 --- a/app/src/harmattan/main.cpp +++ b/app/src/symbian/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,82 +14,79 @@ * along with this program. If not, see . */ -#include "activecolormodel.h" #include "audioplayer.h" #include "categorymodel.h" #include "categorynamemodel.h" #include "clipboard.h" #include "concurrenttransfersmodel.h" -#include "cookiejar.h" #include "database.h" -#include "dbusservice.h" #include "definitions.h" +#include "loggerverbositymodel.h" #include "maskeditem.h" -#include "networkaccessmanagerfactory.h" +#include "mediakeycaptureitem.h" #include "networkproxytypemodel.h" #include "pluginartistmodel.h" #include "plugincategorymodel.h" #include "plugincommentmodel.h" +#include "pluginconfigmodel.h" +#include "pluginmanager.h" #include "pluginnavmodel.h" #include "pluginplaylistmodel.h" #include "pluginsearchtypemodel.h" -#include "pluginsettingsmodel.h" +#include "pluginsettings.h" #include "pluginstreammodel.h" #include "plugintrackmodel.h" #include "resources.h" -#include "resourcesplugins.h" -#include "resourcesrequest.h" #include "screenorientationmodel.h" #include "searchhistorymodel.h" #include "servicemodel.h" #include "settings.h" -#include "shareui.h" #include "soundcloud.h" #include "soundcloudaccountmodel.h" #include "soundcloudartistmodel.h" #include "soundcloudcommentmodel.h" -#include "soundcloudconnectionmodel.h" #include "soundcloudnavmodel.h" #include "soundcloudplaylistmodel.h" #include "soundcloudsearchtypemodel.h" #include "soundcloudstreammodel.h" #include "soundcloudtrackmodel.h" +#include "trackmodel.h" +#include "transfermodel.h" +#include "transferprioritymodel.h" #include "transfers.h" #include "utils.h" #include #include #include -#include #include #include -#include #include +#include #include -#include +#include +#include -inline void registerTypes() { - qmlRegisterType("MusiKloud", 2, 0, "ActiveColorModel"); - qmlRegisterType("MusiKloud", 2, 0, "AudioPlayer"); +inline void registerTypes() { qmlRegisterType("MusiKloud", 2, 0, "CategoryModel"); qmlRegisterType("MusiKloud", 2, 0, "CategoryNameModel"); - qmlRegisterType("MusiKloud", 2, 0, "ConcurrentTransfersModel"); + qmlRegisterType("MusiKloud", 2, 0, "ConcurrentTransfersModel"); + qmlRegisterType("MusiKloud", 2, 0, "LoggerVerbosityModel"); qmlRegisterType("MusiKloud", 2, 0, "MaskedItem"); - qmlRegisterType("MusiKloud", 2, 0, "Track"); qmlRegisterType("MusiKloud", 2, 0, "NetworkProxyTypeModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginArtist"); qmlRegisterType("MusiKloud", 2, 0, "PluginArtistModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginCategoryModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginComment"); qmlRegisterType("MusiKloud", 2, 0, "PluginCommentModel"); + qmlRegisterType("MusiKloud", 2, 0, "PluginConfigModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginNavModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginPlaylist"); qmlRegisterType("MusiKloud", 2, 0, "PluginPlaylistModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginSearchTypeModel"); - qmlRegisterType("MusiKloud", 2, 0, "PluginSettingsModel"); + qmlRegisterType("MusiKloud", 2, 0, "PluginSettings"); qmlRegisterType("MusiKloud", 2, 0, "PluginStreamModel"); qmlRegisterType("MusiKloud", 2, 0, "PluginTrack"); qmlRegisterType("MusiKloud", 2, 0, "PluginTrackModel"); - qmlRegisterType("MusiKloud", 2, 0, "ResourcesRequest"); qmlRegisterType("MusiKloud", 2, 0, "ScreenOrientationModel"); qmlRegisterType("MusiKloud", 2, 0, "SearchHistoryModel"); qmlRegisterType("MusiKloud", 2, 0, "SelectionModel"); @@ -99,7 +96,6 @@ inline void registerTypes() { qmlRegisterType("MusiKloud", 2, 0, "SoundCloudArtistModel"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudComment"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudCommentModel"); - qmlRegisterType("MusiKloud", 2, 0, "SoundCloudConnectionModel"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudNavModel"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudPlaylist"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudPlaylistModel"); @@ -107,59 +103,82 @@ inline void registerTypes() { qmlRegisterType("MusiKloud", 2, 0, "SoundCloudStreamModel"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudTrack"); qmlRegisterType("MusiKloud", 2, 0, "SoundCloudTrackModel"); - qmlRegisterType("MusiKloud", 2, 0, "TrackModel"); + qmlRegisterType("MusiKloud", 2, 0, "TransferModel"); + qmlRegisterType("MusiKloud", 2, 0, "TransferPriorityModel"); qmlRegisterType("QSoundCloud", 1, 0, "AuthenticationRequest"); qmlRegisterType("QSoundCloud", 1, 0, "ResourcesRequest"); qmlRegisterType("QSoundCloud", 1, 0, "StreamsRequest"); + qmlRegisterUncreatableType("MusiKloud", 2, 0, "AudioPlayer", ""); + qmlRegisterUncreatableType("MusiKloud", 2, 0, "AudioPlayerMetaData", ""); + qmlRegisterUncreatableType("MusiKloud", 2, 0, "MKTrack", ""); + qmlRegisterUncreatableType("MusiKloud", 2, 0, "ResourcesRequest", ""); + qmlRegisterUncreatableType("MusiKloud", 2, 0, "TrackModel", ""); qmlRegisterUncreatableType("MusiKloud", 2, 0, "Transfer", ""); + + qRegisterMetaType("ResourcesRequest::Status"); } Q_DECL_EXPORT int main(int argc, char *argv[]) { - QScopedPointer app(MDeclarativeCache::qApplication(argc, argv)); - app.data()->setOrganizationName("MusiKloud2"); - app.data()->setApplicationName("MusiKloud2"); - app.data()->setApplicationVersion(VERSION_NUMBER); + QApplication app(argc, argv); + app.setOrganizationName("MusiKloud2"); + app.setApplicationName("MusiKloud2"); - Settings settings; - Clipboard clipboard; - DBusService dbus; - NetworkAccessManagerFactory factory; + QSslConfiguration config = QSslConfiguration::defaultConfiguration(); + config.setProtocol(QSsl::TlsV1); + config.setPeerVerifyMode(QSslSocket::VerifyNone); + QSslConfiguration::setDefaultConfiguration(config); + + QScopedPointer settings(Settings::instance()); + QScopedPointer player(AudioPlayer::instance()); + QScopedPointer clipboard(Clipboard::instance()); + QScopedPointer plugins(PluginManager::instance()); + QScopedPointer soundcloud(SoundCloud::instance()); + QScopedPointer transfers(Transfers::instance()); + + Logger logger; + MediakeyCaptureItem volumeKeys; Resources resources; - ResourcesPlugins plugins; - ShareUi shareui; - SoundCloud soundcloud; - Transfers transfers; Utils utils; - + + Logger::setFileName(Settings::loggerFileName()); + Logger::setVerbosity(Settings::loggerVerbosity()); + initDatabase(); registerTypes(); - plugins.load(); - settings.setNetworkProxy(); + Settings::setNetworkProxy(); + SoundCloud::init(); + player.data()->setSleepTimerDuration(Settings::sleepTimerDuration()); + volumeKeys.setVolume(player.data()->volume()); QDeclarativeView view; QDeclarativeContext *context = view.rootContext(); - context->setContextProperty("Clipboard", &clipboard); - context->setContextProperty("CookieJar", factory.cookieJar()); - context->setContextProperty("DBus", &dbus); - context->setContextProperty("MainWindow", &view); - context->setContextProperty("Plugins", &plugins); - context->setContextProperty("Resources", &resources); - context->setContextProperty("Settings", &settings); - context->setContextProperty("ShareUi", &shareui); - context->setContextProperty("SoundCloud", &soundcloud); - context->setContextProperty("Transfers", &transfers); - context->setContextProperty("Utils", &utils); + context->setContextProperty("clipboard", clipboard.data()); + context->setContextProperty("logger", &logger); + context->setContextProperty("mkresources", &resources); + context->setContextProperty("player", player.data()); + context->setContextProperty("plugins", plugins.data()); + context->setContextProperty("settings", settings.data()); + context->setContextProperty("soundcloud", soundcloud.data()); + context->setContextProperty("transfers", transfers.data()); + context->setContextProperty("utils", &utils); + context->setContextProperty("volumekeys", &volumeKeys); context->setContextProperty("MAX_RESULTS", MAX_RESULTS); context->setContextProperty("VERSION_NUMBER", VERSION_NUMBER); - - view.engine()->setNetworkAccessManagerFactory(&factory); - - view.setViewport(new QGLWidget); - view.setSource(QUrl::fromLocalFile("/opt/musikloud2/qml/main.qml")); + + view.setSource(QUrl::fromLocalFile(app.applicationDirPath() + "/qml/main.qml")); view.showFullScreen(); + Logger::log(view.errors()); + + QObject::connect(view.engine(), SIGNAL(warnings(QList)), + &logger, SLOT(log(QList))); + QObject::connect(settings.data(), SIGNAL(loggerFileNameChanged(QString)), &logger, SLOT(setFileName(QString))); + QObject::connect(settings.data(), SIGNAL(loggerVerbosityChanged(int)), &logger, SLOT(setVerbosity(int))); + QObject::connect(settings.data(), SIGNAL(sleepTimerDurationChanged(int)), + player.data(), SLOT(setSleepTimerDuration(int))); + QObject::connect(&volumeKeys, SIGNAL(volumeChanged(int)), player.data(), SLOT(setVolume(int))); - return app.data()->exec(); + return app.exec(); } diff --git a/app/src/harmattan/maskeditem.cpp b/app/src/symbian/maskeditem.cpp similarity index 100% rename from app/src/harmattan/maskeditem.cpp rename to app/src/symbian/maskeditem.cpp diff --git a/app/src/harmattan/maskeditem.h b/app/src/symbian/maskeditem.h similarity index 100% rename from app/src/harmattan/maskeditem.h rename to app/src/symbian/maskeditem.h diff --git a/app/src/harmattan/maskeffect.cpp b/app/src/symbian/maskeffect.cpp similarity index 100% rename from app/src/harmattan/maskeffect.cpp rename to app/src/symbian/maskeffect.cpp diff --git a/app/src/harmattan/maskeffect.h b/app/src/symbian/maskeffect.h similarity index 100% rename from app/src/harmattan/maskeffect.h rename to app/src/symbian/maskeffect.h diff --git a/app/src/symbian/mediakeycaptureitem.cpp b/app/src/symbian/mediakeycaptureitem.cpp new file mode 100644 index 0000000..09e6004 --- /dev/null +++ b/app/src/symbian/mediakeycaptureitem.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mediakeycaptureitem.h" +#include + +// A private class to access Symbian RemCon API +class MediakeyCaptureItemPrivate : public QObject, public MRemConCoreApiTargetObserver +{ +public: + MediakeyCaptureItemPrivate(MediakeyCaptureItem *parent); + ~MediakeyCaptureItemPrivate(); + virtual void MrccatoCommand(TRemConCoreApiOperationId aOperationId, + TRemConCoreApiButtonAction aButtonAct); + +public: + QTimer *m_timer; + int m_volume; + bool m_volumeDownPressed; + bool m_volumeUpPressed; + +private: + CRemConInterfaceSelector* iInterfaceSelector; + CRemConCoreApiTarget* iCoreTarget; + MediakeyCaptureItem *d_ptr; +}; + +// Consructor +MediakeyCaptureItem::MediakeyCaptureItem(QObject *parent): QObject(parent) +{ + d_ptr = new MediakeyCaptureItemPrivate(this); + d_ptr->m_timer = new QTimer(this); + d_ptr->m_timer->setInterval(150); + d_ptr->m_timer->setSingleShot(false); + connect(d_ptr->m_timer, SIGNAL(timeout()), this, SLOT(onTimerTriggered())); +} + +// Constructor +MediakeyCaptureItemPrivate::MediakeyCaptureItemPrivate(MediakeyCaptureItem *parent): d_ptr(parent), m_volume(50) +{ + QT_TRAP_THROWING(iInterfaceSelector = CRemConInterfaceSelector::NewL()); + QT_TRAP_THROWING(iCoreTarget = CRemConCoreApiTarget::NewL(*iInterfaceSelector, *this)); + iInterfaceSelector->OpenTargetL(); +} + +// Destructor +MediakeyCaptureItemPrivate::~MediakeyCaptureItemPrivate(){ + delete iInterfaceSelector; + delete iCoreTarget; +} + +// Callback when media keys are pressed +void MediakeyCaptureItemPrivate::MrccatoCommand(TRemConCoreApiOperationId aOperationId, + TRemConCoreApiButtonAction aButtonAct) +{ + if (aButtonAct == ERemConCoreApiButtonRelease) { + m_timer->stop(); + m_volumeDownPressed = false; + m_volumeUpPressed = false; + return; + } + + switch (aOperationId) { + case ERemConCoreApiVolumeUp: + m_volumeUpPressed = true; + m_volumeDownPressed = false; + emit d_ptr->pressed(); + d_ptr->increaseVolume(); + if (aButtonAct == ERemConCoreApiButtonPress) m_timer->start(); + break; + case ERemConCoreApiVolumeDown: + m_volumeDownPressed = true; + m_volumeUpPressed = false; + emit d_ptr->pressed(); + d_ptr->decreaseVolume(); + if (aButtonAct == ERemConCoreApiButtonPress) m_timer->start(); + break; + default: + m_volumeDownPressed = false; + m_volumeUpPressed = false; + return; + } +} + +int MediakeyCaptureItem::volume() const { + return d_ptr->m_volume; +} + +void MediakeyCaptureItem::setVolume(int volume) { + if (volume != d_ptr->m_volume) { + d_ptr->m_volume = volume; + emit volumeChanged(volume); + } +} + +void MediakeyCaptureItem::increaseVolume() { + if (volume() < 95) { + setVolume(volume() + 5); + } + else { + setVolume(100); + } +} + +void MediakeyCaptureItem::decreaseVolume() { + if (volume() > 5) { + setVolume(volume() - 5); + } + else { + setVolume(0); + } +} + +void MediakeyCaptureItem::onTimerTriggered() { + emit pressed(); + + if (d_ptr->m_volumeUpPressed) { + increaseVolume(); + } + else if (d_ptr->m_volumeDownPressed) { + decreaseVolume(); + } +} diff --git a/app/src/symbian/mediakeycaptureitem.h b/app/src/symbian/mediakeycaptureitem.h new file mode 100644 index 0000000..7cd7505 --- /dev/null +++ b/app/src/symbian/mediakeycaptureitem.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MediakeyCaptureItem_H +#define MediakeyCaptureItem_H + +#include +#include +#include +#include +#include + +class QTimer; +class MediakeyCaptureItemPrivate; +class MediakeyCaptureItem : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int volume READ volume NOTIFY volumeChanged) + +public: + MediakeyCaptureItem(QObject *parent = 0); + + int volume() const; + void setVolume(int volume); + +private: + void increaseVolume(); + void decreaseVolume(); + +private Q_SLOTS: + void onTimerTriggered(); + +Q_SIGNALS: + void pressed(); + void volumeChanged(int volume); + +private: + MediakeyCaptureItemPrivate *d_ptr; + + friend class MediakeyCaptureItemPrivate; +}; + +QML_DECLARE_TYPE(MediakeyCaptureItem) + +#endif // MediakeyCaptureItem_H diff --git a/app/src/harmattan/qml/AboutPage.qml b/app/src/symbian/qml/AboutPage.qml similarity index 50% rename from app/src/harmattan/qml/AboutPage.qml rename to app/src/symbian/qml/AboutPage.qml index 553e1a5..5ba8b61 100644 --- a/app/src/harmattan/qml/AboutPage.qml +++ b/app/src/symbian/qml/AboutPage.qml @@ -1,38 +1,36 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as + * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI +import com.nokia.symbian 1.1 MyPage { id: root - + title: qsTr("About") tools: ToolBarLayout { - - BackToolIcon {} + BackToolButton {} } - - Flickable { - id: flicker - + + KeyNavFlickable { + id: flickable + anchors.fill: parent - contentHeight: column.height + UI.PADDING_DOUBLE * 2 - + contentHeight: column.height + platformStyle.paddingLarge + Column { id: column @@ -40,21 +38,17 @@ MyPage { left: parent.left right: parent.right top: parent.top - margins: UI.PADDING_DOUBLE - } - spacing: UI.PADDING_DOUBLE - - PageHeader { - x: -UI.PADDING_DOUBLE - width: parent.width + UI.PADDING_DOUBLE - title: root.title + margins: platformStyle.paddingLarge } + spacing: platformStyle.paddingLarge Image { id: icon - x: parent.width / 2 - width / 2 - source: "file:///usr/share/icons/hicolor/80x80/apps/musikloud2.png" + x: Math.floor((parent.width - width) / 2) + sourceSize.width: 64 + sourceSize.height: 64 + source: "images/musikloud2.png" } Label { @@ -63,8 +57,7 @@ MyPage { width: parent.width horizontalAlignment: Text.AlignHCenter font.bold: true - font.pixelSize: UI.FONT_XLARGE - color: "white" + font.pixelSize: 32 text: "MusiKloud2 " + VERSION_NUMBER } @@ -74,22 +67,35 @@ MyPage { width: parent.width wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter - color: "white" - text: qsTr("A fully-featured client for SoundCloud that can be extended via plugins.") - + "

© Stuart Howarth 2015" + text: qsTr("A plugin-extensible SoundCloud client and music player.

© Stuart Howarth 2017") } Label { width: parent.width + wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter text: qsTr("Contact") + ": showarth@marxoft.co.uk" - onLinkActivated: Qt.openUrlExternally(link) + + " for Symbian'>showarth@marxoft.co.uk" + onLinkActivated: { + Qt.openUrlExternally(link); + root.accept(); + } + } + + TextDelegate { + id: pluginsButton + + x: -platformStyle.paddingLarge + width: parent.width + platformStyle.paddingLarge * 2 + flickableMode: true + subItemIndicator: true + text: qsTr("Plugins") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("AboutPluginsPage.qml")) } } } - + ScrollDecorator { - flickableItem: flicker + flickableItem: flickable } } diff --git a/app/src/symbian/qml/AboutPluginPage.qml b/app/src/symbian/qml/AboutPluginPage.qml new file mode 100644 index 0000000..a89bf1e --- /dev/null +++ b/app/src/symbian/qml/AboutPluginPage.qml @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyPage { + id: root + + property variant plugin + + title: plugin ? plugin.displayName : "" + tools: ToolBarLayout { + BackToolButton {} + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge * 2 + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + width: parent.width + font.bold: true + text: qsTr("Name") + } + + Label { + width: parent.width + text: plugin ? plugin.displayName : "" + } + + Label { + width: parent.width + font.bold: true + text: qsTr("Plugin type") + } + + Label { + width: parent.width + text: plugin ? plugin.pluginType : "" + } + + Label { + width: parent.width + font.bold: true + text: qsTr("Version") + } + + Label { + width: parent.width + text: plugin ? plugin.version : "" + } + } + } +} diff --git a/app/src/symbian/qml/AboutPluginsPage.qml b/app/src/symbian/qml/AboutPluginsPage.qml new file mode 100644 index 0000000..a9c8acf --- /dev/null +++ b/app/src/symbian/qml/AboutPluginsPage.qml @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Services") + tools: ToolBarLayout { + BackToolButton {} + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginConfigModel { + id: pluginModel + } + delegate: TextDelegate { + text: displayName + subItemIndicator: true + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("AboutPluginPage.qml"), + {plugin: pluginModel.itemData(index)}) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No plugins") + visible: pluginModel.count == 0 + } +} diff --git a/app/src/symbian/qml/AccountDelegate.qml b/app/src/symbian/qml/AccountDelegate.qml new file mode 100644 index 0000000..1f9853a --- /dev/null +++ b/app/src/symbian/qml/AccountDelegate.qml @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyListItem { + id: root + + MyListItemText { + id: label + + anchors { + left: root.paddingItem.left + right: loader.item ? loader.left : root.paddingItem.right + rightMargin: loader.item ? platformStyle.paddingLarge : 0 + verticalCenter: root.paddingItem.verticalCenter + } + verticalAlignment: Text.AlignVCenter + role: "Title" + mode: root.mode + text: username + } + + Loader { + id: loader + + anchors { + right: root.paddingItem.right + verticalCenter: root.paddingItem.verticalCenter + } + sourceComponent: active ? checkbox : undefined + } + + Component { + id: checkbox + + CheckBox { + checked: true + + MouseArea { + z: 1 + anchors.fill: parent + } + } + } +} diff --git a/app/src/desktop-qml/qml/soundcloud/SoundCloudSearchLabel.qml b/app/src/symbian/qml/AddFolderPage.qml similarity index 63% rename from app/src/desktop-qml/qml/soundcloud/SoundCloudSearchLabel.qml rename to app/src/symbian/qml/AddFolderPage.qml index 49c5695..86b6495 100644 --- a/app/src/desktop-qml/qml/soundcloud/SoundCloudSearchLabel.qml +++ b/app/src/symbian/qml/AddFolderPage.qml @@ -1,26 +1,28 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtQuick.Controls 1.1 - -Label { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - text: qsTr("Search SoundCloud for tracks, sets or users using the tools above.") -} +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +FileBrowserPage { + id: root + + title: qsTr("Queue folder") + onAccepted: { + media.addUrl(chosenUrl); + appWindow.pageStack.pop(); + } +} diff --git a/app/src/symbian/qml/AddUrlPage.qml b/app/src/symbian/qml/AddUrlPage.qml new file mode 100644 index 0000000..826d094 --- /dev/null +++ b/app/src/symbian/qml/AddUrlPage.qml @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +TextInputPage { + id: root + + title: qsTr("Queue URL") + label: qsTr("URL") + inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoPredictiveText + onAccepted: { + media.addUrl(text); + appWindow.pageStack.pop(); + } +} diff --git a/app/src/symbian/qml/AppWindow.qml b/app/src/symbian/qml/AppWindow.qml new file mode 100644 index 0000000..c72c277 --- /dev/null +++ b/app/src/symbian/qml/AppWindow.qml @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Window { + id: window + + property bool showStatusBar: true + property bool showToolBar: true + property variant initialPage + property alias pageStack: stack + + objectName: "pageStackWindow" + + MyStatusBar { + id: statusBar + + anchors { + top: parent.top + topMargin: window.showStatusBar ? 0 : -height + } + width: parent.width + title: stack.currentPage ? stack.currentPage.title : "" + } + + Item { + id: contentItem + + objectName: "appWindowContent" + width: parent.width + anchors.top: window.showStatusBar ? statusBar.bottom : parent.top + anchors.bottom: parent.bottom + + ToolBar { + id: toolBar + + anchors.bottom: parent.bottom + states: State { + name: "hide" + when: (!window.showToolBar) || (inputContext.softwareInputPanelVisible) + || (inputContext.customSoftwareInputPanelVisible) + PropertyChanges { target: toolBar; height: 0; opacity: 0.0 } + } + } + + PageStack { + id: stack + + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: toolBar.top + } + clip: true + toolBar: toolBar + } + } + + // event preventer when page transition is active + MouseArea { + anchors.fill: parent + enabled: pageStack.busy + } + + Component.onCompleted: if (initialPage) pageStack.push(initialPage); +} diff --git a/app/src/symbian/qml/ArtistDelegate.qml b/app/src/symbian/qml/ArtistDelegate.qml new file mode 100644 index 0000000..28d880a --- /dev/null +++ b/app/src/symbian/qml/ArtistDelegate.qml @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyListItem { + id: root + + Avatar { + id: avatar + + anchors { + left: root.paddingItem.left + top: root.paddingItem.top + bottom: root.paddingItem.bottom + } + width: height + source: thumbnailUrl + placeholderSource: "images/artist.jpg" + enabled: false + } + + MyListItemText { + id: nameLabel + + anchors { + left: avatar.right + leftMargin: platformStyle.paddingLarge + right: root.paddingItem.right + verticalCenter: root.paddingItem.verticalCenter + } + role: "Title" + mode: root.mode + elide: Text.ElideRight + text: name + } +} diff --git a/app/src/harmattan/qml/Avatar.qml b/app/src/symbian/qml/Avatar.qml similarity index 56% rename from app/src/harmattan/qml/Avatar.qml rename to app/src/symbian/qml/Avatar.qml index bda5f3c..6245006 100644 --- a/app/src/harmattan/qml/Avatar.qml +++ b/app/src/symbian/qml/Avatar.qml @@ -1,34 +1,36 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as + * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 1.1 import MusiKloud 2.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI MaskedItem { id: root - property alias source: avatar.source + property url source + property url placeholderSource property alias sourceSize: avatar.sourceSize + property alias asynchronous: avatar.asynchronous property alias fillMode: avatar.fillMode + property alias smooth: avatar.smooth property alias status: avatar.status signal clicked - - opacity: mouseArea.pressed ? UI.OPACITY_DISABLED : UI.OPACITY_ENABLED + signal pressAndHold + mask: Image { width: root.width height: root.height @@ -44,13 +46,26 @@ MaskedItem { source: "images/avatar-frame.png" sourceSize.width: width sourceSize.height: height - smooth: true + smooth: avatar.smooth fillMode: Image.Stretch visible: avatar.status == Image.Ready Image { id: avatar + property bool complete: false + + function update() { + if (complete) { + if (!root.source.toString()) { + source = root.placeholderSource; + } + else { + source = root.source; + } + } + } + z: -1 anchors.fill: parent sourceSize.width: width @@ -59,17 +74,23 @@ MaskedItem { fillMode: Image.PreserveAspectCrop clip: true asynchronous: true - onStatusChanged: if (status == Image.Error) source = "images/avatar.png"; + onStatusChanged: if ((status == Image.Error) && (source == root.source)) source = root.placeholderSource; } } - - + MouseArea { id: mouseArea anchors.fill: parent enabled: root.enabled onClicked: root.clicked() + onPressAndHold: root.pressAndHold() } -} + onSourceChanged: avatar.update() + onPlaceholderSourceChanged: avatar.update() + Component.onCompleted: { + avatar.complete = true; + avatar.update(); + } +} diff --git a/app/src/symbian/qml/BackToolButton.qml b/app/src/symbian/qml/BackToolButton.qml new file mode 100644 index 0000000..1df2d67 --- /dev/null +++ b/app/src/symbian/qml/BackToolButton.qml @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyToolButton { + id: root + + property Item pageToDestroy: null + + iconSource: appWindow.pageStack.depth > 1 ? "toolbar-back" : "images/close.png" + toolTip: appWindow.pageStack.depth > 1 ? qsTr("Back") : qsTr("Exit") + onClicked: { + if (appWindow.pageStack.depth > 1) { + pageToDestroy = appWindow.pageStack.currentPage; + appWindow.pageStack.pop(); + } + else { + Qt.quit(); + } + } + + Connections { + target: pageToDestroy === null ? null : appWindow.pageStack + onBusyChanged: { + if ((!appWindow.pageStack.busy) && (pageToDestroy !== null)) { + pageToDestroy.destroy(); + pageToDestroy = null; + } + } + } +} diff --git a/app/src/symbian/qml/CategorySettingsPage.qml b/app/src/symbian/qml/CategorySettingsPage.qml new file mode 100644 index 0000000..255cd6d --- /dev/null +++ b/app/src/symbian/qml/CategorySettingsPage.qml @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Categories") + tools: ToolBarLayout { + BackToolButton {} + + MyToolButton { + iconSource: "toolbar-add" + toolTip: qsTr("New category") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("EditCategoryPage.qml")) + } + } + + MyListView { + id: view + + anchors.fill: parent + model: CategoryModel { + id: categoryModel + } + delegate: DualTextDelegate { + text: name + subText: path + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("EditCategoryPage.qml"), {name: name, path: path}) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No Categories") + visible: categoryModel.count === 0 + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Edit") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("EditCategoryPage.qml"), + {name: categoryModel.data(view.currentIndex, "name"), + path: categoryModel.data(view.currentIndex, "path")}); + } + + MenuItem { + text: qsTr("Remove") + onClicked: categoryModel.removeCategory(view.currentIndex) + } + } + } + } +} diff --git a/app/src/symbian/qml/CommentDelegate.qml b/app/src/symbian/qml/CommentDelegate.qml new file mode 100644 index 0000000..0775764 --- /dev/null +++ b/app/src/symbian/qml/CommentDelegate.qml @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Item { + id: root + + signal activated + signal clicked + signal pressAndHold + + width: ListView.view ? ListView.view.width : screen.width + height: avatar.height + bodyLabel.height + platformStyle.paddingLarge * 3 + + Avatar { + id: avatar + + anchors { + left: parent.left + top: parent.top + margins: platformStyle.paddingLarge + } + width: platformStyle.graphicSizeSmall + height: platformStyle.graphicSizeSmall + source: thumbnailUrl + placeholderSource: "images/artist.jpg" + enabled: artistId !== "" + onClicked: root.activated(); + } + + Label { + id: artistLabel + + anchors { + left: avatar.right + right: parent.right + verticalCenter: avatar.verticalCenter + margins: platformStyle.paddingLarge + } + font.pixelSize: platformStyle.fontSizeSmall + color: platformStyle.colorNormalMid + elide: Text.ElideRight + text: qsTr("by") + " " + artist + " " + qsTr("on") + " " + date + } + + Label { + id: bodyLabel + + anchors { + left: avatar.right + right: parent.right + top: avatar.bottom + margins: platformStyle.paddingLarge + } + wrapMode: Text.Wrap + text: '"' + body + '"' + } + + MouseArea { + id: mouseArea + + anchors.fill: parent + z: -1 + enabled: root.enabled + onClicked: root.clicked() + onPressAndHold: root.pressAndHold() + } +} diff --git a/app/src/symbian/qml/DualTextDelegate.qml b/app/src/symbian/qml/DualTextDelegate.qml new file mode 100644 index 0000000..8190b18 --- /dev/null +++ b/app/src/symbian/qml/DualTextDelegate.qml @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyListItem { + id: root + + property alias text: label.text + property alias subText: subLabel.text + + MyListItemText { + id: label + + anchors { + left: root.paddingItem.left + right: root.paddingItem.right + top: root.paddingItem.top + } + role: "Title" + mode: root.mode + elide: Text.ElideRight + } + + MyListItemText { + id: subLabel + + anchors { + left: root.paddingItem.left + right: root.paddingItem.right + bottom: root.paddingItem.bottom + } + role: "SubTitle" + mode: root.mode + elide: Text.ElideRight + } +} diff --git a/app/src/symbian/qml/EditCategoryPage.qml b/app/src/symbian/qml/EditCategoryPage.qml new file mode 100644 index 0000000..e7fbfdb --- /dev/null +++ b/app/src/symbian/qml/EditCategoryPage.qml @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +EditPage { + id: root + + property alias name: nameEdit.text + property alias path: pathSelector.subTitle + + title: qsTr("Category") + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + width: parent.width + text: qsTr("Name") + } + + MyTextField { + id: nameEdit + + width: parent.width + onAccepted: closeSoftwareInputPanel() + } + + ValueListItem { + id: pathSelector + + x: -platformStyle.paddingLarge + width: parent.width + platformStyle.paddingLarge * 2 + title: qsTr("Download path") + subTitle: settings.downloadPath + onClicked: { + var page = appWindow.pageStack.push(Qt.resolvedUrl("FileBrowserPage.qml"), + {title: title, startFolder: path}); + page.accepted.connect(function () { page.pageStack.pop(); path = page.chosenFile; }); + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + states: State { + name: "inputContextVisible" + when: inputContext.visible + + PropertyChanges { + target: flickable + contentHeight: flickable.height + } + + PropertyChanges { + target: pathSelector + visible: false + } + } + + onAccepted: { + settings.addCategory(name, path); + appWindow.pageStack.pop(); + } +} diff --git a/app/src/desktop-qml/qml/plugins/PluginSettingsCheckBox.qml b/app/src/symbian/qml/EditPage.qml similarity index 55% rename from app/src/desktop-qml/qml/plugins/PluginSettingsCheckBox.qml rename to app/src/symbian/qml/EditPage.qml index b269ae4..5d4e4c3 100644 --- a/app/src/desktop-qml/qml/plugins/PluginSettingsCheckBox.qml +++ b/app/src/symbian/qml/EditPage.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,26 +14,29 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 +import QtQuick 1.1 +import com.nokia.symbian 1.1 -CheckBox { +MyPage { id: root + + property alias acceptable: acceptButton.enabled - property string key - - function setKey(key, defaultValue) { - root.key = key; - - var value = Settings.value(key); - - if (value === undefined) { - root.checked = (defaultValue === "true"); + signal accepted + signal rejected + + title: qsTr("Edit") + tools: ToolBarLayout { + BackToolButton { + onClicked: root.rejected() } - else { - root.checked = (value === "true"); + + MyToolButton { + id: acceptButton + + iconSource: "images/yes.png" + toolTip: qsTr("Submit") + onClicked: root.accepted() } } - - onCheckedChanged: Settings.setValue(root.key, root.checked) } diff --git a/app/src/symbian/qml/FileBrowserPage.qml b/app/src/symbian/qml/FileBrowserPage.qml new file mode 100644 index 0000000..1de840a --- /dev/null +++ b/app/src/symbian/qml/FileBrowserPage.qml @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import Qt.labs.folderlistmodel 1.0 +import com.nokia.symbian 1.1 + +MyPage { + id: root + + property bool showFiles: false + property string startFolder: "E:/" + property string chosenFile + property string chosenUrl + + signal accepted + signal rejected + + title: qsTr("Choose folder") + tools: ToolBarLayout { + BackToolButton { + onClicked: { + root.chosenFile = ""; + root.chosenUrl = ""; + root.rejected(); + } + } + + MyToolButton { + iconSource: "images/up.png" + toolTip: qsTr("Go up") + onClicked: folderListModel.folder = folderListModel.parentFolder + } + + MyToolButton { + iconSource: "images/yes.png" + toolTip: qsTr("Submit") + enabled: folderText.text !== "" + onClicked: { + if (root.showFiles) { + root.chosenUrl = fileList.chosenFile; + } + else { + root.chosenUrl = folderListModel.folder.toString(); + } + + root.chosenFile = root.chosenUrl.slice(8); + root.accepted(); + } + } + } + + Label { + id: folderText + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + elide: Text.ElideRight + color: platformStyle.colorNormalLink + text: showFiles ? fileList.chosenFile.slice(fileList.chosenFile.lastIndexOf("/") + 1) + : folderListModel.folder.toString().slice(8) + } + + HeaderLabel { + id: header + + anchors { + left: parent.left + right: parent.right + top: folderText.bottom + margins: platformStyle.paddingLarge + } + + text: showFiles ? qsTr("Files") : qsTr("Folders") + } + + MyListView { + id: fileList + + property string chosenFile: "" + + anchors { + left: parent.left + right: parent.right + top: header.bottom + topMargin: platformStyle.paddingLarge + bottom: parent.bottom + } + clip: true + model: FolderListModel { + id: folderListModel + + nameFilters: root.showFiles ? [] : ["*.foo_bar"] + folder: "file:///" + root.startFolder + showDotAndDotDot: false + showDirs: true + } + delegate: MyListItem { + Image { + id: icon + + anchors { + left: paddingItem.left + verticalCenter: paddingItem.verticalCenter + } + + source: "images/folder.png" + } + + MyListItemText { + anchors { + left: icon.right + leftMargin: platformStyle.paddingLarge + right: paddingItem.right + verticalCenter: paddingItem.verticalCenter + } + role: "Title" + elide: Text.ElideRight + text: fileName + } + + onClicked: folderListModel.isFolder(index) ? folderListModel.folder = filePath + : fileList.chosenFile = filePath + } + } + + ScrollDecorator { + flickableItem: fileList + } + + Label { + id: noResultsText + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + font.pixelSize: 32 + font.bold: true + color: platformStyle.colorNormalMid + text: qsTr("Folder empty") + visible: fileList.count === 0 + } +} diff --git a/app/src/symbian/qml/GeneralSettingsPage.qml b/app/src/symbian/qml/GeneralSettingsPage.qml new file mode 100644 index 0000000..607500e --- /dev/null +++ b/app/src/symbian/qml/GeneralSettingsPage.qml @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("General") + tools: ToolBarLayout { + BackToolButton {} + } + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + + ValueSelector { + id: orientationSelector + + width: parent.width + title: qsTr("Screen orientation") + model: ScreenOrientationModel {} + value: settings.screenOrientation + onAccepted: settings.screenOrientation = value + } + + ValueListItem { + id: pathSelector + + width: parent.width + title: qsTr("Download path") + subTitle: settings.downloadPath + onClicked: { + var page = appWindow.pageStack.push(Qt.resolvedUrl("FileBrowserPage.qml"), + {title: title, startFolder: settings.downloadPath}); + page.accepted.connect(function () { page.pageStack.pop(); settings.downloadPath = page.chosenFile; }); + } + } + + TextDelegate { + id: categoriesButton + + flickableMode: true + width: parent.width + subItemIndicator: true + text: qsTr("Categories") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("CategorySettingsPage.qml")) + } + + ValueSelector { + id: concurrentSelector + + width: parent.width + title: qsTr("Maximum concurrent transfers") + model: ConcurrentTransfersModel {} + value: settings.maximumConcurrentTransfers + onAccepted: settings.maximumConcurrentTransfers = value + } + + Item { + width: parent.width + height: platformStyle.paddingLarge + } + + MySwitch { + id: automaticSwitch + + x: platformStyle.paddingLarge + width: parent.width - platformStyle.paddingLarge * 2 + text: qsTr("Start transfers automatically") + checked: settings.startTransfersAutomatically + onCheckedChanged: settings.startTransfersAutomatically = checked + } + + Item { + width: parent.width + height: platformStyle.paddingLarge + } + + MySwitch { + id: restoreQueueSwitch + + x: platformStyle.paddingLarge + width: parent.width - platformStyle.paddingLarge * 2 + text: qsTr("Restore playback queue on startup") + checked: settings.restorePlaybackQueueOnStartup + onCheckedChanged: settings.restorePlaybackQueueOnStartup = checked + } + } + } + + ScrollDecorator { + flickableItem: flickable + } +} diff --git a/app/src/desktop-qml/qml/MessageBox.qml b/app/src/symbian/qml/HeaderLabel.qml similarity index 56% rename from app/src/desktop-qml/qml/MessageBox.qml rename to app/src/symbian/qml/HeaderLabel.qml index a904642..cabbc67 100644 --- a/app/src/desktop-qml/qml/MessageBox.qml +++ b/app/src/symbian/qml/HeaderLabel.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,42 +14,38 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 +import QtQuick 1.1 +import com.nokia.symbian 1.1 -MyDialog { +Item { id: root - + property alias text: label.text - - function showMessage(message) { - title = qsTr("Information"); - text = message; - open(); - } - - function showError(error) { - title = qsTr("Error"); - text = error; - open(); - } - - minimumWidth: 400 - minimumHeight: label.height + 60 - title: qsTr("Information") - content: Label { + + height: 20 + + Label { id: label - + anchors { - left: parent.left right: parent.right top: parent.top } + font.pixelSize: platformStyle.fontSizeSmall + font.bold: true + color: platformStyle.colorNormalMid + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter } - buttons: Button { - text: qsTr("&Ok") - iconName: "dialog-ok" - isDefault: true - onClicked: root.accept() + + Rectangle { + height: 1 + anchors { + left: parent.left + right: label.left + rightMargin: platformStyle.paddingLarge * 2 + verticalCenter: label.verticalCenter + } + color: platformStyle.colorNormalMid } } diff --git a/app/src/symbian/qml/KeyNavFlickable.qml b/app/src/symbian/qml/KeyNavFlickable.qml new file mode 100644 index 0000000..ef607d8 --- /dev/null +++ b/app/src/symbian/qml/KeyNavFlickable.qml @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Flickable { + id: root + + property int currentIndex + property bool loaderFix: false + + onVisibleChanged: { + if (visible) { + if (internal.focusItem) { + internal.focusItem.forceActiveFocus(); + } + else { + forceActiveFocus(); + } + } + } + onCurrentIndexChanged: internal.focusItem = internal.focusItems[currentIndex]; + Component.onCompleted: internal.getKeyboardFocus() + + QtObject { + id: internal + + property variant focusItems: [] + property Item focusItem: null + + function itemGetsFocus(item) { + return (item.hasOwnProperty("checked")) || (item.hasOwnProperty("cursorPosition")) + || (item.hasOwnProperty("subItemIndicator")) || (item.hasOwnProperty("item")) + || (item.hasOwnProperty("model")); + } + + function itemIsLayout(item) { + return item.hasOwnProperty("spacing"); + } + + function getFocusItems(item) { + if (!item) { + item = contentItem; + } + + for (var i = 0; i < item.children.length; i++) { + if (itemGetsFocus(item.children[i])) { + var fi = focusItems; + fi.push(item.children[i]); + focusItems = fi; + } + else if (itemIsLayout(item.children[i])) { + getFocusItems(item.children[i]); + } + } + } + + function getKeyboardFocus() { + getFocusItems(contentItem); + + if (focusItems.length > currentIndex) { + if (loaderFix) { + var fi = focusItems; + fi.unshift(fi.pop()); + focusItems = fi; + } + + focusItem = focusItems[currentIndex]; + } + else { + forceActiveFocus(); + } + } + + onFocusItemChanged: { + if (focusItem) { + focusItem.forceActiveFocus(); + + if (currentIndex == 0) { + contentY = 0; + } + else if (contentY + height < mapFromItem(focusItem, focusItem.x, focusItem.y).y) { + contentY = mapFromItem(focusItem, focusItem.x, focusItem.y).y - height; + } + else if (contentY > focusItem.y) { + contentY = focusItem.y; + } + } + } + } + + function next() { + for (var i = currentIndex + 1; i < internal.focusItems.length; i++) { + if ((internal.focusItems[i].visible) && (internal.focusItems[i].enabled)) { + currentIndex = i; + return; + } + } + } + + function previous() { + for (var i = currentIndex - 1; i >= 0; i--) { + if ((internal.focusItems[i].visible) && (internal.focusItems[i].enabled)) { + currentIndex = i; + return; + } + } + } + + function moveUp() { + if (contentY > 100) { + contentY -= 100; + } + else { + contentY = 0; + } + } + + function moveDown() { + if (contentY < contentHeight - height - 100) { + contentY += 100; + } + else { + contentY = contentHeight - height; + } + } + + Keys.onUpPressed: { + if (symbian.listInteractionMode !== Symbian.KeyNavigation) { + symbian.listInteractionMode = Symbian.KeyNavigation; + } + + if (internal.focusItems.length > 0) { + previous(); + } + else { + moveUp(); + } + + event.accepted = true; + } + Keys.onDownPressed: { + if (symbian.listInteractionMode !== Symbian.KeyNavigation) { + symbian.listInteractionMode = Symbian.KeyNavigation; + } + + if (internal.focusItems.length > 0) { + next(); + } + else { + moveDown(); + } + + event.accepted = true; + } + + Connections { + target: inputContext + onVisibleChanged: if ((!inputContext.visible) && (visible)) forceActiveFocus(); + } + + MouseArea { + z: Number.MAX_VALUE + anchors.fill: parent + onPressed: { + if (symbian.listInteractionMode !== Symbian.TouchInteraction) { + symbian.listInteractionMode = Symbian.TouchInteraction; + } + + mouse.accepted = inputContext.visible; + } + onReleased: root.forceActiveFocus(); + } +} + diff --git a/app/src/desktop-qml/qml/Thumbnail.qml b/app/src/symbian/qml/LinkLabel.qml similarity index 64% rename from app/src/desktop-qml/qml/Thumbnail.qml rename to app/src/symbian/qml/LinkLabel.qml index 3fd4cea..f68dad5 100644 --- a/app/src/desktop-qml/qml/Thumbnail.qml +++ b/app/src/symbian/qml/LinkLabel.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,22 +14,28 @@ * along with this program. If not, see . */ -import QtQuick 2.0 +import QtQuick 1.1 +import com.nokia.symbian 1.1 -Image { +Label { id: root - + + property string link + signal clicked - signal rightClicked - fillMode: Image.PreserveAspectFit - smooth: true - + color: mouseArea.pressed ? Qt.lighter(ACTIVE_COLOR) : ACTIVE_COLOR + MouseArea { id: mouseArea - + anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: mouse.button == Qt.RightButton ? root.rightClicked() : root.clicked() - } + onClicked: { + root.clicked(); + + if (root.link) { + Qt.openUrlExternally(root.link); + } + } + } } diff --git a/app/src/symbian/qml/LogPage.qml b/app/src/symbian/qml/LogPage.qml new file mode 100644 index 0000000..633639e --- /dev/null +++ b/app/src/symbian/qml/LogPage.qml @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyPage { + id: root + + title: qsTr("Log") + tools: ToolBarLayout { + BackToolButton {} + + MyToolButton { + iconSource: "toolbar-delete" + toolTip: qsTr("Clear log") + enabled: textArea.text != "" + onClicked: { + logger.clear(); + textArea.text = ""; + infoBanner.information(qsTr("Log cleared")); + } + } + } + + TextArea { + id: textArea + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + readOnly: true + } + + onStatusChanged: if (status == PageStatus.Active) textArea.text = logger.text; +} diff --git a/app/src/symbian/qml/LoggingSettingsPage.qml b/app/src/symbian/qml/LoggingSettingsPage.qml new file mode 100644 index 0000000..119add7 --- /dev/null +++ b/app/src/symbian/qml/LoggingSettingsPage.qml @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Logging") + tools: ToolBarLayout { + BackToolButton {} + } + + KeyNavFlickable { + id: flicker + + anchors.fill: parent + contentHeight: column.height + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + + ValueSelector { + width: parent.width + title: qsTr("Logging verbosity") + model: LoggerVerbosityModel {} + value: settings.loggerVerbosity + onValueChanged: settings.loggerVerbosity = value + } + + TextDelegate { + width: parent.width + flickableMode: true + subItemIndicator: true + text: qsTr("View log") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("LogPage.qml")) + } + } + } + + ScrollDecorator { + flickableItem: flicker + } +} diff --git a/app/src/symbian/qml/MainPage.qml b/app/src/symbian/qml/MainPage.qml new file mode 100644 index 0000000..e9cb588 --- /dev/null +++ b/app/src/symbian/qml/MainPage.qml @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import "plugins" +import "soundcloud" + +MyPage { + id: root + + function setService(service) { + if (!service) { + service = mkresources.SOUNDCLOUD; + } + + settings.currentService = service; + title = serviceModel.data(serviceModel.match("value", service), "name"); + + switch (service) { + case mkresources.SOUNDCLOUD: + loader.sourceComponent = soundcloudView; + break; + default: + loader.sourceComponent = pluginView; + break; + } + } + + title: "MusiKloud2" + tools: ToolBarLayout { + BackToolButton {} + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + ServiceModel { + id: serviceModel + } + + Loader { + id: loader + + anchors.fill: parent + } + + PopupLoader { + id: popups + } + + Component { + id: soundcloudView + + SoundCloudView { + anchors.fill: parent + } + } + + Component { + id: pluginView + + PluginView { + anchors.fill: parent + } + } + + Component { + id: serviceDialog + + ValueDialog { + focusItem: loader.item + titleText: qsTr("Service") + model: serviceModel + value: settings.currentService + onAccepted: root.setService(value) + } + } + + Component { + id: menu + + MyMenu { + focusItem: loader.item + + MenuLayout { + MenuItem { + text: qsTr("Service") + platformSubItemIndicator: true + onClicked: popups.open(serviceDialog, root) + } + + MenuItem { + text: qsTr("Play folder") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PlayFolderPage.qml")) + } + + MenuItem { + text: qsTr("Queue folder") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("AddFolderPage.qml")) + } + + MenuItem { + text: qsTr("Play URL") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PlayUrlPage.qml")) + } + + MenuItem { + text: qsTr("Queue URL") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("AddUrlPage.qml")) + } + + MenuItem { + text: qsTr("Transfers") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("TransfersPage.qml")) + } + + MenuItem { + text: qsTr("Settings") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) + } + } + } + } +} diff --git a/app/src/symbian/qml/MyButton.qml b/app/src/symbian/qml/MyButton.qml new file mode 100644 index 0000000..4512bef --- /dev/null +++ b/app/src/symbian/qml/MyButton.qml @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Button { + id: root + + Rectangle { + x: -platformStyle.paddingSmall + y: -platformStyle.paddingSmall + width: parent.width + platformStyle.paddingSmall * 2 + height: parent.height + platformStyle.paddingSmall * 2 + visible: (root.activeFocus) && (symbian.listInteractionMode === Symbian.KeyNavigation) + color: "transparent" + radius: 5 + border { + width: 1 + color: root.platformInverted ? platformStyle.colorDisabledLightInverted + : platformStyle.colorDisabledMid + } + } +} diff --git a/app/src/symbian/qml/MyContextMenu.qml b/app/src/symbian/qml/MyContextMenu.qml new file mode 100644 index 0000000..b1cdefa --- /dev/null +++ b/app/src/symbian/qml/MyContextMenu.qml @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +ContextMenu { + property Item focusItem: null + + onStatusChanged: if ((status === DialogStatus.Closed) && (focusItem)) focusItem.forceActiveFocus(); +} diff --git a/app/src/symbian/qml/MyDialog.qml b/app/src/symbian/qml/MyDialog.qml new file mode 100644 index 0000000..0b24fc9 --- /dev/null +++ b/app/src/symbian/qml/MyDialog.qml @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import com.nokia.symbian 1.1 + +Dialog { + property Item focusItem: null + + onClickedOutside: { + privateStyle.play(Symbian.PopupClose); + reject(); + } + onStatusChanged: if ((status === DialogStatus.Closed) && (focusItem)) focusItem.forceActiveFocus(); +} diff --git a/app/src/symbian/qml/MyFlickable.qml b/app/src/symbian/qml/MyFlickable.qml new file mode 100644 index 0000000..c1cc929 --- /dev/null +++ b/app/src/symbian/qml/MyFlickable.qml @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Flickable { + + function moveUp() { + if (contentY > 100) { + contentY -= 100; + } + else { + contentY = 0; + } + } + + function moveDown() { + if (contentY < contentHeight - height - 100) { + contentY += 100; + } + else { + contentY = contentHeight - height; + } + } + + Keys.onUpPressed: moveUp() + Keys.onDownPressed: moveDown() + + Component.onCompleted: forceActiveFocus() + onVisibleChanged: if (visible) forceActiveFocus(); + + Connections { + target: inputContext + onVisibleChanged: if ((!inputContext.visible) && (visible)) forceActiveFocus(); + } +} diff --git a/app/src/symbian/qml/MyInfoBanner.qml b/app/src/symbian/qml/MyInfoBanner.qml new file mode 100644 index 0000000..7d40ab3 --- /dev/null +++ b/app/src/symbian/qml/MyInfoBanner.qml @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import com.nokia.extras 1.1 + +InfoBanner { + id: banner + + function information(message) { + if (symbian.foreground) { + banner.text = message; + banner.open(); + } + } +} diff --git a/app/src/symbian/qml/MyListItem.qml b/app/src/symbian/qml/MyListItem.qml new file mode 100644 index 0000000..80ac558 --- /dev/null +++ b/app/src/symbian/qml/MyListItem.qml @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Item { + id: root + + property string mode: "normal" // Read-only + property alias paddingItem: paddingItem // Read-only + property alias state: internal.state + + property bool enabled: true + property bool subItemIndicator: false + property bool platformInverted: false + property bool flickableMode: false + + signal activated + signal clicked + signal pressAndHold + + implicitWidth: ListView.view ? ListView.view.width : screen.width + implicitHeight: platformStyle.graphicSizeLarge + + Item { + id: background + anchors.fill: parent + + Rectangle { + height: 1 + color: root.platformInverted ? platformStyle.colorDisabledLightInverted + : platformStyle.colorDisabledMid + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } + + Loader { + id: faderLoader + opacity: 0 + anchors.fill: background + sourceComponent: root.mode != "normal" && root.mode != "pressed" ? fader : undefined + } + + BorderImage { + id: highlight + border { + left: platformStyle.borderSizeMedium + top: platformStyle.borderSizeMedium + right: platformStyle.borderSizeMedium + bottom: platformStyle.borderSizeMedium + } + opacity: 0 + anchors.fill: background + } + } + + Component { + id: fader + + BorderImage { + source: privateStyle.imagePath("qtg_fr_list_" + mode, root.platformInverted) + border { + left: platformStyle.borderSizeMedium + top: platformStyle.borderSizeMedium + right: platformStyle.borderSizeMedium + bottom: platformStyle.borderSizeMedium + } + } + } + + MouseArea { + id: mouseArea + z: 1 + anchors.fill: parent + enabled: root.enabled + onPressed: { + symbian.listInteractionMode = Symbian.TouchInteraction + internal.state = "Pressed" + } + onClicked: { + internal.state = "" + root.clicked() + } + onCanceled: { + internal.state = "Canceled" + } + onPressAndHold: { + internal.state = "PressAndHold" + } + onReleased: { + internal.state = "" + } + onExited: { + internal.state = "" + } + } + + Loader { + id: iconLoader + sourceComponent: root.subItemIndicator ? subItemIcon : undefined + anchors { + right: parent.right + rightMargin: privateStyle.scrollBarThickness + verticalCenter: parent.verticalCenter + } + } + + Component { + id: subItemIcon + + Image { + source: privateStyle.imagePath( + root.enabled ? "qtg_graf_drill_down_indicator" + : "qtg_graf_drill_down_indicator_disabled", + root.platformInverted) + mirror: LayoutMirroring.enabled + sourceSize.width: platformStyle.graphicSizeSmall + sourceSize.height: platformStyle.graphicSizeSmall + } + } + + Keys.onReleased: { + if (!event.isAutoRepeat && root.enabled) { + if (event.key == Qt.Key_Select || event.key == Qt.Key_Return || event.key == Qt.Key_Enter) { + event.accepted = !flickableMode + internal.state = "Focused" + } + } + } + + Keys.onPressed: { + if (!event.isAutoRepeat) { + switch (event.key) { + case Qt.Key_Select: + case Qt.Key_Enter: + case Qt.Key_Return: { + if (symbian.listInteractionMode != Symbian.KeyNavigation) + symbian.listInteractionMode = Symbian.KeyNavigation + else + if (root.enabled) { + highlight.source = privateStyle.imagePath("qtg_fr_list_pressed", + root.platformInverted) + highlight.opacity = 1 + releasedEffect.restart() + root.clicked() + } + event.accepted = true + break + } + + case Qt.Key_Space: { + if (symbian.listInteractionMode != Symbian.KeyNavigation) + symbian.listInteractionMode = Symbian.KeyNavigation + else + root.activated() + event.accepted = true + break; + } + + case Qt.Key_Up: { + if (symbian.listInteractionMode != Symbian.KeyNavigation) { + symbian.listInteractionMode = Symbian.KeyNavigation + internal.state = "Focused" + if (!flickableMode) + ListView.view.positionViewAtIndex(index, ListView.Beginning) + } else + if (!flickableMode) + ListView.view.decrementCurrentIndex() + event.accepted = !flickableMode + break + } + + case Qt.Key_Down: { + if (symbian.listInteractionMode != Symbian.KeyNavigation) { + symbian.listInteractionMode = Symbian.KeyNavigation + internal.state = "Focused" + if (!flickableMode) + ListView.view.positionViewAtIndex(index, ListView.Beginning) + } else + if (!flickableMode) + ListView.view.incrementCurrentIndex() + event.accepted = !flickableMode + break + } + + default: { + event.accepted = false + break + } + } + } + + if ((event.key == Qt.Key_Up || event.key == Qt.Key_Down) && (!flickableMode)) + symbian.privateListItemKeyNavigation(ListView.view) + } + + SequentialAnimation { + id: releasedEffect + PropertyAnimation { + target: highlight + property: "opacity" + to: 0 + easing.type: Easing.Linear + duration: 150 + } + } + + Item { + // non-visible item to create a padding boundary that content items can bind to + id: paddingItem + anchors { + fill: parent + leftMargin: platformStyle.paddingLarge + rightMargin: iconLoader.status == Loader.Ready ? + privateStyle.scrollBarThickness + iconLoader.width + platformStyle.paddingMedium : + privateStyle.scrollBarThickness + topMargin: platformStyle.paddingLarge + bottomMargin: platformStyle.paddingLarge + } + } + + StateGroup { + id: internal + + function getMode() { + if (internal.state == "Pressed" || internal.state == "PressAndHold") + return "pressed" + else if (internal.state == "Focused") + return "highlighted" + else if (internal.state == "Disabled") + return "disabled" + else + return "normal" + } + + // Performance optimization: + // Use value assignment when property changes instead of binding to js function + onStateChanged: { root.mode = internal.getMode() } + + function press() { + privateStyle.play(Symbian.BasicItem) + highlight.source = privateStyle.imagePath("qtg_fr_list_pressed", root.platformInverted) + highlight.opacity = 1 + if (root.ListView.view) + root.ListView.view.currentIndex = index + } + + function release() { + if (symbian.listInteractionMode != Symbian.KeyNavigation) + privateStyle.play(Symbian.BasicItem) + releasedEffect.restart() + } + + function releaseHold() { + releasedEffect.restart() + } + + function hold() { + root.pressAndHold() + } + + function disable() { + faderLoader.opacity = 1 + } + + function focus() { + faderLoader.opacity = 1 + } + + function canceled() { + releasedEffect.restart() + } + + states: [ + State { name: "Pressed" }, + State { name: "PressAndHold" }, + State { name: "Disabled"; when: !root.enabled }, + State { name: "Focused"; when: ((root.ListView.isCurrentItem || (flickableMode && activeFocus)) && + symbian.listInteractionMode == Symbian.KeyNavigation) }, + State { name: "Canceled" }, + State { name: "" } + ] + + transitions: [ + Transition { + to: "Pressed" + ScriptAction { script: internal.press() } + }, + Transition { + from: "Pressed" + to: "PressAndHold" + ScriptAction { script: internal.hold() } + }, + Transition { + from: "PressAndHold" + to: "" + ScriptAction { script: internal.releaseHold() } + }, + Transition { + to: "" + ScriptAction { script: internal.release() } + }, + Transition { + to: "Disabled" + ScriptAction { script: internal.disable() } + }, + Transition { + to: "Focused" + ScriptAction { script: internal.focus() } + }, + Transition { + to: "Canceled" + ScriptAction { script: internal.canceled() } + } + ] + } +} diff --git a/app/src/symbian/qml/MyListItemText.qml b/app/src/symbian/qml/MyListItemText.qml new file mode 100644 index 0000000..123825d --- /dev/null +++ b/app/src/symbian/qml/MyListItemText.qml @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 + +Text { + id: root + property string mode: "normal" + property string role: "Title" + property bool platformInverted: false + + // Also role "Heading" taken into account although not explicitely used in evaluations below + font { + family: platformStyle.fontFamilyRegular + pixelSize: (role == "Title" || role == "SelectionTitle") ? platformStyle.fontSizeLarge : platformStyle.fontSizeSmall + weight: (role == "SubTitle" || role == "SelectionSubTitle") ? Font.Light : Font.Normal + } + color: internal.normalColor + elide: Text.ElideRight + horizontalAlignment: root.role != "Heading" ? Text.AlignLeft : Text.AlignRight + clip: true + + // Performance optimization: + // Use value assignment when property changes instead of binding to js function + onModeChanged: color = internal.getColor() + onPlatformInvertedChanged: { + internal.colorMid = platformInverted ? platformStyle.colorNormalMidInverted + : platformStyle.colorNormalMid; + internal.colorLight = platformInverted ? platformStyle.colorNormalLightInverted + : platformStyle.colorNormalLight; + color = internal.getColor(); + } + + QtObject { + id: internal + + // Performance optmization: + // Use tertiary operations even though it doesn't look that good + property color colorMid: root.platformInverted ? platformStyle.colorNormalMidInverted + : platformStyle.colorNormalMid + property color colorLight: root.platformInverted ? platformStyle.colorNormalLightInverted + : platformStyle.colorNormalLight + property color normalColor: (root.role == "SelectionSubTitle" || root.role == "SubTitle") + ? colorMid : colorLight + + function getColor() { + if (root.mode == "pressed") + return root.platformInverted ? platformStyle.colorPressedInverted + : platformStyle.colorPressed + else if (root.mode == "highlighted") + return root.platformInverted ? platformStyle.colorHighlightedInverted + : platformStyle.colorHighlighted + else if (root.mode == "disabled") + return root.platformInverted ? platformStyle.colorDisabledLightInverted + : platformStyle.colorDisabledLight + else + return normalColor + } + } +} diff --git a/app/src/symbian/qml/MyListView.qml b/app/src/symbian/qml/MyListView.qml new file mode 100644 index 0000000..26031c2 --- /dev/null +++ b/app/src/symbian/qml/MyListView.qml @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +ListView { + Component.onCompleted: forceActiveFocus() + cacheBuffer: platformStyle.graphicSizeLarge * 4 + onVisibleChanged: if (visible) forceActiveFocus(); +} diff --git a/app/src/desktop-qml/qml/ToolBarLayout.qml b/app/src/symbian/qml/MyMenu.qml similarity index 69% rename from app/src/desktop-qml/qml/ToolBarLayout.qml rename to app/src/symbian/qml/MyMenu.qml index 402b275..c129dc6 100644 --- a/app/src/desktop-qml/qml/ToolBarLayout.qml +++ b/app/src/symbian/qml/MyMenu.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,14 +14,11 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 +import QtQuick 1.1 +import com.nokia.symbian 1.1 -RowLayout { - anchors { - fill: parent ? parent : undefined - leftMargin: 10 - rightMargin: 10 - } +Menu { + property Item focusItem: null + + onStatusChanged: if ((status === DialogStatus.Closed) && (focusItem)) focusItem.forceActiveFocus(); } diff --git a/app/src/maemo5/plugins/pluginsettingsdialog.h b/app/src/symbian/qml/MyPage.qml similarity index 65% rename from app/src/maemo5/plugins/pluginsettingsdialog.h rename to app/src/symbian/qml/MyPage.qml index 8d310eb..2938f68 100644 --- a/app/src/maemo5/plugins/pluginsettingsdialog.h +++ b/app/src/symbian/qml/MyPage.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -15,17 +15,15 @@ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLUGINSETTINGSDIALOG_H -#define PLUGINSETTINGSDIALOG_H +import QtQuick 1.1 +import com.nokia.symbian 1.1 -#include "dialog.h" +Page { + id: root -class PluginSettingsDialog : public Dialog -{ - Q_OBJECT + property string title: "" + property bool showProgressIndicator: false -public: - explicit PluginSettingsDialog(const QString &name, const QString &fileName, QWidget *parent = 0); -}; - -#endif // PLUGINSETTINGSDIALOG_H + anchors.fill: parent + orientationLock: settings.screenOrientation +} diff --git a/app/src/symbian/qml/MyQueryDialog.qml b/app/src/symbian/qml/MyQueryDialog.qml new file mode 100644 index 0000000..deb8c20 --- /dev/null +++ b/app/src/symbian/qml/MyQueryDialog.qml @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +QueryDialog { + property Item focusItem: null + + acceptButtonText: qsTr("Yes") + rejectButtonText: qsTr("No") + onStatusChanged: if ((status === DialogStatus.Closed) && (focusItem)) focusItem.forceActiveFocus(); +} diff --git a/app/src/symbian/qml/MySelectionDialog.qml b/app/src/symbian/qml/MySelectionDialog.qml new file mode 100644 index 0000000..46ea010 --- /dev/null +++ b/app/src/symbian/qml/MySelectionDialog.qml @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Dialog { + id: root + + property Item focusItem: null + property int selectedIndex: -1 + property bool showProgressIndicator: false + property Component delegate: defaultDelegate + property string titleText + property alias model: listView.model + + title: Item { + anchors.left: parent.left + anchors.right: parent.right + height: platformStyle.graphicSizeSmall + 2 * platformStyle.paddingLarge + + LayoutMirroring.enabled: false + LayoutMirroring.childrenInherit: true + + Item { + id: titleLayoutHelper // needed to make the text mirror correctly + + anchors.left: parent.left + anchors.right: progressIndicator.visible ? progressIndicator.left : titleAreaIcon.left + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: platformStyle.paddingLarge + + Text { + id: titleAreaText + + LayoutMirroring.enabled: root.LayoutMirroring.enabled + + anchors.fill: parent + font { family: platformStyle.fontFamilyRegular; pixelSize: platformStyle.fontSizeLarge } + color: root.platformInverted ? platformStyle.colorNormalLinkInverted + : platformStyle.colorNormalLink + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + text: root.titleText + } + } + + BusyIndicator { + id: progressIndicator + + width: platformStyle.graphicSizeSmall + height: platformStyle.graphicSizeSmall + anchors.right: titleAreaIcon.left + anchors.verticalCenter: parent.verticalCenter + running: visible + visible: root.showProgressIndicator + } + + Image { + id: titleAreaIcon + + anchors.right: parent.right + anchors.rightMargin: platformStyle.paddingLarge + anchors.verticalCenter: parent.verticalCenter + source: privateStyle.imagePath((iconMouseArea.pressed && !iconMouseArea.pressCancelled + ? "qtg_graf_popup_close_pressed" + : "qtg_graf_popup_close_normal"), + root.platformInverted) + sourceSize.height: platformStyle.graphicSizeSmall + sourceSize.width: platformStyle.graphicSizeSmall + + MouseArea { + id: iconMouseArea + + property bool pressCancelled + + anchors.centerIn: parent + width: parent.width + 2 * platformStyle.paddingLarge + height: parent.height + 2 * platformStyle.paddingLarge + enabled: root.status == DialogStatus.Open + + onPressed: { + pressCancelled = false + privateStyle.play(Symbian.BasicButton) + } + onClicked: { + if (!pressCancelled) + root.reject() + } + onReleased: { + if (!pressCancelled) + privateStyle.play(Symbian.PopupClose) + } + onExited: pressCancelled = true + } + } + } + + content: Item { + id: contentItem + + function preferredHeight() { + // Need to create artifical binding to listView.delegate because of QTBUG-19037 + var multiplier = screen.height > screen.width ? 0.45 : 0.6; + var maxHeight = Math.floor(multiplier * screen.height / privateStyle.menuItemHeight) + * privateStyle.menuItemHeight; + return maxHeight; + } + + height: preferredHeight() + width: root.platformContentMaximumWidth + + Item { + // Clipping item with bottom margin added to align content with rounded background graphics + id: clipItem + anchors.fill: parent + anchors.bottomMargin: platformStyle.paddingSmall + clip: true + ListView { + id: listView + + currentIndex : -1 + width: contentItem.width + height: contentItem.height + delegate: root.delegate + clip: true + + Keys.onPressed: { + if (event.key == Qt.Key_Up || event.key == Qt.Key_Down + || event.key == Qt.Key_Left || event.key == Qt.Key_Right + || event.key == Qt.Key_Select || event.key == Qt.Key_Enter + || event.key == Qt.Key_Return) { + symbian.listInteractionMode = Symbian.KeyNavigation + listView.currentIndex = 0 + event.accepted = true + } + } + } + } + ScrollBar { + id: scrollBar + flickableItem: listView + interactive: false + visible: listView.contentHeight > contentItem.height + platformInverted: root.platformInverted + anchors { top: clipItem.top; right: clipItem.right } + } + } + + Component { + id: defaultDelegate + + MenuItem { + text: root.selectedIndex === index ? "

" + name + "

" : name + onClicked: { + root.selectedIndex = index; + root.accept(); + } + + Keys.onPressed: { + if (event.key == Qt.Key_Up || event.key == Qt.Key_Down) + scrollBar.flash() + } + } + } + + onClickedOutside: reject() + onStatusChanged: if ((status === DialogStatus.Closed) && (focusItem)) focusItem.forceActiveFocus(); +} diff --git a/app/src/symbian/qml/MyStatusBar.qml b/app/src/symbian/qml/MyStatusBar.qml new file mode 100644 index 0000000..4e713ab --- /dev/null +++ b/app/src/symbian/qml/MyStatusBar.qml @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** 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.nokia.symbian 1.1 + +Item { + id: root + + implicitWidth: screen.width + implicitHeight: privateStyle.statusBarHeight + property bool platformInverted: false + property alias title: titleText.text + + QtObject { + id: priv + objectName: "priv" + + property bool clickedOpensStatusPanel: symbian.s60Version == Symbian.SV_S60_5_2 ? true : false + property int contentHeight: Math.round(privateStyle.statusBarHeight * 18 / 26) + property int paddingSmallOneQuarter: Math.round(platformStyle.paddingSmall / 4) + property int paddingSmallThreeQuarters: Math.round(platformStyle.paddingSmall * 3 / 4) + + function signalWidthPercentage(signalStrength) { + if (signalStrength < 10) + return 0; + else if (signalStrength < 20) + return 1/5; + else if (signalStrength < 30) + return 2/5; + else if (signalStrength < 60) + return 3/5; + else if (signalStrength < 100) + return 4/5; + else + return 1; + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + + onClicked: { + if (priv.clickedOpensStatusPanel) { + privateStyle.play(Symbian.PopUp) + platformPopupManager.privateShowIndicatorPopup() + } + } + onPressed: { + if (!priv.clickedOpensStatusPanel) { + privateStyle.play(Symbian.PopUp) + platformPopupManager.privateShowIndicatorPopup() + // reset MouseArea state since status panel window eats the release event + symbian.privateSendMouseRelease(mouseArea) + } + } + } + + BorderImage { + source: privateStyle.imagePath("qtg_fr_statusbar", root.platformInverted) + anchors.fill: parent + width: parent.width + + // title text + Text { + id: titleText + color: root.platformInverted ? platformStyle.colorNormalDarkInverted + : platformStyle.colorNormalLight + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: platformStyle.paddingSmall + anchors.right: progressIndicator.visible ? progressIndicator.left : indicators.left + anchors.rightMargin: platformStyle.paddingSmall + elide: Text.ElideRight + font { + family: platformStyle.fontFamilyRegular; + pixelSize: priv.contentHeight + weight: Font.Bold + } + } + + BusyIndicator { + id: progressIndicator + + width: priv.contentHeight + height: priv.contentHeight + anchors { + right: indicators.left + rightMargin: platformStyle.paddingSmall + verticalCenter: parent.verticalCenter + } + running: visible + visible: (stack.currentPage) && (stack.currentPage.showProgressIndicator) + } + + UniversalIndicators { + id: indicators + height: indicatorHeight + anchors { + right: networkMode.left + rightMargin: priv.paddingSmallOneQuarter + verticalCenter: parent.verticalCenter + } + + width: priv.paddingSmallOneQuarter * 2 + 3 * indicatorWidth + + indicatorColor: root.platformInverted ? platformStyle.colorNormalDarkInverted + : platformStyle.colorNormalLight + indicatorWidth: priv.contentHeight // same as height + indicatorHeight: priv.contentHeight + indicatorPadding: priv.paddingSmallOneQuarter + maxIndicatorCount: 3 + } + + // icon for network signal type e.g. 3G, GPRS etc + NetworkIndicator { + id: networkMode + height: priv.contentHeight + width: priv.contentHeight // same as height + anchors.verticalCenter: parent.verticalCenter + anchors.right: offline ? batteryBackground.left : signalBackground.left + anchors.rightMargin: priv.paddingSmallThreeQuarters + color: root.platformInverted ? platformStyle.colorNormalDarkInverted + : platformStyle.colorNormalLight + } + // signal strength + Image { + id: signalBackground + visible: !networkMode.offline + sourceSize.height: priv.contentHeight + sourceSize.width: Math.round(privateStyle.statusBarHeight * 19 / 26) + anchors.verticalCenter: parent.verticalCenter + anchors.right: batteryBackground.left + anchors.rightMargin: priv.paddingSmallThreeQuarters + fillMode: Image.PreserveAspectFit + source: privateStyle.imagePath("qtg_graf_signal_level_bg", root.platformInverted) + Item { + id: signalLevelItem + anchors.left: parent.left + anchors.top: parent.top + height: parent.height + width: priv.signalWidthPercentage(privateNetworkInfo.networkSignalStrength) * parent.width + clip: true + LayoutMirroring.enabled: false + + Image { + sourceSize.width: signalBackground.sourceSize.width + sourceSize.height: signalBackground.sourceSize.height + fillMode: Image.PreserveAspectFit + source: privateStyle.imagePath("qtg_graf_signal_level_full", root.platformInverted) + } + } + } + // battery indicator + Image { + id: batteryBackground + anchors.verticalCenter: parent.verticalCenter + anchors.right: timeItem.left + anchors.rightMargin: priv.paddingSmallThreeQuarters + sourceSize.height: priv.contentHeight + sourceSize.width: Math.round(privateStyle.statusBarHeight * 24 / 26) + fillMode: Image.PreserveAspectFit + source: privateStyle.imagePath((privateBatteryInfo.powerSaveModeEnabled ? + "qtg_graf_battery_level_psm_bg" : + "qtg_graf_battery_level_bg"), root.platformInverted) + + Item { + id: batteryLevel + + property int animatedLevel + + anchors.left: parent.left + anchors.top: parent.top + width: Math.round(privateStyle.statusBarHeight + * ((privateBatteryInfo.charging ? Math.floor(animatedLevel / 100) : + privateBatteryInfo.batteryLevel) + 2) / 13) + height: parent.height + clip: true + LayoutMirroring.enabled: false + + Image { + sourceSize.width: batteryBackground.sourceSize.width + sourceSize.height: batteryBackground.sourceSize.height + + fillMode: Image.PreserveAspectFit + + // Battery state mappings: Levels 0 and 1 are low, 2-4 are medium, 5-7 are full. + // Currently all levels use same graphics with white color. + + source: privateStyle.imagePath((privateBatteryInfo.powerSaveModeEnabled ? + "qtg_graf_battery_level_psm_full" : + "qtg_graf_battery_level_full"), root.platformInverted) + } + } + + Image { + // power save mode indicator + anchors.fill: parent + sourceSize.width: parent.sourceSize.width + sourceSize.height: parent.sourceSize.height + source: privateStyle.imagePath("qtg_graf_battery_psm") + visible: privateBatteryInfo.powerSaveModeEnabled + } + + NumberAnimation { + id: batteryChargingAnimation + loops: Animation.Infinite + running: privateBatteryInfo.charging + target: batteryLevel + property: "animatedLevel" + // Use bigger range (compared to 0-7) in order to make the animation smoother. + from: 0 + to: 799 + duration: 3500 + } + } + // clock + Text { + id: timeItem + width: Math.round(privateStyle.statusBarHeight * 44 / 26) + color: root.platformInverted ? platformStyle.colorNormalDarkInverted + : platformStyle.colorNormalLight + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: platformStyle.paddingSmall + horizontalAlignment: Text.AlignRight + text: symbian.currentTime + font { + family: platformStyle.fontFamilyRegular; + pixelSize: priv.contentHeight + weight: Font.Light + } + } + } +} + diff --git a/app/src/symbian/qml/MySwitch.qml b/app/src/symbian/qml/MySwitch.qml new file mode 100644 index 0000000..228868d --- /dev/null +++ b/app/src/symbian/qml/MySwitch.qml @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Item { + id: root + + property alias text: title.text + property alias checked: switcher.checked + property alias pressed: switcher.pressed + property bool platformInverted: false + + width: parent.width + height: Math.max(title.height, switcher.height) + + Keys.forwardTo: switcher + + Rectangle { + x: platformStyle.paddingSmall + y: platformStyle.paddingSmall + width: parent.width - platformStyle.paddingSmall * 2 + height: parent.height - platformStyle.paddingSmall * 2 + visible: (root.activeFocus) && (symbian.listInteractionMode === Symbian.KeyNavigation) + color: "transparent" + radius: 5 + border { + width: 1 + color: root.platformInverted ? platformStyle.colorDisabledLightInverted + : platformStyle.colorDisabledMid + } + } + + Label { + id: title + + anchors { + left: parent.left + right: switcher.left + rightMargin: platformStyle.paddingSmall + verticalCenter: parent.verticalCenter + } + + platformInverted: root.platformInverted + verticalAlignment: Text.AlignVCenter + maximumLineCount: 2 + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + Switch { + id: switcher + + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + } +} diff --git a/app/src/symbian/qml/MyTextField.qml b/app/src/symbian/qml/MyTextField.qml new file mode 100644 index 0000000..ca84a2a --- /dev/null +++ b/app/src/symbian/qml/MyTextField.qml @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +TextField { + signal accepted + + Keys.onEnterPressed: { + if (acceptableInput) { + accepted(); + } + } + Keys.onReturnPressed: { + if (acceptableInput) { + accepted(); + } + } +} diff --git a/app/src/desktop-qml/qml/Avatar.qml b/app/src/symbian/qml/MyToolButton.qml similarity index 56% rename from app/src/desktop-qml/qml/Avatar.qml rename to app/src/symbian/qml/MyToolButton.qml index 3fd4cea..675d97a 100644 --- a/app/src/desktop-qml/qml/Avatar.qml +++ b/app/src/symbian/qml/MyToolButton.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,22 +14,28 @@ * along with this program. If not, see . */ -import QtQuick 2.0 +import QtQuick 1.1 +import com.nokia.symbian 1.1 -Image { +ToolButton { id: root - - signal clicked - signal rightClicked - - fillMode: Image.PreserveAspectFit - smooth: true - - MouseArea { - id: mouseArea - - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: mouse.button == Qt.RightButton ? root.rightClicked() : root.clicked() - } + + property alias toolTip: ttip.text + + opacity: enabled ? 1 : 0.5 + + ToolTip { + id: ttip + + target: root + visible: false + } + + Timer { + interval: 250 + running: (ttip.text) && (root.pressed) + onTriggered: ttip.visible = true + } + + onPressedChanged: if (!pressed) ttip.visible = false; } diff --git a/app/src/symbian/qml/NetworkSettingsPage.qml b/app/src/symbian/qml/NetworkSettingsPage.qml new file mode 100644 index 0000000..98524cb --- /dev/null +++ b/app/src/symbian/qml/NetworkSettingsPage.qml @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Network") + tools: ToolBarLayout { + BackToolButton { + onClicked: settings.setNetworkProxy() + } + } + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: inputContext.visible ? height : column.height + platformStyle.paddingLarge * 2 + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + MySwitch { + id: proxySwitch + + width: parent.width + text: qsTr("Use network proxy") + checked: settings.networkProxyEnabled + visible: !inputContext.visible + onCheckedChanged: settings.networkProxyEnabled = checked + } + + ValueSelector { + id: typeSelector + + x: -platformStyle.paddingLarge + width: parent.width + platformStyle.paddingLarge * 2 + focusItem: flickable + title: qsTr("Type") + model: NetworkProxyTypeModel {} + value: settings.networkProxyType + visible: !inputContext.visible + onValueChanged: settings.networkProxyType = value + } + + Label { + width: parent.width + elide: Text.ElideRight + text: qsTr("Host") + visible: hostEdit.visible + } + + MyTextField { + id: hostEdit + + width: parent.width + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText + text: settings.networkProxyHost + visible: (!inputContext.visible) || (activeFocus) + onTextChanged: settings.networkProxyHost = text + onAccepted: portEdit.forceActiveFocus() + } + + Label { + width: parent.width + elide: Text.ElideRight + text: qsTr("Port") + visible: portEdit.visible + } + + MyTextField { + id: portEdit + + width: parent.width + inputMethodHints: Qt.ImhDigitsOnly + text: settings.networkProxyPort + visible: (!inputContext.visible) || (activeFocus) + validator: IntValidator { + bottom: 0 + top: 100000 + } + onTextChanged: settings.networkProxyPort = parseInt(text) + onAccepted: closeSoftwareInputPanel() + } + + Label { + width: parent.width + elide: Text.ElideRight + text: qsTr("Username") + visible: usernameEdit.visible + } + + MyTextField { + id: usernameEdit + + width: parent.width + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText + text: settings.networkProxyUsername + visible: (!inputContext.visible) || (activeFocus) + onTextChanged: settings.networkProxyUsername = text + onAccepted: passwordEdit.forceActiveFocus() + } + + Label { + width: parent.width + elide: Text.ElideRight + text: qsTr("Password") + visible: passwordEdit.visible + } + + MyTextField { + id: passwordEdit + + width: parent.width + echoMode: TextInput.Password + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText + text: settings.networkProxyPassword + visible: (!inputContext.visible) || (activeFocus) + onTextChanged: settings.networkProxyPassword = text + onAccepted: closeSoftwareInputPanel() + } + } + } + + ScrollDecorator { + flickableItem: flickable + } +} diff --git a/app/src/symbian/qml/NowPlayingButton.qml b/app/src/symbian/qml/NowPlayingButton.qml new file mode 100644 index 0000000..07b1ef1 --- /dev/null +++ b/app/src/symbian/qml/NowPlayingButton.qml @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +ToolButton { + id: root + + function reset() { + width = 240; + } + + anchors.centerIn: parent + flat: false + iconSource: "images/" + (player.playing ? "play" : player.paused ? "pause" : "stop") + "-active.png" + text: player.metaData.title + visible: player.queueCount > 0 + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("NowPlayingPage.qml")) + onTextChanged: reset() + onVisibleChanged: if (visible) reset(); + onWidthChanged: if (width != 240) reset(); + Component.onCompleted: reset() +} diff --git a/app/src/symbian/qml/NowPlayingPage.qml b/app/src/symbian/qml/NowPlayingPage.qml new file mode 100644 index 0000000..46d8fd8 --- /dev/null +++ b/app/src/symbian/qml/NowPlayingPage.qml @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + property bool playWhenActive: false + + title: qsTr("Now playing") + tools: ToolBarLayout { + BackToolButton {} + + MyToolButton { + iconSource: "images/repeat.png" + toolTip: qsTr("Repeat") + checkable: true + checked: player.repeat + onClicked: player.repeat = !player.repeat + } + + MyToolButton { + iconSource: "images/shuffle.png" + toolTip: qsTr("Shuffle") + checkable: true + checked: player.shuffle + onClicked: player.shuffle = !player.shuffle + } + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + Thumbnail { + id: thumbnail + + anchors { + left: parent.left + top: parent.top + margins: platformStyle.paddingLarge * 2 + } + width: parent.width - platformStyle.paddingLarge * 4 + height: width + source: player.metaData.largeThumbnailUrl + placeholderSource: "images/track-large.jpg" + sourceSize.width: 360 + sourceSize.height: 360 + swipeEnabled: true + opacity: status == Image.Ready ? 1 : 0 + onLeftSwipe: player.next() + onRightSwipe: player.previous() + } + + Item { + id: progressContainer + + anchors { + left: thumbnail.left + right: parent.right + rightMargin: platformStyle.paddingLarge * 2 + topMargin: platformStyle.paddingLarge + bottom: thumbnail.bottom + } + height: progressSlider.height + positionLabel.height + platformStyle.paddingLarge + + Rectangle { + id: progressBackground + + anchors.fill: parent + color: "#000" + opacity: 0.7 + } + + Slider { + id: progressSlider + + anchors { + left: parent.left + leftMargin: platformStyle.paddingSmall + right: parent.right + rightMargin: platformStyle.paddingSmall + top: parent.top + } + minimumValue: 0 + maximumValue: enabled ? player.duration : 1 + enabled: player.seekable + onEnabledChanged: if (!enabled) value = 0; + onPressedChanged: if (!pressed) player.position = value; + onValueChanged: positionLabel.text = utils.formatMSecs(value) + } + + Label { + id: positionLabel + + anchors { + left: parent.left + leftMargin: platformStyle.paddingLarge + top: progressSlider.bottom + } + font.pixelSize: platformStyle.fontSizeSmall + text: "--:--" + } + + Label { + id: bufferLabel + + anchors { + left: positionLabel.right + leftMargin: platformStyle.paddingLarge + right: durationLabel.left + rightMargin: platformStyle.paddingLarge + top: progressSlider.bottom + } + horizontalAlignment: Text.AlignHCenter + font.pixelSize: platformStyle.fontSizeSmall + color: platformStyle.colorNormalMid + elide: Text.ElideRight + } + + Label { + id: durationLabel + + anchors { + right: parent.right + rightMargin: platformStyle.paddingLarge + top: progressSlider.bottom + } + font.pixelSize: platformStyle.fontSizeSmall + text: player.metaData.durationString ? player.metaData.durationString : "--:--" + } + } + + Column { + id: infoColumn + + anchors { + left: thumbnail.left + right: parent.right + rightMargin: platformStyle.paddingLarge * 2 + top: thumbnail.bottom + topMargin: platformStyle.paddingLarge + } + + Label { + id: countLabel + + width: parent.width + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + color: platformStyle.colorNormalMid + font.pixelSize: platformStyle.fontSizeSmall + text: player.queueCount > 0 ? (player.currentIndex + 1) + "/" + player.queueCount + " " + qsTr("tracks") + : qsTr("No tracks") + } + + Label { + id: artistLabel + + width: parent.width + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: player.metaData.artist ? player.metaData.artist : qsTr("Unknown artist") + } + + Label { + id: titleLabel + + width: parent.width + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: player.metaData.title ? player.metaData.title : qsTr("Unknown title") + } + + Label { + id: genreLabel + + width: parent.width + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + font.pixelSize: platformStyle.fontSizeSmall + text: player.metaData.genre ? player.metaData.genre : qsTr("Unknown genre") + } + } + + ButtonRow { + id: buttonRow + + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: platformStyle.paddingLarge * 2 + } + enabled: player.queueCount > 0 + exclusive: false + + Button { + id: previousButton + + iconSource: privateStyle.imagePath("toolbar-mediacontrol-backwards") + onClicked: player.previous() + } + + Button { + id: playPauseButton + + iconSource: privateStyle.imagePath("toolbar-mediacontrol-" + + ((player.paused) || (player.stopped) ? "play" : player.seekable ? "pause" : "stop")) + onClicked: player.playing = !player.playing + } + + Button { + id: nextButton + + iconSource: privateStyle.imagePath("toolbar-mediacontrol-forward") + onClicked: player.next() + } + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: buttonRow + + MenuLayout { + MenuItem { + text: qsTr("Track details") + enabled: player.queueCount > 0 + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("NowPlayingTrackPage.qml")) + } + + MenuItem { + text: qsTr("Playback queue") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PlaybackQueuePage.qml")) + } + + MenuItem { + text: qsTr("Playback settings") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PlaybackSettingsPage.qml")) + } + } + } + } + + Connections { + id: playerConnections + + target: null + onBufferStatusChanged: bufferLabel.text = qsTr("Buffering") + " " + player.bufferStatus + "%" + onPositionChanged: { + if (progressSlider.enabled) { + if (!progressSlider.pressed) { + progressSlider.value = player.position; + } + } + else { + positionLabel.text = player.positionString; + } + } + onStatusChanged: { + switch (player.status) { + case AudioPlayer.Loading: + bufferLabel.text = qsTr("Loading") + break; + case AudioPlayer.Buffering: + bufferLabel.text = qsTr("Buffering") + " " + player.bufferStatus + "%"; + break; + default: + bufferLabel.text = ""; + break; + } + } + } + + Connections { + id: symbianConnections + + target: symbian + onForegroundChanged: { + if (symbian.foreground) { + if (progressSlider.enabled) { + if (!progressSlider.pressed) { + progressSlider.value = player.position; + } + } + else { + positionLabel.text = player.positionString; + } + + switch (player.status) { + case AudioPlayer.Loading: + bufferLabel.text = qsTr("Loading") + break; + case AudioPlayer.Buffering: + bufferLabel.text = qsTr("Buffering") + " " + player.bufferStatus + "%"; + break; + default: + bufferLabel.text = ""; + break; + } + + playerConnections.target = player; + } + else { + playerConnections.target = null; + } + } + } + + states: State { + name: "landscape" + when: !appWindow.inPortrait + + PropertyChanges { + target: thumbnail + anchors.margins: platformStyle.paddingLarge + } + + PropertyChanges { + target: thumbnail + width: parent.height - platformStyle.paddingLarge * 2 + } + + AnchorChanges { + target: progressContainer + anchors { + left: thumbnail.right + top: infoColumn.bottom + bottom: undefined + } + } + + PropertyChanges { + target: progressContainer + anchors { + leftMargin: platformStyle.paddingLarge + rightMargin: platformStyle.paddingLarge + } + } + + PropertyChanges { + target: progressBackground + opacity: 0 + } + + AnchorChanges { + target: infoColumn + anchors { + left: thumbnail.right + top: parent.top + } + } + + PropertyChanges { + target: infoColumn + anchors.leftMargin: platformStyle.paddingLarge * 2 + } + + AnchorChanges { + target: buttonRow + anchors.left: thumbnail.right + } + } + + onStatusChanged: { + if (status === PageStatus.Active) { + if (playWhenActive) { + player.play(); + } + + playWhenActive = false; + + if (progressSlider.enabled) { + if (!progressSlider.pressed) { + progressSlider.value = player.position; + } + } + else { + positionLabel.text = player.positionString; + } + + switch (player.status) { + case AudioPlayer.Loading: + bufferLabel.text = qsTr("Loading") + break; + case AudioPlayer.Buffering: + bufferLabel.text = qsTr("Buffering") + " " + player.bufferStatus + "%"; + break; + default: + bufferLabel.text = ""; + break; + } + + playerConnections.target = player; + } + else { + playerConnections.target = null; + } + } +} diff --git a/app/src/symbian/qml/NowPlayingTrackPage.qml b/app/src/symbian/qml/NowPlayingTrackPage.qml new file mode 100644 index 0000000..2d902c3 --- /dev/null +++ b/app/src/symbian/qml/NowPlayingTrackPage.qml @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Track details") + tools: ToolBarLayout { + BackToolButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: player.metaData.title + } + + HeaderLabel { + id: infoHeader + + width: parent.width + text: qsTr("Details") + } + + Thumbnail { + id: thumbnail + + width: column.height + height: column.height + source: player.metaData.thumbnailUrl + placeholderSource: "images/track.jpg" + enabled: false + } + + Column { + id: column + + width: parent.width - thumbnail.width - parent.spacing + spacing: platformStyle.paddingLarge + + Label { + id: artistLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Artist") + ": " + (player.metaData.artist ? player.metaData.artist : qsTr("Unknown")) + } + + Label { + id: genreLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Genre") + ": " + (player.metaData.genre ? player.metaData.genre : qsTr("Unknown")) + } + + Label { + id: durationLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Duration") + ": " + (player.metaData.durationString ? player.metaData.durationString : qsTr("Unknown")) + } + } + + HeaderLabel { + id: descriptionHeader + + width: parent.width + text: qsTr("Description") + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: player.metaData.description ? player.metaData.description : qsTr("No description") + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + MenuItem { + text: qsTr("Download") + enabled: player.metaData.downloadable + onClicked: { + switch (player.metaData.service) { + case mkresources.SOUNDCLOUD: + appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudDownloadPage.qml")) + .get(player.queue.itemData(player.currentIndex)); + break; + default: + appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginDownloadPage.qml")) + .list(player.queue.itemData(player.currentIndex)); + break; + } + } + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = player.metaData.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + } + } + } +} diff --git a/app/src/symbian/qml/PlayFolderPage.qml b/app/src/symbian/qml/PlayFolderPage.qml new file mode 100644 index 0000000..8c92e09 --- /dev/null +++ b/app/src/symbian/qml/PlayFolderPage.qml @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +FileBrowserPage { + id: root + + title: qsTr("Play folder") + onAccepted: if (!media.playUrl(chosenUrl, true)) appWindow.pageStack.pop(); +} diff --git a/app/src/symbian/qml/PlayUrlPage.qml b/app/src/symbian/qml/PlayUrlPage.qml new file mode 100644 index 0000000..7367cb5 --- /dev/null +++ b/app/src/symbian/qml/PlayUrlPage.qml @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +TextInputPage { + id: root + + title: qsTr("Play URL") + label: qsTr("URL") + inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoPredictiveText + onAccepted: if (!media.playUrl(text, true)) appWindow.pageStack.pop(); +} diff --git a/app/src/symbian/qml/PlaybackQueueDelegate.qml b/app/src/symbian/qml/PlaybackQueueDelegate.qml new file mode 100644 index 0000000..a2426d7 --- /dev/null +++ b/app/src/symbian/qml/PlaybackQueueDelegate.qml @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyListItem { + id: root + + Thumbnail { + id: thumbnail + + anchors { + left: root.paddingItem.left + top: root.paddingItem.top + bottom: root.paddingItem.bottom + } + width: height + source: thumbnailUrl + placeholderSource: "images/track.jpg" + enabled: false + } + + MyListItemText { + id: titleLabel + + anchors { + left: thumbnail.right + leftMargin: platformStyle.paddingLarge + right: loader.left + rightMargin: platformStyle.paddingLarge + top: root.paddingItem.top + } + role: "Title" + mode: root.mode + elide: Text.ElideRight + text: title + } + + MyListItemText { + id: artistLabel + + anchors { + left: titleLabel.left + right: titleLabel.right + bottom: root.paddingItem.bottom + } + role: "SubTitle" + mode: root.mode + elide: Text.ElideRight + text: artist ? artist : qsTr("Unknown artist") + } + + Loader { + id: loader + + anchors { + right: root.paddingItem.right + verticalCenter: root.paddingItem.verticalCenter + } + width: platformStyle.graphicSizeTiny + height: platformStyle.graphicSizeTiny + sourceComponent: player.currentIndex === index ? indicator : undefined + } + + Component { + id: indicator + + Image { + width: platformStyle.graphicSizeTiny + height: platformStyle.graphicSizeTiny + sourceSize.width: platformStyle.graphicSizeTiny + sourceSize.height: platformStyle.graphicSizeTiny + source: "images/" + (player.playing ? "play" : player.paused ? "pause" : "stop") + "-active.png" + smooth: true + } + } +} diff --git a/app/src/symbian/qml/PlaybackQueuePage.qml b/app/src/symbian/qml/PlaybackQueuePage.qml new file mode 100644 index 0000000..4360eae --- /dev/null +++ b/app/src/symbian/qml/PlaybackQueuePage.qml @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Playback queue") + tools: ToolBarLayout { + BackToolButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + MyListView { + id: view + + anchors.fill: parent + model: player.queue + delegate: PlaybackQueueDelegate { + onActivated: player.currentIndex === index ? player.play() : player.setCurrentIndex(index, true) + onClicked: player.currentIndex === index ? player.play() : player.setCurrentIndex(index, true) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No tracks") + visible: player.queueCount === 0 + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Clear") + onClicked: player.clearQueue() + } + } + } + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: player.currentIndex === view.currentIndex ? player.play() + : player.setCurrentIndex(view.currentIndex, true) + } + + MenuItem { + text: qsTr("Download") + enabled: player.queue.data(view.currentIndex, "downloadable") === true + onClicked: { + switch (player.queue.data(view.currentIndex, "service")) { + case mkresources.SOUNDCLOUD: + appWindow.pageStack.push(Qt.resolvedUrl("soundcloud/SoundCloudDownloadPage.qml")) + .get(player.queue.itemData(view.currentIndex)); + break; + default: + appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginDownloadPage.qml")) + .list(player.queue.itemData(view.currentIndex)); + break; + } + } + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = player.queue.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + MenuItem { + text: qsTr("Remove") + onClicked: player.removeTrack(view.currentIndex) + } + } + } + } + + Component.onCompleted: view.positionViewAtIndex(player.currentIndex, ListView.Beginning) +} diff --git a/app/src/symbian/qml/PlaybackSettingsPage.qml b/app/src/symbian/qml/PlaybackSettingsPage.qml new file mode 100644 index 0000000..8b63641 --- /dev/null +++ b/app/src/symbian/qml/PlaybackSettingsPage.qml @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyPage { + id: root + + title: qsTr("Playback settings") + tools: ToolBarLayout { + BackToolButton {} + } + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: inputContext.visible ? height : flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + MySwitch { + id: stopSwitch + + width: parent.width + text: qsTr("Stop after current track") + checked: player.stopAfterCurrentTrack + visible: !inputContext.visible + onCheckedChanged: player.stopAfterCurrentTrack = checked + } + + MySwitch { + id: sleepSwitch + + width: parent.width + text: qsTr("Enable sleep timer") + checked: player.sleepTimerEnabled + visible: !inputContext.visible + onCheckedChanged: player.sleepTimerEnabled = checked + } + + Label { + width: parent.width - sleepDurationField.width - parent.spacing + height: sleepDurationField.height + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + text: qsTr("Sleep timer duration (mins)") + visible: sleepDurationField.visible + } + + MyTextField { + id: sleepDurationField + + inputMethodHints: Qt.ImhDigitsOnly + text: settings.sleepTimerDuration + visible: (!inputContext.visible) || (focus) + onTextChanged: settings.sleepTimerDuration = parseInt(text) + } + + Label { + width: parent.width + elide: Text.ElideRight + color: platformStyle.colorNormalMid + text: player.sleepTimerRemainingString + " " + qsTr("remaining") + visible: (!inputContext.visible) && (player.sleepTimerEnabled) + } + } + } +} diff --git a/app/src/symbian/qml/PlaylistDelegate.qml b/app/src/symbian/qml/PlaylistDelegate.qml new file mode 100644 index 0000000..c59cf93 --- /dev/null +++ b/app/src/symbian/qml/PlaylistDelegate.qml @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyListItem { + id: root + + Thumbnail { + id: thumbnail + + anchors { + left: root.paddingItem.left + top: root.paddingItem.top + bottom: root.paddingItem.bottom + } + width: height + source: thumbnailUrl + placeholderSource: "images/track.jpg" + enabled: false + } + + MyListItemText { + id: titleLabel + + anchors { + left: thumbnail.right + leftMargin: platformStyle.paddingLarge + right: root.paddingItem.right + top: root.paddingItem.top + } + role: "Title" + mode: root.mode + elide: Text.ElideRight + text: title + } + + MyListItemText { + id: artistLabel + + anchors { + left: titleLabel.left + right: titleLabel.right + bottom: root.paddingItem.bottom + } + role: "SubTitle" + mode: root.mode + elide: Text.ElideRight + text: artist ? artist : qsTr("Unknown artist") + } +} diff --git a/app/src/symbian/qml/PluginSettingsPage.qml b/app/src/symbian/qml/PluginSettingsPage.qml new file mode 100644 index 0000000..aaf2071 --- /dev/null +++ b/app/src/symbian/qml/PluginSettingsPage.qml @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + property alias pluginId: plugin.pluginId + property alias pluginSettings: repeater.model + + title: qsTr("Plugin settings") + tools: ToolBarLayout { + BackToolButton {} + } + + PluginSettings { + id: plugin + } + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: inputContext.visible ? height : column.height + platformStyle.paddingLarge * 2 + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Repeater { + id: repeater + + Loader { + function initSourceComponent() { + switch (modelData.type) { + case "boolean": + sourceComponent = checkBox; + break; + case "group": + sourceComponent = group; + break; + case "integer": + sourceComponent = integerField; + break; + case "list": + sourceComponent = valueSelector; + break; + case "password": + sourceComponent = passwordField; + break; + case "text": + sourceComponent = textField; + break; + default: + break; + } + + if (item) { + item.init(modelData); + } + } + + Component.onCompleted: initSourceComponent() + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + } + + Component { + id: checkBox + + MySwitch { + property string key + + function init(modelData, group) { + key = (group ? group + "/" : "") + modelData.key; + text = modelData.label; + checked = plugin.value(key, modelData.value) === true; + } + + width: column.width + visible: !inputContext.visible + onCheckedChanged: plugin.setValue(key, checked) + } + } + + Component { + id: group + + Column { + function init(modelData) { + label.text = modelData.label; + repeater.key = modelData.key; + repeater.model = modelData.settings; + } + + width: column.width + spacing: platformStyle.paddingLarge + + HeaderLabel { + id: label + + width: parent.width + visible: !inputContext.visible + } + + Repeater { + id: repeater + + property string key + + Loader { + function initSourceComponent() { + switch (modelData.type) { + case "boolean": + sourceComponent = checkBox; + break; + case "integer": + sourceComponent = integerField; + break; + case "list": + sourceComponent = valueSelector; + break; + case "password": + sourceComponent = passwordField; + break; + case "text": + sourceComponent = textField; + break; + default: + break; + } + + if (item) { + item.init(modelData, repeater.key); + } + } + + Component.onCompleted: initSourceComponent() + } + } + } + } + + Component { + id: integerField + + Column { + function init(modelData, group) { + label.text = modelData.label; + field.key = (group ? group + "/" : "") + modelData.key; + field.validator.bottom = Math.max(0, parseInt(modelData.minimum)); + field.validator.top = Math.max(1, parseInt(modelData.maximum)); + field.text = plugin.value(field.key, modelData.value); + } + + width: column.width + spacing: platformStyle.paddingLarge + + Label { + id: label + + width: parent.width + elide: Text.ElideRight + visible: field.visible + } + + MyTextField { + id: field + + property string key + + width: parent.width + validator: IntValidator {} + inputMethodHints: Qt.ImhDigitsOnly + visible: (!inputContext.visible) || (activeFocus) + onTextChanged: plugin.setValue(key, text) + onAccepted: closeSoftwareInputPanel() + } + } + } + + Component { + id: passwordField + + Column { + function init(modelData, group) { + label.text = modelData.label; + field.key = (group ? group + "/" : "") + modelData.key; + field.text = plugin.value(field.key, modelData.value); + } + + width: column.width + spacing: platformStyle.paddingLarge + + Label { + id: label + + width: parent.width + elide: Text.ElideRight + visible: field.visible + } + + MyTextField { + id: field + + property string key + + width: parent.width + echoMode: TextInput.Password + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText + visible: (!inputContext.visible) || (activeFocus) + onTextChanged: plugin.setValue(key, text) + onAccepted: closeSoftwareInputPanel() + } + } + } + + Component { + id: textField + + Column { + function init(modelData, group) { + label.text = modelData.label; + field.key = (group ? group + "/" : "") + modelData.key; + field.text = plugin.value(field.key, modelData.value); + } + + width: column.width + spacing: platformStyle.paddingLarge + + Label { + id: label + + width: parent.width + elide: Text.ElideRight + visible: field.visible + } + + MyTextField { + id: field + + property string key + + width: parent.width + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText + visible: (!inputContext.visible) || (activeFocus) + onTextChanged: plugin.setValue(key, text) + onAccepted: closeSoftwareInputPanel() + } + } + } + + Component { + id: valueSelector + + ValueSelector { + property string key + + function init(modelData, group) { + key = (group ? group + "/" : "") + modelData.key + title = modelData.label; + + for (var i = 0; i < modelData.options.length; i++) { + var option = modelData.options[i]; + model.append(option.label, option.value); + } + + value = plugin.value(key, modelData.value); + } + + x: -platformStyle.paddingLarge + width: column.width + platformStyle.paddingLarge * 2 + model: SelectionModel {} + visible: !inputContext.visible + onAccepted: plugin.setValue(key, value) + } + } +} diff --git a/app/src/symbian/qml/PluginsSettingsPage.qml b/app/src/symbian/qml/PluginsSettingsPage.qml new file mode 100644 index 0000000..dc259a0 --- /dev/null +++ b/app/src/symbian/qml/PluginsSettingsPage.qml @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Plugins") + tools: ToolBarLayout { + BackToolButton {} + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginConfigModel { + id: pluginModel + } + delegate: TextDelegate { + text: displayName + subItemIndicator: true + onClicked: settings.length > 0 ? appWindow.pageStack.push(Qt.resolvedUrl("PluginSettingsPage.qml"), + {pluginId: id, pluginSettings: settings, title: displayName}) + : infoBanner.information(qsTr("No settings for this plugin")) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No plugins") + visible: pluginModel.count == 0 + } +} diff --git a/app/src/symbian/qml/PopupLoader.qml b/app/src/symbian/qml/PopupLoader.qml new file mode 100644 index 0000000..c9ce1ec --- /dev/null +++ b/app/src/symbian/qml/PopupLoader.qml @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +QtObject { + function load(popup, parent) { + var obj = popup.createObject(parent); + + if ((obj) && (obj.hasOwnProperty("status"))) { + obj.statusChanged.connect(function () { if (obj.status == DialogStatus.Closed) obj.destroy(); }); + } + + return obj + } + + function open(popup, parent) { + var obj = load(popup, parent); + + if (obj) { + obj.open(); + } + + return obj; + } +} diff --git a/app/src/harmattan/qml/SettingsPage.qml b/app/src/symbian/qml/SettingsPage.qml similarity index 53% rename from app/src/harmattan/qml/SettingsPage.qml rename to app/src/symbian/qml/SettingsPage.qml index 9ffef57..8eaeb1b 100644 --- a/app/src/harmattan/qml/SettingsPage.qml +++ b/app/src/symbian/qml/SettingsPage.qml @@ -1,61 +1,53 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as + * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 1.1 -import com.nokia.meego 1.0 -import "file:///usr/lib/qt4/imports/com/nokia/meego/UIConstants.js" as UI +import com.nokia.symbian 1.1 MyPage { id: root - + title: qsTr("Settings") tools: ToolBarLayout { - - BackToolIcon {} + BackToolButton {} } - - ListView { + + MyListView { id: view - + anchors.fill: parent - model: [qsTr("Media/content"), qsTr("Transfers"), qsTr("Network"), qsTr("Appearance"), - qsTr("Plugins"), qsTr("About")] - header: PageHeader { - title: root.title - } - delegate: DrillDownDelegate { + model: [qsTr("General"), qsTr("Network"), qsTr("Logging"), qsTr("Plugins"), qsTr("About")] + delegate: TextDelegate { + subItemIndicator: true text: modelData onClicked: { switch (index) { case 0: - appWindow.pageStack.push(Qt.resolvedUrl("MediaSettingsPage.qml")); + appWindow.pageStack.push(Qt.resolvedUrl("GeneralSettingsPage.qml")); break; case 1: - appWindow.pageStack.push(Qt.resolvedUrl("TransfersSettingsPage.qml")); + appWindow.pageStack.push(Qt.resolvedUrl("NetworkSettingsPage.qml")); break; case 2: - appWindow.pageStack.push(Qt.resolvedUrl("NetworkSettingsPage.qml")); + appWindow.pageStack.push(Qt.resolvedUrl("LoggingSettingsPage.qml")); break; case 3: - appWindow.pageStack.push(Qt.resolvedUrl("AppearanceSettingsPage.qml")); + appWindow.pageStack.push(Qt.resolvedUrl("PluginsSettingsPage.qml")); break; case 4: - appWindow.pageStack.push(Qt.resolvedUrl("plugins/PluginsSettingsPage.qml")); - break; - case 5: appWindow.pageStack.push(Qt.resolvedUrl("AboutPage.qml")); break; default: @@ -64,7 +56,7 @@ MyPage { } } } - + ScrollDecorator { flickableItem: view } diff --git a/app/src/desktop-qml/qml/LabelDelegate.qml b/app/src/symbian/qml/TextDelegate.qml similarity index 61% rename from app/src/desktop-qml/qml/LabelDelegate.qml rename to app/src/symbian/qml/TextDelegate.qml index daa71ef..41c01c4 100644 --- a/app/src/desktop-qml/qml/LabelDelegate.qml +++ b/app/src/symbian/qml/TextDelegate.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -14,27 +14,24 @@ * along with this program. If not, see . */ -import QtQuick 2.0 -import QtQuick.Controls 1.1 +import QtQuick 1.1 +import com.nokia.symbian 1.1 -ItemDelegate { +MyListItem { id: root - property alias color: label.color - property alias elide: label.elide - property alias font: label.font property alias text: label.text - - Label { + + MyListItemText { id: label anchors { - fill: parent - leftMargin: 2 - rightMargin: 2 + left: root.paddingItem.left + right: root.paddingItem.right + verticalCenter: root.paddingItem.verticalCenter } - color: styleData.textColor - elide: styleData.elideMode - text: styleData.value + role: "Title" + mode: root.mode + elide: Text.ElideRight } } diff --git a/app/src/symbian/qml/TextInputPage.qml b/app/src/symbian/qml/TextInputPage.qml new file mode 100644 index 0000000..1091aba --- /dev/null +++ b/app/src/symbian/qml/TextInputPage.qml @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +EditPage { + id: root + + property alias label: label.text + property string text + property bool multiLine: false + property int inputMethodHints + + title: qsTr("Enter text") + acceptable: text != "" + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: inputContext.visible ? height : column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: label + + width: parent.width + elide: Text.ElideRight + text: qsTr("Text") + } + + Loader { + id: loader + + width: parent.width + sourceComponent: root.multiLine ? textArea : textField + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + Component { + id: textField + + MyTextField { + width: parent.width + inputMethodHints: root.inputMethodHints + onAccepted: root.accept() + onTextChanged: root.text = text + Component.onCompleted: text = root.text + } + } + + Component { + id: textArea + + TextArea { + width: parent.width + inputMethodHints: root.inputMethodHints + onTextChanged: root.text = text + Component.onCompleted: text = root.text + } + } +} diff --git a/app/src/symbian/qml/Thumbnail.qml b/app/src/symbian/qml/Thumbnail.qml new file mode 100644 index 0000000..0dc6838 --- /dev/null +++ b/app/src/symbian/qml/Thumbnail.qml @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 + +Item { + id: root + + property url source + property url placeholderSource + property alias sourceSize: thumbnail.sourceSize + property alias asynchronous: thumbnail.asynchronous + property alias fillMode: thumbnail.fillMode + property alias smooth: thumbnail.smooth + property alias status: thumbnail.status + property bool swipeEnabled: false + + signal clicked + signal leftSwipe + signal rightSwipe + + Image { + id: thumbnail + + property bool complete: false + + function update() { + if (complete) { + if (!root.source.toString()) { + source = root.placeholderSource; + } + else { + source = root.source; + } + } + } + + anchors.fill: parent + fillMode: Image.PreserveAspectFit + asynchronous: true + sourceSize.width: width + sourceSize.height: height + smooth: true + onStatusChanged: if ((status == Image.Error) && (source == root.source)) source = root.placeholderSource; + } + + MouseArea { + id: mouseArea + + property int xPos + property int swipeLength: Math.floor(width / 4) + + anchors.fill: parent + enabled: root.enabled + onPressed: xPos = mouseX + onReleased: { + if (containsMouse) { + if (root.swipeEnabled) { + if ((mouseX - xPos) > swipeLength) { + root.rightSwipe(); + } + else if ((xPos - mouseX) > swipeLength) { + root.leftSwipe(); + } + else { + root.clicked(); + } + } + else { + root.clicked(); + } + } + } + } + + onSourceChanged: thumbnail.update() + onPlaceholderSourceChanged: thumbnail.update() + Component.onCompleted: { + thumbnail.complete = true; + thumbnail.update(); + } +} diff --git a/app/src/symbian/qml/TrackDelegate.qml b/app/src/symbian/qml/TrackDelegate.qml new file mode 100644 index 0000000..dcb67c0 --- /dev/null +++ b/app/src/symbian/qml/TrackDelegate.qml @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyListItem { + id: root + + Thumbnail { + id: thumbnail + + anchors { + left: root.paddingItem.left + top: root.paddingItem.top + bottom: root.paddingItem.bottom + } + z: 10 + width: height + source: thumbnailUrl + placeholderSource: "images/track.jpg" + onClicked: root.activated() + } + + MyListItemText { + id: titleLabel + + anchors { + left: thumbnail.right + leftMargin: platformStyle.paddingLarge + right: root.paddingItem.right + top: root.paddingItem.top + } + role: "Title" + mode: root.mode + elide: Text.ElideRight + text: title + } + + MyListItemText { + id: artistLabel + + anchors { + left: titleLabel.left + right: titleLabel.right + bottom: root.paddingItem.bottom + } + role: "SubTitle" + mode: root.mode + elide: Text.ElideRight + text: artist ? artist : qsTr("Unknown artist") + } +} diff --git a/app/src/symbian/qml/TransferDelegate.qml b/app/src/symbian/qml/TransferDelegate.qml new file mode 100644 index 0000000..f54818d --- /dev/null +++ b/app/src/symbian/qml/TransferDelegate.qml @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyListItem { + id: root + + height: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: root.paddingItem.left + right: root.paddingItem.right + top: root.paddingItem.top + } + + MyListItemText { + id: titleLabel + + width: parent.width - icon.width + mode: root.mode + role: "Title" + text: title + } + + Image { + id: icon + + width: height + height: titleLabel.height + source: transferType === Transfer.Upload ? "images/upload.png" : "images/download.png" + smooth: true + } + + MyListItemText { + id: statusLabel + + width: parent.width + mode: root.mode + role: "SubTitle" + text: statusString + } + + ProgressBar { + id: progressBar + + width: parent.width + minimumValue: 0 + maximumValue: 100 + value: progress + } + + MyListItemText { + id: progressLabel + + width: parent.width + mode: root.mode + role: "SubTitle" + text: progressString + } + } +} diff --git a/app/src/symbian/qml/TransfersPage.qml b/app/src/symbian/qml/TransfersPage.qml new file mode 100644 index 0000000..6f48641 --- /dev/null +++ b/app/src/symbian/qml/TransfersPage.qml @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +MyPage { + id: root + + title: qsTr("Transfers") + tools: ToolBarLayout { + + BackToolButton {} + + MyToolButton { + iconSource: "toolbar-mediacontrol-play" + toolTip: qsTr("Start") + enabled: transfers.count > 0 + onClicked: transfers.start() + } + + MyToolButton { + iconSource: "toolbar-mediacontrol-pause" + toolTip: qsTr("Pause") + enabled: transfers.count > 0 + onClicked: transfers.pause() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: TransferModel { + id: transferModel + } + delegate: TransferDelegate { + onClicked: popups.open(contextMenu, root) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + font.pixelSize: 32 + color: platformStyle.colorNormalMid + text: qsTr("No transfers") + visible: transfers.count === 0 + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + id: contextMenu + + focusItem: view + + MenuLayout { + + MenuItem { + text: qsTr("Start") + enabled: transferModel.data(view.currentIndex, "status") <= Transfer.Queued + onClicked: transfers.get(view.currentIndex).queue() + } + + MenuItem { + text: qsTr("Pause") + enabled: transferModel.data(view.currentIndex, "status") >= Transfer.Downloading + onClicked: transfers.get(view.currentIndex).pause() + } + + MenuItem { + text: qsTr("Priority") + platformSubItemIndicator: true + onClicked: popups.open(priorityDialog, root) + } + + MenuItem { + text: qsTr("Category") + platformSubItemIndicator: true + onClicked: popups.open(categoryDialog, root) + } + + MenuItem { + text: qsTr("Remove") + onClicked: popups.open(removeDialog, root) + } + } + } + } + + Component { + id: priorityDialog + + ValueDialog { + focusItem: view + titleText: qsTr("Priority") + model: TransferPriorityModel {} + onAccepted: transfers.get(view.currentIndex).priority = value + } + } + + Component { + id: categoryDialog + + ValueDialog { + focusItem: view + titleText: qsTr("Category") + model: CategoryNameModel {} + onAccepted: transfers.get(view.currentIndex).category = value + } + } + + Component { + id: removeDialog + + MyQueryDialog { + titleText: qsTr("Remove?") + message: qsTr("Do you want to remove") + " '" + transferModel.data(view.currentIndex, "title")+ "'?" + onAccepted: transfers.get(view.currentIndex).cancel() + } + } +} diff --git a/app/src/harmattan/qml/ValueDialog.qml b/app/src/symbian/qml/ValueDialog.qml similarity index 70% rename from app/src/harmattan/qml/ValueDialog.qml rename to app/src/symbian/qml/ValueDialog.qml index c138cda..b4ccac9 100644 --- a/app/src/harmattan/qml/ValueDialog.qml +++ b/app/src/symbian/qml/ValueDialog.qml @@ -1,23 +1,22 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, + * under the terms and conditions of the GNU General Public License, * version 3, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 +import com.nokia.symbian 1.1 MySelectionDialog { id: root diff --git a/app/src/symbian/qml/ValueListItem.qml b/app/src/symbian/qml/ValueListItem.qml new file mode 100644 index 0000000..c94316d --- /dev/null +++ b/app/src/symbian/qml/ValueListItem.qml @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +MyListItem { + id: root + + property string title: "" + property string subTitle: "" + + flickableMode: true + + Column { + anchors { + verticalCenter: parent.verticalCenter + right: indicator.left + rightMargin: platformStyle.paddingMedium + left: parent.left + leftMargin: platformStyle.paddingLarge + } + + Loader { + anchors.left: parent.left + sourceComponent: title != "" ? titleText : undefined + width: parent.width // elide requires explicit width + } + + Loader { + anchors.left: parent.left + sourceComponent: subTitle != "" ? subTitleText : undefined + width: parent.width // elide requires explicit width + } + } + + Image { + id: indicator + source: root.mode == "disabled" ? privateStyle.imagePath("qtg_graf_choice_list_indicator_disabled", + root.platformInverted) + : privateStyle.imagePath("qtg_graf_choice_list_indicator", + root.platformInverted) + sourceSize.width: platformStyle.graphicSizeSmall + sourceSize.height: platformStyle.graphicSizeSmall + anchors { + right: parent.right + rightMargin: platformStyle.paddingSmall + verticalCenter: parent.verticalCenter + } + } + + Component { + id: titleText + MyListItemText { + mode: root.mode + role: "SelectionTitle" + text: root.title + platformInverted: root.platformInverted + } + } + + Component { + id: subTitleText + MyListItemText { + mode: root.mode + role: "SelectionSubTitle" + text: root.subTitle + platformInverted: root.platformInverted + } + } +} diff --git a/app/src/harmattan/qml/ValueSelector.qml b/app/src/symbian/qml/ValueSelector.qml similarity index 81% rename from app/src/harmattan/qml/ValueSelector.qml rename to app/src/symbian/qml/ValueSelector.qml index 21dafaf..efe895d 100644 --- a/app/src/harmattan/qml/ValueSelector.qml +++ b/app/src/symbian/qml/ValueSelector.qml @@ -1,23 +1,22 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, + * under the terms and conditions of the GNU General Public License, * version 3, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ import QtQuick 1.1 -import com.nokia.meego 1.0 -import MusiKloud 2.0 +import com.nokia.symbian 1.1 ValueListItem { id: root @@ -26,6 +25,7 @@ ValueListItem { property variant model property int selectedIndex: -1 property bool showProgressIndicator + property Item focusItem: null signal accepted signal rejected @@ -41,6 +41,7 @@ ValueListItem { titleText: root.title model: root.model showProgressIndicator: root.showProgressIndicator + focusItem: root.focusItem value: root.value selectedIndex: root.selectedIndex onSelectedIndexChanged: root.selectedIndex = selectedIndex diff --git a/app/src/symbian/qml/VolumeControl.qml b/app/src/symbian/qml/VolumeControl.qml new file mode 100644 index 0000000..ac8840e --- /dev/null +++ b/app/src/symbian/qml/VolumeControl.qml @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 + +Rectangle { + id: root + + width: screen.width + height: 26 + color: "#000" + visible: false + + Image { + id: icon + + source: volumekeys.volume === 0 ? "images/volume-mute.png" : "images/volume.png" + smooth: true + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + } + + ProgressBar { + id: progressBar + + anchors { + left: icon.right + leftMargin: platformStyle.paddingMedium + right: parent.right + rightMargin: platformStyle.paddingMedium + verticalCenter: parent.verticalCenter + } + minimumValue: 0 + maximumValue: 100 + value: volumekeys.volume + } + + Timer { + id: timer + + interval: 2000 + onTriggered: root.visible = false + } + + Connections { + target: volumekeys + onPressed: { + root.visible = true; + timer.restart(); + } + } +} diff --git a/app/src/symbian/qml/images/artist.jpg b/app/src/symbian/qml/images/artist.jpg new file mode 100644 index 0000000..8012261 Binary files /dev/null and b/app/src/symbian/qml/images/artist.jpg differ diff --git a/app/src/harmattan/qml/images/avatar-frame.png b/app/src/symbian/qml/images/avatar-frame.png similarity index 100% rename from app/src/harmattan/qml/images/avatar-frame.png rename to app/src/symbian/qml/images/avatar-frame.png diff --git a/app/src/harmattan/qml/images/avatar-mask.png b/app/src/symbian/qml/images/avatar-mask.png similarity index 100% rename from app/src/harmattan/qml/images/avatar-mask.png rename to app/src/symbian/qml/images/avatar-mask.png diff --git a/app/src/symbian/qml/images/close.png b/app/src/symbian/qml/images/close.png new file mode 100644 index 0000000..f77d345 Binary files /dev/null and b/app/src/symbian/qml/images/close.png differ diff --git a/app/src/symbian/qml/images/download.png b/app/src/symbian/qml/images/download.png new file mode 100644 index 0000000..da47e64 Binary files /dev/null and b/app/src/symbian/qml/images/download.png differ diff --git a/app/src/symbian/qml/images/folder.png b/app/src/symbian/qml/images/folder.png new file mode 100644 index 0000000..2ac114e Binary files /dev/null and b/app/src/symbian/qml/images/folder.png differ diff --git a/app/desktop/harmattan/64/musikloud2.png b/app/src/symbian/qml/images/musikloud2.png similarity index 98% rename from app/desktop/harmattan/64/musikloud2.png rename to app/src/symbian/qml/images/musikloud2.png index 1332df8..417dd53 100644 Binary files a/app/desktop/harmattan/64/musikloud2.png and b/app/src/symbian/qml/images/musikloud2.png differ diff --git a/app/src/symbian/qml/images/no.png b/app/src/symbian/qml/images/no.png new file mode 100644 index 0000000..f77d345 Binary files /dev/null and b/app/src/symbian/qml/images/no.png differ diff --git a/app/src/harmattan/qml/images/pause-accent-color7.png b/app/src/symbian/qml/images/pause-active.png similarity index 100% rename from app/src/harmattan/qml/images/pause-accent-color7.png rename to app/src/symbian/qml/images/pause-active.png diff --git a/app/src/harmattan/qml/images/play-accent-color7.png b/app/src/symbian/qml/images/play-active.png similarity index 100% rename from app/src/harmattan/qml/images/play-accent-color7.png rename to app/src/symbian/qml/images/play-active.png diff --git a/app/src/harmattan/qml/images/repeat-color7.png b/app/src/symbian/qml/images/repeat-active.png similarity index 100% rename from app/src/harmattan/qml/images/repeat-color7.png rename to app/src/symbian/qml/images/repeat-active.png diff --git a/app/src/harmattan/qml/images/repeat.png b/app/src/symbian/qml/images/repeat.png similarity index 100% rename from app/src/harmattan/qml/images/repeat.png rename to app/src/symbian/qml/images/repeat.png diff --git a/app/src/harmattan/qml/images/shuffle-color7.png b/app/src/symbian/qml/images/shuffle-active.png similarity index 100% rename from app/src/harmattan/qml/images/shuffle-color7.png rename to app/src/symbian/qml/images/shuffle-active.png diff --git a/app/src/harmattan/qml/images/shuffle.png b/app/src/symbian/qml/images/shuffle.png similarity index 100% rename from app/src/harmattan/qml/images/shuffle.png rename to app/src/symbian/qml/images/shuffle.png diff --git a/app/src/harmattan/qml/images/stop-accent-color7.png b/app/src/symbian/qml/images/stop-active.png similarity index 100% rename from app/src/harmattan/qml/images/stop-accent-color7.png rename to app/src/symbian/qml/images/stop-active.png diff --git a/app/src/symbian/qml/images/track-large.jpg b/app/src/symbian/qml/images/track-large.jpg new file mode 100644 index 0000000..5d6e5ad Binary files /dev/null and b/app/src/symbian/qml/images/track-large.jpg differ diff --git a/app/src/symbian/qml/images/track.jpg b/app/src/symbian/qml/images/track.jpg new file mode 100644 index 0000000..0cc36cf Binary files /dev/null and b/app/src/symbian/qml/images/track.jpg differ diff --git a/app/src/symbian/qml/images/up.png b/app/src/symbian/qml/images/up.png new file mode 100644 index 0000000..8effac2 Binary files /dev/null and b/app/src/symbian/qml/images/up.png differ diff --git a/app/src/symbian/qml/images/upload.png b/app/src/symbian/qml/images/upload.png new file mode 100644 index 0000000..e3694ef Binary files /dev/null and b/app/src/symbian/qml/images/upload.png differ diff --git a/app/src/symbian/qml/images/volume-mute.png b/app/src/symbian/qml/images/volume-mute.png new file mode 100644 index 0000000..6d982b1 Binary files /dev/null and b/app/src/symbian/qml/images/volume-mute.png differ diff --git a/app/src/symbian/qml/images/volume.png b/app/src/symbian/qml/images/volume.png new file mode 100644 index 0000000..34053cf Binary files /dev/null and b/app/src/symbian/qml/images/volume.png differ diff --git a/app/src/symbian/qml/images/yes.png b/app/src/symbian/qml/images/yes.png new file mode 100644 index 0000000..cde7a67 Binary files /dev/null and b/app/src/symbian/qml/images/yes.png differ diff --git a/app/src/symbian/qml/main.qml b/app/src/symbian/qml/main.qml new file mode 100644 index 0000000..a173cb8 --- /dev/null +++ b/app/src/symbian/qml/main.qml @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 + +AppWindow { + id: appWindow + + showStatusBar: true + showToolBar: true + initialPage: MainPage { + id: mainPage + } + + VolumeControl { + id: volumeControl + } + + MyInfoBanner { + id: infoBanner + } + + QtObject { + id: media + + function addTrack(track) { + player.addTrack(track); + infoBanner.information(qsTr("Track added to playback queue")); + } + + function addTracks(tracks) { + player.addTracks(tracks); + infoBanner.information(tracks.length + " " + qsTr("Tracks added to playback queue")); + } + + function playTrack(track, replacePage) { + player.clearQueue(); + player.addTrack(track); + + if (replacePage) { + appWindow.pageStack.replace(Qt.resolvedUrl("NowPlayingPage.qml"), {playWhenActive: true}); + } + else { + appWindow.pageStack.push(Qt.resolvedUrl("NowPlayingPage.qml"), {playWhenActive: true}); + } + } + + function playTracks(tracks, replacePage) { + player.clearQueue(); + player.addTracks(tracks); + + if (replacePage) { + appWindow.pageStack.replace(Qt.resolvedUrl("NowPlayingPage.qml"), {playWhenActive: true}); + } + else { + appWindow.pageStack.push(Qt.resolvedUrl("NowPlayingPage.qml"), {playWhenActive: true}); + } + } + + function addUrl(url) { + var added = player.addUrl(url); + + if (added > 0) { + infoBanner.information(added + " " + qsTr("Tracks added to playback queue")); + return true; + } + + infoBanner.information(qsTr("No tracks added to playback queue")); + return false; + } + + function playUrl(url, replacePage) { + player.clearQueue(); + var added = player.addUrl(url); + + if (added > 0) { + if (replacePage) { + appWindow.pageStack.replace(Qt.resolvedUrl("NowPlayingPage.qml"), {playWhenActive: true}); + } + else { + appWindow.pageStack.push(Qt.resolvedUrl("NowPlayingPage.qml"), {playWhenActive: true}); + } + + return true; + } + + infoBanner.information(qsTr("No tracks added to playback queue")); + return false; + } + } + + Component.onCompleted: { + plugins.load(); + transfers.restore(); + mainPage.setService(settings.currentService); + + if (settings.restorePlaybackQueueOnStartup) { + player.restoreQueue(); + } + + player.error.connect(function(errorString) { + infoBanner.information(errorString); + }); + + transfers.transferAdded.connect(function() { + infoBanner.information(qsTr("Track added to transfers")); + }); + } +} diff --git a/app/src/symbian/qml/plugins/PluginArtistPage.qml b/app/src/symbian/qml/plugins/PluginArtistPage.qml new file mode 100644 index 0000000..2be2def --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginArtistPage.qml @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function load(artistOrId) { + if (Qt.isQtObject(artistOrId)) { + artist.loadArtist(artistOrId); + } + else { + artist.loadArtist(settings.currentService, artistOrId); + } + } + + title: artist.name ? artist.name : qsTr("Artist") + tools: ToolBarLayout { + BackToolButton { + onClicked: artist.cancel() + } + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + PluginArtist { + id: artist + + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: artist.name ? artist.name : qsTr("Unknown artist") + } + + Avatar { + id: avatar + + width: platformStyle.graphicSizeLarge + height: platformStyle.graphicSizeLarge + source: artist.thumbnailUrl + placeholderSource: "../images/artist.jpg" + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: artist.description ? utils.toRichText(artist.description) : qsTr("No description") + onLinkActivated: { + var resource = mkresources.getResourceFromUrl(link); + + if (resource.service !== artist.service) { + Qt.openUrlExternally(link); + return; + } + + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); + break; + default: + break; + } + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + Repeater { + model: artist.actions + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + artist.del(modelData.type, modelData.id); + break; + case "insert": + artist.insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + + MenuItem { + text: qsTr("Tracks") + enabled: artist.tracksId !== "" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTracksPage.qml"), {title: text}) + .list(artist.tracksId) + } + + MenuItem { + text: qsTr("Playlists") + enabled: artist.playlistsId !== "" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistsPage.qml"), {title: text}) + .list(artist.playlistsId) + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginArtistsPage.qml b/app/src/symbian/qml/plugins/PluginArtistsPage.qml new file mode 100644 index 0000000..f5d64df --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginArtistsPage.qml @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function list(id) { + artistModel.list(id); + } + + function search(query, order) { + artistModel.search(query, order); + } + + title: qsTr("Artists") + tools: ToolBarLayout { + BackToolButton { + onClicked: artistModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: artistModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginArtistModel { + id: artistModel + + service: settings.currentService + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: ArtistDelegate { + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(artistModel.get(index)) + onPressAndHold: if (actions.length) popups.open(contextMenu, root); + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + Repeater { + model: artistModel.data(view.currentIndex, "actions") + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + artistModel.get(view.currentIndex).del(modelData.type, modelData.id); + break; + case "insert": + artistModel.get(view.currentIndex).insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginCategoriesPage.qml b/app/src/symbian/qml/plugins/PluginCategoriesPage.qml new file mode 100644 index 0000000..749bdde --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginCategoriesPage.qml @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function list(id) { + categoryModel.list(id); + } + + function search(query, order) { + categoryModel.search(query, order); + } + + title: qsTr("Categories") + tools: ToolBarLayout { + BackToolButton { + onClicked: categoryModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: categoryModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginCategoryModel { + id: categoryModel + + service: settings.currentService + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: TextDelegate { + text: name + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTracksPage.qml")) + .list(value.tracksId) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } +} diff --git a/app/src/symbian/qml/plugins/PluginCommentsPage.qml b/app/src/symbian/qml/plugins/PluginCommentsPage.qml new file mode 100644 index 0000000..fe375b5 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginCommentsPage.qml @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function list(id) { + commentModel.list(id); + } + + function search(query, order) { + commentModel.search(query, order); + } + + title: qsTr("Comments") + tools: ToolBarLayout { + BackToolButton { + onClicked: commentModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: commentModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginCommentModel { + id: commentModel + + service: settings.currentService + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: CommentDelegate { + onActivated: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(artistId) + onPressAndHold: if (actions.length) popups.open(contextMenu, root); + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + Repeater { + model: commentModel.data(view.currentIndex, "actions") + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + commentModel.get(view.currentIndex).del(modelData.type, modelData.id); + break; + case "insert": + commentModel.get(view.currentIndex).insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginDownloadPage.qml b/app/src/symbian/qml/plugins/PluginDownloadPage.qml new file mode 100644 index 0000000..236bc37 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginDownloadPage.qml @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +EditPage { + id: root + + property variant track + + function list(trackData) { + track = trackData; + streamModel.service = track.service; + + if (!track.streamUrl.toString()) { + streamModel.list(track.id); + } + else { + streamModel.clear(); + streamModel.append(tr("Default format"), ""); + } + } + + title: qsTr("Download") + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + + ValueSelector { + id: streamSelector + + width: parent.width + model: PluginStreamModel { + id: streamModel + + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: { + root.showProgressIndicator = true; + streamSelector.showProgressIndicator = true; + root.acceptable = false; + return; + } + case ResourcesRequest.Ready: + if (streamModel.count > 0) { + streamSelector.selectedIndex = Math.max(0, match("name", settings.defaultDownloadFormat(service))); + root.acceptable = true; + } + else { + root.acceptable = false; + } + + break; + case ResourcesRequest.Failed: { + infoBanner.information(errorString); + root.acceptable = false; + break; + } + default: + break; + } + + root.showProgressIndicator = false; + streamSelector.showProgressIndicator = false; + } + } + title: qsTr("Audio format") + } + + ValueSelector { + id: categorySelector + + width: parent.width + model: CategoryNameModel { + id: categoryModel + } + title: qsTr("Category") + value: settings.defaultCategory + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + onAccepted: { + settings.setDefaultDownloadFormat(track.service, + streamModel.data(streamSelector.selectedIndex, "name")); + settings.defaultCategory = categorySelector.value; + transfers.addDownloadTransfer(track.service, track.id, streamSelector.value.id, track.streamUrl, + track.title, categorySelector.value); + appWindow.pageStack.pop(); + } + + onRejected: streamModel.cancel() +} diff --git a/app/src/symbian/qml/plugins/PluginPlaylistPage.qml b/app/src/symbian/qml/plugins/PluginPlaylistPage.qml new file mode 100644 index 0000000..0a1e934 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginPlaylistPage.qml @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function load(playlistOrId) { + if (Qt.isQtObject(playlistOrId)) { + playlist.loadPlaylist(playlistOrId); + trackModel.list(playlist.tracksId); + } + else { + playlist.loadPlaylist(settings.currentService, playlistOrId); + } + } + + title: playlist.title ? playlist.title : qsTr("Playlist") + tools: ToolBarLayout { + BackToolButton { + onClicked: { + playlist.cancel(); + trackModel.cancel(); + } + } + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + PluginPlaylist { + id: playlist + + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case ResourcesRequest.Ready: + trackModel.list(playlist.tracksId); + return; + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + PluginTrackModel { + id: trackModel + + service: playlist.service + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case ResourcesRequest.Ready: + if (count == 0) { + infoBanner.information(qsTr("Playlist has no tracks")); + } + + break; + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: playlist.title ? playlist.title : qsTr("Unknown title") + } + + HeaderLabel { + id: infoHeader + + width: parent.width + text: qsTr("Details") + } + + Thumbnail { + id: thumbnail + + width: column.height + height: column.height + source: playlist.thumbnailUrl + placeholderSource: "../images/track.jpg" + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.playTracks(tracks); + } + } + + Column { + id: column + + width: parent.width - thumbnail.width - parent.spacing + spacing: platformStyle.paddingLarge + + Label { + id: artistLabel + + width: parent.width + elide: Text.ElideRight + clip: true + text: qsTr("Artist") + ": " + (playlist.artistId ? "" + + playlist.artist + "" : qsTr("Unknown")) + onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")) + .load(playlist.artistId) + } + + Label { + id: genreLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Genre") + ": " + (playlist.genre ? playlist.genre : qsTr("Unknown")) + } + + Label { + id: durationLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Duration") + ": " + + (playlist.durationString ? playlist.durationString : qsTr("Unknown")) + } + } + + HeaderLabel { + id: descriptionHeader + + width: parent.width + text: qsTr("Description") + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: playlist.description ? utils.toRichText(playlist.description) : qsTr("No description") + onLinkActivated: { + var resource = mkresources.getResourceFromUrl(link); + + if (resource.service !== playlist.service) { + Qt.openUrlExternally(link); + return; + } + + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); + break; + default: + break; + } + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + MenuItem { + text: qsTr("Play") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.playTracks(tracks); + } + } + + MenuItem { + text: qsTr("Queue") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.addTracks(tracks); + } + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = playlist.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + MenuItem { + text: qsTr("Tracks") + enabled: trackModel.count > 0 + onClicked: appWindow.pageStack.push(tracksPage) + } + } + } + } + + Component { + id: tracksPage + + MyPage { + id: root + + title: qsTr("Tracks") + tools: ToolBarLayout { + BackToolButton {} + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + MyListView { + id: view + + anchors.fill: parent + model: trackModel + delegate: TrackDelegate { + onActivated: media.playTrack(trackModel.get(index)); + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(trackModel.get(index)) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Component { + id: menu + + MyMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.playTracks(tracks); + } + } + + MenuItem { + text: qsTr("Queue") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.addTracks(tracks); + } + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = playlist.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + } + } + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: media.playTrack(trackModel.get(view.currentIndex)); + } + + MenuItem { + text: qsTr("Queue") + onClicked: media.addTrack(trackModel.get(view.currentIndex)) + } + + MenuItem { + text: qsTr("Download") + enabled: trackModel.data(view.currentIndex, "downloadable") === true + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginDownloadPage.qml")) + .list(trackModel.itemData(view.currentIndex)); + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = trackModel.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + Repeater { + model: trackModel.data(view.currentIndex, "actions") + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + trackModel.get(view.currentIndex).del(modelData.type, modelData.id); + break; + case "insert": + trackModel.get(view.currentIndex).insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginPlaylistsPage.qml b/app/src/symbian/qml/plugins/PluginPlaylistsPage.qml new file mode 100644 index 0000000..747f8d4 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginPlaylistsPage.qml @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function list(id) { + playlistModel.list(id); + } + + function search(query, order) { + playlistModel.search(query, order); + } + + title: qsTr("Playlists") + tools: ToolBarLayout { + BackToolButton { + onClicked: playlistModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: playlistModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginPlaylistModel { + id: playlistModel + + service: settings.currentService + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: PlaylistDelegate { + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")) + .load(playlistModel.get(index)) + onPressAndHold: popups.open(contextMenu, root); + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = playlistModel.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + Repeater { + model: playlistModel.data(view.currentIndex, "actions") + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + playlistModel.get(view.currentIndex).del(modelData.type, modelData.id); + break; + case "insert": + playlistModel.get(view.currentIndex).insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginSearchPage.qml b/app/src/symbian/qml/plugins/PluginSearchPage.qml new file mode 100644 index 0000000..026ed08 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginSearchPage.qml @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +EditPage { + id: root + + function search() { + if (!searchField.text) { + return; + } + + var resource = mkresources.getResourceFromUrl(searchField.text); + + if ((resource) && (resource.service === settings.currentService)) { + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); + return; + case mkresources.PLAYLIST: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); + return; + case mkresources.TRACK: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); + return; + default: + break; + } + } + + settings.addSearch(searchField.text); + settings.setDefaultSearchType(settings.currentService, + searchTypeModel.data(searchTypeSelector.selectedIndex, "name")); + + switch (searchTypeSelector.value.type) { + case mkresources.ARTIST: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginArtistsPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .search(searchField.text, searchTypeSelector.value.order); + break; + case mkresources.CATEGORY: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginCategoriesPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .search(searchField.text, searchTypeSelector.value.order); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginPlaylistsPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .search(searchField.text, searchTypeSelector.value.order); + break; + case mkresources.TRACK: + appWindow.pageStack.replace(Qt.resolvedUrl("PluginTracksPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .search(searchField.text, searchTypeSelector.value.order); + break; + default: + break; + } + } + + title: qsTr("Search") + acceptable: searchField.text !== "" + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + width: parent.width + elide: Text.ElideRight + text: qsTr("Query") + } + + MyTextField { + id: searchField + + width: parent.width + onAccepted: root.accepted() + } + + ValueSelector { + id: searchTypeSelector + + x: -platformStyle.paddingLarge + width: parent.width + platformStyle.paddingLarge * 2 + title: qsTr("Search for") + model: PluginSearchTypeModel { + id: searchTypeModel + + service: settings.currentService + } + selectedIndex: Math.max(0, searchTypeModel.match("name", + settings.defaultSearchType(settings.currentService))) + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + states: State { + name: "inputContextVisible" + when: inputContext.visible + + PropertyChanges { + target: flickable + contentHeight: flickable.height + } + + PropertyChanges { + target: searchTypeSelector + visible: false + } + } + + onAccepted: search() +} diff --git a/app/src/symbian/qml/plugins/PluginTrackPage.qml b/app/src/symbian/qml/plugins/PluginTrackPage.qml new file mode 100644 index 0000000..479efa0 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginTrackPage.qml @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function load(trackOrId) { + if (Qt.isQtObject(trackOrId)) { + track.loadTrack(trackOrId); + } + else { + track.loadTrack(settings.currentService, trackOrId); + } + } + + title: track.title ? track.title : qsTr("Track") + tools: ToolBarLayout { + BackToolButton { + onClicked: track.cancel() + } + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + PluginTrack { + id: track + + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: track.title ? track.title : qsTr("Unknown title") + } + + HeaderLabel { + id: infoHeader + + width: parent.width + text: qsTr("Details") + } + + Thumbnail { + id: thumbnail + + width: column.height + height: column.height + source: track.thumbnailUrl + placeholderSource: "../images/track.jpg" + onClicked: media.playTrack(track) + } + + Column { + id: column + + width: parent.width - thumbnail.width - parent.spacing + spacing: platformStyle.paddingLarge + + Label { + id: artistLabel + + width: parent.width + elide: Text.ElideRight + clip: true + text: qsTr("Artist") + ": " + (track.artistId ? "" + + track.artist + "" : qsTr("Unknown")) + onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")) + .load(track.artistId) + } + + Label { + id: genreLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Genre") + ": " + (track.genre ? track.genre : qsTr("Unknown")) + } + + Label { + id: durationLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Duration") + ": " + (track.durationString ? track.durationString : qsTr("Unknown")) + } + } + + HeaderLabel { + id: descriptionHeader + + width: parent.width + text: qsTr("Description") + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: track.description ? utils.toRichText(track.description) : qsTr("No description") + onLinkActivated: { + var resource = mkresources.getResourceFromUrl(link); + + if (resource.service !== track.service) { + Qt.openUrlExternally(link); + return; + } + + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistPage.qml")).load(resource.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistPage.qml")).load(resource.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(resource.id); + break; + default: + break; + } + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: media.playTrack(track) + } + + MenuItem { + text: qsTr("Queue") + onClicked: media.addTrack(track) + } + + MenuItem { + text: qsTr("Download") + enabled: track.downloadable + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginDownloadPage.qml")).list(track) + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = track.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + Repeater { + model: track.actions + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + track.del(modelData.type, modelData.id); + break; + case "insert": + track.insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + + MenuItem { + text: qsTr("Related") + enabled: track.relatedTracksId !== "" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTracksPage.qml"), {title: text}) + .list(track.relatedTracksId) + } + + MenuItem { + text: qsTr("Comments") + enabled: track.commentsId !== "" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginCommentsPage.qml"), {title: text}) + .list(track.commentsId) + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginTracksPage.qml b/app/src/symbian/qml/plugins/PluginTracksPage.qml new file mode 100644 index 0000000..1e53dcc --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginTracksPage.qml @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +MyPage { + id: root + + function list(id) { + trackModel.list(id); + } + + function search(query, order) { + trackModel.search(query, order); + } + + title: qsTr("Tracks") + tools: ToolBarLayout { + BackToolButton { + onClicked: trackModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: trackModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: PluginTrackModel { + id: trackModel + + service: settings.currentService + onStatusChanged: { + switch (status) { + case ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: TrackDelegate { + onActivated: media.playTrack(trackModel.get(index)); + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginTrackPage.qml")).load(trackModel.get(index)) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: media.playTrack(trackModel.get(view.currentIndex)); + } + + MenuItem { + text: qsTr("Queue") + onClicked: media.addTrack(trackModel.get(view.currentIndex)) + } + + MenuItem { + text: qsTr("Download") + enabled: trackModel.data(view.currentIndex, "downloadable") === true + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("PluginDownloadPage.qml")) + .list(trackModel.itemData(view.currentIndex)); + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = trackModel.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + Repeater { + model: trackModel.data(view.currentIndex, "actions") + + MenuItem { + text: modelData.label + onClicked: { + switch (modelData.method) { + case "del": + trackModel.get(view.currentIndex).del(modelData.type, modelData.id); + break; + case "insert": + trackModel.get(view.currentIndex).insert(modelData.type, modelData.id); + break; + default: + break; + } + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/plugins/PluginView.qml b/app/src/symbian/qml/plugins/PluginView.qml new file mode 100644 index 0000000..a6de611 --- /dev/null +++ b/app/src/symbian/qml/plugins/PluginView.qml @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +Item { + id: root + + MyListView { + id: view + + anchors.fill: parent + model: PluginNavModel { + id: navModel + + service: settings.currentService + } + delegate: TextDelegate { + subItemIndicator: true + text: name + onClicked: { + if (!value) { + appWindow.pageStack.push(Qt.resolvedUrl("PluginSearchPage.qml")); + return; + } + + switch (value.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginArtistsPage.qml"), {title: value.label}) + .list(value.id); + break; + case mkresources.CATEGORY: + appWindow.pageStack.push(Qt.resolvedUrl("PluginCategoriesPage.qml"), {title: value.label}) + .list(value.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("PluginPlaylistsPage.qml"), {title: value.label}) + .list(value.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("PluginTracksPage.qml"), {title: value.label}) + .list(value.id); + break; + default: + break; + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudAccountsPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudAccountsPage.qml new file mode 100644 index 0000000..b0922fc --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudAccountsPage.qml @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + title: qsTr("Accounts") + tools: ToolBarLayout { + BackToolButton { + onClicked: { + authRequest.cancel(); + userRequest.cancel(); + } + } + + MyToolButton { + iconSource: "toolbar-add" + onClicked: { + var page = appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudAuthPage.qml")); + page.accepted.connect(function() { authRequest.exchangeCodeForAccessToken(page.code); }); + } + } + } + + MyListView { + id: view + + anchors.fill: parent + model: SoundCloudAccountModel { + id: accountModel + } + delegate: AccountDelegate { + onClicked: infoBanner.information(accountModel.selectAccount(index) + ? qsTr("You have selected account") + " '" + username + "'" : accountModel.errorString) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + font.pixelSize: 32 + color: platformStyle.colorNormalMid + text: qsTr("No accounts") + visible: accountModel.count === 0 + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Remove") + onClicked: infoBanner.information(accountModel.removeAccount(view.currentIndex) + ? qsTr("Account removed. Please visit the SoundCloud website to revoke the access token") + : accountModel.errorString) + } + } + } + } + + QSoundCloud.AuthenticationRequest { + id: authRequest + + clientId: soundcloud.clientId + clientSecret: soundcloud.clientSecret + redirectUri: soundcloud.redirectUri + scopes: soundcloud.scopes + onStatusChanged: { + switch (status) { + case QSoundCloud.AuthenticationRequest.Loading: + root.showProgressIndicator = true; + return; + case QSoundCloud.AuthenticationRequest.Ready: + if (result.access_token) { + userRequest.accessToken = result.access_token; + userRequest.refreshToken = (result.refresh_token ? result.refresh_token : ""); + userRequest.get("/me"); + return; + } + + break; + case QSoundCloud.AuthenticationRequest.Failed: + infoBanner.information(soundcloud.getErrorString(result)); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + QSoundCloud.ResourcesRequest { + id: userRequest + + clientId: soundcloud.clientId + clientSecret: soundcloud.clientSecret + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case QSoundCloud.ResourcesRequest.Ready: + if (accountModel.addAccount(result.id, result.username, accessToken, refreshToken, + soundcloud.scopes.join(" "))) { + infoBanner.information(qsTr("You are signed in to account") + " '" + result.username + "'"); + } + else { + infoBanner.information(accountModel.errorString); + } + + break; + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(soundcloud.getErrorString(result)); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudArtistPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudArtistPage.qml new file mode 100644 index 0000000..674bc68 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudArtistPage.qml @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function load(artistOrId) { + artist.loadArtist(artistOrId); + + if ((Qt.isQtObject(artistOrId)) && (!artist.followed) && (soundcloud.userId !== "")) { + artist.checkIfFollowed(); + } + } + + title: artist.name ? artist.name : qsTr("Artist") + tools: ToolBarLayout { + BackToolButton { + onClicked: artist.cancel() + } + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + SoundCloudArtist { + id: artist + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case QSoundCloud.ResourcesRequest.Ready: + if ((!followed) && (soundcloud.userId !== "")) { + root.showProgressIndicator = false; + checkIfFollowed(); + return; + } + + break; + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: artist.name ? artist.name : qsTr("Unknown artist") + } + + Avatar { + id: avatar + + width: platformStyle.graphicSizeLarge + height: platformStyle.graphicSizeLarge + source: artist.thumbnailUrl + placeholderSource: "../images/artist.jpg" + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: artist.description ? utils.toRichText(artist.description) : qsTr("No description") + onLinkActivated: { + var resource = mkresources.getResourceFromUrl(link); + + if (resource.service !== artist.service) { + Qt.openUrlExternally(link); + return; + } + + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); + break; + default: + break; + } + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + MenuItem { + text: artist.followed ? qsTr("Unfollow") : qsTr("Follow") + enabled: (soundcloud.userId) && (soundcloud.userId !== artist.id) + onClicked: artist.followed ? artist.unfollow() : artist.follow() + } + + MenuItem { + text: qsTr("Tracks") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTracksPage.qml"), {title: text}) + .get("/users/" + artist.id + "/tracks", {limit: MAX_RESULTS}) + } + + MenuItem { + text: qsTr("Favourites") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTracksPage.qml"), {title: text}) + .get("/users/" + artist.id + "/favorites", {limit: MAX_RESULTS}) + } + + MenuItem { + text: qsTr("Sets") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistsPage.qml"), {title: text}) + .get("/users/" + artist.id + "/playlists", {limit: MAX_RESULTS}) + } + + MenuItem { + text: qsTr("Followings") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistsPage.qml"), {title: text}) + .get("/users/" + artist.id + "/followings", {limit: MAX_RESULTS}) + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudArtistsPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudArtistsPage.qml new file mode 100644 index 0000000..a935813 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudArtistsPage.qml @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function get(path, filters) { + artistModel.get(path, filters); + } + + title: qsTr("Artists") + tools: ToolBarLayout { + BackToolButton { + onClicked: artistModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: artistModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: SoundCloudArtistModel { + id: artistModel + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: ArtistDelegate { + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")) + .load(artistModel.get(index)) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudAuthPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudAuthPage.qml new file mode 100644 index 0000000..9713bd1 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudAuthPage.qml @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import QtQuick 1.1 +import QtWebKit 1.0 +import com.nokia.symbian 1.1 +import ".." + +MyPage { + id: root + + property string code + + signal accepted + signal rejected + + title: qsTr("Authentication") + showProgressIndicator: webView.status === WebView.Loading + tools: ToolBarLayout { + BackToolButton { + onClicked: root.rejected() + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentWidth: webView.width + contentHeight: webView.height + + WebView { + id: webView + + preferredWidth: flickable.width + preferredHeight: flickable.height + settings.privateBrowsingEnabled: true + opacity: status === WebView.Loading ? 0 : 1 + url: soundcloud.authUrl() + onUrlChanged: { + var s = url.toString(); + + if (/code=/i.test(s)) { + root.code = s.split("code=")[1].split("#")[0]; + root.accepted(); + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + onAccepted: appWindow.pageStack.pop() +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudCommentPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudCommentPage.qml new file mode 100644 index 0000000..c84cbae --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudCommentPage.qml @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +TextInputPage { + id: root + + property string trackId + + title: qsTr("Add comment") + label: qsTr("Comment") + multiLine: true + + SoundCloudComment { + id: comment + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: + root.showProgressIndicator = true; + root.acceptable = false; + return; + case QSoundCloud.ResourcesRequest.Ready: + appWindow.pageStack.pop(); + break; + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + root.acceptable = (root.text != ""); + } + } + + onAccepted: { + var data = {}; + data["track_id"] = trackId; + data["body"] = text; + comment.addComment(data); + } + + onRejected: comment.cancel() +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudCommentsPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudCommentsPage.qml new file mode 100644 index 0000000..beb2037 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudCommentsPage.qml @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function get(path, filters) { + commentModel.get(path, filters); + } + + title: qsTr("Comments") + tools: ToolBarLayout { + BackToolButton { + onClicked: commentModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: menuButton + + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + MyListView { + id: view + + anchors.fill: parent + model: SoundCloudCommentModel { + id: commentModel + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: { + root.showProgressIndicator = true; + menuButton.enabled = false; + label.visible = false; + return; + } + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + menuButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: CommentDelegate { + onActivated: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(artistId) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Add comment") + enabled: soundcloud.userId !== "" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudCommentPage.qml"), + {trackId: commentModel.data(view.currentIndex, "trackId")}) + } + + MenuItem { + text: qsTr("Reload") + onClicked: commentModel.reload() + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudDownloadPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudDownloadPage.qml new file mode 100644 index 0000000..23ca838 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudDownloadPage.qml @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +EditPage { + id: root + + property variant track + + function get(trackData) { + track = trackData; + streamModel.get(track.id); + } + + title: qsTr("Download") + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + + ValueSelector { + id: streamSelector + + width: parent.width + model: SoundCloudStreamModel { + id: streamModel + + onStatusChanged: { + switch (status) { + case QSoundCloud.StreamsRequest.Loading: { + root.showProgressIndicator = true; + streamSelector.showProgressIndicator = true; + root.acceptable = false; + return; + } + case QSoundCloud.StreamsRequest.Ready: + if (streamModel.count > 0) { + streamSelector.selectedIndex + = Math.max(0, match("name", settings.defaultDownloadFormat(mkresources.SOUNDCLOUD))); + root.acceptable = true; + } + else { + root.acceptable = false; + } + + break; + case QSoundCloud.StreamsRequest.Failed: { + infoBanner.information(errorString); + root.acceptable = false; + break; + } + default: + break; + } + + root.showProgressIndicator = false; + streamSelector.showProgressIndicator = false; + } + } + title: qsTr("Audio format") + } + + ValueSelector { + id: categorySelector + + width: parent.width + model: CategoryNameModel { + id: categoryModel + } + title: qsTr("Category") + value: settings.defaultCategory + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + onAccepted: { + settings.setDefaultDownloadFormat(mkresources.SOUNDCLOUD, + streamModel.data(streamSelector.selectedIndex, "name")); + settings.defaultCategory = categorySelector.value; + transfers.addDownloadTransfer(mkresources.SOUNDCLOUD, track.id, streamSelector.value.id, track.streamUrl, + track.title, categorySelector.value); + appWindow.pageStack.pop(); + } + + onRejected: streamModel.cancel() +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudPlaylistPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudPlaylistPage.qml new file mode 100644 index 0000000..8f7abe4 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudPlaylistPage.qml @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function load(playlistOrId) { + playlist.loadPlaylist(playlistOrId); + + if (Qt.isQtObject(playlistOrId)) { + trackModel.get("/playlists/" + playlist.id); + } + } + + title: playlist.title ? playlist.title : qsTr("Set") + tools: ToolBarLayout { + BackToolButton { + onClicked: { + playlist.cancel(); + trackModel.cancel(); + } + } + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + SoundCloudPlaylist { + id: playlist + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case QSoundCloud.ResourcesRequest.Ready: + trackModel.get("/playlists/" + id); + return; + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + SoundCloudTrackModel { + id: trackModel + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case QSoundCloud.ResourcesRequest.Ready: + if (count == 0) { + infoBanner.information(qsTr("Set has no tracks")); + } + + break; + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: playlist.title ? playlist.title : qsTr("Unknown title") + } + + HeaderLabel { + id: infoHeader + + width: parent.width + text: qsTr("Details") + } + + Thumbnail { + id: thumbnail + + width: column.height + height: column.height + source: playlist.thumbnailUrl + placeholderSource: "../images/track.jpg" + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.playTracks(tracks); + } + } + + Column { + id: column + + width: parent.width - thumbnail.width - parent.spacing + spacing: platformStyle.paddingLarge + + Label { + id: artistLabel + + width: parent.width + elide: Text.ElideRight + clip: true + text: qsTr("Artist") + ": " + (playlist.artistId ? "" + + playlist.artist + "" : qsTr("Unknown")) + onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")) + .load(playlist.artistId) + } + + Label { + id: genreLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Genre") + ": " + (playlist.genre ? playlist.genre : qsTr("Unknown")) + } + + Label { + id: durationLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Duration") + ": " + + (playlist.durationString ? playlist.durationString : qsTr("Unknown")) + } + } + + HeaderLabel { + id: descriptionHeader + + width: parent.width + text: qsTr("Description") + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: playlist.description ? utils.toRichText(playlist.description) : qsTr("No description") + onLinkActivated: { + var resource = mkresources.getResourceFromUrl(link); + + if (resource.service !== mkresources.SOUNDCLOUD) { + Qt.openUrlExternally(link); + return; + } + + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); + break; + default: + break; + } + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + MenuItem { + text: qsTr("Play") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.playTracks(tracks); + } + } + + MenuItem { + text: qsTr("Queue") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.addTracks(tracks); + } + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = playlist.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + MenuItem { + text: qsTr("Tracks") + enabled: trackModel.count > 0 + onClicked: appWindow.pageStack.push(tracksPage) + } + } + } + } + + Component { + id: tracksPage + + MyPage { + id: root + + title: qsTr("Tracks") + tools: ToolBarLayout { + BackToolButton {} + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + MyListView { + id: view + + anchors.fill: parent + model: trackModel + delegate: TrackDelegate { + onActivated: media.playTrack(trackModel.get(index)) + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")) + .load(trackModel.get(index)) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Component { + id: menu + + MyMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.playTracks(tracks); + } + } + + MenuItem { + text: qsTr("Queue") + enabled: trackModel.count > 0 + onClicked: { + var tracks = []; + + for (var i = 0; i < trackModel.count; i++) { + tracks.push(trackModel.get(i)); + } + + media.addTracks(tracks); + } + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = playlist.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + } + } + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: media.playTrack(trackModel.get(view.currentIndex)) + } + + MenuItem { + text: qsTr("Queue") + onClicked: media.addTrack(trackModel.get(view.currentIndex)) + } + + MenuItem { + text: qsTr("Download") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudDownloadPage.qml")) + .get(trackModel.itemData(view.currentIndex)); + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = trackModel.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + MenuItem { + text: trackModel.data(view.currentIndex, "favourited") ? qsTr("Unfavourite") : qsTr("Favourite") + enabled: soundcloud.userId !== "" + onClicked: { + var track = trackModel.get(view.currentIndex); + + if (track.favourited) { + track.unfavourite(); + } + else { + track.favourite(); + } + } + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudPlaylistsPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudPlaylistsPage.qml new file mode 100644 index 0000000..4784b7a --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudPlaylistsPage.qml @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function get(path, filters) { + playlistModel.get(path, filters); + } + + title: qsTr("Sets") + tools: ToolBarLayout { + BackToolButton { + onClicked: playlistModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: playlistModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: SoundCloudPlaylistModel { + id: playlistModel + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: PlaylistDelegate { + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")) + .load(playlistModel.get(index)) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = playlistModel.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudSearchPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudSearchPage.qml new file mode 100644 index 0000000..4a98396 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudSearchPage.qml @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +EditPage { + id: root + + function search() { + if (!searchField.text) { + return; + } + + var resource = mkresources.getResourceFromUrl(searchField.text); + + if ((resource) && (resource.service === mkresources.SOUNDCLOUD)) { + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.replace(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); + return; + case mkresources.PLAYLIST: + appWindow.pageStack.replace(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); + return; + case mkresources.TRACK: + appWindow.pageStack.replace(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); + return; + default: + break; + } + } + + settings.addSearch(searchField.text); + settings.setDefaultSearchType(mkresources.SOUNDCLOUD, + searchTypeModel.data(searchTypeSelector.selectedIndex, "name")); + + switch (searchTypeSelector.value.type) { + case mkresources.ARTIST: + appWindow.pageStack.replace(Qt.resolvedUrl("SoundCloudArtistsPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .get("/users", {q: searchField.text, limit: MAX_RESULTS}); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.replace(Qt.resolvedUrl("SoundCloudPlaylistsPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .get("/playlists", {q: searchField.text, limit: MAX_RESULTS}); + break; + case mkresources.TRACK: + appWindow.pageStack.replace(Qt.resolvedUrl("SoundCloudTracksPage.qml"), + {title: qsTr("Search") + " '" + searchField.text + "'"}) + .get("/tracks", {q: searchField.text, limit: MAX_RESULTS}); + break; + default: + break; + } + } + + title: qsTr("Search") + acceptable: searchField.text !== "" + + KeyNavFlickable { + id: flickable + + anchors.fill: parent + contentHeight: column.height + platformStyle.paddingLarge + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + width: parent.width + elide: Text.ElideRight + text: qsTr("Query") + } + + MyTextField { + id: searchField + + width: parent.width + onAccepted: root.accepted() + } + + ValueSelector { + id: searchTypeSelector + + x: -platformStyle.paddingLarge + width: parent.width + platformStyle.paddingLarge * 2 + title: qsTr("Search for") + model: SoundCloudSearchTypeModel { + id: searchTypeModel + } + selectedIndex: Math.max(0, searchTypeModel.match("name", + settings.defaultSearchType(mkresources.SOUNDCLOUD))) + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + states: State { + name: "inputContextVisible" + when: inputContext.visible + + PropertyChanges { + target: flickable + contentHeight: flickable.height + } + + PropertyChanges { + target: searchTypeSelector + visible: false + } + } + + onAccepted: search() +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudTrackPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudTrackPage.qml new file mode 100644 index 0000000..815dcd8 --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudTrackPage.qml @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function load(trackOrId) { + track.loadTrack(trackOrId); + } + + title: track.title ? track.title : qsTr("Track") + tools: ToolBarLayout { + BackToolButton { + onClicked: track.cancel() + } + + NowPlayingButton {} + + MyToolButton { + iconSource: "toolbar-view-menu" + toolTip: qsTr("Options") + onClicked: popups.open(menu, root) + } + } + + SoundCloudTrack { + id: track + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: + root.showProgressIndicator = true; + return; + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + } + } + + MyFlickable { + id: flickable + + anchors.fill: parent + contentHeight: flow.height + platformStyle.paddingLarge * 2 + + Flow { + id: flow + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: platformStyle.paddingLarge + } + spacing: platformStyle.paddingLarge + + Label { + id: titleLabel + + width: parent.width + wrapMode: Text.Wrap + font.pixelSize: platformStyle.fontSizeLarge + text: track.title ? track.title : qsTr("Unknown title") + } + + HeaderLabel { + id: infoHeader + + width: parent.width + text: qsTr("Details") + } + + Thumbnail { + id: thumbnail + + width: column.height + height: column.height + source: track.thumbnailUrl + placeholderSource: "../images/track.jpg" + onClicked: media.playTrack(track) + } + + Column { + id: column + + width: parent.width - thumbnail.width - parent.spacing + spacing: platformStyle.paddingLarge + + Label { + id: artistLabel + + width: parent.width + elide: Text.ElideRight + clip: true + text: qsTr("Artist") + ": " + (track.artistId ? "" + + track.artist + "" : qsTr("Unknown")) + onLinkActivated: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")) + .load(track.artistId) + } + + Label { + id: genreLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Genre") + ": " + (track.genre ? track.genre : qsTr("Unknown")) + } + + Label { + id: durationLabel + + width: parent.width + elide: Text.ElideRight + text: qsTr("Duration") + ": " + (track.durationString ? track.durationString : qsTr("Unknown")) + } + } + + HeaderLabel { + id: descriptionHeader + + width: parent.width + text: qsTr("Description") + } + + Label { + id: descriptionLabel + + width: parent.width + wrapMode: Text.Wrap + text: track.description ? utils.toRichText(track.description) : qsTr("No description") + onLinkActivated: { + var resource = mkresources.getResourceFromUrl(link); + + if (resource.service !== mkresources.SOUNDCLOUD) { + Qt.openUrlExternally(link); + return; + } + + switch (resource.type) { + case mkresources.ARTIST: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistPage.qml")).load(resource.id); + break; + case mkresources.PLAYLIST: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistPage.qml")).load(resource.id); + break; + case mkresources.TRACK: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")).load(resource.id); + break; + default: + break; + } + } + } + } + } + + ScrollDecorator { + flickableItem: flickable + } + + PopupLoader { + id: popups + } + + Component { + id: menu + + MyMenu { + focusItem: flickable + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: media.playTrack(track) + } + + MenuItem { + text: qsTr("Queue") + onClicked: media.addTrack(track) + } + + MenuItem { + text: qsTr("Download") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudDownloadPage.qml")).get(track) + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = track.url; + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + MenuItem { + text: track.favourited ? qsTr("Unfavourite") : qsTr("Favourite") + enabled: soundcloud.userId !== "" + onClicked: track.favourited ? track.unfavourite() : track.favourite() + } + + MenuItem { + text: qsTr("Related") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTracksPage.qml"), {title: text}) + .get("/tracks/" + track.id + "/related", {limit: MAX_RESULTS}) + } + + MenuItem { + text: qsTr("Comments") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudCommentsPage.qml"), {title: text}) + .get("/tracks/" + track.id + "/comments", {limit: MAX_RESULTS}) + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudTracksPage.qml b/app/src/symbian/qml/soundcloud/SoundCloudTracksPage.qml new file mode 100644 index 0000000..0cfafef --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudTracksPage.qml @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import QSoundCloud 1.0 as QSoundCloud +import ".." + +MyPage { + id: root + + function get(path, filters) { + trackModel.get(path, filters); + } + + title: qsTr("Tracks") + tools: ToolBarLayout { + BackToolButton { + onClicked: trackModel.cancel() + } + + NowPlayingButton {} + + MyToolButton { + id: reloadButton + + iconSource: "toolbar-refresh" + toolTip: qsTr("Reload") + onClicked: trackModel.reload() + } + } + + MyListView { + id: view + + anchors.fill: parent + model: SoundCloudTrackModel { + id: trackModel + + onStatusChanged: { + switch (status) { + case QSoundCloud.ResourcesRequest.Loading: { + root.showProgressIndicator = true; + reloadButton.enabled = false; + label.visible = false; + return; + } + case QSoundCloud.ResourcesRequest.Failed: + infoBanner.information(errorString); + break; + default: + break; + } + + root.showProgressIndicator = false; + reloadButton.enabled = true; + label.visible = (count === 0); + } + } + delegate: TrackDelegate { + onActivated: media.playTrack(trackModel.get(index)) + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTrackPage.qml")) + .load(trackModel.get(index)) + onPressAndHold: popups.open(contextMenu, root) + } + } + + ScrollDecorator { + flickableItem: view + } + + Label { + id: label + + anchors { + fill: parent + margins: platformStyle.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: platformStyle.colorNormalMid + font.bold: true + font.pixelSize: 32 + text: qsTr("No results") + visible: false + } + + PopupLoader { + id: popups + } + + Component { + id: contextMenu + + MyContextMenu { + focusItem: view + + MenuLayout { + MenuItem { + text: qsTr("Play") + onClicked: media.playTrack(trackModel.get(view.currentIndex)) + } + + MenuItem { + text: qsTr("Queue") + onClicked: media.addTrack(trackModel.get(view.currentIndex)) + } + + MenuItem { + text: qsTr("Download") + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudDownloadPage.qml")) + .get(trackModel.itemData(view.currentIndex)); + } + + MenuItem { + text: qsTr("Copy URL") + onClicked: { + clipboard.text = trackModel.data(view.currentIndex, "url"); + infoBanner.information(qsTr("URL copied to clipboard")); + } + } + + MenuItem { + text: trackModel.data(view.currentIndex, "favourited") ? qsTr("Unfavourite") : qsTr("Favourite") + enabled: soundcloud.userId !== "" + onClicked: { + var track = trackModel.get(view.currentIndex); + + if (track.favourited) { + track.unfavourite(); + } + else { + track.favourite(); + } + } + } + } + } + } +} diff --git a/app/src/symbian/qml/soundcloud/SoundCloudView.qml b/app/src/symbian/qml/soundcloud/SoundCloudView.qml new file mode 100644 index 0000000..b77206f --- /dev/null +++ b/app/src/symbian/qml/soundcloud/SoundCloudView.qml @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 1.1 +import com.nokia.symbian 1.1 +import MusiKloud 2.0 +import ".." + +Item { + id: root + + MyListView { + id: view + + anchors.fill: parent + model: SoundCloudNavModel { + id: navModel + } + delegate: TextDelegate { + subItemIndicator: true + text: display + onClicked: { + switch (index) { + case 0: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudAccountsPage.qml")); + break; + case 1: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudSearchPage.qml")); + break; + case 2: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTracksPage.qml"), {title: display}) + .get("/me/tracks", {limit: MAX_RESULTS}); + break; + case 3: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudTracksPage.qml"), {title: display}) + .get("/me/favorites", {limit: MAX_RESULTS}); + break; + case 4: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudPlaylistsPage.qml"), {title: display}) + .get("/me/playlists", {limit: MAX_RESULTS}); + break; + case 5: + appWindow.pageStack.push(Qt.resolvedUrl("SoundCloudArtistsPage.qml"), {title: display}) + .get("/me/followings", {limit: MAX_RESULTS}); + break; + default: + break; + } + } + } + } + + Connections { + target: soundcloud + onCommentAdded: infoBanner.information(qsTr("Your comment has been added")) + onArtistFollowed: infoBanner.information(qsTr("You have followed") + " " + artist.name) + onArtistUnfollowed: infoBanner.information(qsTr("You have unfollowed") + " " + artist.name) + onTrackFavourited: infoBanner.information("'" + track.title + "' " + qsTr("added to favourites")) + onTrackUnfavourited: infoBanner.information("'" + track.title + "' " + qsTr("removed from favourites")) + } +} diff --git a/app/src/harmattan/screenorientationmodel.h b/app/src/symbian/screenorientationmodel.h similarity index 75% rename from app/src/harmattan/screenorientationmodel.h rename to app/src/symbian/screenorientationmodel.h index 92c4a71..c1506b8 100644 --- a/app/src/harmattan/screenorientationmodel.h +++ b/app/src/symbian/screenorientationmodel.h @@ -1,16 +1,16 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 as + * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ diff --git a/app/src/base/settings.cpp b/app/src/symbian/settings.cpp similarity index 56% rename from app/src/base/settings.cpp rename to app/src/symbian/settings.cpp index 6d407cf..0e03055 100644 --- a/app/src/base/settings.cpp +++ b/app/src/symbian/settings.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -19,55 +19,23 @@ #include "resources.h" #include #include -#ifdef MUSIKLOUD_DEBUG -#include -#endif Settings* Settings::self = 0; -Settings::Settings(QObject *parent) : - QObject(parent) +Settings::Settings() : + QObject() { - if (!self) { - self = this; - } } Settings::~Settings() { - if (self == this) { - self = 0; - } + self = 0; } Settings* Settings::instance() { - return self; -} - -#if (defined MEEGO_EDITION_HARMATTAN) || (SYMBIAN_OS) -QString Settings::activeColor() const { - return value("Appearance/activeColor", "#0881cb").toString(); -} - -void Settings::setActiveColor(const QString &color) { - if (color != activeColor()) { - setValue("Appearance/activeColor", color); - emit activeColorChanged(); - } -} - -QString Settings::activeColorString() const { - return value("Appearance/activeColorString", "color7").toString(); + return self ? self : self = new Settings; } -void Settings::setActiveColorString(const QString &s) { - if (s != activeColorString()) { - setValue("Appearance/activeColorString", s); - emit activeColorStringChanged(); - } -} -#endif - -QStringList Settings::categoryNames() const { +QStringList Settings::categoryNames() { QSettings settings; settings.beginGroup("Categories"); QStringList names = settings.childKeys(); @@ -77,7 +45,7 @@ QStringList Settings::categoryNames() const { return names; } -QList Settings::categories() const { +QList Settings::categories() { QList list; QSettings settings; settings.beginGroup("Categories"); @@ -104,13 +72,19 @@ void Settings::setCategories(const QList &c) { } settings.endGroup(); - emit categoriesChanged(); + + if (self) { + emit self->categoriesChanged(); + } } void Settings::addCategory(const QString &name, const QString &path) { if (path != downloadPath(name)) { setValue("Categories/" + name, path); - emit categoriesChanged(); + + if (self) { + emit self->categoriesChanged(); + } } } @@ -120,79 +94,114 @@ void Settings::removeCategory(const QString &name) { if (settings.contains(name)) { settings.remove(name); - emit categoriesChanged(); + + if (self) { + emit self->categoriesChanged(); + } } settings.endGroup(); } -QString Settings::defaultCategory() const { +QString Settings::defaultCategory() { return value("Transfers/defaultCategory", tr("Default")).toString(); } void Settings::setDefaultCategory(const QString &category) { if (category != defaultCategory()) { setValue("Transfers/defaultCategory", category); - emit defaultCategoryChanged(); - } -} -bool Settings::clipboardMonitorEnabled() const { - return value("Content/clipboardMonitorEnabled", false).toBool(); -} - -void Settings::setClipboardMonitorEnabled(bool enabled) { - if (enabled != clipboardMonitorEnabled()) { - setValue("Content/clipboardMonitorEnabled", enabled); - emit clipboardMonitorEnabledChanged(); + if (self) { + emit self->defaultCategoryChanged(category); + } } } -QString Settings::currentService() const { +QString Settings::currentService() { return value("Content/currentService", Resources::SOUNDCLOUD).toString(); } void Settings::setCurrentService(const QString &service) { if (service != currentService()) { setValue("Content/currentService", service); - emit currentServiceChanged(); + + if (self) { + emit self->currentServiceChanged(service); + } + } +} + +QString Settings::customTransferCommand() { + return value("Transfers/customCommand").toString(); +} + +void Settings::setCustomTransferCommand(const QString &command) { + if (command != customTransferCommand()) { + setValue("Transfers/customCommand", command); + + if (self) { + emit self->customTransferCommandChanged(command); + } } } -QString Settings::defaultDownloadFormat(const QString &service) const { +bool Settings::customTransferCommandEnabled() { + return value("Transfers/customCommandEnabled", false).toBool(); +} + +void Settings::setCustomTransferCommandEnabled(bool enabled) { + if (enabled != customTransferCommandEnabled()) { + setValue("Transfers/customCommandEnabled", enabled); + + if (self) { + emit self->customTransferCommandEnabledChanged(enabled); + } + } +} + +QString Settings::defaultDownloadFormat(const QString &service) { return value("DownloadFormats/" + service).toString(); } void Settings::setDefaultDownloadFormat(const QString &service, const QString &format) { if (format != defaultDownloadFormat(service)) { setValue("DownloadFormats/" + service, format); - emit downloadFormatsChanged(); + + if (self) { + emit self->downloadFormatsChanged(); + } } } -QString Settings::defaultPlaybackFormat(const QString &service) const { +QString Settings::defaultPlaybackFormat(const QString &service) { return value("PlaybackFormats/" + service).toString(); } void Settings::setDefaultPlaybackFormat(const QString &service, const QString &format) { if (format != defaultPlaybackFormat(service)) { setValue("PlaybackFormats/" + service, format); - emit playbackFormatsChanged(); + + if (self) { + emit self->playbackFormatsChanged(); + } } } -QString Settings::defaultSearchType(const QString &service) const { +QString Settings::defaultSearchType(const QString &service) { return value("Search/searchType/" + service).toString(); } void Settings::setDefaultSearchType(const QString &service, const QString &type) { if (type != defaultSearchType(service)) { setValue("Search/searchType/" + service, type); - emit defaultSearchTypeChanged(); + + if (self) { + emit self->defaultSearchTypeChanged(); + } } } -QString Settings::downloadPath() const { +QString Settings::downloadPath() { QString path = value("Transfers/downloadPath", DOWNLOAD_PATH).toString(); if (!path.endsWith("/")) { @@ -202,67 +211,103 @@ QString Settings::downloadPath() const { return path; } -QString Settings::downloadPath(const QString &category) const { +QString Settings::downloadPath(const QString &category) { return value("Categories/" + category, downloadPath()).toString(); } void Settings::setDownloadPath(const QString &path) { if (path != downloadPath()) { setValue("Transfers/downloadPath", path); - emit downloadPathChanged(); + + if (self) { + emit self->downloadPathChanged(path); + } } } -int Settings::maximumConcurrentTransfers() const { +QString Settings::loggerFileName() { + return value("Logger/fileName", APP_CONFIG_PATH + "log").toString(); +} + +void Settings::setLoggerFileName(const QString &fileName) { + if (fileName != loggerFileName()) { + setValue("Logger/fileName", fileName); + + if (self) { + emit self->loggerFileNameChanged(fileName); + } + } +} + +int Settings::loggerVerbosity() { + return value("Logger/verbosity", 1).toInt(); +} + +void Settings::setLoggerVerbosity(int verbosity) { + if (verbosity != loggerVerbosity()) { + setValue("Logger/verbosity", verbosity); + + if (self) { + emit self->loggerVerbosityChanged(verbosity); + } + } +} + +int Settings::maximumConcurrentTransfers() { return qBound(1, value("Transfers/maximumConcurrentTransfers", 1).toInt(), MAX_CONCURRENT_TRANSFERS); } void Settings::setMaximumConcurrentTransfers(int maximum) { if (maximum != maximumConcurrentTransfers()) { - setValue("Transfers/maximumConcurrentTransfers", qBound(1, maximum, MAX_CONCURRENT_TRANSFERS)); - emit maximumConcurrentTransfersChanged(); + maximum = qBound(1, maximum, MAX_CONCURRENT_TRANSFERS); + setValue("Transfers/maximumConcurrentTransfers", maximum); + + if (self) { + emit self->maximumConcurrentTransfersChanged(maximum); + } } } void Settings::setNetworkProxy() { - if (!networkProxyEnabled()) { + if (networkProxyEnabled()) { + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::ProxyType(networkProxyType()), + networkProxyHost(), networkProxyPort(), networkProxyUsername(), + networkProxyPassword())); + } + else { QNetworkProxy::setApplicationProxy(QNetworkProxy()); - return; } - - QNetworkProxy proxy(QNetworkProxy::ProxyType(networkProxyType()), networkProxyHost(), networkProxyPort(), - networkProxyUsername(), networkProxyPassword()); - - QNetworkProxy::setApplicationProxy(proxy); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Settings::setNetworkProxy" << networkProxyType() << networkProxyHost() << networkProxyPort() - << networkProxyUsername() << networkProxyPassword(); -#endif } -bool Settings::networkProxyEnabled() const { +bool Settings::networkProxyEnabled() { return value("Network/networkProxyEnabled", false).toBool(); } void Settings::setNetworkProxyEnabled(bool enabled) { if (enabled != networkProxyEnabled()) { setValue("Network/networkProxyEnabled", enabled); - emit networkProxyChanged(); + + if (self) { + emit self->networkProxyChanged(); + } } } -QString Settings::networkProxyHost() const { +QString Settings::networkProxyHost() { return value("Network/networkProxyHost").toString(); } void Settings::setNetworkProxyHost(const QString &host) { if (host != networkProxyHost()) { setValue("Network/networkProxyHost", host); - emit networkProxyChanged(); + + if (self) { + emit self->networkProxyChanged(); + } } } -QString Settings::networkProxyPassword() const { +QString Settings::networkProxyPassword() { return QByteArray::fromBase64(value("Network/networkProxyPassword").toByteArray()); } @@ -271,65 +316,93 @@ void Settings::setNetworkProxyPassword(const QString &password) { if (pass != networkProxyPassword()) { setValue("Network/networkProxyPassword", pass); - emit networkProxyChanged(); + + if (self) { + emit self->networkProxyChanged(); + } } } -int Settings::networkProxyPort() const { +int Settings::networkProxyPort() { return value("Network/networkProxyPort", 80).toInt(); } void Settings::setNetworkProxyPort(int port) { if (port != networkProxyPort()) { setValue("Network/networkProxyPort", port); - emit networkProxyChanged(); + + if (self) { + emit self->networkProxyChanged(); + } } } -int Settings::networkProxyType() const { +int Settings::networkProxyType() { return value("Network/networkProxyType", QNetworkProxy::ProxyType(QNetworkProxy::HttpProxy)).toInt(); } void Settings::setNetworkProxyType(int type) { if (type != networkProxyType()) { setValue("Network/networkProxyType", type); - emit networkProxyChanged(); + + if (self) { + emit self->networkProxyChanged(); + } } } -QString Settings::networkProxyUsername() const { +QString Settings::networkProxyUsername() { return value("Network/networkProxyUsername").toString(); } void Settings::setNetworkProxyUsername(const QString &username) { if (username != networkProxyUsername()) { setValue("Network/networkProxyUsername", username); - emit networkProxyChanged(); + + if (self) { + emit self->networkProxyChanged(); + } + } +} + +bool Settings::restorePlaybackQueueOnStartup() { + return value("Playback/restorePlaybackQueueOnStartup", true).toBool(); +} + +void Settings::setRestorePlaybackQueueOnStartup(bool enabled) { + if (enabled != restorePlaybackQueueOnStartup()) { + setValue("Playback/restorePlaybackQueueOnStartup", enabled); + + if (self) { + emit self->restorePlaybackQueueOnStartupChanged(enabled); + } } } -int Settings::screenOrientation() const { -#ifdef Q_WS_MAEMO_5 - return value("Appearance/screenOrientation", Qt::WA_Maemo5LandscapeOrientation).toInt(); -#else - return value("Appearance/screenOrientation", 0).toInt(); -#endif +int Settings::screenOrientation() { + return value("UI/screenOrientation", 0).toInt(); } void Settings::setScreenOrientation(int orientation) { if (orientation != screenOrientation()) { - setValue("Appearance/screenOrientation", orientation); - emit screenOrientationChanged(); + setValue("UI/screenOrientation", orientation); + + if (self) { + emit self->screenOrientationChanged(orientation); + } } } -QStringList Settings::searchHistory() const { +QStringList Settings::searchHistory() { return value("Search/searchHistory").toStringList(); } void Settings::setSearchHistory(const QStringList &searches) { setValue("Search/searchHistory", searches); - emit searchHistoryChanged(); + + if (self) { + emit self->searchHistoryChanged(); + } } void Settings::addSearch(const QString &query) { @@ -345,24 +418,38 @@ void Settings::removeSearch(const QString &query) { setSearchHistory(searches); } -bool Settings::startTransfersAutomatically() const { +int Settings::sleepTimerDuration() { + return qMax(1, value("Playback/sleepTimerDuration", 30).toInt()); +} + +void Settings::setSleepTimerDuration(int duration) { + if ((duration != sleepTimerDuration()) && (duration > 0)) { + setValue("Playback/sleepTimerDuration", duration); + + if (self) { + emit self->sleepTimerDurationChanged(duration); + } + } +} + +bool Settings::startTransfersAutomatically() { return value("Transfers/startTransfersAutomatically", true).toBool(); } void Settings::setStartTransfersAutomatically(bool enabled) { if (enabled != startTransfersAutomatically()) { setValue("Transfers/startTransfersAutomatically", enabled); - emit startTransfersAutomaticallyChanged(); + + if (self) { + emit self->startTransfersAutomaticallyChanged(enabled); + } } } -QVariant Settings::value(const QString &key, const QVariant &defaultValue) const { +QVariant Settings::value(const QString &key, const QVariant &defaultValue) { return QSettings().value(key, defaultValue); } void Settings::setValue(const QString &key, const QVariant &value) { QSettings().setValue(key, value); -#ifdef MUSIKLOUD_DEBUG - qDebug() << "Settings::setValue" << key << value; -#endif } diff --git a/app/src/symbian/settings.h b/app/src/symbian/settings.h new file mode 100644 index 0000000..8a72fc0 --- /dev/null +++ b/app/src/symbian/settings.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include + +struct Category { + QString name; + QString path; +}; + +class Settings : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QStringList categoryNames READ categoryNames NOTIFY categoriesChanged) + Q_PROPERTY(QString defaultCategory READ defaultCategory WRITE setDefaultCategory NOTIFY defaultCategoryChanged) + Q_PROPERTY(QString currentService READ currentService WRITE setCurrentService NOTIFY currentServiceChanged) + Q_PROPERTY(QString customTransferCommand READ customTransferCommand WRITE setCustomTransferCommand + NOTIFY customTransferCommandChanged) + Q_PROPERTY(bool customTransferCommandEnabled READ customTransferCommandEnabled WRITE setCustomTransferCommandEnabled + NOTIFY customTransferCommandEnabledChanged) + Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) + Q_PROPERTY(QString loggerFileName READ loggerFileName WRITE setLoggerFileName NOTIFY loggerFileNameChanged) + Q_PROPERTY(int loggerVerbosity READ loggerVerbosity WRITE setLoggerVerbosity NOTIFY loggerVerbosityChanged) + Q_PROPERTY(int maximumConcurrentTransfers READ maximumConcurrentTransfers WRITE setMaximumConcurrentTransfers + NOTIFY maximumConcurrentTransfersChanged) + Q_PROPERTY(bool networkProxyEnabled READ networkProxyEnabled WRITE setNetworkProxyEnabled + NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyHost READ networkProxyHost WRITE setNetworkProxyHost NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyPassword READ networkProxyPassword WRITE setNetworkProxyPassword + NOTIFY networkProxyChanged) + Q_PROPERTY(int networkProxyPort READ networkProxyPort WRITE setNetworkProxyPort NOTIFY networkProxyChanged) + Q_PROPERTY(int networkProxyType READ networkProxyType WRITE setNetworkProxyType NOTIFY networkProxyChanged) + Q_PROPERTY(QString networkProxyUsername READ networkProxyUsername WRITE setNetworkProxyUsername + NOTIFY networkProxyChanged) + Q_PROPERTY(bool restorePlaybackQueueOnStartup READ restorePlaybackQueueOnStartup + WRITE setRestorePlaybackQueueOnStartup NOTIFY restorePlaybackQueueOnStartupChanged) + Q_PROPERTY(int screenOrientation READ screenOrientation WRITE setScreenOrientation NOTIFY screenOrientationChanged) + Q_PROPERTY(QStringList searchHistory READ searchHistory WRITE setSearchHistory NOTIFY searchHistoryChanged) + Q_PROPERTY(int sleepTimerDuration READ sleepTimerDuration WRITE setSleepTimerDuration + NOTIFY sleepTimerDurationChanged) + Q_PROPERTY(bool startTransfersAutomatically READ startTransfersAutomatically WRITE setStartTransfersAutomatically + NOTIFY startTransfersAutomaticallyChanged) + +public: + ~Settings(); + + static Settings* instance(); + + static QStringList categoryNames(); + static QList categories(); + static void setCategories(const QList &c); + + static QString currentService(); + + static QString customTransferCommand(); + static bool customTransferCommandEnabled(); + + static QString defaultCategory(); + + Q_INVOKABLE static QString defaultDownloadFormat(const QString &service); + Q_INVOKABLE static QString defaultPlaybackFormat(const QString &service); + + Q_INVOKABLE static QString defaultSearchType(const QString &service); + + static QString downloadPath(); + Q_INVOKABLE static QString downloadPath(const QString &category); + + static QString loggerFileName(); + static int loggerVerbosity(); + + static int maximumConcurrentTransfers(); + + static bool networkProxyEnabled(); + static QString networkProxyHost(); + static QString networkProxyPassword(); + static int networkProxyPort(); + static int networkProxyType(); + static QString networkProxyUsername(); + + static bool restorePlaybackQueueOnStartup(); + + static int screenOrientation(); + + static QStringList searchHistory(); + + static int sleepTimerDuration(); + + static bool startTransfersAutomatically(); + + Q_INVOKABLE static QVariant value(const QString &key, const QVariant &defaultValue = QVariant()); + +public Q_SLOTS: + static void addCategory(const QString &name, const QString &path); + static void setDefaultCategory(const QString &category); + static void removeCategory(const QString &name); + + static void setCurrentService(const QString &service); + + static void setCustomTransferCommand(const QString &command); + static void setCustomTransferCommandEnabled(bool enabled); + + static void setDefaultDownloadFormat(const QString &service, const QString &format); + static void setDefaultPlaybackFormat(const QString &service, const QString &format); + + static void setDefaultSearchType(const QString &service, const QString &type); + + static void setDownloadPath(const QString &path); + + static void setLoggerFileName(const QString &fileName); + static void setLoggerVerbosity(int verbosity); + + static void setMaximumConcurrentTransfers(int maximum); + + static void setNetworkProxy(); + static void setNetworkProxyEnabled(bool enabled); + static void setNetworkProxyHost(const QString &host); + static void setNetworkProxyPassword(const QString &password); + static void setNetworkProxyPort(int port); + static void setNetworkProxyType(int type); + static void setNetworkProxyUsername(const QString &username); + + static void setRestorePlaybackQueueOnStartup(bool enabled); + + static void setScreenOrientation(int orientation); + + static void setSearchHistory(const QStringList &searches); + static void addSearch(const QString &query); + static void removeSearch(const QString &query); + + static void setSleepTimerDuration(int duration); + + static void setStartTransfersAutomatically(bool enabled); + + static void setValue(const QString &key, const QVariant &value); + +Q_SIGNALS: + void categoriesChanged(); + void defaultCategoryChanged(const QString &category); + void clipboardMonitorEnabledChanged(bool enabled); + void currentServiceChanged(const QString &service); + void customTransferCommandChanged(const QString &command); + void customTransferCommandEnabledChanged(bool enabled); + void defaultSearchTypeChanged(); + void downloadFormatsChanged(); + void downloadPathChanged(const QString &path); + void loggerFileNameChanged(const QString &fileName); + void loggerVerbosityChanged(int verbosity); + void maximumConcurrentTransfersChanged(int maximum); + void networkProxyChanged(); + void playbackFormatsChanged(); + void restorePlaybackQueueOnStartupChanged(bool enabled); + void screenOrientationChanged(int orientation); + void searchHistoryChanged(); + void sleepTimerDurationChanged(int duration); + void startTransfersAutomaticallyChanged(bool enabled); + +private: + Settings(); + + static Settings *self; +}; + +#endif // SETTINGS_H diff --git a/app/src/symbian/transfer.cpp b/app/src/symbian/transfer.cpp new file mode 100644 index 0000000..9189deb --- /dev/null +++ b/app/src/symbian/transfer.cpp @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2017 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "transfer.h" +#include "definitions.h" +#include "logger.h" +#include "settings.h" +#include "utils.h" +#include +#include +#include +#include + +Transfer::Transfer(QObject *parent) : + QObject(parent), + m_nam(0), + m_reply(0), + m_process(0), + m_ownNetworkAccessManager(false), + m_canceled(false), + m_category(tr("Default")), + m_customCommandOverrideEnabled(false), + m_priority(NormalPriority), + m_progress(0), + m_size(0), + m_bytesTransferred(0), + m_redirects(0), + m_status(Paused), + m_transferType(Download), + m_metadataSet(false) +{ +} + +void Transfer::setNetworkAccessManager(QNetworkAccessManager *manager) { + if ((m_nam) && (m_ownNetworkAccessManager)) { + delete m_nam; + } + + m_nam = manager; + m_ownNetworkAccessManager = false; +} + +qint64 Transfer::bytesTransferred() const { + return m_bytesTransferred; +} + +QString Transfer::category() const { + return m_category; +} + +void Transfer::setCategory(const QString &c) { + if (c != category()) { + m_category = c; + emit categoryChanged(); + } +} + +QString Transfer::customCommand() const { + return m_customCommand; +} + +void Transfer::setCustomCommand(const QString &c) { + if (c != customCommand()) { + m_customCommand = c; + emit customCommandChanged(); + } +} + +bool Transfer::customCommandOverrideEnabled() const { + return m_customCommandOverrideEnabled; +} + +void Transfer::setCustomCommandOverrideEnabled(bool enabled) { + if (enabled != customCommandOverrideEnabled()) { + m_customCommandOverrideEnabled = enabled; + emit customCommandOverrideEnabledChanged(); + } +} + +QString Transfer::downloadPath() const { + return m_downloadPath; +} + +void Transfer::setDownloadPath(const QString &path) { + if (path != downloadPath()) { + m_downloadPath = path.endsWith("/") ? path : path + "/"; + emit downloadPathChanged(); + + if (!fileName().isEmpty()) { + m_file.setFileName(downloadPath() + fileName()); + m_bytesTransferred = m_file.size(); + + if ((m_size > 0) && (m_bytesTransferred > 0)) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } +} + +QString Transfer::errorString() const { + return m_errorString; +} + +void Transfer::setErrorString(const QString &es) { + m_errorString = es; +} + +QString Transfer::fileName() const { + return m_fileName; +} + +void Transfer::setFileName(const QString &name) { + if (name != fileName()) { + m_fileName = name; + + switch (transferType()) { + case Transfer::Download: + m_fileName.replace(ILLEGAL_FILENAME_CHARS_RE, "_"); + break; + default: + break; + } + + emit fileNameChanged(); + + if (!downloadPath().isEmpty()) { + m_file.setFileName(downloadPath() + fileName()); + m_bytesTransferred = m_file.size(); + + if ((m_size > 0) && (m_bytesTransferred > 0)) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } +} + +QString Transfer::id() const { + return m_id; +} + +void Transfer::setId(const QString &i) { + if (i != id()) { + m_id = i; + emit idChanged(); + } +} + +Transfer::Priority Transfer::priority() const { + return m_priority; +} + +void Transfer::setPriority(Priority p) { + if (p != priority()) { + m_priority = p; + emit priorityChanged(); + } +} + +QString Transfer::priorityString() const { + switch (priority()) { + case HighPriority: + return tr("High"); + case NormalPriority: + return tr("Normal"); + case LowPriority: + return tr("Low"); + default: + return QString(); + } +} + +int Transfer::progress() const { + return m_progress; +} + +void Transfer::setProgress(int p) { + if (p != progress()) { + m_progress = p; + emit progressChanged(); + } +} + +QString Transfer::progressString() const { + return tr("%1 of %2 (%3%)").arg(Utils::formatBytes(bytesTransferred())).arg(Utils::formatBytes(size())) + .arg(progress()); +} + +QString Transfer::service() const { + return m_service; +} + +void Transfer::setService(const QString &s) { + if (s != service()) { + m_service = s; + emit serviceChanged(); + } +} + +qint64 Transfer::size() const { + return m_size; +} + +void Transfer::setSize(qint64 s) { + if (s != size()) { + m_size = s; + emit sizeChanged(); + + if ((m_size > 0) && (m_bytesTransferred > 0)) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } +} + +Transfer::Status Transfer::status() const { + return m_status; +} + +void Transfer::setStatus(Status s) { + if (s != status()) { + m_status = s; + Logger::log(QString("Transfer::setStatus(). ID: %1, Status: %2").arg(id()).arg(statusString()), + Logger::LowVerbosity); + emit statusChanged(); + } +} + +QString Transfer::statusString() const { + switch (status()) { + case Paused: + return tr("Paused"); + case Canceled: + return tr("Canceled"); + case Failed: + return tr("Failed: %1").arg(errorString()); + case Completed: + return tr("Completed"); + case Queued: + return tr("Queued"); + case Connecting: + return tr("Connecting"); + case Downloading: + return tr("Downloading"); + case Uploading: + return tr("Uploading"); + case ExecutingCustomCommand: + return tr("Executing custom command"); + default: + return QString(); + } +} + +QString Transfer::streamId() const { + return m_streamId; +} + +void Transfer::setStreamId(const QString &si) { + if (si != streamId()) { + m_streamId = si; + emit streamIdChanged(); + } +} + +QUrl Transfer::streamUrl() const { + return m_streamUrl; +} + +void Transfer::setStreamUrl(const QUrl &url) { + if (url != streamUrl()) { + m_streamUrl = url; + emit streamUrlChanged(); + } +} + +QString Transfer::title() const { + return m_title; +} + +void Transfer::setTitle(const QString &t) { + if (t != title()) { + m_title = t; + emit titleChanged(); + } +} + +QString Transfer::trackId() const { + return m_trackId; +} + +void Transfer::setTrackId(const QString &i) { + if (i != trackId()) { + m_trackId = i; + emit trackIdChanged(); + } +} + +Transfer::TransferType Transfer::transferType() const { + return m_transferType; +} + +void Transfer::setTransferType(TransferType type) { + if (type != transferType()) { + m_transferType = type; + emit transferTypeChanged(); + } +} + +QUrl Transfer::url() const { + return m_reply ? m_reply->url() : QUrl(); +} + +void Transfer::queue() { + switch (status()) { + case Canceled: + case Completed: + case Queued: + case Connecting: + case Downloading: + case Uploading: + case ExecutingCustomCommand: + return; + default: + break; + } + + setStatus(Queued); +} + +void Transfer::start() { + switch (status()) { + case Canceled: + case Completed: + case Connecting: + case Downloading: + case Uploading: + case ExecutingCustomCommand: + return; + default: + break; + } + + switch (transferType()) { + case Upload: + return; + default: + break; + } + + setStatus(Connecting); + + if (streamUrl().isEmpty()) { + listStreams(); + } + else { + startDownload(streamUrl()); + } +} + +void Transfer::pause() { + switch (status()) { + case Paused: + case Canceled: + case Completed: + case Connecting: + case ExecutingCustomCommand: + return; + default: + break; + } + + if ((m_reply) && (m_reply->isRunning())) { + m_canceled = false; + m_reply->abort(); + } + else { + setStatus(Paused); + } +} + +void Transfer::cancel() { + switch (status()) { + case Canceled: + case Completed: + case ExecutingCustomCommand: + return; + default: + break; + } + + if ((m_reply) && (m_reply->isRunning())) { + m_canceled = true; + m_reply->abort(); + } + else { + m_file.remove(); + QDir().rmdir(downloadPath()); + setStatus(Canceled); + } +} + +void Transfer::startDownload(const QUrl &u) { + Logger::log("Transfer::startDownload(). URL: " + u.toString(), Logger::LowVerbosity); + QDir().mkpath(downloadPath()); + + if (!m_file.open(m_file.exists() ? QFile::Append : QFile::WriteOnly)) { + setErrorString(m_file.errorString()); + setStatus(Failed); + return; + } + + if (!m_nam) { + m_nam = new QNetworkAccessManager(this); + m_ownNetworkAccessManager = true; + } + + QNetworkRequest request(u); + request.setRawHeader("User-Agent", USER_AGENT); + + if (m_bytesTransferred > 0) { + request.setRawHeader("Range", "bytes=" + QByteArray::number(m_bytesTransferred) + "-"); + } + + setStatus(Downloading); + + m_redirects = 0; + m_reply = m_nam->get(request); + connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); + connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); + connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +void Transfer::followRedirect(const QUrl &u) { + Logger::log("Transfer::followRedirect(). URL: " + u.toString(), Logger::LowVerbosity); + QDir().mkpath(downloadPath()); + + if (!m_file.open(m_file.exists() ? QFile::Append : QFile::WriteOnly)) { + setErrorString(m_file.errorString()); + setStatus(Failed); + return; + } + + m_redirects++; + + if (!m_nam) { + m_nam = new QNetworkAccessManager(this); + m_ownNetworkAccessManager = true; + } + + QNetworkRequest request(u); + request.setRawHeader("User-Agent", USER_AGENT); + + if (m_bytesTransferred > 0) { + request.setRawHeader("Range", "bytes=" + QByteArray::number(m_bytesTransferred) + "-"); + } + + m_reply = m_nam->get(request); + connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(onReplyMetaDataChanged())); + connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReplyReadyRead())); + connect(m_reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +bool Transfer::executeCustomCommands() { + Logger::log("Transfer::executeCustomCommands()", Logger::LowVerbosity); + m_commands.clear(); + QString command = customCommand(); + const QString defaultCommand = Settings::customTransferCommand(); + const bool defaultEnabled = (!defaultCommand.isEmpty()) && (Settings::customTransferCommandEnabled()); + + if (!command.isEmpty()) { + command.replace("%f", downloadPath() + fileName()); + m_commands << command; + Logger::log("Transfer::executeCustomCommands(): Adding custom command: " + command, Logger::LowVerbosity); + } + + if ((defaultEnabled) && ((command.isEmpty()) || (!customCommandOverrideEnabled()))) { + command = defaultCommand; + command.replace("%f", downloadPath() + fileName()); + m_commands << command; + Logger::log("Transfer::executeCustomCommands(): Adding custom command: " + command, Logger::LowVerbosity); + } + + if (!m_commands.isEmpty()) { + setStatus(ExecutingCustomCommand); + executeCustomCommand(m_commands.takeFirst()); + return true; + } + + return false; +} + +void Transfer::executeCustomCommand(const QString &command) { + if (!m_process) { + m_process = new QProcess(this); + connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onCustomCommandFinished(int))); + connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onCustomCommandError())); + } + + Logger::log("Transfer::executeCustomCommand(): " + command, Logger::LowVerbosity); + m_process->start(command); +} + +void Transfer::moveDownloadedFiles() { + Logger::log("Transfer::moveDownloadedFiles()", Logger::LowVerbosity); + QDir destDir(Settings::downloadPath(category())); + + if (!destDir.mkpath(destDir.path())) { + setErrorString(tr("Cannot make download path %1").arg(destDir.path())); + setStatus(Failed); + return; + } + + QDir downDir(downloadPath()); + + foreach (const QFileInfo &info, downDir.entryInfoList(QDir::Files)) { + int i = 0; + QString suffix = info.suffix(); + + if (suffix.isEmpty()) { + Logger::log("Transfer::moveDownloadedFiles(). Using default suffix .mp3", Logger::MediumVerbosity); + suffix = "mp3"; + } + + QString newFileName = QString("%1/%2.%3").arg(destDir.path()).arg(info.completeBaseName()).arg(suffix); + + while ((destDir.exists(newFileName)) && (i < 100)) { + i++; + newFileName = (i == 1 ? QString("%1(%2)%3").arg(newFileName.left(newFileName.lastIndexOf('.'))) + .arg(i).arg(newFileName.mid(newFileName.lastIndexOf('.'))) + : QString("%1(%2)%3").arg(newFileName.left(newFileName.lastIndexOf('('))) + .arg(i).arg(newFileName.mid(newFileName.lastIndexOf('.')))); + } + + if (!destDir.rename(info.absoluteFilePath(), newFileName)) { + setErrorString(tr("Cannot rename downloaded file to %1").arg(newFileName)); + setStatus(Failed); + return; + } + } + + downDir.rmdir(downDir.path()); + setErrorString(QString()); + setStatus(Completed); +} + +void Transfer::onReplyMetaDataChanged() { + if ((m_metadataSet) || (m_reply->error() != QNetworkReply::NoError) || (!m_reply->rawHeader("Location").isEmpty())) { + return; + } + + qint64 bytes = m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); + + if (bytes <= 0) { + bytes = m_reply->rawHeader("Content-Length").toLongLong(); + } + + if (bytes > 0) { + setSize(bytes + bytesTransferred()); + } + + m_metadataSet = true; +} + +void Transfer::onReplyReadyRead() { + if (!m_metadataSet) { + return; + } + + const qint64 bytes = m_reply->bytesAvailable(); + + if (bytes < DOWNLOAD_BUFFER_SIZE) { + return; + } + + if (m_file.write(m_reply->read(bytes)) == -1) { + m_reply->deleteLater(); + m_reply = 0; + setErrorString(tr("Cannot write to file - %1").arg(m_file.errorString())); + setStatus(Failed); + return; + } + + m_bytesTransferred += bytes; + emit bytesTransferredChanged(); + + if (m_size > 0) { + setProgress(m_bytesTransferred * 100 / m_size); + } +} + +void Transfer::onReplyFinished() { + const QString redirect = QString::fromUtf8(m_reply->rawHeader("Location")); + + if (!redirect.isEmpty()) { + m_file.close(); + m_reply->deleteLater(); + m_reply = 0; + + if (m_redirects < MAX_REDIRECTS) { + followRedirect(redirect); + } + else { + setErrorString(tr("Maximum redirects reached")); + setStatus(Failed); + } + + return; + } + + const QNetworkReply::NetworkError error = m_reply->error(); + const QString errorString = m_reply->errorString(); + + if ((m_reply->isOpen()) && (error == QNetworkReply::NoError) && (m_file.isOpen())) { + const qint64 bytes = m_reply->bytesAvailable(); + + if ((bytes > 0) && (m_metadataSet)) { + m_file.write(m_reply->read(bytes)); + m_bytesTransferred += bytes; + + if (m_size > 0) { + setProgress(m_bytesTransferred * 100 / m_size); + } + } + } + + m_file.close(); + m_reply->deleteLater(); + m_reply = 0; + + switch (error) { + case QNetworkReply::NoError: + break; + case QNetworkReply::OperationCanceledError: + setErrorString(QString()); + + if (m_canceled) { + m_file.remove(); + QDir().rmdir(downloadPath()); + setStatus(Canceled); + } + else { + setStatus(Paused); + } + + return; + default: + setErrorString(errorString); + setStatus(Failed); + return; + } + + if (!executeCustomCommands()) { + moveDownloadedFiles(); + } +} + +void Transfer::onCustomCommandFinished(int exitCode) { + if (exitCode != 0) { + Logger::log("Transfer::onCustomCommandFinished(): Error: " + m_process->readAllStandardError()); + } + + if (!m_commands.isEmpty()) { + executeCustomCommand(m_commands.takeFirst()); + } + else { + moveDownloadedFiles(); + } +} + +void Transfer::onCustomCommandError() { + Logger::log("Transfer::onCustomCommandError(): " + m_process->errorString()); + + if (!m_commands.isEmpty()) { + executeCustomCommand(m_commands.takeFirst()); + } + else { + moveDownloadedFiles(); + } +} diff --git a/app/src/base/transfer.h b/app/src/symbian/transfer.h similarity index 75% rename from app/src/base/transfer.h rename to app/src/symbian/transfer.h index 98fca0e..8def9a8 100644 --- a/app/src/base/transfer.h +++ b/app/src/symbian/transfer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2017 Stuart Howarth * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -17,29 +17,25 @@ #ifndef TRANSFER_H #define TRANSFER_H -#include -#include +#include #include -#include -#include - -#ifdef MEEGO_EDITION_HARMATTAN -namespace TransferUI { - class Client; - class Transfer; -} -#endif +#include +#include +#include -class AudioConverter; class QNetworkAccessManager; class QNetworkReply; +class QProcess; class Transfer : public QObject { Q_OBJECT - Q_PROPERTY(qint64 bytesTransferred READ bytesTransferred NOTIFY progressChanged) + Q_PROPERTY(qint64 bytesTransferred READ bytesTransferred NOTIFY bytesTransferredChanged) Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) + Q_PROPERTY(QString customCommand READ customCommand WRITE setCustomCommand NOTIFY customCommandChanged) + Q_PROPERTY(bool customCommandOverrideEnabled READ customCommandOverrideEnabled WRITE setCustomCommandOverrideEnabled + NOTIFY customCommandOverrideEnabledChanged) Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY statusChanged) Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged) @@ -47,7 +43,7 @@ class Transfer : public QObject Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) Q_PROPERTY(QString priorityString READ priorityString NOTIFY priorityChanged) Q_PROPERTY(int progress READ progress NOTIFY progressChanged) - Q_PROPERTY(QString resourceId READ resourceId WRITE setResourceId NOTIFY resourceIdChanged) + Q_PROPERTY(QString progressString READ progressString NOTIFY progressChanged) Q_PROPERTY(QString service READ service NOTIFY serviceChanged) Q_PROPERTY(qint64 size READ size WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(Status status READ status NOTIFY statusChanged) @@ -55,14 +51,15 @@ class Transfer : public QObject Q_PROPERTY(QString streamId READ streamId WRITE setStreamId NOTIFY streamIdChanged) Q_PROPERTY(QUrl streamUrl READ streamUrl WRITE setStreamUrl NOTIFY streamUrlChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QString trackId READ trackId WRITE setTrackId NOTIFY trackIdChanged) Q_PROPERTY(TransferType transferType READ transferType WRITE setTransferType NOTIFY transferTypeChanged) - Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) + Q_PROPERTY(QUrl url READ url NOTIFY statusChanged) Q_ENUMS(Priority Status TransferType) public: enum Priority { - HighPriority, + HighPriority = 0, NormalPriority, LowPriority }; @@ -76,17 +73,16 @@ class Transfer : public QObject Connecting, Downloading, Uploading, - Converting, + ExecutingCustomCommand, Unknown }; enum TransferType { - Download, + Download = 0, Upload }; explicit Transfer(QObject *parent = 0); - ~Transfer(); void setNetworkAccessManager(QNetworkAccessManager *manager); @@ -95,11 +91,16 @@ class Transfer : public QObject QString category() const; void setCategory(const QString &c); + QString customCommand() const; + void setCustomCommand(const QString &c); + bool customCommandOverrideEnabled() const; + void setCustomCommandOverrideEnabled(bool enabled); + QString downloadPath() const; void setDownloadPath(const QString &path); QString errorString() const; - + QString fileName() const; void setFileName(const QString &fn); @@ -111,10 +112,8 @@ class Transfer : public QObject QString priorityString() const; int progress() const; - - QString resourceId() const; - void setResourceId(const QString &ri); - + QString progressString() const; + QString service() const; qint64 size() const; @@ -125,17 +124,19 @@ class Transfer : public QObject QString streamId() const; void setStreamId(const QString &si); - QUrl streamUrl() const; void setStreamUrl(const QUrl &url); QString title() const; void setTitle(const QString &title); + QString trackId() const; + void setTrackId(const QString &i); + TransferType transferType() const; void setTransferType(TransferType type); - QUrl url() const; + QUrl url() const; public Q_SLOTS: void queue(); @@ -146,9 +147,6 @@ public Q_SLOTS: protected: virtual void listStreams() = 0; - QString fileExtension() const; - void setFileExtension(const QString &ext); - void setErrorString(const QString &es); void setProgress(int p); @@ -156,44 +154,45 @@ public Q_SLOTS: void setService(const QString &s); void setStatus(Status s); - - void setUrl(const QUrl &u); void startDownload(const QUrl &u); void followRedirect(const QUrl &u); - - void moveDownloadedFiles(); + + bool executeCustomCommands(); + void executeCustomCommand(const QString &command); + + void moveDownloadedFiles(); private Q_SLOTS: void onReplyMetaDataChanged(); void onReplyReadyRead(); void onReplyFinished(); + void onCustomCommandFinished(int exitCode); + void onCustomCommandError(); Q_SIGNALS: + void bytesTransferredChanged(); void categoryChanged(); + void customCommandChanged(); + void customCommandOverrideEnabledChanged(); void downloadPathChanged(); void fileNameChanged(); void idChanged(); void priorityChanged(); void progressChanged(); - void resourceIdChanged(); void serviceChanged(); void sizeChanged(); void statusChanged(); void streamIdChanged(); void streamUrlChanged(); void titleChanged(); + void trackIdChanged(); void transferTypeChanged(); - void urlChanged(); -private: -#ifdef MEEGO_EDITION_HARMATTAN - static TransferUI::Client *tuiClient; - TransferUI::Transfer *m_tuiTransfer; -#endif - - QNetworkAccessManager *m_nam; +private: + QPointer m_nam; QNetworkReply *m_reply; + QProcess *m_process; QFile m_file; @@ -202,12 +201,13 @@ private Q_SLOTS: QString m_category; + QString m_customCommand; + bool m_customCommandOverrideEnabled; + QString m_downloadPath; QString m_errorString; - QString m_fileExtension; - QString m_fileName; QString m_id; @@ -215,9 +215,7 @@ private Q_SLOTS: Priority m_priority; int m_progress; - - QString m_resourceId; - + QString m_service; qint64 m_size; @@ -228,17 +226,17 @@ private Q_SLOTS: Status m_status; QString m_streamId; - QUrl m_streamUrl; QString m_title; + QString m_trackId; + TransferType m_transferType; + + QStringList m_commands; - QUrl m_url; -#ifdef SYMBIAN_OS - QByteArray m_buffer; -#endif + bool m_metadataSet; }; #endif // TRANSFER_H diff --git a/plugins/internetradio/AUTHORS b/plugins/internetradio/AUTHORS deleted file mode 100644 index b678761..0000000 --- a/plugins/internetradio/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Stuart Howarth diff --git a/plugins/internetradio/COPYING b/plugins/internetradio/COPYING deleted file mode 100644 index 94a9ed0..0000000 --- a/plugins/internetradio/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/plugins/internetradio/README.md b/plugins/internetradio/README.md deleted file mode 100644 index 7023d5e..0000000 --- a/plugins/internetradio/README.md +++ /dev/null @@ -1,30 +0,0 @@ -#MusiKloud2-InternetRadio - -MusiKloud2-InternetRadio is a plugin for MusiKloud2 providing access to internet radio stations. - -#Dependencies - -python-simplejson - -#Usage - -The following resources are supported: - - - - - - - - - - - - - - - - - - -
ResourceMethods
tracklist, search, get
categorylist
streamlist
diff --git a/plugins/internetradio/debian/changelog b/plugins/internetradio/debian/changelog new file mode 100644 index 0000000..351567d --- /dev/null +++ b/plugins/internetradio/debian/changelog @@ -0,0 +1,19 @@ +musikloud2-internetradio (0.0.4) unstable; urgency=low + * Fix audio stream retrieval from playlist URLs. + + -- Stuart Howarth Fri, 13 Jan 2017 01:27:25 +0000 + +musikloud2-internetradio (0.0.3) unstable; urgency=low + * Use JavaScript plugin API. + + -- Stuart Howarth Wed, 04 Jan 2017 00:21:44 +0000 + +musikloud2-internetradio (0.0.2) unstable; urgency=low + * Use cuteRadio Data API hosted at marxoft.co.uk. + + -- Stuart Howarth Sun, 13 Sep 2015 02:32:34 +0000 + +musikloud2-internetradio (0.0.1) unstable; urgency=low + * Initial release. + + -- Stuart Howarth Mon, 29 Jun 2015 21:07:43 +0000 diff --git a/plugins/mixcloud/debian_maemo5/compat b/plugins/internetradio/debian/compat similarity index 100% rename from plugins/mixcloud/debian_maemo5/compat rename to plugins/internetradio/debian/compat diff --git a/plugins/internetradio/debian_maemo5/control b/plugins/internetradio/debian/control similarity index 83% rename from plugins/internetradio/debian_maemo5/control rename to plugins/internetradio/debian/control index 50b67c7..10ae4fc 100644 --- a/plugins/internetradio/debian_maemo5/control +++ b/plugins/internetradio/debian/control @@ -7,7 +7,7 @@ Build-Depends: debhelper (>= 5) Standards-Version: 3.7.3 Package: musikloud2-internetradio -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, python-simplejson +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends} Description: A plugin for MusiKloud2 providing access to internet radio stations XB-Maemo-Display-Name: MusiKloud2-InternetRadio diff --git a/plugins/internetradio/debian_maemo5/rules b/plugins/internetradio/debian/rules similarity index 89% rename from plugins/internetradio/debian_maemo5/rules rename to plugins/internetradio/debian/rules index 34c20d0..8a06610 100755 --- a/plugins/internetradio/debian_maemo5/rules +++ b/plugins/internetradio/debian/rules @@ -36,9 +36,8 @@ install: build dh_clean -k dh_installdirs - mkdir -p debian/musikloud2-internetradio/opt/musikloud2/plugins/internetradio - cp internetradio.plugin debian/musikloud2-internetradio/opt/musikloud2/plugins - cp internetradio.py debian/musikloud2-internetradio/opt/musikloud2/plugins/internetradio + mkdir -p debian/musikloud2-internetradio/opt/musikloud2/plugins + cp musikloud2-internetradio.js musikloud2-internetradio.json debian/musikloud2-internetradio/opt/musikloud2/plugins # Build architecture-independent files here. binary-indep: build install diff --git a/plugins/internetradio/debian_harmattan/changelog b/plugins/internetradio/debian_harmattan/changelog deleted file mode 100644 index 4fd959c..0000000 --- a/plugins/internetradio/debian_harmattan/changelog +++ /dev/null @@ -1,9 +0,0 @@ -musikloud2-internetradio (0.0.2) unstable; urgency=low - * Use cuteRadio Data API hosted at marxoft.co.uk. - - -- Stuart Howarth Sun, 13 Sep 2015 02:32:34 +0000 - -musikloud2-internetradio (0.0.1) unstable; urgency=low - * Initial release. - - -- Stuart Howarth Fri, 07 Aug 2015 23:06:43 +0000 diff --git a/plugins/internetradio/debian_harmattan/compat b/plugins/internetradio/debian_harmattan/compat deleted file mode 100644 index 7f8f011..0000000 --- a/plugins/internetradio/debian_harmattan/compat +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/plugins/internetradio/debian_harmattan/control b/plugins/internetradio/debian_harmattan/control deleted file mode 100644 index b18b88e..0000000 --- a/plugins/internetradio/debian_harmattan/control +++ /dev/null @@ -1,97 +0,0 @@ -Source: musikloud2-internetradio -Section: user/multimedia -Priority: optional -Maintainer: Stuart Howarth -Homepage: http://marxoft.co.uk/projects/musikloud2 -Build-Depends: debhelper (>= 5) -Standards-Version: 3.7.3 - -Package: musikloud2-internetradio -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, musikloud2, python -Description: A plugin for MusiKloud2 providing access to internet radio stations -XB-Maemo-Display-Name: MusiKloud2-InternetRadio -XB-Maemo-Icon-26: - iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A - /wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9wCFAEQDTki4YcAABHRSURBVHja - 3Vt5mFxVlf+de+979Wqvru5Op0OWThSaxNCQZSYSGUc0ojKgA3zgIIKC+gkiMyqu4/ahzIgCDptG - Z1wQgoy4C+KgIjIxw5YQSLMFUEwT0tl6SVdXV9V7754zf7yq6up0J+nuBAze76uvuutt9/zO7571 - PsJBjls3l3B2Z3zMb5+5R3DMjBHXkDgxRRoEpQlKa0UGoP3dz0IktCJWiFmEA7bW+hSsf479r5yW - HXPuD54awduPThzU/GlaV4ngD48M4YQloxO69bHCvJTR8+IGLULUXgmly4ocpQRHCCFHhIwCxYgk - eqxMPBMRAQMVEQwBGATkBSW0OeZQtyL0jgS0q2R5y1mvSvbULl376DD+risJEL1EAFTHmkcGF+Ti - zpkxo5YFVjoDlnlgZAUAi0AirPb6lv1PiAiESJbGb0XVY4RBo6jH0fRU2bcbBqz64XmLE8+96Az4 - 8dNlnHGUBwC4rXtkRTauriLhxaWAkxWGAxlVqoxKSft6JDUelUZCTAiQVMGhxmtjCoHnqCKA7iEf - l575qsRDAPDTjSWctiR+aAG46eGiyaaoU1m+ydFq6XBgEVocFsPRhJSr4FteD1+dty30n3n/0lw4 - mWvVfim+qR8A8NMnijOyHl+vrH2sEtqlfcVAKgELM+Nw+JQDK7uGA6kEdjkc+8SMmLr2J48UWgHg - 5o2D02PArY8UcPZxady8aeDYnFE3lgM+rmLr9CYcnkOIiDwDuFptHKzIu85bmuv+/oY+vGNZ8+QB - +O7GPpy/pBk3PTx8fJPr3zVUkXTIUluLh6vwjfaCjAbSMb2nf9i+5d0rWu773sO78a6lLZNnwLce - 3n1Mq6H/GyjbFLO8HAQfB4RSRPm4Luyu8MoLlrc+NmkG3Hz/jnbPpbuKvj0msJCqZ3rZDRGIo0Ep - V2/yYd70jqX57fsF4DvdfdADFR2Lq6sqof1QORT8NYy4oxBz9NUhOZ8YKrO9aGXLvhmwZtPOo6jk - by6Uueae6WUuvxBAKU/Dh3PkBStmPNt40Ox9Nhf9W8pBiNDKX4PwNSXLSMWS5+BmAMePWwK33SY4 - 6yzCfz6wfaUXltYNlFjoMBJe6tGjHMQ9IPmEphH2Vrz/hJkPQgQgihjQs+AJAIBnK1cWShbCAnnp - lLPXRAVMBj65sAIIW8SLO+EgQJjIQyk97acNjQjSCf8qAK/98W+34IxGil/z+96jsjT8QP8I5+hF - 1H1jMlT7OySDUABmC/HLmN17P17z5LeQHXkBAOAGI/jl4o+g5xUnIxZPQikFmsYkRYB8Ug2OqMyy - i09o+xMAmHtu34wTT+1ESpfPKJZsipkPqcAsDbplgQ5LMMEwxFowM2LFnTjx8W9iUe9aMEUqYVKw - yoGQAgFwuILhvh0YbhmANg6M4057PsWyJGPuyFkArvjtD56AOfHUzoiMYWVJyQ8Ny6ET3PhDaNv9 - OLzyAMAhQhHM37YOxz5/JzRHqa4ACIxB2Y2Pc0tU/yaUigWURopI5SxIMabL0pLPTtxRSwBg1dsX - RTbg6/+7ZV6lXDw6tBZyiAAoUwxvu+/zmLPzIcT9IRC4Gp4ZhMZDGAXuDULur0YAjBSH0d/fj1gq - h0w2C6XUtJwUE1Dxg86v3f3c3IvfML/HRNr355UDO9faQ0f/ChjtfZvg2jKsdicMJyc7/dAK/ubI - drzxpOPQV3Fw+/o/obk5D9eL143iVGxCJbRzPe3PBVAFICg3B2GYtfbQJPhEhNAGkYhEB196AmCM - RszReMPiV+JNr16EG3/1AJ7cthvxVAbGcacEQBCiKe4ELQCgPv24wDLPstZCooGD/YSWUS5XDlj+ - ms6wLNBK4T2nrMSyjiZs39oDvzwCay2EeTLzE2stwsC24acC09a7xfUtdYWhhbUHN2EBYBRhZtbD - 0R05xNZqIDzkGESRgwjOPmkFWnNJ3Pg/GzC3owPGiU2qMBqCEDhu11cTWxwTVopOGIZHh9aCefpM - tSxIegaaCK9ddAT6Aw2j1fQT+hp79iEQEUFE8Prli9A3OIzfPbYV+ZYZ0NocCAQKAYQcLjI84hqx - gWYbtofWYro2kEXQ2Z7DsfNn4I4Nz0ErghwonhAZF+5WdAxCGkRAwaSRL++CBu+/gkyEjplN4Ae6 - UYrFYLwEHOMASu0XYcvcrtlqI2DFIjlrGQeKAfzQwtEKfhAAALTSAAHlSoDXLFqMkh+AmaPAuyqg - AFAi0BJWI3KBCLDdm4mB+EwopUHGoBLLonvGCSjl5iIe96BSebz15+dA+0P7IwJEBCu6OrGiqxM/ - +f3D2DlUxvpnepHL5aJ7TwSEAByGTaxIGQ58ZUOb2V8M4AchWIB3rzoGP1q3GScunoPQMgqVAEGl - gkTcQ8kPoqZGZEzrAJAI+nQGG5OLEaRaEfPiiMVcDGY6UMzNQyKRQDKZRDKZQJfnIBmPw/M8SCIH - 55ca8A/scWpAnP66pQCA//rZvVj7eA+aW2ZAG2ccekyAtTbNISljbUhhaGORC6RxNHUdgzcun4+7 - Nz2PYzta8aM/PIUF7XmEltE7MIzOWbPRX6zAWo7C2AZLXBs7vZl4ouPNmD9nNppaW5FOpzCrKqjn - eYjFYnAcB47jwBgDpRQC14NVakqutzbe949/j76BX+DZXQNIZnLQ2hknmg3DOCshwzYkZouJgiAS - hnI1OtryYP4zBIDlSMMsDLYMrVUkcBU+ywzBWACUcdDaNhMLj+7ErFmzEI/HYYyB1hpKqXpyUxOC - iCAETMckiQiICCevXIyrv/9rVBwHrpcctxQsK7AlMmIt2DIsWzTGasVSGee+/hjc8dCfIMIRQAKw - 3dvXog4AALC11U5PAwBKIRFPIJvNIpfLwXUnDlwa6Txdd1TzDl2dCxAUB8DKhdIutOOMSbmtVbAK - UFFzIWKAtRbWWgRhiLaMhxOPewX8IIRUU1WgmrJWi2VR/28sGLa+BPaamKJx2t77c6gCpxqQ7z3j - JOzp3wW/UoYNw7p81jLEMoQtlGVLlnlMFFWp+Dj1+IVVbTAEDK66CGYZ1XBD51OkauGZ64HKVCN/ - OsSFiNcfvwzDQ3sQBuVIcTVFccR4y5aMtVxdAqNRUBiG0NV/am4tqhPIGCPHDZoHSZUpPArIFNbs - uGLJoUrKyiX4FR+OZ0Eyem9mhrWAEZaox2dtfSJhGIAlMkHWciSYtaNGEFI3hHUhhCAkDUtAprRm - axOzRLAMlCRqXE6XEzVgbVCBDQNYy1AYBZpZQZjENDYZ6xbShpA65SOBbfX4OCOIBhaAqkBNjQEQ - ASuFSrmCcPX5YLFgBlRp6OALM9VINZKj0QswrCgYQURl28AAtgKlqM4AVG9QA6S29qVGd4wavaks - gdo5VimU/vvzsPd8GyjsjspgtU0A07QLRIRLL7saiXgCIAIz172cQMBswMxQzCJiubomIiuptcKv - 7++urvkIObZjl0QjzccaF4YVIBwXV8s+Ex9/0z2wv1kNGu6L/LVSACmAaoVwmZaHeOTJZ+B4CYA0 - mKUuH1uOanYCMSxKWFAWZq/2CEWEezc+g6e39NaFs3UGjF0CXBPciSEYKSBXeB6JDVuRLQMSlCPM - Zd9bY9gygt5ngT27AC8+QTAmCMmBVS6I1KTX/sbHngQrF7FYAqR0ZOQbijNMGGGl2JDWDKI9Iuzx - 6B4X5HJZXHHj7TCJpuiCuk2oRnoYdX2+SaB059ewqO8+XND7R8x5fAuOIICNV6fcvjNJRmgtDI3v - wwkABUFvcg52NnVitmMO6CqJCFu2bsOHv3ANJJaBE0+CSI1hrIraEUNEWowVMAsPMXNbI2uV1ugr - ltHsMYIwrPv10WwvmjwL8OrC/Sg9+GW4jgZACNz4qCAH7LARYJzRGsBeAloo3Nf+ZuTbZiOdSlWL - ofvWvO8HOO29l8JNN6Mp2wztxsAAqDE9JwFD9jCIjXZcS6WRF4TtkXunA46XxGBhGB/7jzWgWBKD - Q8Ow1mKkVEIQMioVH4WhPZA/PgiXLIQc0ATiGr8IOzyI4kgJheJIlK8T6tnjwOK3Ilx+LhKP/gLK - BlAEWFHQYrF64afQ0TEPM1qaobRBqeKj4gdjgBIWKKVw7/3rcfFnr8QRczqQzLfBSaRBpLF3r4MU - AJYXQCakT3xvrTu0e9s1uweGLgp44m4wEYGUwnNPbULHkYtgi/3gIAA5MXiFrVgzeB1K8Yl3ZSkI - flfI4eP9y9HW0oJYIlmdgdR7tyIhyhYYGh5GfvdTSKkAL9gUCrE8Mi4hnkzD9RLVhgihHtFUCwXF - Ygnbdw8imcki39yKWDKDWCIF7cRAapxKxFGg5nz2uqb2eR81V5x3gn/hl29+FCI4UFl8zisXIbQM - xJvAJoBSCqXUbPyychxOlm6UyB3d+VavaAm+PbgAJpFD4OUAk6hWa/ZqkTEjpeMoJnIYCkMoCJq1 - hjIOyHERagO7j/XvNuUwv3UutONCGxfacUFKg0WA8XVOcogAv7LpS2evCAwR4f1X3LSTolx+/xam - gUqkVJQGxFK4NfMPeMXgDhxJuxEgqtPHFMNDiOV/XgU/PQv51pnw0k2RVvZZ6OQoABNbjwFIRe5w - v5tUaqk0EVT1m6sGeuLTHQDYXd8f4HjpnapY6We2+ekkZP06h3eq9+B1Iw/iHU43NBhX9i/Eg7Yd - zc1pZDJ5xFJN0I5XFWh8WBCJp0F69NDkQyCJWugCRNsaZL9dJqWcPpNI7awD4OaaelT/zh62nJ9O - EqKURksmiQfN8bi7tAQ2DBDPWbS7LhwvAcdLQBk3cp1TaL5ObS6TzD2iGmWPSbf2jAH53M+uvm3H - UPlMO83uqEiUUrMddZmkNJSuFSYPj/0WRhFaM/Hb1nzxwrcDgPrqx9YAAOLZpvUxQ8F0u0EAgZSO - DJEbg3ZjUMZULT5wKDpOh+LjGgqS2aaHAODqD98yqpZP/3D9/Kc3rHu4bzjMTa84sa+A5/DYalSL - AptTzsAxf7uq63OnL9oauWkAb7nqSvzbmcufSyQz3Y6iadYjaIq/v/QYOIqQSmUe/dzpi7Z+/ks/ - QR2A8+d+FACQbZ39L2nPQA5lSeawEB0QgNIJg2zbnEsA4LJPnjaxet7977es275j+0rfRhuP/yrk - FxFXg9pnzlz73U+d89qxkepeI5FPnZ9KeCAikhejv/0XEJ6IKB33RDnxC8aH6g3jI7fei7Sa80w6 - lflC3FWH1QI+mCp53FFIpjOXzZq/8I8Xrr77wBbqkuvvyA/09d6+e7Cw0g/lZbsUIuoTtTSl16Vb - Zpy6+oNvG5i0if7YTb9v37r5iUd2FcozmF9+IIiIKEXUlkvseOXCJcdedvaKHRNnqxMx4Bt34srz - Xtc7Y0HXqtZ0rM/RRLXtJS8HwUUEjiGakfZ25ectXHXZ2St2fPzbv5uak77o+jux+pKT8anv3DOz - Z8uzP95TKK4sBRYiEMJh+AZBVOMUIlDc0chlkus6urpOv/z0FTsvvP4OfOOSU6YepXzoa3fgmotP - wcXX/ry5ODz4z8XhwmeGRirKtxCq10roL63xevkhpkGpRIwzmcxlqWz+hus+cEr/R773W3z1Xaum - HL6NG5f/upue3/TM0eXS0Jrh4T1Lh8sWgR1NXF9qHKQhWnM0IRXTSGWy61Ne7p1HnLDw6X99deek - lquZ7AO3b94m3/zo6U8CWHbxDb9aUejfdlWpWFxcCjlZCdiResOUGtokL8aLkwICQSkg5ugg7qhi - MpnqTjW3X3rDB97yEABcesPdBx3AT2p84qa1C/p6e84sFfYsCxidoZV5QWizoa221Gv7B+oVH9q7 - RD4OKKrW/Ma+NhsxzBgNo82go6nHKDyVSGU35ObM/+FV56x88V+d3Zt/n7z8+7jis+fUf/rgN++a - Vxnsm+ePFFpClnYYp0tEjhKWI1gkx2wzbG0M1fa5jKvURq1LUgTSpqKUHlJKDZLCC0poM6zfrRX1 - OoncrmQ+v+Xa951Uf3n645ffiq98+p9e+penAeC9X78D3/rAWAv7s4LgN7fc5QalYUd8X1sbKhsG - KgwDUmyp2mmiRv1rpaVarxJtXFHGZWMMK9exMScdnHLuSf5JmbHTvXD1nfjGRScf1Pz/H4wQBsF5 - hGO/AAAAAElFTkSuQmCC diff --git a/plugins/internetradio/debian_harmattan/postinst b/plugins/internetradio/debian_harmattan/postinst deleted file mode 100755 index 032729c..0000000 --- a/plugins/internetradio/debian_harmattan/postinst +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e - -chmod a+x /opt/musikloud2/plugins/internetradio/internetradio.py - -exit 0 diff --git a/plugins/internetradio/debian_harmattan/rules b/plugins/internetradio/debian_harmattan/rules deleted file mode 100755 index 34c20d0..0000000 --- a/plugins/internetradio/debian_harmattan/rules +++ /dev/null @@ -1,79 +0,0 @@ -#!/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 - - touch configure-stamp - - -build: build-stamp - -build-stamp: configure-stamp - dh_testdir - - touch $@ - -clean: - dh_testdir - dh_testroot - rm -f build-stamp configure-stamp - - dh_clean - -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - - mkdir -p debian/musikloud2-internetradio/opt/musikloud2/plugins/internetradio - cp internetradio.plugin debian/musikloud2-internetradio/opt/musikloud2/plugins - cp internetradio.py debian/musikloud2-internetradio/opt/musikloud2/plugins/internetradio - -# 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_shlibdeps # Uncomment this line for use without Qt Creator - dh_gencontrol - dh_md5sums - dh_builddeb - -binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/plugins/internetradio/debian_maemo5/changelog b/plugins/internetradio/debian_maemo5/changelog deleted file mode 100644 index 2cbce03..0000000 --- a/plugins/internetradio/debian_maemo5/changelog +++ /dev/null @@ -1,9 +0,0 @@ -musikloud2-internetradio (0.0.2) unstable; urgency=low - * Use cuteRadio Data API hosted at marxoft.co.uk. - - -- Stuart Howarth Sun, 13 Sep 2015 02:32:34 +0000 - -musikloud2-internetradio (0.0.1) unstable; urgency=low - * Initial release. - - -- Stuart Howarth Mon, 29 Jun 2015 21:07:43 +0000 diff --git a/plugins/internetradio/internetradio.plugin b/plugins/internetradio/internetradio.plugin deleted file mode 100644 index 3180194..0000000 --- a/plugins/internetradio/internetradio.plugin +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/plugins/internetradio/internetradio.pro b/plugins/internetradio/internetradio.pro new file mode 100755 index 0000000..b045261 --- /dev/null +++ b/plugins/internetradio/internetradio.pro @@ -0,0 +1,40 @@ +TEMPLATE = subdirs + +maemo5 { + config.files = musikloud2-internetradio.json + config.path = /opt/musikloud2/plugins + + plugin.files = musikloud2-internetradio.js + plugin.path = /opt/musikloud2/plugins + + INSTALLS += \ + config \ + plugin + +} else:symbian { + config.sources = musikloud2-internetradio.json + config.path = !:/musikloud2/plugins + + plugin.sources = musikloud2-internetradio.js + plugin.path = !:/musikloud2/plugins + + vendorinfo += "%{\"Stuart Howarth\"}" ":\"Stuart Howarth\"" + internetradio_deployment.pkg_prerules += vendorinfo + + DEPLOYMENT.display_name = MusiKloud2 Internet Radio + DEPLOYMENT += \ + internetradio_deployment \ + config \ + plugin + +} else:unix { + config.files = musikloud2-internetradio.json + config.path = /usr/share/musikloud2/plugins + + plugin.files = musikloud2-internetradio.js + plugin.path = /usr/share/musikloud2/plugins + + INSTALLS += \ + config \ + plugin +} diff --git a/plugins/internetradio/internetradio.py b/plugins/internetradio/internetradio.py deleted file mode 100755 index ee02603..0000000 --- a/plugins/internetradio/internetradio.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env python - -# Copyright (C) 2015 Stuart Howarth -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from datetime import datetime -import getopt -import re -import sys -import urllib2 -import xml.etree.ElementTree as elemtree - -try: - import json -except ImportError: - import simplejson as json - -API_URL = 'http://marxoft.co.uk/api/cuteradio' -COUNTRIES_URL = API_URL + '/countries' -GENRES_URL = API_URL + '/genres' -LANGUAGES_URL = API_URL + '/languages' -STATIONS_URL = API_URL + '/stations' - -PLAYLIST_EXTS = ['asx', 'm3u', 'pls', 'ram', 'smil'] - -MAX_RESULTS = 20 -MAX_RETRIES = 8 - -class ResourceError(Exception): - pass - -def format_date(date_string, date_format = '%a, %d %b %Y %H:%M:%S %Z'): - try: - return datetime.strptime(date_string, date_format).strftime('%d %b %Y') - except: - return date_string - -def get_response(url): - try: - return urllib2.urlopen(url).read() - except: - raise ResourceError('{"error": "Unable to retrieve data from %s"}' % url) - -def get_playlist_ext(url): - try: - ext = url.split('.')[-1].lower() - - if ext in PLAYLIST_EXTS: - return ext - except IndexError: - pass - - return None - -def url_is_playlist(url): - return get_playlist_ext(url) is not None - -def get_stream_url_from_unknown_file(response): - try: - return re.search(r'(http(s|)|mms)://[^\s\"<>]+', response).group(0) - except: - return None - -def get_stream_url_from_asx_file(response): - try: - return elemtree.fromstring(response.lower()).find('entry').find('entryref').attrib['href'] - except: - return get_stream_from_unknown_file(response) - -def get_stream_url_from_m3u_file(response): - try: - return re.search(r'(http(s|)|mms)://\S+', response).group(0) - except: - return get_stream_url_from_unknown_file(response) - -def get_stream_url_from_pls_file(response): - try: - return re.search(r'(?<=File\d=)\S+', response).group(0) - except: - return get_stream_url_from_unknown_file(response) - -def get_stream_url_from_smil_file(response): - try: - return elemtree.fromstring(response.lower()).find('body').find('audio').attrib['src'] - except: - return get_stream_from_unknown_file(response) - -def get_stream_url(url): - retries = 0 - - ext = get_playlist_ext(url) - - while ext is not None and retries < MAX_RETRIES: - response = get_response(url) - - if ext == 'asx': - url = get_stream_url_from_asx_file(response) - elif ext == 'm3u': - url = get_stream_url_from_m3u_file(response) - elif ext == 'pls': - url = get_stream_url_from_pls_file(response) - elif ext == 'ram': - url = get_stream_url_from_ram_file(response) - elif ext == 'smil': - url = get_stream_url_from_smil_file(response) - - ext = get_playlist_ext(url) - retries += 1 - - return url - -def list_tracks(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - if url[0:7] != 'http://': - # Get related stations - url = '%s?genre=%s&limit=%d' % (STATIONS_URL, get_track(url)['genre'], MAX_RESULTS) - - response = json.loads(get_response(url)) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - result = {} - tracks = [] - - for item in response['items']: - try: - track = {} - track['description'] = item['description'] - track['downloadable'] = False - track['duration'] = 0 - track['genre'] = item['genre'] - track['id'] = item['id'] - track['title'] = item['title'] - track['url'] = item['source'] - - if not url_is_playlist(item['source']): - track['streamUrl'] = item['source'] - - tracks.append(track) - except: - pass - - result['items'] = tracks - - try: - result['next'] = API_URL + response['next'] - except: - pass - - return result - -def search_tracks(query, order): - return list_tracks('%s?search=%s&limit=%d' % (STATIONS_URL, query, MAX_RESULTS)) - -def get_track(id): - if not id: - raise ResourceError('{"error": "No ID specified"}') - - try: - item = json.loads(get_response(STATIONS_URL + '/' + id))['items'][0] - track = {} - track['description'] = item['description'] - track['downloadable'] = False - track['duration'] = 0 - track['genre'] = item['genre'] - track['id'] = STATIONS_URL + '/' + item['id'] - track['title'] = item['title'] - track['url'] = item['source'] - - if not url_is_playlist(item['source']): - track['streamUrl'] = item['source'] - - return track - except: - raise ResourceError('{"error": "No station found"}') - -def list_categories(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - response = json.loads(get_response(url)) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - result = {} - categories = [] - category_type = 'genre' - - try: - path = url.split('?')[0].split('/')[-1] - - if path == 'countries': - category_type = 'country' - elif path == 'languages': - category_type = 'language' - except: - pass - - for item in response['items']: - try: - category = {} - category['id'] = '%s?%s=%s&limit=%d' % (STATIONS_URL, category_type, item['name'], MAX_RESULTS) - category['title'] = item['name'] - categories.append(category) - except: - pass - - result['items'] = categories - - try: - result['next'] = API_URL + response['next'] - except: - pass - - return result - -def list_streams(id): - if not id: - raise ResourceError('{"error": "No ID specified"}') - - result = {} - streams = [] - - try: - stream = {} - stream['description'] = 'Live radio stream' - stream['id'] = '0' - stream['url'] = get_stream_url(get_track(id)['url']) - streams.append(stream) - except: - pass - - result['items'] = streams - return result - -def list_items(resource, id): - if not resource or resource == 'track': - return list_tracks(id) - - if resource == 'category': - return list_categories(id) - - if resource == 'stream': - return list_streams(id) - - return [] - -def search_items(resoruce, query, order): - if not resource or resource == 'track': - return search_tracks(query, order) - - return [] - -def get_item(resource, id): - if not resource or resource == 'track': - return get_track(id) - - return {} - -def main(method, resource, id, query, order): - if method == 'list': - print json.dumps(list_items(resource, id)) - elif method == 'search': - print json.dumps(search_items(resource, query, order)) - elif method == 'get': - print json.dumps(get_item(resource, id)) - else: - raise ResourceError('{"error": "Invalid method specified: %s"}' % method) - -if __name__ == '__main__': - (opts, args) = getopt.getopt(sys.argv[1:], 'm:r:i:q:o:') - - method = 'list' - resource = 'track' - id = '' - query = '' - order = '' - - for o, a in opts: - if o == '-m': - method = a - elif o == '-r': - resource = a - elif o == '-i': - id = a - elif o == '-q': - query = a - elif o == '-o': - order = a - - try: - main(method, resource, id, query, order) - exit(0) - except ResourceError, e: - print e.args[0] - exit(1) diff --git a/plugins/internetradio/musikloud2-internetradio.js b/plugins/internetradio/musikloud2-internetradio.js new file mode 100644 index 0000000..28ecf18 --- /dev/null +++ b/plugins/internetradio/musikloud2-internetradio.js @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var API_URL = "http://marxoft.co.uk/api/cuteradio"; +var COUNTRIES_URL = API_URL + "/countries"; +var GENRES_URL = API_URL + "/genres"; +var LANGUAGES_URL = API_URL + "/languages"; +var STATIONS_URL = API_URL + "/stations"; + +var PLAYLIST_EXTS = ["asx", "m3u", "pls", "ram", "smil"]; + +var MAX_RETRIES = 8; + +var USER_AGENT = "Wget/1.13.4 (linux-gnu)"; + +var request = null; +var retries = 0; + +function getPlaylistExt(url) { + var ext = url.substring(url.lastIndexOf(".") + 1).toLowerCase(); + + if (PLAYLIST_EXTS.indexOf(ext) !== -1) { + return ext; + } + + return null; +} + +function urlIsPlaylist(url) { + return getPlaylistExt(url) !== null; +} + +function getStreamUrlFromUnknownFile(response) { + try { + return /(http(s|)|mms):\/\/[^\s"<>]+/.exec(response)[0]; + } + catch(err) { + return null; + } +} + +function getStreamUrlFromASXFile(response) { + return getStreamUrlFromUnknownFile(response); +} + +function getStreamUrlFromM3UFile(response) { + try { + return /(http(s|)|mms):\/\/\S+/.exec(response)[0]; + } + catch(err) { + return null; + } +} + +function getStreamUrlFromPLSFile(response) { + try { + return /(File\d=)(\S+)/.exec(response)[1]; + } + catch(err) { + return null; + } +} + +function getStreamUrlFromRAMFile(response) { + return getStreamUrlFromUnknownFile(response) +} + +function getStreamUrlFromSMILFile(response) { + return getStreamUrlFromUnknownFile(response) +} + +function listStations(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var result = JSON.parse(request.responseText); + + for (var i = 0; i < result.items.length; i++) { + result.items[i]["downloadable"] = false; + result.items[i]["id"] = result.items[i].source; + result.items[i]["relatedTracksId"] = STATIONS_URL + "?genre=" + result.items[i].genre; + result.items[i]["url"] = result.items[i].source; + + if (!urlIsPlaylist(result.items[i].source)) { + result.items[i]["streamUrl"] = result.items[i].source; + } + } + + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function searchStations(query, order) { + listStations(STATIONS_URL + "?search=" + query + "&sort=" + order); +} + +function listCategories(url) { + var category = "genres"; + + try { + var path = url.split("?")[0]; + category = path.substring(path.lastIndexOf("/") + 1); + } + catch(err) {} + + switch (category) { + case "genres": + listGenres(url); + break; + case "countries": + listCountries(url); + break; + case "languages": + listLanguages(url); + break; + default: + error("Invalid category type"); + break; + } +} + +function listGenres(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var result = JSON.parse(request.responseText); + + for (var i = 0; i < result.items.length; i++) { + result.items[i]["title"] = result.items[i].name; + result.items[i]["tracksId"] = STATIONS_URL + "?genre=" + result.items[i].name; + } + + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listCountries(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var result = JSON.parse(request.responseText); + + for (var i = 0; i < result.items.length; i++) { + result.items[i]["title"] = result.items[i].name; + result.items[i]["tracksId"] = STATIONS_URL + "?country=" + result.items[i].name; + } + + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listLanguages(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var result = JSON.parse(request.responseText); + + for (var i = 0; i < result.items.length; i++) { + result.items[i]["title"] = result.items[i].name; + result.items[i]["tracksId"] = STATIONS_URL + "?language=" + result.items[i].name; + } + + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listStreams(url) { + var ext = getPlaylistExt(url); + + if (!ext) { + retries = 0; + finished({"items": [{"description": "Live radio stream", "id": "live", "url": url}]}); + return; + } + + if (retries >= MAX_RETRIES) { + retries = 0; + error("Maximum retries reached"); + return; + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + switch (ext) { + case "asx": + url = getStreamUrlFromASXFile(request.responseText); + break; + case "m3u": + url = getStreamUrlFromM3UFile(request.responseText); + break; + case "pls": + url = getStreamUrlFromPLSFile(request.responseText); + break; + case "ram": + url = getStreamUrlFromRAMFile(request.responseText); + break; + case "smil": + url = getStreamUrlFromSMILFile(request.responseText); + break; + default: + url = getStreamUrlFromUnknownFile(request.responseText); + break; + } + + ++retries; + listStreams(url); + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function list(resource, id) { + switch (resource) { + case "track": + listStations(id); + return true; + case "category": + listCategories(id); + return true; + case "stream": + listStreams(id); + return true; + default: + return false; + } +} + +function search(resource, query, order) { + switch (resource) { + case "track": + searchStations(query, order); + return true; + default: + return false; + } +} + +function cancel() { + if (request) { + request.abort(); + } + + return true; +} diff --git a/plugins/internetradio/musikloud2-internetradio.json b/plugins/internetradio/musikloud2-internetradio.json new file mode 100644 index 0000000..5741262 --- /dev/null +++ b/plugins/internetradio/musikloud2-internetradio.json @@ -0,0 +1,41 @@ +{ + "type": "js", + "version": 2, + "name": "Internet radio", + "resources": [ + { + "type": "track", + "method": "list", + "label": "All stations", + "id": "/stations" + }, + + { + "type": "track", + "method": "search", + "label": "Stations", + "order": "title" + }, + + { + "type": "category", + "method": "list", + "label": "Stations by genre", + "id": "/genres" + }, + + { + "type": "category", + "method": "list", + "label": "Stations by country", + "id": "/countries" + }, + + { + "type": "category", + "method": "list", + "label": "Stations by language", + "id": "/languages" + } + ] +} diff --git a/plugins/mixcloud/AUTHORS b/plugins/mixcloud/AUTHORS deleted file mode 100644 index b678761..0000000 --- a/plugins/mixcloud/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Stuart Howarth diff --git a/plugins/mixcloud/COPYING b/plugins/mixcloud/COPYING deleted file mode 100644 index 94a9ed0..0000000 --- a/plugins/mixcloud/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/plugins/mixcloud/README.md b/plugins/mixcloud/README.md deleted file mode 100644 index 24e6297..0000000 --- a/plugins/mixcloud/README.md +++ /dev/null @@ -1,30 +0,0 @@ -#MusiKloud2-Mixcloud - -MusiKloud2-Mixcloud is a plugin for MusiKloud2 providing access to content from Mixcloud. - -#Dependencies - -python-simplejson - -#Usage - -The following resources are supported: - - - - - - - - - - - - - - - - - - -
ResourceMethods
tracklist, search, get
categorylist
streamlist
diff --git a/plugins/mixcloud/debian/changelog b/plugins/mixcloud/debian/changelog new file mode 100644 index 0000000..daa8cf5 --- /dev/null +++ b/plugins/mixcloud/debian/changelog @@ -0,0 +1,19 @@ +musikloud2-mixcloud (0.0.4) unstable; urgency=low + * Improve audio streams retrieval. + + -- Stuart Howarth Fri, 13 Jan 2017 01:27:25 +0000 + +musikloud2-mixcloud (0.0.3) unstable; urgency=low + * Use JavaScript plugin API. + + -- Stuart Howarth Wed, 04 Jan 2017 00:21:53 +0000 + +musikloud2-mixcloud (0.0.2) unstable; urgency=low + * Remove get/list/search for artists. + + -- Stuart Howarth Sun, 09 Aug 2015 15:57:43 +0000 + +musikloud2-mixcloud (0.0.1) unstable; urgency=low + * Initial release. + + -- Stuart Howarth Mon, 29 Jun 2015 00:40:43 +0000 diff --git a/plugins/podcasts/debian/compat b/plugins/mixcloud/debian/compat similarity index 100% rename from plugins/podcasts/debian/compat rename to plugins/mixcloud/debian/compat diff --git a/plugins/mixcloud/debian_maemo5/control b/plugins/mixcloud/debian/control similarity index 82% rename from plugins/mixcloud/debian_maemo5/control rename to plugins/mixcloud/debian/control index 6237eae..20f421d 100644 --- a/plugins/mixcloud/debian_maemo5/control +++ b/plugins/mixcloud/debian/control @@ -7,7 +7,7 @@ Build-Depends: debhelper (>= 5) Standards-Version: 3.7.3 Package: musikloud2-mixcloud -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, python-simplejson +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends} Description: A plugin for MusiKloud2 providing access to content from Mixcloud XB-Maemo-Display-Name: MusiKloud2-Mixcloud diff --git a/plugins/mixcloud/debian_harmattan/rules b/plugins/mixcloud/debian/rules similarity index 87% rename from plugins/mixcloud/debian_harmattan/rules rename to plugins/mixcloud/debian/rules index 8b44705..e3c8be0 100755 --- a/plugins/mixcloud/debian_harmattan/rules +++ b/plugins/mixcloud/debian/rules @@ -36,9 +36,8 @@ install: build dh_clean -k dh_installdirs - mkdir -p debian/musikloud2-mixcloud/opt/musikloud2/plugins/mixcloud - cp mixcloud.plugin debian/musikloud2-mixcloud/opt/musikloud2/plugins - cp mixcloud.py debian/musikloud2-mixcloud/opt/musikloud2/plugins/mixcloud + mkdir -p debian/musikloud2-mixcloud/opt/musikloud2/plugins + cp musikloud2-mixcloud.js musikloud2-mixcloud.json debian/musikloud2-mixcloud/opt/musikloud2/plugins # Build architecture-independent files here. binary-indep: build install diff --git a/plugins/mixcloud/debian_harmattan/changelog b/plugins/mixcloud/debian_harmattan/changelog deleted file mode 100644 index 534298c..0000000 --- a/plugins/mixcloud/debian_harmattan/changelog +++ /dev/null @@ -1,4 +0,0 @@ -musikloud2-mixcloud (0.0.2) unstable; urgency=low - * Initial release. - - -- Stuart Howarth Sun, 09 Aug 2015 15:57:43 +0000 diff --git a/plugins/mixcloud/debian_harmattan/compat b/plugins/mixcloud/debian_harmattan/compat deleted file mode 100644 index 7f8f011..0000000 --- a/plugins/mixcloud/debian_harmattan/compat +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/plugins/mixcloud/debian_harmattan/control b/plugins/mixcloud/debian_harmattan/control deleted file mode 100644 index c96dd25..0000000 --- a/plugins/mixcloud/debian_harmattan/control +++ /dev/null @@ -1,97 +0,0 @@ -Source: musikloud2-mixcloud -Section: user/multimedia -Priority: optional -Maintainer: Stuart Howarth -Homepage: http://marxoft.co.uk/projects/musikloud2 -Build-Depends: debhelper (>= 5) -Standards-Version: 3.7.3 - -Package: musikloud2-mixcloud -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, musikloud2, python -Description: A plugin for MusiKloud2 providing access to content from Mixcloud -XB-Maemo-Display-Name: MusiKloud2-Mixcloud -XB-Maemo-Icon-26: - iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A - /wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9wCFAEQDTki4YcAABHRSURBVHja - 3Vt5mFxVlf+de+979Wqvru5Op0OWThSaxNCQZSYSGUc0ojKgA3zgIIKC+gkiMyqu4/ahzIgCDptG - Z1wQgoy4C+KgIjIxw5YQSLMFUEwT0tl6SVdXV9V7754zf7yq6up0J+nuBAze76uvuutt9/zO7571 - PsJBjls3l3B2Z3zMb5+5R3DMjBHXkDgxRRoEpQlKa0UGoP3dz0IktCJWiFmEA7bW+hSsf479r5yW - HXPuD54awduPThzU/GlaV4ngD48M4YQloxO69bHCvJTR8+IGLULUXgmly4ocpQRHCCFHhIwCxYgk - eqxMPBMRAQMVEQwBGATkBSW0OeZQtyL0jgS0q2R5y1mvSvbULl376DD+risJEL1EAFTHmkcGF+Ti - zpkxo5YFVjoDlnlgZAUAi0AirPb6lv1PiAiESJbGb0XVY4RBo6jH0fRU2bcbBqz64XmLE8+96Az4 - 8dNlnHGUBwC4rXtkRTauriLhxaWAkxWGAxlVqoxKSft6JDUelUZCTAiQVMGhxmtjCoHnqCKA7iEf - l575qsRDAPDTjSWctiR+aAG46eGiyaaoU1m+ydFq6XBgEVocFsPRhJSr4FteD1+dty30n3n/0lw4 - mWvVfim+qR8A8NMnijOyHl+vrH2sEtqlfcVAKgELM+Nw+JQDK7uGA6kEdjkc+8SMmLr2J48UWgHg - 5o2D02PArY8UcPZxady8aeDYnFE3lgM+rmLr9CYcnkOIiDwDuFptHKzIu85bmuv+/oY+vGNZ8+QB - +O7GPpy/pBk3PTx8fJPr3zVUkXTIUluLh6vwjfaCjAbSMb2nf9i+5d0rWu773sO78a6lLZNnwLce - 3n1Mq6H/GyjbFLO8HAQfB4RSRPm4Luyu8MoLlrc+NmkG3Hz/jnbPpbuKvj0msJCqZ3rZDRGIo0Ep - V2/yYd70jqX57fsF4DvdfdADFR2Lq6sqof1QORT8NYy4oxBz9NUhOZ8YKrO9aGXLvhmwZtPOo6jk - by6Uueae6WUuvxBAKU/Dh3PkBStmPNt40Ox9Nhf9W8pBiNDKX4PwNSXLSMWS5+BmAMePWwK33SY4 - 6yzCfz6wfaUXltYNlFjoMBJe6tGjHMQ9IPmEphH2Vrz/hJkPQgQgihjQs+AJAIBnK1cWShbCAnnp - lLPXRAVMBj65sAIIW8SLO+EgQJjIQyk97acNjQjSCf8qAK/98W+34IxGil/z+96jsjT8QP8I5+hF - 1H1jMlT7OySDUABmC/HLmN17P17z5LeQHXkBAOAGI/jl4o+g5xUnIxZPQikFmsYkRYB8Ug2OqMyy - i09o+xMAmHtu34wTT+1ESpfPKJZsipkPqcAsDbplgQ5LMMEwxFowM2LFnTjx8W9iUe9aMEUqYVKw - yoGQAgFwuILhvh0YbhmANg6M4057PsWyJGPuyFkArvjtD56AOfHUzoiMYWVJyQ8Ny6ET3PhDaNv9 - OLzyAMAhQhHM37YOxz5/JzRHqa4ACIxB2Y2Pc0tU/yaUigWURopI5SxIMabL0pLPTtxRSwBg1dsX - RTbg6/+7ZV6lXDw6tBZyiAAoUwxvu+/zmLPzIcT9IRC4Gp4ZhMZDGAXuDULur0YAjBSH0d/fj1gq - h0w2C6XUtJwUE1Dxg86v3f3c3IvfML/HRNr355UDO9faQ0f/ChjtfZvg2jKsdicMJyc7/dAK/ubI - drzxpOPQV3Fw+/o/obk5D9eL143iVGxCJbRzPe3PBVAFICg3B2GYtfbQJPhEhNAGkYhEB196AmCM - RszReMPiV+JNr16EG3/1AJ7cthvxVAbGcacEQBCiKe4ELQCgPv24wDLPstZCooGD/YSWUS5XDlj+ - ms6wLNBK4T2nrMSyjiZs39oDvzwCay2EeTLzE2stwsC24acC09a7xfUtdYWhhbUHN2EBYBRhZtbD - 0R05xNZqIDzkGESRgwjOPmkFWnNJ3Pg/GzC3owPGiU2qMBqCEDhu11cTWxwTVopOGIZHh9aCefpM - tSxIegaaCK9ddAT6Aw2j1fQT+hp79iEQEUFE8Prli9A3OIzfPbYV+ZYZ0NocCAQKAYQcLjI84hqx - gWYbtofWYro2kEXQ2Z7DsfNn4I4Nz0ErghwonhAZF+5WdAxCGkRAwaSRL++CBu+/gkyEjplN4Ae6 - UYrFYLwEHOMASu0XYcvcrtlqI2DFIjlrGQeKAfzQwtEKfhAAALTSAAHlSoDXLFqMkh+AmaPAuyqg - AFAi0BJWI3KBCLDdm4mB+EwopUHGoBLLonvGCSjl5iIe96BSebz15+dA+0P7IwJEBCu6OrGiqxM/ - +f3D2DlUxvpnepHL5aJ7TwSEAByGTaxIGQ58ZUOb2V8M4AchWIB3rzoGP1q3GScunoPQMgqVAEGl - gkTcQ8kPoqZGZEzrAJAI+nQGG5OLEaRaEfPiiMVcDGY6UMzNQyKRQDKZRDKZQJfnIBmPw/M8SCIH - 55ca8A/scWpAnP66pQCA//rZvVj7eA+aW2ZAG2ccekyAtTbNISljbUhhaGORC6RxNHUdgzcun4+7 - Nz2PYzta8aM/PIUF7XmEltE7MIzOWbPRX6zAWo7C2AZLXBs7vZl4ouPNmD9nNppaW5FOpzCrKqjn - eYjFYnAcB47jwBgDpRQC14NVakqutzbe949/j76BX+DZXQNIZnLQ2hknmg3DOCshwzYkZouJgiAS - hnI1OtryYP4zBIDlSMMsDLYMrVUkcBU+ywzBWACUcdDaNhMLj+7ErFmzEI/HYYyB1hpKqXpyUxOC - iCAETMckiQiICCevXIyrv/9rVBwHrpcctxQsK7AlMmIt2DIsWzTGasVSGee+/hjc8dCfIMIRQAKw - 3dvXog4AALC11U5PAwBKIRFPIJvNIpfLwXUnDlwa6Txdd1TzDl2dCxAUB8DKhdIutOOMSbmtVbAK - UFFzIWKAtRbWWgRhiLaMhxOPewX8IIRUU1WgmrJWi2VR/28sGLa+BPaamKJx2t77c6gCpxqQ7z3j - JOzp3wW/UoYNw7p81jLEMoQtlGVLlnlMFFWp+Dj1+IVVbTAEDK66CGYZ1XBD51OkauGZ64HKVCN/ - OsSFiNcfvwzDQ3sQBuVIcTVFccR4y5aMtVxdAqNRUBiG0NV/am4tqhPIGCPHDZoHSZUpPArIFNbs - uGLJoUrKyiX4FR+OZ0Eyem9mhrWAEZaox2dtfSJhGIAlMkHWciSYtaNGEFI3hHUhhCAkDUtAprRm - axOzRLAMlCRqXE6XEzVgbVCBDQNYy1AYBZpZQZjENDYZ6xbShpA65SOBbfX4OCOIBhaAqkBNjQEQ - ASuFSrmCcPX5YLFgBlRp6OALM9VINZKj0QswrCgYQURl28AAtgKlqM4AVG9QA6S29qVGd4wavaks - gdo5VimU/vvzsPd8GyjsjspgtU0A07QLRIRLL7saiXgCIAIz172cQMBswMxQzCJiubomIiuptcKv - 7++urvkIObZjl0QjzccaF4YVIBwXV8s+Ex9/0z2wv1kNGu6L/LVSACmAaoVwmZaHeOTJZ+B4CYA0 - mKUuH1uOanYCMSxKWFAWZq/2CEWEezc+g6e39NaFs3UGjF0CXBPciSEYKSBXeB6JDVuRLQMSlCPM - Zd9bY9gygt5ngT27AC8+QTAmCMmBVS6I1KTX/sbHngQrF7FYAqR0ZOQbijNMGGGl2JDWDKI9Iuzx - 6B4X5HJZXHHj7TCJpuiCuk2oRnoYdX2+SaB059ewqO8+XND7R8x5fAuOIICNV6fcvjNJRmgtDI3v - wwkABUFvcg52NnVitmMO6CqJCFu2bsOHv3ANJJaBE0+CSI1hrIraEUNEWowVMAsPMXNbI2uV1ugr - ltHsMYIwrPv10WwvmjwL8OrC/Sg9+GW4jgZACNz4qCAH7LARYJzRGsBeAloo3Nf+ZuTbZiOdSlWL - ofvWvO8HOO29l8JNN6Mp2wztxsAAqDE9JwFD9jCIjXZcS6WRF4TtkXunA46XxGBhGB/7jzWgWBKD - Q8Ow1mKkVEIQMioVH4WhPZA/PgiXLIQc0ATiGr8IOzyI4kgJheJIlK8T6tnjwOK3Ilx+LhKP/gLK - BlAEWFHQYrF64afQ0TEPM1qaobRBqeKj4gdjgBIWKKVw7/3rcfFnr8QRczqQzLfBSaRBpLF3r4MU - AJYXQCakT3xvrTu0e9s1uweGLgp44m4wEYGUwnNPbULHkYtgi/3gIAA5MXiFrVgzeB1K8Yl3ZSkI - flfI4eP9y9HW0oJYIlmdgdR7tyIhyhYYGh5GfvdTSKkAL9gUCrE8Mi4hnkzD9RLVhgihHtFUCwXF - Ygnbdw8imcki39yKWDKDWCIF7cRAapxKxFGg5nz2uqb2eR81V5x3gn/hl29+FCI4UFl8zisXIbQM - xJvAJoBSCqXUbPyychxOlm6UyB3d+VavaAm+PbgAJpFD4OUAk6hWa/ZqkTEjpeMoJnIYCkMoCJq1 - hjIOyHERagO7j/XvNuUwv3UutONCGxfacUFKg0WA8XVOcogAv7LpS2evCAwR4f1X3LSTolx+/xam - gUqkVJQGxFK4NfMPeMXgDhxJuxEgqtPHFMNDiOV/XgU/PQv51pnw0k2RVvZZ6OQoABNbjwFIRe5w - v5tUaqk0EVT1m6sGeuLTHQDYXd8f4HjpnapY6We2+ekkZP06h3eq9+B1Iw/iHU43NBhX9i/Eg7Yd - zc1pZDJ5xFJN0I5XFWh8WBCJp0F69NDkQyCJWugCRNsaZL9dJqWcPpNI7awD4OaaelT/zh62nJ9O - EqKURksmiQfN8bi7tAQ2DBDPWbS7LhwvAcdLQBk3cp1TaL5ObS6TzD2iGmWPSbf2jAH53M+uvm3H - UPlMO83uqEiUUrMddZmkNJSuFSYPj/0WRhFaM/Hb1nzxwrcDgPrqx9YAAOLZpvUxQ8F0u0EAgZSO - DJEbg3ZjUMZULT5wKDpOh+LjGgqS2aaHAODqD98yqpZP/3D9/Kc3rHu4bzjMTa84sa+A5/DYalSL - AptTzsAxf7uq63OnL9oauWkAb7nqSvzbmcufSyQz3Y6iadYjaIq/v/QYOIqQSmUe/dzpi7Z+/ks/ - QR2A8+d+FACQbZ39L2nPQA5lSeawEB0QgNIJg2zbnEsA4LJPnjaxet7977es275j+0rfRhuP/yrk - FxFXg9pnzlz73U+d89qxkepeI5FPnZ9KeCAikhejv/0XEJ6IKB33RDnxC8aH6g3jI7fei7Sa80w6 - lflC3FWH1QI+mCp53FFIpjOXzZq/8I8Xrr77wBbqkuvvyA/09d6+e7Cw0g/lZbsUIuoTtTSl16Vb - Zpy6+oNvG5i0if7YTb9v37r5iUd2FcozmF9+IIiIKEXUlkvseOXCJcdedvaKHRNnqxMx4Bt34srz - Xtc7Y0HXqtZ0rM/RRLXtJS8HwUUEjiGakfZ25ectXHXZ2St2fPzbv5uak77o+jux+pKT8anv3DOz - Z8uzP95TKK4sBRYiEMJh+AZBVOMUIlDc0chlkus6urpOv/z0FTsvvP4OfOOSU6YepXzoa3fgmotP - wcXX/ry5ODz4z8XhwmeGRirKtxCq10roL63xevkhpkGpRIwzmcxlqWz+hus+cEr/R773W3z1Xaum - HL6NG5f/upue3/TM0eXS0Jrh4T1Lh8sWgR1NXF9qHKQhWnM0IRXTSGWy61Ne7p1HnLDw6X99deek - lquZ7AO3b94m3/zo6U8CWHbxDb9aUejfdlWpWFxcCjlZCdiResOUGtokL8aLkwICQSkg5ugg7qhi - MpnqTjW3X3rDB97yEABcesPdBx3AT2p84qa1C/p6e84sFfYsCxidoZV5QWizoa221Gv7B+oVH9q7 - RD4OKKrW/Ma+NhsxzBgNo82go6nHKDyVSGU35ObM/+FV56x88V+d3Zt/n7z8+7jis+fUf/rgN++a - Vxnsm+ePFFpClnYYp0tEjhKWI1gkx2wzbG0M1fa5jKvURq1LUgTSpqKUHlJKDZLCC0poM6zfrRX1 - OoncrmQ+v+Xa951Uf3n645ffiq98+p9e+penAeC9X78D3/rAWAv7s4LgN7fc5QalYUd8X1sbKhsG - KgwDUmyp2mmiRv1rpaVarxJtXFHGZWMMK9exMScdnHLuSf5JmbHTvXD1nfjGRScf1Pz/H4wQBsF5 - hGO/AAAAAElFTkSuQmCC diff --git a/plugins/mixcloud/debian_harmattan/postinst b/plugins/mixcloud/debian_harmattan/postinst deleted file mode 100755 index 2136d68..0000000 --- a/plugins/mixcloud/debian_harmattan/postinst +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e - -chmod a+x /opt/musikloud2/plugins/mixcloud/mixcloud.py - -exit 0 diff --git a/plugins/mixcloud/debian_maemo5/changelog b/plugins/mixcloud/debian_maemo5/changelog deleted file mode 100644 index 4949ad5..0000000 --- a/plugins/mixcloud/debian_maemo5/changelog +++ /dev/null @@ -1,9 +0,0 @@ -musikloud2-mixcloud (0.0.2) unstable; urgency=low - * Remove get/list/search for artists. - - -- Stuart Howarth Sun, 09 Aug 2015 15:57:43 +0000 - -musikloud2-mixcloud (0.0.1) unstable; urgency=low - * Initial release. - - -- Stuart Howarth Mon, 29 Jun 2015 00:40:43 +0000 diff --git a/plugins/mixcloud/mixcloud.plugin b/plugins/mixcloud/mixcloud.plugin deleted file mode 100644 index bd54b39..0000000 --- a/plugins/mixcloud/mixcloud.plugin +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/plugins/mixcloud/mixcloud.pro b/plugins/mixcloud/mixcloud.pro new file mode 100755 index 0000000..1e2659b --- /dev/null +++ b/plugins/mixcloud/mixcloud.pro @@ -0,0 +1,40 @@ +TEMPLATE = subdirs + +maemo5 { + config.files = musikloud2-mixcloud.json + config.path = /opt/musikloud2/plugins + + plugin.files = musikloud2-mixcloud.js + plugin.path = /opt/musikloud2/plugins + + INSTALLS += \ + config \ + plugin + +} else:symbian { + config.sources = musikloud2-mixcloud.json + config.path = !:/musikloud2/plugins + + plugin.sources = musikloud2-mixcloud.js + plugin.path = !:/musikloud2/plugins + + vendorinfo += "%{\"Stuart Howarth\"}" ":\"Stuart Howarth\"" + mixcloud_deployment.pkg_prerules += vendorinfo + + DEPLOYMENT.display_name = MusiKloud2 Mixcloud + DEPLOYMENT += \ + mixcloud_deployment \ + config \ + plugin + +} else:unix { + config.files = musikloud2-mixcloud.json + config.path = /usr/share/musikloud2/plugins + + plugin.files = musikloud2-mixcloud.js + plugin.path = /usr/share/musikloud2/plugins + + INSTALLS += \ + config \ + plugin +} diff --git a/plugins/mixcloud/mixcloud.py b/plugins/mixcloud/mixcloud.py deleted file mode 100755 index f8910f6..0000000 --- a/plugins/mixcloud/mixcloud.py +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/env python - -# Copyright (C) 2015 Stuart Howarth -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from datetime import datetime -import getopt -import re -import sys -import urllib2 - -try: - import json -except ImportError: - import simplejson as json - -API_URL = 'https://api.mixcloud.com' - -class ResourceError(Exception): - pass - -def check_url(url): - try: - request = urllib2.Request(url) - request.get_method = lambda : 'HEAD' - urllib2.urlopen(request) - return True - except: - return False - -def format_date(date_string, date_format = '%Y-%m-%dT%H:%M:%SZ'): - try: - return datetime.strptime(date_string, date_format).strftime('%d %b %Y') - except: - return date_string - -def get_page(url): - try: - return urllib2.urlopen(url).read() - except: - raise ResourceError('{"error": "Unable to retrieve page from %s"}' % url) - -def list_tracks(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - response = json.loads(get_page(url.replace('www.mixcloud.com', 'api.mixcloud.com'))) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - result = {} - tracks = [] - - try: - for item in response['data']: - try: - track = {} - track['artist'] = item['user']['name'] - track['artistId'] = API_URL + item['user']['key'] - track['date'] = format_date(item['created_time']) - track['duration'] = item['audio_length'] * 1000 - - try: - track['genre'] = item['tags'][0]['name'] - except IndexError: - pass - - track['id'] = item['url'] - track['largeThumbnailUrl'] = item['pictures']['large'] - track['thumbnailUrl'] = item['pictures']['medium'] - track['title'] = item['name'] - track['url'] = item['url'] - tracks.append(track) - except: - pass - except KeyError: - pass - - result['items'] = tracks - - try: - result['next'] = response['paging']['next'] - except: - pass - - return result - -def search_tracks(query, order): - return list_tracks('%s/search/?q=%s&type=cloudcast' % (API_URL, query.replace(' ', '+'))) - -def get_track(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - response = json.loads(get_page(url.replace('www.mixcloud.com', 'api.mixcloud.com'))) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - try: - track = {} - track['artist'] = response['user']['name'] - track['artistId'] = API_URL + response['user']['key'] - track['date'] = format_date(response['created_time']) - track['duration'] = response['audio_length'] * 1000 - - try: - track['genre'] = item['tags'][0]['name'] - except IndexError: - pass - - track['id'] = response['url'] - track['largeThumbnailUrl'] = response['pictures']['large'] - track['thumbnailUrl'] = response['pictures']['medium'] - track['title'] = response['name'] - track['url'] = response['url'] - return track - except: - raise ResourceError('{"error": "Cloudcast not found"}') - -def list_artists(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - response = json.loads(get_page(url.replace('www.mixcloud.com', 'api.mixcloud.com'))) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - result = {} - artists = [] - - for item in response['data']: - try: - artist = {} - artist['id'] = API_URL + item['key'] + 'cloudcasts/' - artist['largeThumbnailUrl'] = item['pictures']['large'] - artist['name'] = item['name'] - artist['thumbnailUrl'] = item['pictures']['medium'] - artists.append(artist) - except: - pass - - result['items'] = tracks - - try: - result['next'] = response['paging']['next'] - except: - pass - - return result - -def search_artists(query, order): - return list_artists('%s/search/?q=%s&type=user' % (API_URL, query.replace(' ', '+'))) - -def get_artist(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - response = json.loads(get_page(url.replace('www.mixcloud.com', 'api.mixcloud.com'))) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - try: - artist = {} - artist['id'] = API_URL + response['key'] + 'cloudcasts/' - artist['largeThumbnailUrl'] = response['pictures']['large'] - artist['name'] = response['name'] - artist['thumbnailUrl'] = response['pictures']['medium'] - return artist - except: - raise ResourceError('{"error": "User not found"}') - -def list_categories(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - try: - response = json.loads(get_page(url.replace('www.mixcloud.com', 'api.mixcloud.com'))) - except: - raise ResourceError('{"error": "Unable to parse response from %s"}' % url) - - result = {} - categories = [] - - for item in response['data']: - try: - category = {} - category['id'] = API_URL + item['key'] + 'cloudcasts/' - category['title'] = item['name'] - categories.append(category) - except: - pass - - result['items'] = categories - - try: - result['next'] = response['paging']['next'] - except: - pass - - return result - -def list_streams(url): - if not url: - raise ResourceError('{"error": "No URL specified"}') - - page = get_page(url.replace('api.mixcloud.com', 'www.mixcloud.com')) - result = {} - streams = [] - - try: - preview_url = re.search(r'(?<=m-preview=")[^"]+', page).group(0) - stream_url = preview_url.replace('https://', 'http://').replace('/previews/', '/c/originals/') - - if check_url(stream_url): - stream = {} - stream['description'] = 'MP3' - stream['ext'] = 'mp3' - stream['id'] = '0' - stream['url'] = stream_url - streams.append(stream) - else: - stream_url = stream_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/') - - if check_url(stream_url): - stream = {} - stream['description'] = 'M4A' - stream['ext'] = 'm4a' - stream['id'] = '0' - stream['url'] = stream_url - streams.append(stream) - except: - pass - - result['items'] = streams - return result - -def get_item(resource, id): - if resource == 'track': - return get_track(id) - - if resource == 'artist': - return get_artist(id) - - return {} - -def list_items(resource, id): - if not resource or resource == 'track': - return list_tracks(id) - - if resource == 'artist': - return list_artists(id) - - if resource == 'category': - return list_categories(id) - - if resource == 'stream': - return list_streams(id) - - return [] - -def search_items(resoruce, query, order): - if not resource or resource == 'track': - return search_tracks(query, order) - - if resource == 'artist': - return search_artists(query, order) - - return [] - -def main(method, resource, id, query, order): - if method == 'list': - print json.dumps(list_items(resource, id)) - elif method == 'search': - print json.dumps(search_items(resource, query, order)) - elif method == 'get': - print json.dumps(get_item(resource, id)) - else: - raise ResourceError('{"error": "Invalid method specified: %s"}' % method) - -if __name__ == '__main__': - (opts, args) = getopt.getopt(sys.argv[1:], 'm:r:i:q:o:') - - method = 'list' - resource = 'track' - id = '' - query = '' - order = '' - - for o, a in opts: - if o == '-m': - method = a - elif o == '-r': - resource = a - elif o == '-i': - id = a - elif o == '-q': - query = a - elif o == '-o': - order = a - - try: - main(method, resource, id, query, order) - exit(0) - except ResourceError, e: - print e.args[0] - exit(1) diff --git a/plugins/mixcloud/musikloud2-mixcloud.js b/plugins/mixcloud/musikloud2-mixcloud.js new file mode 100644 index 0000000..c64fafa --- /dev/null +++ b/plugins/mixcloud/musikloud2-mixcloud.js @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var API_URL = "https://api.mixcloud.com"; +var WEB_URL = "https://www.mixcloud.com"; +var USER_AGENT = "Wget/1.13.4 (linux-gnu)"; +var STREAMS_COOKIE = "s=3lu41hy4i1seitbhbtqkt5x5v94wwziy"; + +var request = null; + +function formatDate(iso) { + try { + var date = iso.substring(0, iso.indexOf("T")).split("-"); + return new Date(date[0], date[1] - 1, date[2]).toLocaleDateString(); + } + catch(err) { + return null; + } +} + +function artistToItem(artist) { + var item = {}; + item["description"] = artist.biog; + item["largeThumbnailUrl"] = artist.pictures.large; + item["name"] = artist.name; + item["thumbnailUrl"] = artist.pictures.medium; + item["tracksId"] = artist.key + "cloudcasts/"; + item["url"] = artist.url; + return item; +} + +function cloudcastToItem(cloudcast) { + var item = {}; + item["artist"] = cloudcast.user.name; + item["artistId"] = cloudcast.user.key; + item["commentsId"] = cloudcast.key + "comments/"; + item["date"] = formatDate(cloudcast.created_time); + item["description"] = cloudcast.description; + item["duration"] = cloudcast.audio_length * 1000; + item["id"] = cloudcast.key; + item["largeThumbnailUrl"] = cloudcast.pictures.large; + item["playCount"] = cloudcast.listener_count; + item["relatedTracksId"] = cloudcast.user.key + "cloudcasts/"; + item["thumbnailUrl"] = cloudcast.pictures.medium; + item["title"] = cloudcast.name; + item["url"] = cloudcast.url; + + try { + item["genre"] = cloudcast.tags[0].name; + } + catch(err) {} + + return item; +} + +function categoryToItem(category) { + var item = {}; + item["id"] = category.key; + item["title"] = category.name; + item["tracksId"] = category.key + "cloudcasts/"; + return item; +} + +function commentToItem(comment) { + var item = {}; + item["artist"] = comment.user.name; + item["artistId"] = comment.user.key; + item["body"] = comment.comment; + item["date"] = formatDate(comment.submit_date); + item["id"] = comment.key; + item["thumbnailUrl"] = comment.user.pictures.medium; + item["url"] = comment.url; + return item; +} + +function getArtist(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + else { + url = url.replace("www.mixcloud.com", "api.mixcloud.com"); + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var response = JSON.parse(request.responseText); + finished(artistToItem(response)); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listCloudcasts(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + else { + url = url.replace("www.mixcloud.com", "api.mixcloud.com"); + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var response = JSON.parse(request.responseText); + var result = {}; + var items = []; + + for (var i = 0; i < response.data.length; i++) { + items.push(cloudcastToItem(response.data[i])); + } + + result["items"] = items; + + try { + result["next"] = response.paging.next; + } + catch(err) {} + + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function searchCloudcasts(query) { + listCloudcasts(API_URL + "/search/?q=" + query + "&type=cloudcast"); +} + +function getCloudcast(url) { + url = url.replace("www.mixcloud.com", "api.mixcloud.com"); + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var response = JSON.parse(request.responseText); + finished(cloudcastToItem(response)); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listCategories(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + else { + url = url.replace("www.mixcloud.com", "api.mixcloud.com"); + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var response = JSON.parse(request.responseText); + var result = {}; + var items = []; + + for (var i = 0; i < response.data.length; i++) { + items.push(categoryToItem(response.data[i])); + } + + result["items"] = items; + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listComments(url) { + if (url.indexOf("/") === 0) { + url = API_URL + url; + } + else { + url = url.replace("www.mixcloud.com", "api.mixcloud.com"); + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var response = JSON.parse(request.responseText); + var result = {}; + var items = []; + + for (var i = 0; i < response.data.length; i++) { + items.push(commentToItem(response.data[i])); + } + + result["items"] = items; + + try { + result["next"] = response.paging.next; + } + catch(err) {} + + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.send(); +} + +function listStreams(url) { + if (url.indexOf("/") === 0) { + url = WEB_URL + url; + } + else { + url = url.replace("api.mixcloud.com", "www.mixcloud.com"); + } + + request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + try { + var result = {}; + var items = []; + var response = request.responseText; + var previewUrl = /m-preview="([^"]+)/.exec(response)[1]; + var streamHost = /stream(\d+)\.mixcloud\.com/.exec(response)[0]; + var streamUrl = "http://" + streamHost + previewUrl.split("mixcloud.com")[1] + .replace("/previews/", "/c/m4a/64/").replace(/\.mp3$/, ".m4a"); + var item = {}; + item["description"] = "M4A"; + item["ext"] = "m4a"; + item["id"] = "m4a"; + item["url"] = streamUrl; + items.push(item); + result["items"] = items; + finished(result); + } + catch(err) { + error(err); + } + } + } + + request.open("GET", url); + request.setRequestHeader("User-Agent", USER_AGENT); + request.setRequestHeader("Cookie", STREAMS_COOKIE); + request.send(); +} + +function get(resource, id) { + switch (resource) { + case "artist": + getArtist(id); + return true; + case "track": + getCloudcast(id); + return true; + default: + return false; + } +} + +function list(resource, id) { + switch (resource) { + case "track": + listCloudcasts(id); + return true; + case "category": + listCategories(id); + return true; + case "comment": + listComments(id); + return true; + case "stream": + listStreams(id); + return true; + default: + return false; + } +} + +function search(resource, query, order) { + switch (resource) { + case "track": + searchCloudcasts(query); + return true; + default: + return false; + } +} + +function cancel() { + if (request) { + request.abort(); + } + + return true; +} diff --git a/plugins/mixcloud/musikloud2-mixcloud.json b/plugins/mixcloud/musikloud2-mixcloud.json new file mode 100644 index 0000000..8fa636d --- /dev/null +++ b/plugins/mixcloud/musikloud2-mixcloud.json @@ -0,0 +1,45 @@ +{ + "type": "js", + "version": 2, + "name": "Mixcloud", + "resources": [ + { + "method": "list", + "type": "track", + "label": "Popular cloudcasts", + "id": "/popular/" + }, + + { + "method": "list", + "type": "track", + "label": "New cloudcasts", + "id": "/new/" + }, + + { + "method": "list", + "type": "category", + "label": "Categories", + "id": "/categories/" + }, + + { + "method": "search", + "type": "track", + "label": "Cloudcasts" + }, + + { + "method": "get", + "type": "track", + "regExp": "http(s|)://(www|api)\\.mixcloud\\.com/\\w+/[\\w-]+(/|)$" + }, + + { + "method": "get", + "type": "artist", + "regExp": "http(s|)://(www|api)\\.mixcloud\\.com/\\w+(/|)$" + } + ] +} diff --git a/plugins/podcasts/AUTHORS b/plugins/podcasts/AUTHORS deleted file mode 100644 index b678761..0000000 --- a/plugins/podcasts/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Stuart Howarth diff --git a/plugins/podcasts/COPYING b/plugins/podcasts/COPYING deleted file mode 100644 index 94a9ed0..0000000 --- a/plugins/podcasts/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/plugins/podcasts/README.md b/plugins/podcasts/README.md deleted file mode 100644 index 513a496..0000000 --- a/plugins/podcasts/README.md +++ /dev/null @@ -1,3 +0,0 @@ -#MusiKloud2-Podcasts - -MusiKloud2-Podcasts is a plugin for MusiKloud2 providing access to audio podcasts. diff --git a/plugins/podcasts/debian/changelog b/plugins/podcasts/debian/changelog deleted file mode 100644 index 649a681..0000000 --- a/plugins/podcasts/debian/changelog +++ /dev/null @@ -1,4 +0,0 @@ -musikloud2-podcasts (0.0.1) unstable; urgency=low - * Initial release. - - -- Stuart Howarth Sat, 20 Jun 2015 03:02:43 +0000 diff --git a/plugins/podcasts/debian/control b/plugins/podcasts/debian/control deleted file mode 100644 index d31529b..0000000 --- a/plugins/podcasts/debian/control +++ /dev/null @@ -1,13 +0,0 @@ -Source: musikloud2-podcasts -Section: user/multimedia -Priority: optional -Maintainer: Stuart Howarth -Homepage: http://marxoft.co.uk/projects/musikloud2 -Build-Depends: debhelper (>= 5), libqt4-dev -Standards-Version: 3.7.3 - -Package: musikloud2-podcasts -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: A plugin for MusiKloud2 providing access to audio podcasts -XB-Maemo-Display-Name: MusiKloud2-Podcasts diff --git a/plugins/podcasts/plugin/linux/podcasts.plugin b/plugins/podcasts/plugin/linux/podcasts.plugin deleted file mode 100644 index 27177c4..0000000 --- a/plugins/podcasts/plugin/linux/podcasts.plugin +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/podcasts/plugin/symbian/podcasts.plugin b/plugins/podcasts/plugin/symbian/podcasts.plugin deleted file mode 100644 index 43ebe82..0000000 --- a/plugins/podcasts/plugin/symbian/podcasts.plugin +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/podcasts/podcasts.pro b/plugins/podcasts/podcasts.pro deleted file mode 100644 index 30b4be7..0000000 --- a/plugins/podcasts/podcasts.pro +++ /dev/null @@ -1,32 +0,0 @@ -TEMPLATE = app -TARGET = musikloud2-podcasts -QT += network xml -QT -= gui - -HEADERS += \ - src/json.h \ - src/rss.h - -SOURCES += \ - src/json.cpp \ - src/main.cpp \ - src/rss.cpp - -symbian { - plugin.files = plugin/symbian/podcasts.plugin - plugin.path = !:/.config/MusiKloud2/plugins - - settings.files = settings/podcasts.settings - settings.path = !:/.config/MusiKloud2/plugins/podcasts -} else:unix { - plugin.files = plugin/linux/podcasts.plugin - plugin.path = /opt/musikloud2/plugins - - settings.files = settings/podcasts.settings - settings.path = /opt/musikloud2/plugins/podcasts - - target.path = /opt/musikloud2/plugins/podcasts - - INSTALLS += plugin settings target -} - \ No newline at end of file diff --git a/plugins/podcasts/settings/podcasts.settings b/plugins/podcasts/settings/podcasts.settings deleted file mode 100644 index 6de6733..0000000 --- a/plugins/podcasts/settings/podcasts.settings +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/plugins/podcasts/src/json.cpp b/plugins/podcasts/src/json.cpp deleted file mode 100644 index a7ffd79..0000000 --- a/plugins/podcasts/src/json.cpp +++ /dev/null @@ -1,588 +0,0 @@ -/* Copyright 2011 Eeli Reilin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. 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. - * - * THIS SOFTWARE IS PROVIDED BY ''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 EELI REILIN 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. - * - * The views and conclusions contained in the software and documentation - * are those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Eeli Reilin. - */ - -/** - * \file json.cpp - */ - -#include "json.h" -#include - -namespace QtJson -{ - - -static QString sanitizeString(QString str) -{ - str.replace(QLatin1String("\\"), QLatin1String("\\\\")); - str.replace(QLatin1String("\""), QLatin1String("\\\"")); - str.replace(QLatin1String("\b"), QLatin1String("\\b")); - str.replace(QLatin1String("\f"), QLatin1String("\\f")); - str.replace(QLatin1String("\n"), QLatin1String("\\n")); - str.replace(QLatin1String("\r"), QLatin1String("\\r")); - str.replace(QLatin1String("\t"), QLatin1String("\\t")); - return QString(QLatin1String("\"%1\"")).arg(str); -} - -static QByteArray join(const QList &list, const QByteArray &sep) -{ - QByteArray res; - Q_FOREACH(const QByteArray &i, list) - { - if(!res.isEmpty()) - { - res += sep; - } - res += i; - } - return res; -} - -/** - * parse - */ -QVariant Json::parse(const QString &json) -{ - bool success = true; - return Json::parse(json, success); -} - -/** - * parse - */ -QVariant Json::parse(const QString &json, bool &success) -{ - success = true; - - //Return an empty QVariant if the JSON data is either null or empty - if(!json.isNull() || !json.isEmpty()) - { - QString data = json; - //We'll start from index 0 - int index = 0; - - //Parse the first value - QVariant value = Json::parseValue(data, index, success); - - //Return the parsed value - return value; - } - else - { - //Return the empty QVariant - return QVariant(); - } -} - -QByteArray Json::serialize(const QVariant &data) -{ - bool success = true; - return Json::serialize(data, success); -} - -QByteArray Json::serialize(const QVariant &data, bool &success) -{ - QByteArray str; - success = true; - - if(!data.isValid()) // invalid or null? - { - str = "null"; - } - else if((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? - { - QList values; - const QVariantList list = data.toList(); - Q_FOREACH(const QVariant& v, list) - { - QByteArray serializedValue = serialize(v); - if(serializedValue.isNull()) - { - success = false; - break; - } - values << serializedValue; - } - - str = "[ " + join( values, ", " ) + " ]"; - } - else if(data.type() == QVariant::Map) // variant is a map? - { - const QVariantMap vmap = data.toMap(); - QMapIterator it( vmap ); - str = "{ "; - QList pairs; - while(it.hasNext()) - { - it.next(); - QByteArray serializedValue = serialize(it.value()); - if(serializedValue.isNull()) - { - success = false; - break; - } - pairs << sanitizeString(it.key()).toUtf8() + ": " + serializedValue; - } - str += join(pairs, ", "); - str += " }"; - } - else if((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array? - { - str = sanitizeString(data.toString()).toUtf8(); - } - else if(data.type() == QVariant::Double) // double? - { - str = QByteArray::number(data.toDouble()); - if(!str.contains(".") && ! str.contains("e")) - { - str += ".0"; - } - } - else if (data.type() == QVariant::Bool) // boolean value? - { - str = data.toBool() ? "true" : "false"; - } - else if (data.type() == QVariant::ULongLong) // large unsigned number? - { - str = QByteArray::number(data.value()); - } - else if ( data.canConvert() ) // any signed number? - { - str = QByteArray::number(data.value()); - } - else if (data.canConvert()) - { - str = QString::number(data.value()).toUtf8(); - } - else if (data.canConvert()) // can value be converted to string? - { - // this will catch QDate, QDateTime, QUrl, ... - str = sanitizeString(data.toString()).toUtf8(); - } - else - { - success = false; - } - if (success) - { - return str; - } - else - { - return QByteArray(); - } -} - -/** - * parseValue - */ -QVariant Json::parseValue(const QString &json, int &index, bool &success) -{ - //Determine what kind of data we should parse by - //checking out the upcoming token - switch(Json::lookAhead(json, index)) - { - case JsonTokenString: - return Json::parseString(json, index, success); - case JsonTokenNumber: - return Json::parseNumber(json, index); - case JsonTokenCurlyOpen: - return Json::parseObject(json, index, success); - case JsonTokenSquaredOpen: - return Json::parseArray(json, index, success); - case JsonTokenTrue: - Json::nextToken(json, index); - return QVariant(true); - case JsonTokenFalse: - Json::nextToken(json, index); - return QVariant(false); - case JsonTokenNull: - Json::nextToken(json, index); - return QVariant(); - case JsonTokenNone: - break; - } - - //If there were no tokens, flag the failure and return an empty QVariant - success = false; - return QVariant(); -} - -/** - * parseObject - */ -QVariant Json::parseObject(const QString &json, int &index, bool &success) -{ - QVariantMap map; - int token; - - //Get rid of the whitespace and increment index - Json::nextToken(json, index); - - //Loop through all of the key/value pairs of the object - bool done = false; - while(!done) - { - //Get the upcoming token - token = Json::lookAhead(json, index); - - if(token == JsonTokenNone) - { - success = false; - return QVariantMap(); - } - else if(token == JsonTokenComma) - { - Json::nextToken(json, index); - } - else if(token == JsonTokenCurlyClose) - { - Json::nextToken(json, index); - return map; - } - else - { - //Parse the key/value pair's name - QString name = Json::parseString(json, index, success).toString(); - - if(!success) - { - return QVariantMap(); - } - - //Get the next token - token = Json::nextToken(json, index); - - //If the next token is not a colon, flag the failure - //return an empty QVariant - if(token != JsonTokenColon) - { - success = false; - return QVariant(QVariantMap()); - } - - //Parse the key/value pair's value - QVariant value = Json::parseValue(json, index, success); - - if(!success) - { - return QVariantMap(); - } - - //Assign the value to the key in the map - map[name] = value; - } - } - - //Return the map successfully - return QVariant(map); -} - -/** - * parseArray - */ -QVariant Json::parseArray(const QString &json, int &index, bool &success) -{ - QVariantList list; - - Json::nextToken(json, index); - - bool done = false; - while(!done) - { - int token = Json::lookAhead(json, index); - - if(token == JsonTokenNone) - { - success = false; - return QVariantList(); - } - else if(token == JsonTokenComma) - { - Json::nextToken(json, index); - } - else if(token == JsonTokenSquaredClose) - { - Json::nextToken(json, index); - break; - } - else - { - QVariant value = Json::parseValue(json, index, success); - - if(!success) - { - return QVariantList(); - } - - list.push_back(value); - } - } - - return QVariant(list); -} - -/** - * parseString - */ -QVariant Json::parseString(const QString &json, int &index, bool &success) -{ - QString s; - QChar c; - - Json::eatWhitespace(json, index); - - c = json[index++]; - - bool complete = false; - while(!complete) - { - if(index == json.size()) - { - break; - } - - c = json[index++]; - - if(c == '\"') - { - complete = true; - break; - } - else if(c == '\\') - { - if(index == json.size()) - { - break; - } - - c = json[index++]; - - if(c == '\"') - { - s.append('\"'); - } - else if(c == '\\') - { - s.append('\\'); - } - else if(c == '/') - { - s.append('/'); - } - else if(c == 'b') - { - s.append('\b'); - } - else if(c == 'f') - { - s.append('\f'); - } - else if(c == 'n') - { - s.append('\n'); - } - else if(c == 'r') - { - s.append('\r'); - } - else if(c == 't') - { - s.append('\t'); - } - else if(c == 'u') - { - int remainingLength = json.size() - index; - - if(remainingLength >= 4) - { - QString unicodeStr = json.mid(index, 4); - - int symbol = unicodeStr.toInt(0, 16); - - s.append(QChar(symbol)); - - index += 4; - } - else - { - break; - } - } - } - else - { - s.append(c); - } - } - - if(!complete) - { - success = false; - return QVariant(); - } - - return QVariant(s); -} - -/** - * parseNumber - */ -QVariant Json::parseNumber(const QString &json, int &index) -{ - Json::eatWhitespace(json, index); - - int lastIndex = Json::lastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - QString numberStr; - - numberStr = json.mid(index, charLength); - - index = lastIndex + 1; - - if (numberStr.contains('.')) { - return QVariant(numberStr.toDouble(NULL)); - } else if (numberStr.startsWith('-')) { - return QVariant(numberStr.toLongLong(NULL)); - } else { - return QVariant(numberStr.toULongLong(NULL)); - } -} - -/** - * lastIndexOfNumber - */ -int Json::lastIndexOfNumber(const QString &json, int index) -{ - int lastIndex; - - for(lastIndex = index; lastIndex < json.size(); lastIndex++) - { - if(QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) - { - break; - } - } - - return lastIndex -1; -} - -/** - * eatWhitespace - */ -void Json::eatWhitespace(const QString &json, int &index) -{ - for(; index < json.size(); index++) - { - if(QString(" \t\n\r").indexOf(json[index]) == -1) - { - break; - } - } -} - -/** - * lookAhead - */ -int Json::lookAhead(const QString &json, int index) -{ - int saveIndex = index; - return Json::nextToken(json, saveIndex); -} - -/** - * nextToken - */ -int Json::nextToken(const QString &json, int &index) -{ - Json::eatWhitespace(json, index); - - if(index == json.size()) - { - return JsonTokenNone; - } - - QChar c = json[index]; - index++; - switch(c.toLatin1()) - { - case '{': return JsonTokenCurlyOpen; - case '}': return JsonTokenCurlyClose; - case '[': return JsonTokenSquaredOpen; - case ']': return JsonTokenSquaredClose; - case ',': return JsonTokenComma; - case '"': return JsonTokenString; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': return JsonTokenNumber; - case ':': return JsonTokenColon; - } - - index--; - - int remainingLength = json.size() - index; - - //True - if(remainingLength >= 4) - { - if (json[index] == 't' && json[index + 1] == 'r' && - json[index + 2] == 'u' && json[index + 3] == 'e') - { - index += 4; - return JsonTokenTrue; - } - } - - //False - if (remainingLength >= 5) - { - if (json[index] == 'f' && json[index + 1] == 'a' && - json[index + 2] == 'l' && json[index + 3] == 's' && - json[index + 4] == 'e') - { - index += 5; - return JsonTokenFalse; - } - } - - //Null - if (remainingLength >= 4) - { - if (json[index] == 'n' && json[index + 1] == 'u' && - json[index + 2] == 'l' && json[index + 3] == 'l') - { - index += 4; - return JsonTokenNull; - } - } - - return JsonTokenNone; -} - - -} //end namespace diff --git a/plugins/podcasts/src/json.h b/plugins/podcasts/src/json.h deleted file mode 100644 index cf0499d..0000000 --- a/plugins/podcasts/src/json.h +++ /dev/null @@ -1,204 +0,0 @@ -/* Copyright 2011 Eeli Reilin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. 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. - * - * THIS SOFTWARE IS PROVIDED BY ''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 EELI REILIN 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. - * - * The views and conclusions contained in the software and documentation - * are those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Eeli Reilin. - */ - -/** - * \file json.h - */ - -#ifndef JSON_H -#define JSON_H - -#include -#include - -namespace QtJson -{ - -/** - * \enum JsonToken - */ -enum JsonToken -{ - JsonTokenNone = 0, - JsonTokenCurlyOpen = 1, - JsonTokenCurlyClose = 2, - JsonTokenSquaredOpen = 3, - JsonTokenSquaredClose = 4, - JsonTokenColon = 5, - JsonTokenComma = 6, - JsonTokenString = 7, - JsonTokenNumber = 8, - JsonTokenTrue = 9, - JsonTokenFalse = 10, - JsonTokenNull = 11 -}; - -/** - * \class Json - * \brief A JSON data parser - * - * Json parses a JSON data into a QVariant hierarchy. - */ -class Json -{ - public: - /** - * Parse a JSON string - * - * \param json The JSON data - */ - static QVariant parse(const QString &json); - - /** - * Parse a JSON string - * - * \param json The JSON data - * \param success The success of the parsing - */ - static QVariant parse(const QString &json, bool &success); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - */ - static QByteArray serialize(const QVariant &data); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QByteArray Textual JSON representation - */ - static QByteArray serialize(const QVariant &data, bool &success); - - private: - /** - * Parses a value starting from index - * - * \param json The JSON data - * \param index The start index - * \param success The success of the parse process - * - * \return QVariant The parsed value - */ - static QVariant parseValue(const QString &json, int &index, - bool &success); - - /** - * Parses an object starting from index - * - * \param json The JSON data - * \param index The start index - * \param success The success of the object parse - * - * \return QVariant The parsed object map - */ - static QVariant parseObject(const QString &json, int &index, - bool &success); - - /** - * Parses an array starting from index - * - * \param json The JSON data - * \param index The starting index - * \param success The success of the array parse - * - * \return QVariant The parsed variant array - */ - static QVariant parseArray(const QString &json, int &index, - bool &success); - - /** - * Parses a string starting from index - * - * \param json The JSON data - * \param index The starting index - * \param success The success of the string parse - * - * \return QVariant The parsed string - */ - static QVariant parseString(const QString &json, int &index, - bool &success); - - /** - * Parses a number starting from index - * - * \param json The JSON data - * \param index The starting index - * - * \return QVariant The parsed number - */ - static QVariant parseNumber(const QString &json, int &index); - - /** - * Get the last index of a number starting from index - * - * \param json The JSON data - * \param index The starting index - * - * \return The last index of the number - */ - static int lastIndexOfNumber(const QString &json, int index); - - /** - * Skip unwanted whitespace symbols starting from index - * - * \param json The JSON data - * \param index The start index - */ - static void eatWhitespace(const QString &json, int &index); - - /** - * Check what token lies ahead - * - * \param json The JSON data - * \param index The starting index - * - * \return int The upcoming token - */ - static int lookAhead(const QString &json, int index); - - /** - * Get the next JSON token - * - * \param json The JSON data - * \param index The starting index - * - * \return int The next JSON token - */ - static int nextToken(const QString &json, int &index); -}; - - -} //end namespace - -#endif //JSON_H diff --git a/plugins/podcasts/src/main.cpp b/plugins/podcasts/src/main.cpp deleted file mode 100644 index f6d27d0..0000000 --- a/plugins/podcasts/src/main.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "rss.h" -#include -#include -#include -#include - -int main(int argc, char *argv[]) { - QCoreApplication app(argc, argv); - Rss rss; - - QStringList args = app.arguments(); - QString method("list"); - QString resource("track"); - - int i = 0; - - if ((i = args.indexOf("-m") + 1) > 0) { - if (i < args.size()) { - method = args.at(i); - } - } - - if ((i = args.indexOf("-r") + 1) > 0) { - if (i < args.size()) { - resource = args.at(i); - } - } - - if (resource != "track") { - std::cout << qPrintable(QString("{\"error\": \"%1\"}").arg(QObject::tr("Resource '%1' is not supported") - .arg(resource))); - return 1; - } - - if (method == "list") { - QString id; - - if ((i = args.indexOf("-i") + 1) > 0) { - if (i < args.size()) { - id = args.at(i); - } - } - - if (id.isEmpty()) { - QStringList urls = QSettings("MusiKloud2", "MusiKloud2").value("Podcasts/feeds").toString().remove(' ').split(','); - - if (urls.isEmpty()) { - std::cout << qPrintable(QString("{\"error\": \"%1\"}").arg(QObject::tr("No feed URLs specified"))); - return 1; - } - else { - rss.listTracks(urls); - } - } - else { - rss.listTracks(id); - } - } - else { - std::cout << qPrintable(QString("{\"error\": \"%1\"}").arg(QObject::tr("Method '%1' is not supported") - .arg(method))); - return 1; - } - - return app.exec(); -} diff --git a/plugins/podcasts/src/rss.cpp b/plugins/podcasts/src/rss.cpp deleted file mode 100644 index 67c8dbe..0000000 --- a/plugins/podcasts/src/rss.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2015 Stuart Howarth - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "rss.h" -#include "json.h" -#include -#include -#include -#include -#include -#include -#include -#include - -static const int MAX_REDIRECTS = 8; - -bool dateGreaterThan(QVariant &one, QVariant &two) { - return one.toMap().value("_dt").toDateTime() > two.toMap().value("_dt").toDateTime(); -} - -Rss::Rss(QObject *parent) : - QObject(parent), - m_nam(new QNetworkAccessManager(this)), - m_redirects(0) -{ - connect(m_nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(parseTracks(QNetworkReply*))); -} - -void Rss::listTracks(const QStringList &urls) { - m_urls = urls; - - if (m_urls.isEmpty()) { - std::cout << qPrintable(QString("{\"error\": \"%1\"}").arg(tr("No feed URLs specified"))); - QCoreApplication::exit(1); - return; - } - - listTracks(m_urls.takeFirst()); -} - -void Rss::listTracks(const QString &url) { - m_redirects = 0; - m_nam->get(QNetworkRequest(url)); -} - -void Rss::followRedirect(const QUrl &url) { - m_redirects++; - m_nam->get(QNetworkRequest(url)); -} - -void Rss::parseTracks(QNetworkReply *reply) { - if (!reply) { - std::cout << qPrintable(QString("{\"error\": \"%1\"}").arg(tr("Network error"))); - QCoreApplication::exit(1); - return; - } - - if (reply->error() != QNetworkReply::NoError) { - reply->deleteLater(); - std::cout << qPrintable(QString("{\"error\": \"%1: %2\"}").arg(tr("Network error")).arg(reply->errorString())); - QCoreApplication::exit(1); - return; - } - - QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - - if (!redirect.isNull()) { - reply->deleteLater(); - - if (m_redirects < MAX_REDIRECTS) { - followRedirect(redirect.toString()); - } - else { - std::cout << qPrintable(QString("{\"error\": \"%1: %2\"}").arg(tr("Network error")) - .arg(tr("Maximum redirects reached"))); - QCoreApplication::exit(1); - } - - return; - } - - QDomDocument doc; - - if (!doc.setContent(reply->readAll(), true)) { - reply->deleteLater(); - std::cout << qPrintable(QString("{\"error\": \"%1\"}").arg(tr("Unable to parse XML"))); - QCoreApplication::exit(1); - return; - } - - QDomElement docElem = doc.documentElement(); - QDomNodeList items = docElem.elementsByTagName("item"); - QDomNode channelElem = docElem.firstChildElement("channel"); - QString thumbnailUrl = channelElem.firstChildElement("image").attribute("href"); - QString genre = channelElem.firstChildElement("category").attribute("text"); - - for (int i = 0; i < items.size(); i++) { - QDomElement item = items.at(i).toElement(); - QDateTime dt = QDateTime::fromString(item.firstChildElement("pubDate").text().section(' ', 0, -2), - "ddd, dd MMM yyyy hh:mm:ss"); - QString streamUrl = item.firstChildElement("enclosure").attribute("url"); - - QVariantMap result; - result["_dt"] = dt; - result["artist"] = item.firstChildElement("author").text(); - result["date"] = dt.toString("dd MMM yyyy"); - result["description"] = item.firstChildElement("description").text(); - result["duration"] = item.firstChildElement("duration").text(); - result["format"] = streamUrl.mid(streamUrl.lastIndexOf('.') + 1).toUpper(); - result["genre"] = genre; - result["id"] = reply->url(); - result["largeThumbnailUrl"] = thumbnailUrl; - result["streamUrl"] = streamUrl; - result["thumbnailUrl"] = thumbnailUrl; - result["title"] = item.firstChildElement("title").text(); - result["url"] = item.firstChildElement("link").text(); - m_results << result; - } - - reply->deleteLater(); - - if (m_urls.isEmpty()) { - printResult(); - } - else { - listTracks(m_urls.takeFirst()); - } -} - -void Rss::printResult() { - if (!m_results.isEmpty()) { - qSort(m_results.begin(), m_results.end(), dateGreaterThan); - } - - std::cout << QByteArray("{\"items\": " + QtJson::Json::serialize(m_results) + "}").constData(); - QCoreApplication::quit(); -} diff --git a/plugins/src/debian/changelog b/plugins/src/debian/changelog new file mode 100644 index 0000000..637091b --- /dev/null +++ b/plugins/src/debian/changelog @@ -0,0 +1,5 @@ +musikloud2-dev (0.0.1) unstable; urgency=low + + * Initial release. + + -- Stuart Howarth Wed, 04 Jan 2017 00:47:04 +0000 diff --git a/plugins/src/debian/compat b/plugins/src/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/plugins/src/debian/compat @@ -0,0 +1 @@ +5 diff --git a/plugins/src/debian/control b/plugins/src/debian/control new file mode 100644 index 0000000..3fe1bfc --- /dev/null +++ b/plugins/src/debian/control @@ -0,0 +1,47 @@ +Source: musikloud2-dev +Section: user/multimedia +Priority: optional +Maintainer: Stuart Howarth +Build-Depends: debhelper (>= 5) +Standards-Version: 3.7.3 +Homepage: http://marxoft.co.uk/projects/musikloud2 + +Package: musikloud2-dev +Architecture: all +Description: Development header files for MusiKloud2 plugins +XB-Maemo-Icon-26: + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI + WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wYTFjYP41harQAAABl0RVh0Q29tbWVudABDcmVhdGVk + IHdpdGggR0lNUFeBDhcAAAcXSURBVGje7ZpriF1XFcd/a+99zj2Pe+dxZ+5kMk2TycOkSapjgsFK + aaVWi48iooWCClaKfmixBBEEqX4QGqTgFwtJP7YqRQmlqH0pRbFKSEuatBEqxSRNY6aTZDKTzOM+ + zzl7++HeTDLMM9NkZGAWnA+Xe8/Z67/22v/1X+seyaxz42WLY2WZAG2xwoyXLT9/bpRqw64oAKGv + +Nk3ixgHVBuWan2l7UEzaxQr3FYBrAJYBbAKYOnmcFgE55ZO4WZ5HG066XDorIFxNXRaw9gybckw + p6MBPOOhlP7/A8hQZChSJ1jn6GwMsaZ2gp7G+/TWTlFMBikmQxSTIUrJIGfUrey95SCl9iLiR8hy + A7AOrLNYayGrs6H8Dluqb7Gl8habq8eJ7ASCQ5xDmC5XnGiqmaN86SzFOI/2AhC1fAAcsPPyX/jM + xd+xrvYufY3TKCBDyMRgReMWiqnLyOpj1KoVjB+hjb+8ALZMHGLXxGs4UdRVeH33OyiEmu/cW6St + GPLKkRHK9ZgwjFrnQW4yC7lWColaONJzBEApoafT58t39PCbnw5wzw5oVEZIk/qi2GnJANKsqQg9 + fWNkeGYd1joeeeBjPPzFIuMjZ8mS2oIg1NKcd3z2do+BTQGf31W44bT7hU+v45Gv3cLY6BA2S24c + AOegI6/YtTnH3QMFHKDVQp2TQzmLdinKpjTEY8zroKJjZJ4+8O7dfdzaUWVyfJQ0TXBz/NbMR484 + R5o5jFGICNVGxg8+18Hr/6qS2ZlHrEmXFuUsymUkohnO9TIc9TGaX894YR2SL5Llu9l68kW2n351 + TgBR6PP49/dw8myV3//tAsPlHLkgRqTpy5wABDAaNvRo6incsc3j0LsNquVLdPZ2kg/VrLFwCKNe + keH8ei60b2akfROV4kbycUQhjijEMZvyMVEU4cVthJVj2FMWEaY5dK31lQr0lQrcOVDi8QPHOHGh + RhAXEW2mA7hyeyOx3Lc75OyIZc/WgGMn62xYG3H8A8sT393KgZfGmGMtzuV6efnjj9G/ZRulrk7W + xRFxGBCGIUEQ4HkexhiMMVjtUw4XT7lKCfse3c1jT/6ToUkhKhSnYm+mNt+mdBdg+4aQc5crpC1y + sdbhHNQT10yrOczTio19JQZ2bKVUKuF5HlprlGpuuVJXD0sC13xePP3u/dbt7P3lEfwwBncNgMw6 + OiLLg/d0kc3i5aKWECHIBRQKBeI4nubwkp43i63tiugvOc7XJnBxAM6hcI5aI+Hb93Z8hEc37xSR + qeumjFICnzUdikZtHGebKaIcgE3Zc1ueJFue0YprXUuxNcWApF7GufRqHehuUzi3PI4nQM1BOlWC + rm9hEYdNGzjbnAsZgIlKyk3a9VYBdKTWUfv7r7GvPoUrX8JvVMH4tMrNou3DC5cRpbjisBFgsmoZ + Gqmh5Lq8WlQqOOdIq2Wq+x/Cvfk8eLlmMcI1BaBb/C7U6g0Gz42jzRpENHIlhcLA59mX/0scLqyu + nXVk4tFQPmWJSRZY24lQ+/P+pvN+eE3D0oyWtsmiyWN4ZIL3zozhh21Iq/00AL7ncfTEGHF8gT07 + uudlmrReY8uJ35J/7Vl+NPEOCR7SiufsLSbYN54H482qk8Zy3XhGz0u7V+zJp19C5brwcm0o0a1D + LAKiyIUF/np0jHKljtFqRkwcUOQibQe+wpeOPErP5WOkViE2m5fdnYPG1ruQZLqqVM5SlYC3N95P + qT3GGDMniCzL+MX+P/HGv8foKG3A5CJQAiKtSiygvYD2zoBnXv6AOM6zqbeIiOAbjSjBM0LXoV/R + fv4IDS+cRdZafKPI+WZaHfAFvPt/yOToIMXjL4BzaCxn8hs5vOVB+j5xJ5v611MozJTlSZLwjzff + 4+nnXmd4Mkdf/ycJ8iWU9qYCJqMTmfvJMxenxusiCmtTxi9doK2tANVBCNZgR47yvcpBBsZemTUV + DlfXsC/7Bp29m/Bz8RQIhyNLEyaqFdLz/yEuDzJGjomol/YwJt/WRRAV0NrjWipMM8uZDy+j/ZiO + 4lrijl6CfBfaixARwpyw76HumWrUOYuIor1rbZNpCtvAWei+i1Pn3uZT/JGEq/nsScaE9Xiqeh+2 + eydp1I/zwmm74JwlCBLSwm3Ukzo5mxFpg/YClBeQiiGVmWm4fruHNjmMF6BNgGgzo8qbebuXVnQR + QRvDi50P48bP8tX6H6bOxeFGP0/Uvo7ft4Oe3q2E+VKLIWSWOVyriRbX/F4EmaenajoroASZ44xd + 11QizAW80PdjDo4+QKHyPpOpMBr1UOztJG5fSy4qoow/vxbSN7ZIXhcApT068p2kuZ2kyWZiZ8lr + jfFCtAlQelkmlUsHICKI8fC0wc/lpxUruZla5EYPtqTZB35kfb/6/8AqgFUAqwBWPgAjNF+cgJX3 + socAstJft/kf0E+q1YJmRAgAAAAASUVORK5CYII= diff --git a/plugins/mixcloud/debian_maemo5/rules b/plugins/src/debian/rules similarity index 83% rename from plugins/mixcloud/debian_maemo5/rules rename to plugins/src/debian/rules index 8b44705..6625939 100755 --- a/plugins/mixcloud/debian_maemo5/rules +++ b/plugins/src/debian/rules @@ -36,9 +36,8 @@ install: build dh_clean -k dh_installdirs - mkdir -p debian/musikloud2-mixcloud/opt/musikloud2/plugins/mixcloud - cp mixcloud.plugin debian/musikloud2-mixcloud/opt/musikloud2/plugins - cp mixcloud.py debian/musikloud2-mixcloud/opt/musikloud2/plugins/mixcloud + mkdir -p debian/musikloud2-dev/usr/include/musikloud2 + cp *.h debian/musikloud2-dev/usr/include/musikloud2 # Build architecture-independent files here. binary-indep: build install @@ -70,7 +69,6 @@ binary-arch: build install # dh_perl # dh_makeshlibs dh_installdeb -# dh_shlibdeps # Uncomment this line for use without Qt Creator dh_gencontrol dh_md5sums dh_builddeb diff --git a/plugins/src/resourcesrequest.h b/plugins/src/resourcesrequest.h new file mode 100755 index 0000000..bd36655 --- /dev/null +++ b/plugins/src/resourcesrequest.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 Stuart Howarth + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RESOURCESREQUEST_H +#define RESOURCESREQUEST_H + +#include +#include + +class QNetworkAccessManager; + +class ResourcesRequest : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString errorString READ errorString NOTIFY finished) + Q_PROPERTY(QVariant result READ result NOTIFY finished) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_ENUMS(Status) + +public: + enum Status { + Null = 0, + Loading, + Canceled, + Ready, + Failed + }; + + explicit ResourcesRequest(QObject *parent = 0) : QObject(parent) {} + + virtual QString errorString() const { return QString(); } + + virtual QVariant result() const { return QVariant(); } + + virtual Status status() const { return Null; } + +public Q_SLOTS: + virtual bool cancel() { return false; } + virtual bool del(const QString &, const QString &, const QString &, const QString &) { return false; } + virtual bool get(const QString &, const QString &) { return false; } + virtual bool insert(const QString &, const QString &, const QString &, const QString &) { return false; } + virtual bool list(const QString &, const QString &) { return false; } + virtual bool search(const QString &, const QString &, const QString &) { return false; } + +Q_SIGNALS: + void statusChanged(ResourcesRequest::Status s); + void finished(); +}; + +#endif // RESOURCESREQUEST_H diff --git a/app/src/maemo5/plugins/pluginsettingsspinbox.h b/plugins/src/serviceplugin.h old mode 100644 new mode 100755 similarity index 51% rename from app/src/maemo5/plugins/pluginsettingsspinbox.h rename to plugins/src/serviceplugin.h index 18fd3eb..2b04b97 --- a/app/src/maemo5/plugins/pluginsettingsspinbox.h +++ b/plugins/src/serviceplugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Stuart Howarth + * Copyright (C) 2016 Stuart Howarth * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -15,33 +15,20 @@ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLUGINSETTINGSSPINBOX_H -#define PLUGINSETTINGSSPINBOX_H +#ifndef SERVICEPLUGIN_H +#define SERVICEPLUGIN_H -#include -#include +#include "resourcesrequest.h" -class PluginSettingsSpinbox : public QSpinBox +class ServicePlugin { - Q_OBJECT public: - explicit PluginSettingsSpinbox(QWidget *parent = 0); - - inline QString key() const { return m_key; } - inline QVariant defaultValue() const { return m_default; } - -public Q_SLOTS: - void setKey(const QString &key); - void setDefaultValue(const QVariant &value); - void load(); - -private Q_SLOTS: - void onValueEdited(); - -private: - QString m_key; - QVariant m_default; + virtual ~ServicePlugin() {} + + virtual ResourcesRequest* createRequest(QObject *parent = 0) = 0; }; -#endif // PLUGINSETTINGSSPINBOX_H +Q_DECLARE_INTERFACE(ServicePlugin, "org.musikloud2.ServicePlugin") + +#endif // SERVICEPLUGIN_H