Add Deezer support
|
@ -1,7 +1,5 @@
|
|||
# Strawberry Music Player
|
||||
# Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
# This file was part of Clementine.
|
||||
# Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
#
|
||||
# Strawberry is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -111,14 +109,13 @@ pkg_check_modules(PHONON phonon4qt5)
|
|||
pkg_check_modules(SQLITE REQUIRED sqlite3>=3.7)
|
||||
pkg_check_modules(LIBPULSE libpulse)
|
||||
pkg_check_modules(CHROMAPRINT libchromaprint)
|
||||
#if(CHROMAPRINT_FOUND)
|
||||
# set(HAVE_CHROMAPRINT ON)
|
||||
#endif()
|
||||
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
|
||||
pkg_check_modules(LIBMTP libmtp>=1.0)
|
||||
pkg_check_modules(IMOBILEDEVICE libimobiledevice-1.0)
|
||||
pkg_check_modules(USBMUXD libusbmuxd)
|
||||
pkg_check_modules(PLIST libplist)
|
||||
pkg_check_modules(LIBDEEZER libdeezer)
|
||||
pkg_check_modules(LIBDZMEDIA libdzmedia)
|
||||
|
||||
if(WIN32)
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
@ -287,6 +284,18 @@ optional_component(PHONON OFF "Engine: Phonon backend"
|
|||
DEPENDS "phonon4qt5" PHONON_FOUND
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
optional_component(DEEZER ON "Engine: Deezer backend"
|
||||
DEPENDS "libdeezer" LIBDEEZER_FOUND
|
||||
)
|
||||
else ()
|
||||
optional_component(DEEZER ON "Engine: Deezer backend"
|
||||
DEPENDS "Linux" LINUX
|
||||
DEPENDS "libdeezer" LIBDEEZER_FOUND
|
||||
DEPENDS "libpulse" LIBPULSE_FOUND
|
||||
)
|
||||
endif()
|
||||
|
||||
optional_component(LIBPULSE ON "Pulse audio integration"
|
||||
DEPENDS "libpulse" LIBPULSE_FOUND
|
||||
)
|
||||
|
@ -336,6 +345,10 @@ optional_component(SPARKLE ON "Sparkle integration"
|
|||
DEPENDS "Sparkle" SPARKLE
|
||||
)
|
||||
|
||||
optional_component(DZMEDIA ON "DZMedia"
|
||||
DEPENDS "libdzmedia" LIBDZMEDIA_FOUND
|
||||
)
|
||||
|
||||
#if(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
|
||||
#add_subdirectory(ext/gstafc)
|
||||
#endif(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
|
||||
|
@ -374,6 +387,6 @@ add_custom_target(uninstall
|
|||
|
||||
# Show a summary of what we have enabled
|
||||
summary_show()
|
||||
if(NOT HAVE_GSTREAMER AND NOT HAVE_XINE AND NOT HAVE_VLC AND NOT HAVE_PHONON)
|
||||
message(FATAL_ERROR "You need to enable either GStreamer, Xine, VLC or Phonon to compile!")
|
||||
if(NOT HAVE_GSTREAMER AND NOT HAVE_XINE AND NOT HAVE_VLC AND NOT HAVE_PHONON AND NOT HAVE_DEEZER)
|
||||
message(FATAL_ERROR "You need to enable either GStreamer, Xine, VLC, Phonon or Deezer to compile!")
|
||||
endif()
|
||||
|
|
438
data/data.qrc
|
@ -7,6 +7,7 @@
|
|||
<file>schema/device-schema.sql</file>
|
||||
<file>style/strawberry.css</file>
|
||||
<file>misc/playing_tooltip.txt</file>
|
||||
<file>misc/oauthsuccess.html</file>
|
||||
<file>pictures/strawberry.png</file>
|
||||
<file>pictures/strawbs-transparent.png</file>
|
||||
<file>pictures/noalbumart.png</file>
|
||||
|
@ -27,442 +28,7 @@
|
|||
<file>pictures/osd_background.png</file>
|
||||
<file>pictures/osd_shadow_corner.png</file>
|
||||
<file>pictures/osd_shadow_edge.png</file>
|
||||
<file>icons/128x128/albums.png</file>
|
||||
<file>icons/128x128/alsa.png</file>
|
||||
<file>icons/128x128/application-exit.png</file>
|
||||
<file>icons/128x128/applications-internet.png</file>
|
||||
<file>icons/128x128/bluetooth.png</file>
|
||||
<file>icons/128x128/cdcase.png</file>
|
||||
<file>icons/128x128/cd.png</file>
|
||||
<file>icons/128x128/configure.png</file>
|
||||
<file>icons/128x128/device-ipod-nano.png</file>
|
||||
<file>icons/128x128/device-ipod.png</file>
|
||||
<file>icons/128x128/device-phone.png</file>
|
||||
<file>icons/128x128/device.png</file>
|
||||
<file>icons/128x128/device-usb-drive.png</file>
|
||||
<file>icons/128x128/device-usb-flash.png</file>
|
||||
<file>icons/128x128/dialog-error.png</file>
|
||||
<file>icons/128x128/dialog-information.png</file>
|
||||
<file>icons/128x128/dialog-ok-apply.png</file>
|
||||
<file>icons/128x128/dialog-password.png</file>
|
||||
<file>icons/128x128/dialog-warning.png</file>
|
||||
<file>icons/128x128/document-download.png</file>
|
||||
<file>icons/128x128/document-new.png</file>
|
||||
<file>icons/128x128/document-open-folder.png</file>
|
||||
<file>icons/128x128/document-open.png</file>
|
||||
<file>icons/128x128/document-save.png</file>
|
||||
<file>icons/128x128/document-search.png</file>
|
||||
<file>icons/128x128/download.png</file>
|
||||
<file>icons/128x128/edit-clear-list.png</file>
|
||||
<file>icons/128x128/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/128x128/edit-copy.png</file>
|
||||
<file>icons/128x128/edit-delete.png</file>
|
||||
<file>icons/128x128/edit-find.png</file>
|
||||
<file>icons/128x128/edit-redo.png</file>
|
||||
<file>icons/128x128/edit-rename.png</file>
|
||||
<file>icons/128x128/edit-undo.png</file>
|
||||
<file>icons/128x128/electrocompaniet.png</file>
|
||||
<file>icons/128x128/equalizer.png</file>
|
||||
<file>icons/128x128/folder-new.png</file>
|
||||
<file>icons/128x128/folder.png</file>
|
||||
<file>icons/128x128/folder-sound.png</file>
|
||||
<file>icons/128x128/footsteps.png</file>
|
||||
<file>icons/128x128/go-down.png</file>
|
||||
<file>icons/128x128/go-home.png</file>
|
||||
<file>icons/128x128/go-jump.png</file>
|
||||
<file>icons/128x128/go-next.png</file>
|
||||
<file>icons/128x128/go-previous.png</file>
|
||||
<file>icons/128x128/go-up.png</file>
|
||||
<file>icons/128x128/gstreamer.png</file>
|
||||
<file>icons/128x128/headset.png</file>
|
||||
<file>icons/128x128/help-hint.png</file>
|
||||
<file>icons/128x128/intel.png</file>
|
||||
<file>icons/128x128/jack.png</file>
|
||||
<file>icons/128x128/keyboard.png</file>
|
||||
<file>icons/128x128/list-add.png</file>
|
||||
<file>icons/128x128/list-remove.png</file>
|
||||
<file>icons/128x128/mcintosh-player.png</file>
|
||||
<file>icons/128x128/mcintosh-text.png</file>
|
||||
<file>icons/128x128/media-eject.png</file>
|
||||
<file>icons/128x128/media-forward.png</file>
|
||||
<file>icons/128x128/media-pause.png</file>
|
||||
<file>icons/128x128/media-play.png</file>
|
||||
<file>icons/128x128/media-rewind.png</file>
|
||||
<file>icons/128x128/media-stop.png</file>
|
||||
<file>icons/128x128/nvidia.png</file>
|
||||
<file>icons/128x128/play2.png</file>
|
||||
<file>icons/128x128/realtek.png</file>
|
||||
<file>icons/128x128/search.png</file>
|
||||
<file>icons/128x128/soundcard.png</file>
|
||||
<file>icons/128x128/speaker.png</file>
|
||||
<file>icons/128x128/star-grey.png</file>
|
||||
<file>icons/128x128/star.png</file>
|
||||
<file>icons/128x128/strawberry.png</file>
|
||||
<file>icons/128x128/strawberry.svg</file>
|
||||
<file>icons/128x128/tools-wizard.png</file>
|
||||
<file>icons/128x128/view-choose.png</file>
|
||||
<file>icons/128x128/view-fullscreen.png</file>
|
||||
<file>icons/128x128/view-media-lyrics.png</file>
|
||||
<file>icons/128x128/view-media-playlist.png</file>
|
||||
<file>icons/128x128/view-media-visualization.png</file>
|
||||
<file>icons/128x128/view-refresh.png</file>
|
||||
<file>icons/128x128/vinyl.png</file>
|
||||
<file>icons/128x128/vlc.png</file>
|
||||
<file>icons/128x128/xine.png</file>
|
||||
<file>icons/128x128/zoom-in.png</file>
|
||||
<file>icons/128x128/zoom-out.png</file>
|
||||
<file>icons/128x128/tidal.png</file>
|
||||
<file>icons/64x64/albums.png</file>
|
||||
<file>icons/64x64/alsa.png</file>
|
||||
<file>icons/64x64/application-exit.png</file>
|
||||
<file>icons/64x64/applications-internet.png</file>
|
||||
<file>icons/64x64/bluetooth.png</file>
|
||||
<file>icons/64x64/cdcase.png</file>
|
||||
<file>icons/64x64/cd.png</file>
|
||||
<file>icons/64x64/configure.png</file>
|
||||
<file>icons/64x64/device-ipod-nano.png</file>
|
||||
<file>icons/64x64/device-ipod.png</file>
|
||||
<file>icons/64x64/device-phone.png</file>
|
||||
<file>icons/64x64/device.png</file>
|
||||
<file>icons/64x64/device-usb-drive.png</file>
|
||||
<file>icons/64x64/device-usb-flash.png</file>
|
||||
<file>icons/64x64/dialog-error.png</file>
|
||||
<file>icons/64x64/dialog-information.png</file>
|
||||
<file>icons/64x64/dialog-ok-apply.png</file>
|
||||
<file>icons/64x64/dialog-password.png</file>
|
||||
<file>icons/64x64/dialog-warning.png</file>
|
||||
<file>icons/64x64/document-download.png</file>
|
||||
<file>icons/64x64/document-new.png</file>
|
||||
<file>icons/64x64/document-open-folder.png</file>
|
||||
<file>icons/64x64/document-open.png</file>
|
||||
<file>icons/64x64/document-save.png</file>
|
||||
<file>icons/64x64/document-search.png</file>
|
||||
<file>icons/64x64/download.png</file>
|
||||
<file>icons/64x64/edit-clear-list.png</file>
|
||||
<file>icons/64x64/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/64x64/edit-copy.png</file>
|
||||
<file>icons/64x64/edit-delete.png</file>
|
||||
<file>icons/64x64/edit-find.png</file>
|
||||
<file>icons/64x64/edit-redo.png</file>
|
||||
<file>icons/64x64/edit-rename.png</file>
|
||||
<file>icons/64x64/edit-undo.png</file>
|
||||
<file>icons/64x64/electrocompaniet.png</file>
|
||||
<file>icons/64x64/equalizer.png</file>
|
||||
<file>icons/64x64/folder-new.png</file>
|
||||
<file>icons/64x64/folder.png</file>
|
||||
<file>icons/64x64/folder-sound.png</file>
|
||||
<file>icons/64x64/footsteps.png</file>
|
||||
<file>icons/64x64/go-down.png</file>
|
||||
<file>icons/64x64/go-home.png</file>
|
||||
<file>icons/64x64/go-jump.png</file>
|
||||
<file>icons/64x64/go-next.png</file>
|
||||
<file>icons/64x64/go-previous.png</file>
|
||||
<file>icons/64x64/go-up.png</file>
|
||||
<file>icons/64x64/gstreamer.png</file>
|
||||
<file>icons/64x64/headset.png</file>
|
||||
<file>icons/64x64/help-hint.png</file>
|
||||
<file>icons/64x64/intel.png</file>
|
||||
<file>icons/64x64/jack.png</file>
|
||||
<file>icons/64x64/keyboard.png</file>
|
||||
<file>icons/64x64/list-add.png</file>
|
||||
<file>icons/64x64/list-remove.png</file>
|
||||
<file>icons/64x64/mcintosh-player.png</file>
|
||||
<file>icons/64x64/mcintosh-text.png</file>
|
||||
<file>icons/64x64/media-eject.png</file>
|
||||
<file>icons/64x64/media-forward.png</file>
|
||||
<file>icons/64x64/media-pause.png</file>
|
||||
<file>icons/64x64/media-play.png</file>
|
||||
<file>icons/64x64/media-rewind.png</file>
|
||||
<file>icons/64x64/media-stop.png</file>
|
||||
<file>icons/64x64/nvidia.png</file>
|
||||
<file>icons/64x64/play2.png</file>
|
||||
<file>icons/64x64/pulseaudio.png</file>
|
||||
<file>icons/64x64/realtek.png</file>
|
||||
<file>icons/64x64/search.png</file>
|
||||
<file>icons/64x64/soundcard.png</file>
|
||||
<file>icons/64x64/speaker.png</file>
|
||||
<file>icons/64x64/star-grey.png</file>
|
||||
<file>icons/64x64/star.png</file>
|
||||
<file>icons/64x64/strawberry.png</file>
|
||||
<file>icons/64x64/tools-wizard.png</file>
|
||||
<file>icons/64x64/view-choose.png</file>
|
||||
<file>icons/64x64/view-fullscreen.png</file>
|
||||
<file>icons/64x64/view-media-lyrics.png</file>
|
||||
<file>icons/64x64/view-media-playlist.png</file>
|
||||
<file>icons/64x64/view-media-visualization.png</file>
|
||||
<file>icons/64x64/view-refresh.png</file>
|
||||
<file>icons/64x64/vinyl.png</file>
|
||||
<file>icons/64x64/vlc.png</file>
|
||||
<file>icons/64x64/xine.png</file>
|
||||
<file>icons/64x64/zoom-in.png</file>
|
||||
<file>icons/64x64/zoom-out.png</file>
|
||||
<file>icons/64x64/tidal.png</file>
|
||||
<file>icons/48x48/albums.png</file>
|
||||
<file>icons/48x48/alsa.png</file>
|
||||
<file>icons/48x48/application-exit.png</file>
|
||||
<file>icons/48x48/applications-internet.png</file>
|
||||
<file>icons/48x48/bluetooth.png</file>
|
||||
<file>icons/48x48/cdcase.png</file>
|
||||
<file>icons/48x48/cd.png</file>
|
||||
<file>icons/48x48/configure.png</file>
|
||||
<file>icons/48x48/device-ipod-nano.png</file>
|
||||
<file>icons/48x48/device-ipod.png</file>
|
||||
<file>icons/48x48/device-phone.png</file>
|
||||
<file>icons/48x48/device.png</file>
|
||||
<file>icons/48x48/device-usb-drive.png</file>
|
||||
<file>icons/48x48/device-usb-flash.png</file>
|
||||
<file>icons/48x48/dialog-error.png</file>
|
||||
<file>icons/48x48/dialog-information.png</file>
|
||||
<file>icons/48x48/dialog-ok-apply.png</file>
|
||||
<file>icons/48x48/dialog-password.png</file>
|
||||
<file>icons/48x48/dialog-warning.png</file>
|
||||
<file>icons/48x48/document-download.png</file>
|
||||
<file>icons/48x48/document-new.png</file>
|
||||
<file>icons/48x48/document-open-folder.png</file>
|
||||
<file>icons/48x48/document-open.png</file>
|
||||
<file>icons/48x48/document-save.png</file>
|
||||
<file>icons/48x48/document-search.png</file>
|
||||
<file>icons/48x48/download.png</file>
|
||||
<file>icons/48x48/edit-clear-list.png</file>
|
||||
<file>icons/48x48/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/48x48/edit-copy.png</file>
|
||||
<file>icons/48x48/edit-delete.png</file>
|
||||
<file>icons/48x48/edit-find.png</file>
|
||||
<file>icons/48x48/edit-redo.png</file>
|
||||
<file>icons/48x48/edit-rename.png</file>
|
||||
<file>icons/48x48/edit-undo.png</file>
|
||||
<file>icons/48x48/electrocompaniet.png</file>
|
||||
<file>icons/48x48/equalizer.png</file>
|
||||
<file>icons/48x48/folder-new.png</file>
|
||||
<file>icons/48x48/folder.png</file>
|
||||
<file>icons/48x48/folder-sound.png</file>
|
||||
<file>icons/48x48/footsteps.png</file>
|
||||
<file>icons/48x48/go-down.png</file>
|
||||
<file>icons/48x48/go-home.png</file>
|
||||
<file>icons/48x48/go-jump.png</file>
|
||||
<file>icons/48x48/go-next.png</file>
|
||||
<file>icons/48x48/go-previous.png</file>
|
||||
<file>icons/48x48/go-up.png</file>
|
||||
<file>icons/48x48/gstreamer.png</file>
|
||||
<file>icons/48x48/headset.png</file>
|
||||
<file>icons/48x48/help-hint.png</file>
|
||||
<file>icons/48x48/intel.png</file>
|
||||
<file>icons/48x48/jack.png</file>
|
||||
<file>icons/48x48/keyboard.png</file>
|
||||
<file>icons/48x48/list-add.png</file>
|
||||
<file>icons/48x48/list-remove.png</file>
|
||||
<file>icons/48x48/mcintosh-player.png</file>
|
||||
<file>icons/48x48/mcintosh.png</file>
|
||||
<file>icons/48x48/mcintosh-text.png</file>
|
||||
<file>icons/48x48/media-eject.png</file>
|
||||
<file>icons/48x48/media-forward.png</file>
|
||||
<file>icons/48x48/media-pause.png</file>
|
||||
<file>icons/48x48/media-playlist-repeat.png</file>
|
||||
<file>icons/48x48/media-playlist-shuffle.png</file>
|
||||
<file>icons/48x48/media-play.png</file>
|
||||
<file>icons/48x48/media-rewind.png</file>
|
||||
<file>icons/48x48/media-stop.png</file>
|
||||
<file>icons/48x48/nvidia.png</file>
|
||||
<file>icons/48x48/play2.png</file>
|
||||
<file>icons/48x48/pulseaudio.png</file>
|
||||
<file>icons/48x48/realtek.png</file>
|
||||
<file>icons/48x48/search.png</file>
|
||||
<file>icons/48x48/soundcard.png</file>
|
||||
<file>icons/48x48/speaker.png</file>
|
||||
<file>icons/48x48/star-grey.png</file>
|
||||
<file>icons/48x48/star.png</file>
|
||||
<file>icons/48x48/strawberry.png</file>
|
||||
<file>icons/48x48/tools-wizard.png</file>
|
||||
<file>icons/48x48/view-choose.png</file>
|
||||
<file>icons/48x48/view-fullscreen.png</file>
|
||||
<file>icons/48x48/view-media-lyrics.png</file>
|
||||
<file>icons/48x48/view-media-playlist.png</file>
|
||||
<file>icons/48x48/view-media-visualization.png</file>
|
||||
<file>icons/48x48/view-refresh.png</file>
|
||||
<file>icons/48x48/vinyl.png</file>
|
||||
<file>icons/48x48/vlc.png</file>
|
||||
<file>icons/48x48/xine.png</file>
|
||||
<file>icons/48x48/zoom-in.png</file>
|
||||
<file>icons/48x48/zoom-out.png</file>
|
||||
<file>icons/48x48/tidal.png</file>
|
||||
<file>icons/32x32/albums.png</file>
|
||||
<file>icons/32x32/alsa.png</file>
|
||||
<file>icons/32x32/application-exit.png</file>
|
||||
<file>icons/32x32/applications-internet.png</file>
|
||||
<file>icons/32x32/bluetooth.png</file>
|
||||
<file>icons/32x32/cdcase.png</file>
|
||||
<file>icons/32x32/cd.png</file>
|
||||
<file>icons/32x32/configure.png</file>
|
||||
<file>icons/32x32/device-ipod-nano.png</file>
|
||||
<file>icons/32x32/device-ipod.png</file>
|
||||
<file>icons/32x32/device-phone.png</file>
|
||||
<file>icons/32x32/device.png</file>
|
||||
<file>icons/32x32/device-usb-drive.png</file>
|
||||
<file>icons/32x32/device-usb-flash.png</file>
|
||||
<file>icons/32x32/dialog-error.png</file>
|
||||
<file>icons/32x32/dialog-information.png</file>
|
||||
<file>icons/32x32/dialog-ok-apply.png</file>
|
||||
<file>icons/32x32/dialog-password.png</file>
|
||||
<file>icons/32x32/dialog-warning.png</file>
|
||||
<file>icons/32x32/document-download.png</file>
|
||||
<file>icons/32x32/document-new.png</file>
|
||||
<file>icons/32x32/document-open-folder.png</file>
|
||||
<file>icons/32x32/document-open.png</file>
|
||||
<file>icons/32x32/document-save.png</file>
|
||||
<file>icons/32x32/document-search.png</file>
|
||||
<file>icons/32x32/download.png</file>
|
||||
<file>icons/32x32/edit-clear-list.png</file>
|
||||
<file>icons/32x32/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/32x32/edit-copy.png</file>
|
||||
<file>icons/32x32/edit-delete.png</file>
|
||||
<file>icons/32x32/edit-find.png</file>
|
||||
<file>icons/32x32/edit-redo.png</file>
|
||||
<file>icons/32x32/edit-rename.png</file>
|
||||
<file>icons/32x32/edit-undo.png</file>
|
||||
<file>icons/32x32/electrocompaniet.png</file>
|
||||
<file>icons/32x32/equalizer.png</file>
|
||||
<file>icons/32x32/folder-new.png</file>
|
||||
<file>icons/32x32/folder.png</file>
|
||||
<file>icons/32x32/folder-sound.png</file>
|
||||
<file>icons/32x32/footsteps.png</file>
|
||||
<file>icons/32x32/go-down.png</file>
|
||||
<file>icons/32x32/go-home.png</file>
|
||||
<file>icons/32x32/go-jump.png</file>
|
||||
<file>icons/32x32/go-next.png</file>
|
||||
<file>icons/32x32/go-previous.png</file>
|
||||
<file>icons/32x32/go-up.png</file>
|
||||
<file>icons/32x32/gstreamer.png</file>
|
||||
<file>icons/32x32/headset.png</file>
|
||||
<file>icons/32x32/help-hint.png</file>
|
||||
<file>icons/32x32/intel.png</file>
|
||||
<file>icons/32x32/jack.png</file>
|
||||
<file>icons/32x32/keyboard.png</file>
|
||||
<file>icons/32x32/list-add.png</file>
|
||||
<file>icons/32x32/list-remove.png</file>
|
||||
<file>icons/32x32/mcintosh-player.png</file>
|
||||
<file>icons/32x32/mcintosh.png</file>
|
||||
<file>icons/32x32/mcintosh-text.png</file>
|
||||
<file>icons/32x32/media-eject.png</file>
|
||||
<file>icons/32x32/media-forward.png</file>
|
||||
<file>icons/32x32/media-pause.png</file>
|
||||
<file>icons/32x32/media-playlist-repeat.png</file>
|
||||
<file>icons/32x32/media-playlist-shuffle.png</file>
|
||||
<file>icons/32x32/media-play.png</file>
|
||||
<file>icons/32x32/media-rewind.png</file>
|
||||
<file>icons/32x32/media-stop.png</file>
|
||||
<file>icons/32x32/nvidia.png</file>
|
||||
<file>icons/32x32/play2.png</file>
|
||||
<file>icons/32x32/pulseaudio.png</file>
|
||||
<file>icons/32x32/realtek.png</file>
|
||||
<file>icons/32x32/search.png</file>
|
||||
<file>icons/32x32/soundcard.png</file>
|
||||
<file>icons/32x32/speaker.png</file>
|
||||
<file>icons/32x32/star-grey.png</file>
|
||||
<file>icons/32x32/star.png</file>
|
||||
<file>icons/32x32/strawberry.png</file>
|
||||
<file>icons/32x32/strawberry.svg</file>
|
||||
<file>icons/32x32/tools-wizard.png</file>
|
||||
<file>icons/32x32/view-choose.png</file>
|
||||
<file>icons/32x32/view-fullscreen.png</file>
|
||||
<file>icons/32x32/view-media-lyrics.png</file>
|
||||
<file>icons/32x32/view-media-playlist.png</file>
|
||||
<file>icons/32x32/view-media-visualization.png</file>
|
||||
<file>icons/32x32/view-refresh.png</file>
|
||||
<file>icons/32x32/vinyl.png</file>
|
||||
<file>icons/32x32/vlc.png</file>
|
||||
<file>icons/32x32/xine.png</file>
|
||||
<file>icons/32x32/zoom-in.png</file>
|
||||
<file>icons/32x32/zoom-out.png</file>
|
||||
<file>icons/32x32/tidal.png</file>
|
||||
<file>icons/22x22/albums.png</file>
|
||||
<file>icons/22x22/alsa.png</file>
|
||||
<file>icons/22x22/application-exit.png</file>
|
||||
<file>icons/22x22/applications-internet.png</file>
|
||||
<file>icons/22x22/bluetooth.png</file>
|
||||
<file>icons/22x22/cdcase.png</file>
|
||||
<file>icons/22x22/cd.png</file>
|
||||
<file>icons/22x22/configure.png</file>
|
||||
<file>icons/22x22/device-ipod-nano.png</file>
|
||||
<file>icons/22x22/device-ipod.png</file>
|
||||
<file>icons/22x22/device-phone.png</file>
|
||||
<file>icons/22x22/device.png</file>
|
||||
<file>icons/22x22/device-usb-drive.png</file>
|
||||
<file>icons/22x22/device-usb-flash.png</file>
|
||||
<file>icons/22x22/dialog-error.png</file>
|
||||
<file>icons/22x22/dialog-information.png</file>
|
||||
<file>icons/22x22/dialog-ok-apply.png</file>
|
||||
<file>icons/22x22/dialog-password.png</file>
|
||||
<file>icons/22x22/dialog-warning.png</file>
|
||||
<file>icons/22x22/document-download.png</file>
|
||||
<file>icons/22x22/document-new.png</file>
|
||||
<file>icons/22x22/document-open-folder.png</file>
|
||||
<file>icons/22x22/document-open.png</file>
|
||||
<file>icons/22x22/document-save.png</file>
|
||||
<file>icons/22x22/document-search.png</file>
|
||||
<file>icons/22x22/download.png</file>
|
||||
<file>icons/22x22/edit-clear-list.png</file>
|
||||
<file>icons/22x22/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/22x22/edit-copy.png</file>
|
||||
<file>icons/22x22/edit-delete.png</file>
|
||||
<file>icons/22x22/edit-find.png</file>
|
||||
<file>icons/22x22/edit-redo.png</file>
|
||||
<file>icons/22x22/edit-rename.png</file>
|
||||
<file>icons/22x22/edit-undo.png</file>
|
||||
<file>icons/22x22/electrocompaniet.png</file>
|
||||
<file>icons/22x22/equalizer.png</file>
|
||||
<file>icons/22x22/folder-new.png</file>
|
||||
<file>icons/22x22/folder.png</file>
|
||||
<file>icons/22x22/folder-sound.png</file>
|
||||
<file>icons/22x22/footsteps.png</file>
|
||||
<file>icons/22x22/go-down.png</file>
|
||||
<file>icons/22x22/go-home.png</file>
|
||||
<file>icons/22x22/go-jump.png</file>
|
||||
<file>icons/22x22/go-next.png</file>
|
||||
<file>icons/22x22/go-previous.png</file>
|
||||
<file>icons/22x22/go-up.png</file>
|
||||
<file>icons/22x22/gstreamer.png</file>
|
||||
<file>icons/22x22/headset.png</file>
|
||||
<file>icons/22x22/help-hint.png</file>
|
||||
<file>icons/22x22/intel.png</file>
|
||||
<file>icons/22x22/jack.png</file>
|
||||
<file>icons/22x22/keyboard.png</file>
|
||||
<file>icons/22x22/list-add.png</file>
|
||||
<file>icons/22x22/list-remove.png</file>
|
||||
<file>icons/22x22/mcintosh-player.png</file>
|
||||
<file>icons/22x22/mcintosh.png</file>
|
||||
<file>icons/22x22/mcintosh-text.png</file>
|
||||
<file>icons/22x22/media-eject.png</file>
|
||||
<file>icons/22x22/media-forward.png</file>
|
||||
<file>icons/22x22/media-pause.png</file>
|
||||
<file>icons/22x22/media-playlist-repeat.png</file>
|
||||
<file>icons/22x22/media-playlist-shuffle.png</file>
|
||||
<file>icons/22x22/media-play.png</file>
|
||||
<file>icons/22x22/media-rewind.png</file>
|
||||
<file>icons/22x22/media-stop.png</file>
|
||||
<file>icons/22x22/nvidia.png</file>
|
||||
<file>icons/22x22/play2.png</file>
|
||||
<file>icons/22x22/pulseaudio.png</file>
|
||||
<file>icons/22x22/realtek.png</file>
|
||||
<file>icons/22x22/search.png</file>
|
||||
<file>icons/22x22/soundcard.png</file>
|
||||
<file>icons/22x22/speaker.png</file>
|
||||
<file>icons/22x22/star-grey.png</file>
|
||||
<file>icons/22x22/star.png</file>
|
||||
<file>icons/22x22/strawberry.png</file>
|
||||
<file>icons/22x22/strawberry.svg</file>
|
||||
<file>icons/22x22/tools-wizard.png</file>
|
||||
<file>icons/22x22/view-choose.png</file>
|
||||
<file>icons/22x22/view-fullscreen.png</file>
|
||||
<file>icons/22x22/view-media-lyrics.png</file>
|
||||
<file>icons/22x22/view-media-playlist.png</file>
|
||||
<file>icons/22x22/view-media-visualization.png</file>
|
||||
<file>icons/22x22/view-refresh.png</file>
|
||||
<file>icons/22x22/vinyl.png</file>
|
||||
<file>icons/22x22/vlc.png</file>
|
||||
<file>icons/22x22/xine.png</file>
|
||||
<file>icons/22x22/zoom-in.png</file>
|
||||
<file>icons/22x22/zoom-out.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>pictures/deezer.png</file>
|
||||
<file>fonts/HumongousofEternitySt.ttf</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
444
data/icons.qrc
|
@ -1,4 +1,444 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
<file>icons/128x128/albums.png</file>
|
||||
<file>icons/128x128/alsa.png</file>
|
||||
<file>icons/128x128/application-exit.png</file>
|
||||
<file>icons/128x128/applications-internet.png</file>
|
||||
<file>icons/128x128/bluetooth.png</file>
|
||||
<file>icons/128x128/cdcase.png</file>
|
||||
<file>icons/128x128/cd.png</file>
|
||||
<file>icons/128x128/configure.png</file>
|
||||
<file>icons/128x128/device-ipod-nano.png</file>
|
||||
<file>icons/128x128/device-ipod.png</file>
|
||||
<file>icons/128x128/device-phone.png</file>
|
||||
<file>icons/128x128/device.png</file>
|
||||
<file>icons/128x128/device-usb-drive.png</file>
|
||||
<file>icons/128x128/device-usb-flash.png</file>
|
||||
<file>icons/128x128/dialog-error.png</file>
|
||||
<file>icons/128x128/dialog-information.png</file>
|
||||
<file>icons/128x128/dialog-ok-apply.png</file>
|
||||
<file>icons/128x128/dialog-password.png</file>
|
||||
<file>icons/128x128/dialog-warning.png</file>
|
||||
<file>icons/128x128/document-download.png</file>
|
||||
<file>icons/128x128/document-new.png</file>
|
||||
<file>icons/128x128/document-open-folder.png</file>
|
||||
<file>icons/128x128/document-open.png</file>
|
||||
<file>icons/128x128/document-save.png</file>
|
||||
<file>icons/128x128/document-search.png</file>
|
||||
<file>icons/128x128/download.png</file>
|
||||
<file>icons/128x128/edit-clear-list.png</file>
|
||||
<file>icons/128x128/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/128x128/edit-copy.png</file>
|
||||
<file>icons/128x128/edit-delete.png</file>
|
||||
<file>icons/128x128/edit-find.png</file>
|
||||
<file>icons/128x128/edit-redo.png</file>
|
||||
<file>icons/128x128/edit-rename.png</file>
|
||||
<file>icons/128x128/edit-undo.png</file>
|
||||
<file>icons/128x128/electrocompaniet.png</file>
|
||||
<file>icons/128x128/equalizer.png</file>
|
||||
<file>icons/128x128/folder-new.png</file>
|
||||
<file>icons/128x128/folder.png</file>
|
||||
<file>icons/128x128/folder-sound.png</file>
|
||||
<file>icons/128x128/footsteps.png</file>
|
||||
<file>icons/128x128/go-down.png</file>
|
||||
<file>icons/128x128/go-home.png</file>
|
||||
<file>icons/128x128/go-jump.png</file>
|
||||
<file>icons/128x128/go-next.png</file>
|
||||
<file>icons/128x128/go-previous.png</file>
|
||||
<file>icons/128x128/go-up.png</file>
|
||||
<file>icons/128x128/gstreamer.png</file>
|
||||
<file>icons/128x128/headset.png</file>
|
||||
<file>icons/128x128/help-hint.png</file>
|
||||
<file>icons/128x128/intel.png</file>
|
||||
<file>icons/128x128/jack.png</file>
|
||||
<file>icons/128x128/keyboard.png</file>
|
||||
<file>icons/128x128/list-add.png</file>
|
||||
<file>icons/128x128/list-remove.png</file>
|
||||
<file>icons/128x128/mcintosh-player.png</file>
|
||||
<file>icons/128x128/mcintosh-text.png</file>
|
||||
<file>icons/128x128/media-eject.png</file>
|
||||
<file>icons/128x128/media-forward.png</file>
|
||||
<file>icons/128x128/media-pause.png</file>
|
||||
<file>icons/128x128/media-play.png</file>
|
||||
<file>icons/128x128/media-rewind.png</file>
|
||||
<file>icons/128x128/media-stop.png</file>
|
||||
<file>icons/128x128/nvidia.png</file>
|
||||
<file>icons/128x128/play2.png</file>
|
||||
<file>icons/128x128/realtek.png</file>
|
||||
<file>icons/128x128/search.png</file>
|
||||
<file>icons/128x128/soundcard.png</file>
|
||||
<file>icons/128x128/speaker.png</file>
|
||||
<file>icons/128x128/star-grey.png</file>
|
||||
<file>icons/128x128/star.png</file>
|
||||
<file>icons/128x128/strawberry.png</file>
|
||||
<file>icons/128x128/strawberry.svg</file>
|
||||
<file>icons/128x128/tools-wizard.png</file>
|
||||
<file>icons/128x128/view-choose.png</file>
|
||||
<file>icons/128x128/view-fullscreen.png</file>
|
||||
<file>icons/128x128/view-media-lyrics.png</file>
|
||||
<file>icons/128x128/view-media-playlist.png</file>
|
||||
<file>icons/128x128/view-media-visualization.png</file>
|
||||
<file>icons/128x128/view-refresh.png</file>
|
||||
<file>icons/128x128/vinyl.png</file>
|
||||
<file>icons/128x128/vlc.png</file>
|
||||
<file>icons/128x128/xine.png</file>
|
||||
<file>icons/128x128/zoom-in.png</file>
|
||||
<file>icons/128x128/zoom-out.png</file>
|
||||
<file>icons/128x128/tidal.png</file>
|
||||
<file>icons/128x128/deezer.png</file>
|
||||
<file>icons/64x64/albums.png</file>
|
||||
<file>icons/64x64/alsa.png</file>
|
||||
<file>icons/64x64/application-exit.png</file>
|
||||
<file>icons/64x64/applications-internet.png</file>
|
||||
<file>icons/64x64/bluetooth.png</file>
|
||||
<file>icons/64x64/cdcase.png</file>
|
||||
<file>icons/64x64/cd.png</file>
|
||||
<file>icons/64x64/configure.png</file>
|
||||
<file>icons/64x64/device-ipod-nano.png</file>
|
||||
<file>icons/64x64/device-ipod.png</file>
|
||||
<file>icons/64x64/device-phone.png</file>
|
||||
<file>icons/64x64/device.png</file>
|
||||
<file>icons/64x64/device-usb-drive.png</file>
|
||||
<file>icons/64x64/device-usb-flash.png</file>
|
||||
<file>icons/64x64/dialog-error.png</file>
|
||||
<file>icons/64x64/dialog-information.png</file>
|
||||
<file>icons/64x64/dialog-ok-apply.png</file>
|
||||
<file>icons/64x64/dialog-password.png</file>
|
||||
<file>icons/64x64/dialog-warning.png</file>
|
||||
<file>icons/64x64/document-download.png</file>
|
||||
<file>icons/64x64/document-new.png</file>
|
||||
<file>icons/64x64/document-open-folder.png</file>
|
||||
<file>icons/64x64/document-open.png</file>
|
||||
<file>icons/64x64/document-save.png</file>
|
||||
<file>icons/64x64/document-search.png</file>
|
||||
<file>icons/64x64/download.png</file>
|
||||
<file>icons/64x64/edit-clear-list.png</file>
|
||||
<file>icons/64x64/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/64x64/edit-copy.png</file>
|
||||
<file>icons/64x64/edit-delete.png</file>
|
||||
<file>icons/64x64/edit-find.png</file>
|
||||
<file>icons/64x64/edit-redo.png</file>
|
||||
<file>icons/64x64/edit-rename.png</file>
|
||||
<file>icons/64x64/edit-undo.png</file>
|
||||
<file>icons/64x64/electrocompaniet.png</file>
|
||||
<file>icons/64x64/equalizer.png</file>
|
||||
<file>icons/64x64/folder-new.png</file>
|
||||
<file>icons/64x64/folder.png</file>
|
||||
<file>icons/64x64/folder-sound.png</file>
|
||||
<file>icons/64x64/footsteps.png</file>
|
||||
<file>icons/64x64/go-down.png</file>
|
||||
<file>icons/64x64/go-home.png</file>
|
||||
<file>icons/64x64/go-jump.png</file>
|
||||
<file>icons/64x64/go-next.png</file>
|
||||
<file>icons/64x64/go-previous.png</file>
|
||||
<file>icons/64x64/go-up.png</file>
|
||||
<file>icons/64x64/gstreamer.png</file>
|
||||
<file>icons/64x64/headset.png</file>
|
||||
<file>icons/64x64/help-hint.png</file>
|
||||
<file>icons/64x64/intel.png</file>
|
||||
<file>icons/64x64/jack.png</file>
|
||||
<file>icons/64x64/keyboard.png</file>
|
||||
<file>icons/64x64/list-add.png</file>
|
||||
<file>icons/64x64/list-remove.png</file>
|
||||
<file>icons/64x64/mcintosh-player.png</file>
|
||||
<file>icons/64x64/mcintosh-text.png</file>
|
||||
<file>icons/64x64/media-eject.png</file>
|
||||
<file>icons/64x64/media-forward.png</file>
|
||||
<file>icons/64x64/media-pause.png</file>
|
||||
<file>icons/64x64/media-play.png</file>
|
||||
<file>icons/64x64/media-rewind.png</file>
|
||||
<file>icons/64x64/media-stop.png</file>
|
||||
<file>icons/64x64/nvidia.png</file>
|
||||
<file>icons/64x64/play2.png</file>
|
||||
<file>icons/64x64/pulseaudio.png</file>
|
||||
<file>icons/64x64/realtek.png</file>
|
||||
<file>icons/64x64/search.png</file>
|
||||
<file>icons/64x64/soundcard.png</file>
|
||||
<file>icons/64x64/speaker.png</file>
|
||||
<file>icons/64x64/star-grey.png</file>
|
||||
<file>icons/64x64/star.png</file>
|
||||
<file>icons/64x64/strawberry.png</file>
|
||||
<file>icons/64x64/tools-wizard.png</file>
|
||||
<file>icons/64x64/view-choose.png</file>
|
||||
<file>icons/64x64/view-fullscreen.png</file>
|
||||
<file>icons/64x64/view-media-lyrics.png</file>
|
||||
<file>icons/64x64/view-media-playlist.png</file>
|
||||
<file>icons/64x64/view-media-visualization.png</file>
|
||||
<file>icons/64x64/view-refresh.png</file>
|
||||
<file>icons/64x64/vinyl.png</file>
|
||||
<file>icons/64x64/vlc.png</file>
|
||||
<file>icons/64x64/xine.png</file>
|
||||
<file>icons/64x64/zoom-in.png</file>
|
||||
<file>icons/64x64/zoom-out.png</file>
|
||||
<file>icons/64x64/tidal.png</file>
|
||||
<file>icons/64x64/deezer.png</file>
|
||||
<file>icons/48x48/albums.png</file>
|
||||
<file>icons/48x48/alsa.png</file>
|
||||
<file>icons/48x48/application-exit.png</file>
|
||||
<file>icons/48x48/applications-internet.png</file>
|
||||
<file>icons/48x48/bluetooth.png</file>
|
||||
<file>icons/48x48/cdcase.png</file>
|
||||
<file>icons/48x48/cd.png</file>
|
||||
<file>icons/48x48/configure.png</file>
|
||||
<file>icons/48x48/device-ipod-nano.png</file>
|
||||
<file>icons/48x48/device-ipod.png</file>
|
||||
<file>icons/48x48/device-phone.png</file>
|
||||
<file>icons/48x48/device.png</file>
|
||||
<file>icons/48x48/device-usb-drive.png</file>
|
||||
<file>icons/48x48/device-usb-flash.png</file>
|
||||
<file>icons/48x48/dialog-error.png</file>
|
||||
<file>icons/48x48/dialog-information.png</file>
|
||||
<file>icons/48x48/dialog-ok-apply.png</file>
|
||||
<file>icons/48x48/dialog-password.png</file>
|
||||
<file>icons/48x48/dialog-warning.png</file>
|
||||
<file>icons/48x48/document-download.png</file>
|
||||
<file>icons/48x48/document-new.png</file>
|
||||
<file>icons/48x48/document-open-folder.png</file>
|
||||
<file>icons/48x48/document-open.png</file>
|
||||
<file>icons/48x48/document-save.png</file>
|
||||
<file>icons/48x48/document-search.png</file>
|
||||
<file>icons/48x48/download.png</file>
|
||||
<file>icons/48x48/edit-clear-list.png</file>
|
||||
<file>icons/48x48/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/48x48/edit-copy.png</file>
|
||||
<file>icons/48x48/edit-delete.png</file>
|
||||
<file>icons/48x48/edit-find.png</file>
|
||||
<file>icons/48x48/edit-redo.png</file>
|
||||
<file>icons/48x48/edit-rename.png</file>
|
||||
<file>icons/48x48/edit-undo.png</file>
|
||||
<file>icons/48x48/electrocompaniet.png</file>
|
||||
<file>icons/48x48/equalizer.png</file>
|
||||
<file>icons/48x48/folder-new.png</file>
|
||||
<file>icons/48x48/folder.png</file>
|
||||
<file>icons/48x48/folder-sound.png</file>
|
||||
<file>icons/48x48/footsteps.png</file>
|
||||
<file>icons/48x48/go-down.png</file>
|
||||
<file>icons/48x48/go-home.png</file>
|
||||
<file>icons/48x48/go-jump.png</file>
|
||||
<file>icons/48x48/go-next.png</file>
|
||||
<file>icons/48x48/go-previous.png</file>
|
||||
<file>icons/48x48/go-up.png</file>
|
||||
<file>icons/48x48/gstreamer.png</file>
|
||||
<file>icons/48x48/headset.png</file>
|
||||
<file>icons/48x48/help-hint.png</file>
|
||||
<file>icons/48x48/intel.png</file>
|
||||
<file>icons/48x48/jack.png</file>
|
||||
<file>icons/48x48/keyboard.png</file>
|
||||
<file>icons/48x48/list-add.png</file>
|
||||
<file>icons/48x48/list-remove.png</file>
|
||||
<file>icons/48x48/mcintosh-player.png</file>
|
||||
<file>icons/48x48/mcintosh.png</file>
|
||||
<file>icons/48x48/mcintosh-text.png</file>
|
||||
<file>icons/48x48/media-eject.png</file>
|
||||
<file>icons/48x48/media-forward.png</file>
|
||||
<file>icons/48x48/media-pause.png</file>
|
||||
<file>icons/48x48/media-playlist-repeat.png</file>
|
||||
<file>icons/48x48/media-playlist-shuffle.png</file>
|
||||
<file>icons/48x48/media-play.png</file>
|
||||
<file>icons/48x48/media-rewind.png</file>
|
||||
<file>icons/48x48/media-stop.png</file>
|
||||
<file>icons/48x48/nvidia.png</file>
|
||||
<file>icons/48x48/play2.png</file>
|
||||
<file>icons/48x48/pulseaudio.png</file>
|
||||
<file>icons/48x48/realtek.png</file>
|
||||
<file>icons/48x48/search.png</file>
|
||||
<file>icons/48x48/soundcard.png</file>
|
||||
<file>icons/48x48/speaker.png</file>
|
||||
<file>icons/48x48/star-grey.png</file>
|
||||
<file>icons/48x48/star.png</file>
|
||||
<file>icons/48x48/strawberry.png</file>
|
||||
<file>icons/48x48/tools-wizard.png</file>
|
||||
<file>icons/48x48/view-choose.png</file>
|
||||
<file>icons/48x48/view-fullscreen.png</file>
|
||||
<file>icons/48x48/view-media-lyrics.png</file>
|
||||
<file>icons/48x48/view-media-playlist.png</file>
|
||||
<file>icons/48x48/view-media-visualization.png</file>
|
||||
<file>icons/48x48/view-refresh.png</file>
|
||||
<file>icons/48x48/vinyl.png</file>
|
||||
<file>icons/48x48/vlc.png</file>
|
||||
<file>icons/48x48/xine.png</file>
|
||||
<file>icons/48x48/zoom-in.png</file>
|
||||
<file>icons/48x48/zoom-out.png</file>
|
||||
<file>icons/48x48/tidal.png</file>
|
||||
<file>icons/32x32/albums.png</file>
|
||||
<file>icons/32x32/alsa.png</file>
|
||||
<file>icons/32x32/application-exit.png</file>
|
||||
<file>icons/32x32/applications-internet.png</file>
|
||||
<file>icons/32x32/bluetooth.png</file>
|
||||
<file>icons/32x32/cdcase.png</file>
|
||||
<file>icons/32x32/cd.png</file>
|
||||
<file>icons/32x32/configure.png</file>
|
||||
<file>icons/32x32/device-ipod-nano.png</file>
|
||||
<file>icons/32x32/device-ipod.png</file>
|
||||
<file>icons/32x32/device-phone.png</file>
|
||||
<file>icons/32x32/device.png</file>
|
||||
<file>icons/32x32/device-usb-drive.png</file>
|
||||
<file>icons/32x32/device-usb-flash.png</file>
|
||||
<file>icons/32x32/dialog-error.png</file>
|
||||
<file>icons/32x32/dialog-information.png</file>
|
||||
<file>icons/32x32/dialog-ok-apply.png</file>
|
||||
<file>icons/32x32/dialog-password.png</file>
|
||||
<file>icons/32x32/dialog-warning.png</file>
|
||||
<file>icons/32x32/document-download.png</file>
|
||||
<file>icons/32x32/document-new.png</file>
|
||||
<file>icons/32x32/document-open-folder.png</file>
|
||||
<file>icons/32x32/document-open.png</file>
|
||||
<file>icons/32x32/document-save.png</file>
|
||||
<file>icons/32x32/document-search.png</file>
|
||||
<file>icons/32x32/download.png</file>
|
||||
<file>icons/32x32/edit-clear-list.png</file>
|
||||
<file>icons/32x32/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/32x32/edit-copy.png</file>
|
||||
<file>icons/32x32/edit-delete.png</file>
|
||||
<file>icons/32x32/edit-find.png</file>
|
||||
<file>icons/32x32/edit-redo.png</file>
|
||||
<file>icons/32x32/edit-rename.png</file>
|
||||
<file>icons/32x32/edit-undo.png</file>
|
||||
<file>icons/32x32/electrocompaniet.png</file>
|
||||
<file>icons/32x32/equalizer.png</file>
|
||||
<file>icons/32x32/folder-new.png</file>
|
||||
<file>icons/32x32/folder.png</file>
|
||||
<file>icons/32x32/folder-sound.png</file>
|
||||
<file>icons/32x32/footsteps.png</file>
|
||||
<file>icons/32x32/go-down.png</file>
|
||||
<file>icons/32x32/go-home.png</file>
|
||||
<file>icons/32x32/go-jump.png</file>
|
||||
<file>icons/32x32/go-next.png</file>
|
||||
<file>icons/32x32/go-previous.png</file>
|
||||
<file>icons/32x32/go-up.png</file>
|
||||
<file>icons/32x32/gstreamer.png</file>
|
||||
<file>icons/32x32/headset.png</file>
|
||||
<file>icons/32x32/help-hint.png</file>
|
||||
<file>icons/32x32/intel.png</file>
|
||||
<file>icons/32x32/jack.png</file>
|
||||
<file>icons/32x32/keyboard.png</file>
|
||||
<file>icons/32x32/list-add.png</file>
|
||||
<file>icons/32x32/list-remove.png</file>
|
||||
<file>icons/32x32/mcintosh-player.png</file>
|
||||
<file>icons/32x32/mcintosh.png</file>
|
||||
<file>icons/32x32/mcintosh-text.png</file>
|
||||
<file>icons/32x32/media-eject.png</file>
|
||||
<file>icons/32x32/media-forward.png</file>
|
||||
<file>icons/32x32/media-pause.png</file>
|
||||
<file>icons/32x32/media-playlist-repeat.png</file>
|
||||
<file>icons/32x32/media-playlist-shuffle.png</file>
|
||||
<file>icons/32x32/media-play.png</file>
|
||||
<file>icons/32x32/media-rewind.png</file>
|
||||
<file>icons/32x32/media-stop.png</file>
|
||||
<file>icons/32x32/nvidia.png</file>
|
||||
<file>icons/32x32/play2.png</file>
|
||||
<file>icons/32x32/pulseaudio.png</file>
|
||||
<file>icons/32x32/realtek.png</file>
|
||||
<file>icons/32x32/search.png</file>
|
||||
<file>icons/32x32/soundcard.png</file>
|
||||
<file>icons/32x32/speaker.png</file>
|
||||
<file>icons/32x32/star-grey.png</file>
|
||||
<file>icons/32x32/star.png</file>
|
||||
<file>icons/32x32/strawberry.png</file>
|
||||
<file>icons/32x32/strawberry.svg</file>
|
||||
<file>icons/32x32/tools-wizard.png</file>
|
||||
<file>icons/32x32/view-choose.png</file>
|
||||
<file>icons/32x32/view-fullscreen.png</file>
|
||||
<file>icons/32x32/view-media-lyrics.png</file>
|
||||
<file>icons/32x32/view-media-playlist.png</file>
|
||||
<file>icons/32x32/view-media-visualization.png</file>
|
||||
<file>icons/32x32/view-refresh.png</file>
|
||||
<file>icons/32x32/vinyl.png</file>
|
||||
<file>icons/32x32/vlc.png</file>
|
||||
<file>icons/32x32/xine.png</file>
|
||||
<file>icons/32x32/zoom-in.png</file>
|
||||
<file>icons/32x32/zoom-out.png</file>
|
||||
<file>icons/32x32/tidal.png</file>
|
||||
<file>icons/32x32/deezer.png</file>
|
||||
<file>icons/22x22/albums.png</file>
|
||||
<file>icons/22x22/alsa.png</file>
|
||||
<file>icons/22x22/application-exit.png</file>
|
||||
<file>icons/22x22/applications-internet.png</file>
|
||||
<file>icons/22x22/bluetooth.png</file>
|
||||
<file>icons/22x22/cdcase.png</file>
|
||||
<file>icons/22x22/cd.png</file>
|
||||
<file>icons/22x22/configure.png</file>
|
||||
<file>icons/22x22/device-ipod-nano.png</file>
|
||||
<file>icons/22x22/device-ipod.png</file>
|
||||
<file>icons/22x22/device-phone.png</file>
|
||||
<file>icons/22x22/device.png</file>
|
||||
<file>icons/22x22/device-usb-drive.png</file>
|
||||
<file>icons/22x22/device-usb-flash.png</file>
|
||||
<file>icons/22x22/dialog-error.png</file>
|
||||
<file>icons/22x22/dialog-information.png</file>
|
||||
<file>icons/22x22/dialog-ok-apply.png</file>
|
||||
<file>icons/22x22/dialog-password.png</file>
|
||||
<file>icons/22x22/dialog-warning.png</file>
|
||||
<file>icons/22x22/document-download.png</file>
|
||||
<file>icons/22x22/document-new.png</file>
|
||||
<file>icons/22x22/document-open-folder.png</file>
|
||||
<file>icons/22x22/document-open.png</file>
|
||||
<file>icons/22x22/document-save.png</file>
|
||||
<file>icons/22x22/document-search.png</file>
|
||||
<file>icons/22x22/download.png</file>
|
||||
<file>icons/22x22/edit-clear-list.png</file>
|
||||
<file>icons/22x22/edit-clear-locationbar-ltr.png</file>
|
||||
<file>icons/22x22/edit-copy.png</file>
|
||||
<file>icons/22x22/edit-delete.png</file>
|
||||
<file>icons/22x22/edit-find.png</file>
|
||||
<file>icons/22x22/edit-redo.png</file>
|
||||
<file>icons/22x22/edit-rename.png</file>
|
||||
<file>icons/22x22/edit-undo.png</file>
|
||||
<file>icons/22x22/electrocompaniet.png</file>
|
||||
<file>icons/22x22/equalizer.png</file>
|
||||
<file>icons/22x22/folder-new.png</file>
|
||||
<file>icons/22x22/folder.png</file>
|
||||
<file>icons/22x22/folder-sound.png</file>
|
||||
<file>icons/22x22/footsteps.png</file>
|
||||
<file>icons/22x22/go-down.png</file>
|
||||
<file>icons/22x22/go-home.png</file>
|
||||
<file>icons/22x22/go-jump.png</file>
|
||||
<file>icons/22x22/go-next.png</file>
|
||||
<file>icons/22x22/go-previous.png</file>
|
||||
<file>icons/22x22/go-up.png</file>
|
||||
<file>icons/22x22/gstreamer.png</file>
|
||||
<file>icons/22x22/headset.png</file>
|
||||
<file>icons/22x22/help-hint.png</file>
|
||||
<file>icons/22x22/intel.png</file>
|
||||
<file>icons/22x22/jack.png</file>
|
||||
<file>icons/22x22/keyboard.png</file>
|
||||
<file>icons/22x22/list-add.png</file>
|
||||
<file>icons/22x22/list-remove.png</file>
|
||||
<file>icons/22x22/mcintosh-player.png</file>
|
||||
<file>icons/22x22/mcintosh.png</file>
|
||||
<file>icons/22x22/mcintosh-text.png</file>
|
||||
<file>icons/22x22/media-eject.png</file>
|
||||
<file>icons/22x22/media-forward.png</file>
|
||||
<file>icons/22x22/media-pause.png</file>
|
||||
<file>icons/22x22/media-playlist-repeat.png</file>
|
||||
<file>icons/22x22/media-playlist-shuffle.png</file>
|
||||
<file>icons/22x22/media-play.png</file>
|
||||
<file>icons/22x22/media-rewind.png</file>
|
||||
<file>icons/22x22/media-stop.png</file>
|
||||
<file>icons/22x22/nvidia.png</file>
|
||||
<file>icons/22x22/play2.png</file>
|
||||
<file>icons/22x22/pulseaudio.png</file>
|
||||
<file>icons/22x22/realtek.png</file>
|
||||
<file>icons/22x22/search.png</file>
|
||||
<file>icons/22x22/soundcard.png</file>
|
||||
<file>icons/22x22/speaker.png</file>
|
||||
<file>icons/22x22/star-grey.png</file>
|
||||
<file>icons/22x22/star.png</file>
|
||||
<file>icons/22x22/strawberry.png</file>
|
||||
<file>icons/22x22/strawberry.svg</file>
|
||||
<file>icons/22x22/tools-wizard.png</file>
|
||||
<file>icons/22x22/view-choose.png</file>
|
||||
<file>icons/22x22/view-fullscreen.png</file>
|
||||
<file>icons/22x22/view-media-lyrics.png</file>
|
||||
<file>icons/22x22/view-media-playlist.png</file>
|
||||
<file>icons/22x22/view-media-visualization.png</file>
|
||||
<file>icons/22x22/view-refresh.png</file>
|
||||
<file>icons/22x22/vinyl.png</file>
|
||||
<file>icons/22x22/vlc.png</file>
|
||||
<file>icons/22x22/xine.png</file>
|
||||
<file>icons/22x22/zoom-in.png</file>
|
||||
<file>icons/22x22/zoom-out.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>icons/22x22/deezer.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 4.1 KiB |
|
@ -0,0 +1,41 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="http://www.strawbs.org/favicon.ico" rel="shortcut icon">
|
||||
<title>tr("Return to Strawberry")</title>
|
||||
|
||||
<style>
|
||||
#container {
|
||||
margin: 6em auto 0px auto;
|
||||
max-width: 400px;
|
||||
font-family: 'arial regular', arial, sans-serif;
|
||||
}
|
||||
|
||||
#container img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
#container h1 {
|
||||
margin: 0px 0px 0.75em 0px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#container p {
|
||||
margin-top: 0px;
|
||||
margin-left: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<h1>tr("Success!")</h1>
|
||||
<img src="data:image/png;base64,@IMAGE_DATA@"/>
|
||||
<p>tr("Please close your browser and return to Strawberry.")</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 28 KiB |
|
@ -1,7 +1,5 @@
|
|||
# Strawberry Music Player
|
||||
# Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
# This file was part of Clementine.
|
||||
# Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
#
|
||||
# Strawberry is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -59,6 +57,14 @@ if(HAVE_PHONON)
|
|||
include_directories(${PHONON_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBDEEZER)
|
||||
include_directories(${DEEZER_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBDZMEDIA)
|
||||
include_directories(${DZMEDIA_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||
include_directories(${TAGLIB_INCLUDE_DIRS})
|
||||
|
||||
|
@ -212,6 +218,7 @@ set(SOURCES
|
|||
settings/appearancesettingspage.cpp
|
||||
settings/notificationssettingspage.cpp
|
||||
settings/tidalsettingspage.cpp
|
||||
settings/deezersettingspage.cpp
|
||||
|
||||
dialogs/about.cpp
|
||||
dialogs/console.cpp
|
||||
|
@ -257,6 +264,7 @@ set(SOURCES
|
|||
internet/internetmodel.cpp
|
||||
internet/internetservice.cpp
|
||||
internet/internetplaylistitem.cpp
|
||||
internet/localredirectserver.cpp
|
||||
|
||||
tidal/tidalservice.cpp
|
||||
tidal/tidalsearch.cpp
|
||||
|
@ -266,6 +274,14 @@ set(SOURCES
|
|||
tidal/tidalsearchitemdelegate.cpp
|
||||
tidal/tidalurlhandler.cpp
|
||||
|
||||
deezer/deezerservice.cpp
|
||||
deezer/deezersearch.cpp
|
||||
deezer/deezersearchview.cpp
|
||||
deezer/deezersearchmodel.cpp
|
||||
deezer/deezersearchsortmodel.cpp
|
||||
deezer/deezersearchitemdelegate.cpp
|
||||
deezer/deezerurlhandler.cpp
|
||||
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
|
@ -379,6 +395,7 @@ set(HEADERS
|
|||
settings/appearancesettingspage.h
|
||||
settings/notificationssettingspage.h
|
||||
settings/tidalsettingspage.h
|
||||
settings/deezersettingspage.h
|
||||
|
||||
dialogs/about.h
|
||||
dialogs/errordialog.h
|
||||
|
@ -422,6 +439,7 @@ set(HEADERS
|
|||
internet/internetservice.h
|
||||
internet/internetmimedata.h
|
||||
internet/internetsongmimedata.h
|
||||
internet/localredirectserver.h
|
||||
|
||||
tidal/tidalservice.h
|
||||
tidal/tidalsearch.h
|
||||
|
@ -429,6 +447,12 @@ set(HEADERS
|
|||
tidal/tidalsearchmodel.h
|
||||
tidal/tidalurlhandler.h
|
||||
|
||||
deezer/deezerservice.h
|
||||
deezer/deezersearch.h
|
||||
deezer/deezersearchview.h
|
||||
deezer/deezersearchmodel.h
|
||||
deezer/deezerurlhandler.h
|
||||
|
||||
)
|
||||
|
||||
set(UI
|
||||
|
@ -465,6 +489,7 @@ set(UI
|
|||
settings/appearancesettingspage.ui
|
||||
settings/notificationssettingspage.ui
|
||||
settings/tidalsettingspage.ui
|
||||
settings/deezersettingspage.ui
|
||||
|
||||
equalizer/equalizer.ui
|
||||
equalizer/equalizerslider.ui
|
||||
|
@ -483,10 +508,11 @@ set(UI
|
|||
globalshortcuts/globalshortcutgrabber.ui
|
||||
|
||||
tidal/tidalsearchview.ui
|
||||
deezer/deezersearchview.ui
|
||||
|
||||
)
|
||||
|
||||
set(RESOURCES ../data/data.qrc)
|
||||
set(RESOURCES ../data/data.qrc ../data/icons.qrc)
|
||||
set(OTHER_SOURCES)
|
||||
|
||||
option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
|
||||
|
@ -524,6 +550,12 @@ optional_source(HAVE_PHONON
|
|||
HEADERS engine/phononengine.h
|
||||
)
|
||||
|
||||
# Deezer
|
||||
optional_source(HAVE_DEEZER
|
||||
SOURCES engine/deezerengine.cpp
|
||||
HEADERS engine/deezerengine.h
|
||||
)
|
||||
|
||||
# Lastfm
|
||||
optional_source(HAVE_LIBLASTFM
|
||||
SOURCES
|
||||
|
@ -906,6 +938,14 @@ if(HAVE_PHONON)
|
|||
target_link_libraries(strawberry_lib ${PHONON_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_DEEZER)
|
||||
target_link_libraries(strawberry_lib ${LIBDEEZER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_DZMEDIA)
|
||||
target_link_libraries(strawberry_lib ${LIBDZMEDIA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBLASTFM)
|
||||
target_link_libraries(strawberry_lib ${LASTFM5_LIBRARIES})
|
||||
endif(HAVE_LIBLASTFM)
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Collection advanced grouping</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -358,6 +358,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
/* This file is part of Strawberry.
|
||||
|
||||
Strawberry is free software: you can 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.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H_IN
|
||||
#define CONFIG_H_IN
|
||||
|
@ -38,6 +41,7 @@
|
|||
#cmakedefine HAVE_SPARKLE
|
||||
#cmakedefine HAVE_CHROMAPRINT
|
||||
#cmakedefine HAVE_TAGLIB_DSFFILE
|
||||
#cmakedefine HAVE_DZMEDIA
|
||||
#cmakedefine IMOBILEDEVICE_USES_UDIDS
|
||||
#cmakedefine USE_INSTALL_PREFIX
|
||||
#cmakedefine USE_SYSTEM_SHA2
|
||||
|
@ -46,6 +50,7 @@
|
|||
#cmakedefine HAVE_VLC
|
||||
#cmakedefine HAVE_XINE
|
||||
#cmakedefine HAVE_PHONON
|
||||
#cmakedefine HAVE_DEEZER
|
||||
|
||||
#endif // CONFIG_H_IN
|
||||
|
||||
|
|
|
@ -562,6 +562,7 @@
|
|||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
|
||||
#include "internet/internetmodel.h"
|
||||
#include "tidal/tidalsearch.h"
|
||||
#include "deezer/deezersearch.h"
|
||||
|
||||
bool Application::kIsPortable = false;
|
||||
|
||||
|
@ -108,15 +109,16 @@ class ApplicationImpl {
|
|||
return loader;
|
||||
}),
|
||||
current_art_loader_([=]() { return new CurrentArtLoader(app, app); }),
|
||||
internet_model_([=]() { return new InternetModel(app, app); }),
|
||||
tidal_search_([=]() { return new TidalSearch(app, app); }),
|
||||
lyrics_providers_([=]() {
|
||||
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
||||
lyrics_providers->AddProvider(new AuddLyricsProvider(app));
|
||||
lyrics_providers->AddProvider(new APISeedsLyricsProvider(app));
|
||||
return lyrics_providers;
|
||||
})
|
||||
{ }
|
||||
lyrics_providers->AddProvider(new APISeedsLyricsProvider(app));
|
||||
return lyrics_providers;
|
||||
}),
|
||||
internet_model_([=]() { return new InternetModel(app, app); }),
|
||||
tidal_search_([=]() { return new TidalSearch(app, app); }),
|
||||
deezer_search_([=]() { return new DeezerSearch(app, app); })
|
||||
{}
|
||||
|
||||
Lazy<TagReaderClient> tag_reader_client_;
|
||||
Lazy<Database> database_;
|
||||
|
@ -133,9 +135,10 @@ class ApplicationImpl {
|
|||
Lazy<CoverProviders> cover_providers_;
|
||||
Lazy<AlbumCoverLoader> album_cover_loader_;
|
||||
Lazy<CurrentArtLoader> current_art_loader_;
|
||||
Lazy<LyricsProviders> lyrics_providers_;
|
||||
Lazy<InternetModel> internet_model_;
|
||||
Lazy<TidalSearch> tidal_search_;
|
||||
Lazy<LyricsProviders> lyrics_providers_;
|
||||
Lazy<DeezerSearch> deezer_search_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -181,73 +184,27 @@ void Application::MoveToThread(QObject *object, QThread *thread) {
|
|||
}
|
||||
|
||||
void Application::AddError(const QString& message) { emit ErrorAdded(message); }
|
||||
|
||||
void Application::ReloadSettings() { emit SettingsChanged(); }
|
||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
||||
|
||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
|
||||
emit SettingsDialogRequested(page);
|
||||
}
|
||||
|
||||
AlbumCoverLoader *Application::album_cover_loader() const {
|
||||
return p_->album_cover_loader_.get();
|
||||
}
|
||||
|
||||
TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_client_.get(); }
|
||||
Appearance *Application::appearance() const { return p_->appearance_.get(); }
|
||||
|
||||
CoverProviders *Application::cover_providers() const {
|
||||
return p_->cover_providers_.get();
|
||||
}
|
||||
|
||||
CurrentArtLoader *Application::current_art_loader() const {
|
||||
return p_->current_art_loader_.get();
|
||||
}
|
||||
|
||||
Database *Application::database() const { return p_->database_.get(); }
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
DeviceManager *Application::device_manager() const {
|
||||
return p_->device_manager_.get();
|
||||
}
|
||||
#endif
|
||||
|
||||
SCollection *Application::collection() const { return p_->collection_.get(); }
|
||||
|
||||
CollectionBackend *Application::collection_backend() const {
|
||||
return collection()->backend();
|
||||
}
|
||||
|
||||
CollectionModel *Application::collection_model() const { return collection()->model(); }
|
||||
|
||||
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
||||
Player *Application::player() const { return p_->player_.get(); }
|
||||
|
||||
PlaylistBackend *Application::playlist_backend() const {
|
||||
return p_->playlist_backend_.get();
|
||||
}
|
||||
|
||||
PlaylistManager *Application::playlist_manager() const {
|
||||
return p_->playlist_manager_.get();
|
||||
}
|
||||
|
||||
TagReaderClient *Application::tag_reader_client() const {
|
||||
return p_->tag_reader_client_.get();
|
||||
}
|
||||
|
||||
TaskManager *Application::task_manager() const {
|
||||
return p_->task_manager_.get();
|
||||
}
|
||||
|
||||
EngineDevice *Application::enginedevice() const {
|
||||
return p_->enginedevice_.get();
|
||||
}
|
||||
|
||||
InternetModel* Application::internet_model() const {
|
||||
return p_->internet_model_.get();
|
||||
}
|
||||
|
||||
TidalSearch* Application::tidal_search() const {
|
||||
return p_->tidal_search_.get();
|
||||
}
|
||||
|
||||
LyricsProviders *Application::lyrics_providers() const {
|
||||
return p_->lyrics_providers_.get();
|
||||
}
|
||||
EngineDevice *Application::enginedevice() const { return p_->enginedevice_.get(); }
|
||||
#ifndef Q_OS_WIN
|
||||
DeviceManager *Application::device_manager() const { return p_->device_manager_.get(); }
|
||||
#endif
|
||||
SCollection *Application::collection() const { return p_->collection_.get(); }
|
||||
CollectionBackend *Application::collection_backend() const { return collection()->backend(); }
|
||||
CollectionModel *Application::collection_model() const { return collection()->model(); }
|
||||
AlbumCoverLoader *Application::album_cover_loader() const { return p_->album_cover_loader_.get(); }
|
||||
CoverProviders *Application::cover_providers() const { return p_->cover_providers_.get(); }
|
||||
CurrentArtLoader *Application::current_art_loader() const { return p_->current_art_loader_.get(); }
|
||||
LyricsProviders *Application::lyrics_providers() const { return p_->lyrics_providers_.get(); }
|
||||
PlaylistBackend *Application::playlist_backend() const { return p_->playlist_backend_.get(); }
|
||||
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
||||
InternetModel *Application::internet_model() const { return p_->internet_model_.get(); }
|
||||
TidalSearch *Application::tidal_search() const { return p_->tidal_search_.get(); }
|
||||
DeezerSearch *Application::deezer_search() const { return p_->deezer_search_.get(); }
|
||||
|
|
|
@ -51,9 +51,10 @@ class DeviceManager;
|
|||
class CoverProviders;
|
||||
class AlbumCoverLoader;
|
||||
class CurrentArtLoader;
|
||||
class LyricsProviders;
|
||||
class InternetModel;
|
||||
class TidalSearch;
|
||||
class LyricsProviders;
|
||||
class DeezerSearch;
|
||||
|
||||
class Application : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -75,6 +76,8 @@ class Application : public QObject {
|
|||
#endif
|
||||
|
||||
SCollection *collection() const;
|
||||
CollectionBackend *collection_backend() const;
|
||||
CollectionModel *collection_model() const;
|
||||
|
||||
PlaylistBackend *playlist_backend() const;
|
||||
PlaylistManager *playlist_manager() const;
|
||||
|
@ -83,13 +86,11 @@ class Application : public QObject {
|
|||
AlbumCoverLoader *album_cover_loader() const;
|
||||
CurrentArtLoader *current_art_loader() const;
|
||||
|
||||
CollectionBackend *collection_backend() const;
|
||||
CollectionModel *collection_model() const;
|
||||
LyricsProviders *lyrics_providers() const;
|
||||
|
||||
InternetModel *internet_model() const;
|
||||
TidalSearch *tidal_search() const;
|
||||
|
||||
LyricsProviders *lyrics_providers() const;
|
||||
DeezerSearch *deezer_search() const;
|
||||
|
||||
void MoveToNewThread(QObject *object);
|
||||
void MoveToThread(QObject *object, QThread *thread);
|
||||
|
|
|
@ -200,6 +200,7 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
// Resources
|
||||
Q_INIT_RESOURCE(data);
|
||||
Q_INIT_RESOURCE(icons);
|
||||
|
||||
Application app;
|
||||
|
||||
|
|
|
@ -134,7 +134,11 @@
|
|||
#include "settings/playlistsettingspage.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
|
||||
#include "tidal/tidalsearchview.h"
|
||||
#include "deezer/deezersearchview.h"
|
||||
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||
# include "musicbrainz/tagfetcher.h"
|
||||
|
@ -200,6 +204,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
return manager;
|
||||
}),
|
||||
tidal_search_view_(new TidalSearchView(app_, this)),
|
||||
deezer_search_view_(new DeezerSearchView(app_, this)),
|
||||
playlist_menu_(new QMenu(this)),
|
||||
playlist_add_to_another_(nullptr),
|
||||
playlistitem_actions_separator_(nullptr),
|
||||
|
@ -243,7 +248,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
ui_->volume->setValue(volume);
|
||||
VolumeChanged(volume);
|
||||
|
||||
// Initialise the tidal search widget
|
||||
// Initialise the search widget
|
||||
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
||||
|
||||
// Add tabs to the fancy tab widget
|
||||
|
@ -255,6 +260,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
ui_->tabs->addTab(device_view_, IconLoader::Load("device"), tr("Devices"));
|
||||
#endif
|
||||
ui_->tabs->addTab(tidal_search_view_, IconLoader::Load("tidal"), tr("Tidal", "Tidal"));
|
||||
ui_->tabs->addTab(deezer_search_view_, IconLoader::Load("deezer"), tr("Deezer", "Deezer"));
|
||||
//ui_->tabs->AddSpacer();
|
||||
|
||||
// Add the playing widget to the fancy tab widget
|
||||
|
@ -515,6 +521,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
|
||||
// Tidal
|
||||
connect(tidal_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
// Deezer
|
||||
connect(deezer_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
|
||||
// Playlist menu
|
||||
playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay()));
|
||||
|
@ -710,12 +718,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
|
||||
ReloadSettings();
|
||||
|
||||
// Tidal search shortcut
|
||||
QAction *tidal_search_action = new QAction(this);
|
||||
tidal_search_action->setShortcuts(QList<QKeySequence>() << QKeySequence("Ctrl+F") << QKeySequence("Ctrl+L"));
|
||||
addAction(tidal_search_action);
|
||||
connect(tidal_search_action, SIGNAL(triggered()), SLOT(FocusTidalSearchField()));
|
||||
|
||||
// Reload pretty OSD to avoid issues with fonts
|
||||
osd_->ReloadPrettyOSDSettings();
|
||||
|
||||
|
@ -809,6 +811,7 @@ void MainWindow::ReloadAllSettings() {
|
|||
collection_view_->ReloadSettings();
|
||||
ui_->playlist->view()->ReloadSettings();
|
||||
tidal_search_view_->ReloadSettings();
|
||||
deezer_search_view_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
|
@ -2311,39 +2314,6 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::FocusTidalSearchField() {
|
||||
ui_->tabs->setCurrentWidget(tidal_search_view_);
|
||||
tidal_search_view_->FocusSearchField();
|
||||
}
|
||||
|
||||
void MainWindow::DoTidalSearch(const QString& query) {
|
||||
FocusTidalSearchField();
|
||||
tidal_search_view_->StartSearch(query);
|
||||
}
|
||||
|
||||
void MainWindow::SearchForArtist() {
|
||||
|
||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(playlist_menu_index_.row()));
|
||||
Song song = item->Metadata();
|
||||
if (!song.albumartist().isEmpty()) {
|
||||
DoTidalSearch(song.albumartist().simplified());
|
||||
}
|
||||
else if (!song.artist().isEmpty()) {
|
||||
DoTidalSearch(song.artist().simplified());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::SearchForAlbum() {
|
||||
|
||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(playlist_menu_index_.row()));
|
||||
Song song = item->Metadata();
|
||||
if (!song.album().isEmpty()) {
|
||||
DoTidalSearch(song.album().simplified());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::LoadCoverFromFile() {
|
||||
album_cover_choice_controller_->LoadCoverFromFile(&song_);
|
||||
}
|
||||
|
@ -2402,4 +2372,3 @@ void MainWindow::GetCoverAutomatically() {
|
|||
if (search) album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ class TranscodeDialog;
|
|||
class Ui_MainWindow;
|
||||
class Windows7ThumbBar;
|
||||
class TidalSearchView;
|
||||
class DeezerSearchView;
|
||||
|
||||
class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
Q_OBJECT
|
||||
|
@ -273,11 +274,6 @@ signals:
|
|||
|
||||
void ShowConsole();
|
||||
|
||||
void FocusTidalSearchField();
|
||||
void DoTidalSearch(const QString& query);
|
||||
void SearchForArtist();
|
||||
void SearchForAlbum();
|
||||
|
||||
void LoadCoverFromFile();
|
||||
void SaveCoverToFile();
|
||||
void LoadCoverFromURL();
|
||||
|
@ -342,6 +338,7 @@ signals:
|
|||
#endif
|
||||
|
||||
TidalSearchView *tidal_search_view_;
|
||||
DeezerSearchView *deezer_search_view_;
|
||||
|
||||
QAction *collection_show_all_;
|
||||
QAction *collection_show_duplicates_;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Strawberry Music Player</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
|
@ -553,7 +553,7 @@
|
|||
</action>
|
||||
<action name="action_about_strawberry">
|
||||
<property name="icon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -782,6 +782,7 @@
|
|||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
#endif
|
||||
|
||||
#include "tidal/tidalsearch.h"
|
||||
#include "deezer/deezersearch.h"
|
||||
|
||||
void RegisterMetaTypes() {
|
||||
|
||||
|
@ -118,4 +119,7 @@ void RegisterMetaTypes() {
|
|||
qRegisterMetaType<TidalSearch::ResultList>("TidalSearch::ResultList");
|
||||
qRegisterMetaType<TidalSearch::Result>("TidalSearch::Result");
|
||||
|
||||
qRegisterMetaType<DeezerSearch::ResultList>("DeezerSearch::ResultList");
|
||||
qRegisterMetaType<DeezerSearch::Result>("DeezerSearch::Result");
|
||||
|
||||
}
|
||||
|
|
|
@ -59,6 +59,9 @@
|
|||
#ifdef HAVE_VLC
|
||||
# include "engine/vlcengine.h"
|
||||
#endif
|
||||
#ifdef HAVE_DEEZER
|
||||
# include "engine/deezerengine.h"
|
||||
#endif
|
||||
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "playlist/playlist.h"
|
||||
|
@ -70,6 +73,8 @@
|
|||
#include "settings/backendsettingspage.h"
|
||||
#include "settings/behavioursettingspage.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
|
@ -103,7 +108,7 @@ Player::~Player() {
|
|||
|
||||
void Player::CreateEngine(Engine::EngineType enginetype) {
|
||||
|
||||
Engine::EngineType use_enginetype = Engine::None;
|
||||
Engine::EngineType use_enginetype(Engine::None);
|
||||
|
||||
for (int i = 0 ; use_enginetype == Engine::None ; i++) {
|
||||
switch(enginetype) {
|
||||
|
@ -131,6 +136,15 @@ void Player::CreateEngine(Engine::EngineType enginetype) {
|
|||
use_enginetype=Engine::Phonon;
|
||||
engine_.reset(new PhononEngine(app_->task_manager()));
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_DEEZER
|
||||
case Engine::Deezer:{
|
||||
use_enginetype=Engine::Deezer;
|
||||
DeezerEngine *deezerengine = new DeezerEngine(app_->task_manager());
|
||||
connect(this, SIGNAL(Authenticated()), deezerengine, SLOT(LoadAccessToken()));
|
||||
engine_.reset(deezerengine);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
if (i > 0) { qFatal("No engine available!"); }
|
||||
|
@ -144,7 +158,7 @@ void Player::CreateEngine(Engine::EngineType enginetype) {
|
|||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
s.setValue("engine", EngineName(use_enginetype));
|
||||
s.setValue("output", engine_->DefaultOutput());
|
||||
s.setValue("device", QVariant(""));
|
||||
s.setValue("device", QVariant());
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
|
@ -499,7 +513,7 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
|
|||
current_item_ = app_->playlist_manager()->active()->current_item();
|
||||
const QUrl url = current_item_->Url();
|
||||
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
|
||||
// It's already loading
|
||||
if (url == loading_async_) return;
|
||||
|
||||
|
@ -761,3 +775,7 @@ void Player::UrlHandlerDestroyed(QObject *object) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
void Player::HandleAuthentication() {
|
||||
emit Authenticated();
|
||||
}
|
||||
|
|
|
@ -114,6 +114,9 @@ class PlayerInterface : public QObject {
|
|||
|
||||
// The toggle parameter is true when user requests to toggle visibility for Pretty OSD
|
||||
void ForceShowOSD(Song, bool toogle);
|
||||
|
||||
void Authenticated();
|
||||
|
||||
};
|
||||
|
||||
class Player : public PlayerInterface {
|
||||
|
@ -176,6 +179,8 @@ class Player : public PlayerInterface {
|
|||
void Play();
|
||||
void ShowOSD();
|
||||
void TogglePrettyOSD();
|
||||
|
||||
void HandleAuthentication();
|
||||
|
||||
private slots:
|
||||
void EngineStateChanged(Engine::State);
|
||||
|
|
|
@ -298,7 +298,7 @@ uint Song::mtime() const { return d->mtime_; }
|
|||
uint Song::ctime() const { return d->ctime_; }
|
||||
int Song::filesize() const { return d->filesize_; }
|
||||
Song::FileType Song::filetype() const { return d->filetype_; }
|
||||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal; }
|
||||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Deezer; }
|
||||
bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
|
||||
bool Song::is_collection_song() const {
|
||||
return !is_cdda() && !is_stream() && id() != -1;
|
||||
|
@ -384,6 +384,7 @@ QString Song::TextForSource(Source source) {
|
|||
case Song::Source_Device: return QObject::tr("Device");
|
||||
case Song::Source_Stream: return QObject::tr("Stream");
|
||||
case Song::Source_Tidal: return QObject::tr("Tidal");
|
||||
case Song::Source_Deezer: return QObject::tr("Deezer");
|
||||
default: return QObject::tr("Unknown");
|
||||
}
|
||||
|
||||
|
@ -398,6 +399,7 @@ QIcon Song::IconForSource(Source source) {
|
|||
case Song::Source_Device: return IconLoader::Load("device");
|
||||
case Song::Source_Stream: return IconLoader::Load("applications-internet");
|
||||
case Song::Source_Tidal: return IconLoader::Load("tidal");
|
||||
case Song::Source_Deezer: return IconLoader::Load("deezer");
|
||||
default: return IconLoader::Load("edit-delete");
|
||||
}
|
||||
|
||||
|
|
|
@ -98,6 +98,7 @@ class Song {
|
|||
Source_Device = 4,
|
||||
Source_Stream = 5,
|
||||
Source_Tidal = 6,
|
||||
Source_Deezer = 7,
|
||||
};
|
||||
|
||||
enum FileType {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string>Export covers</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -202,6 +202,7 @@
|
|||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Cover Manager</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
|
@ -297,6 +297,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Load cover from URL</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -79,6 +79,7 @@
|
|||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QStringBuilder>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QIcon>
|
||||
#include <QPainter>
|
||||
#include <QTimerEvent>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "internet/internetsongmimedata.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezerservice.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
const int DeezerSearch::kDelayedSearchTimeoutMs = 200;
|
||||
const int DeezerSearch::kMaxResultsPerEmission = 2000;
|
||||
const int DeezerSearch::kArtHeight = 32;
|
||||
|
||||
DeezerSearch::DeezerSearch(Application *app, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
service_(app->internet_model()->Service<DeezerService>()),
|
||||
name_("Deezer"),
|
||||
id_("deezer"),
|
||||
icon_(IconLoader::Load("deezer")),
|
||||
searches_next_id_(1),
|
||||
art_searches_next_id_(1) {
|
||||
|
||||
cover_loader_options_.desired_height_ = kArtHeight;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||
connect(this, SIGNAL(SearchAsyncSig(int, QString, DeezerSettingsPage::SearchBy)), this, SLOT(DoSearchAsync(int, QString, DeezerSettingsPage::SearchBy)));
|
||||
connect(this, SIGNAL(ResultsAvailable(int, DeezerSearch::ResultList)), SLOT(ResultsAvailableSlot(int, DeezerSearch::ResultList)));
|
||||
connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage)));
|
||||
connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString)));
|
||||
connect(service_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximumSlot(int)));
|
||||
connect(service_, SIGNAL(UpdateProgress(int)), SLOT(UpdateProgressSlot(int)));
|
||||
connect(service_, SIGNAL(SearchResults(int, SongList)), SLOT(SearchDone(int, SongList)));
|
||||
connect(service_, SIGNAL(SearchError(int, QString)), SLOT(HandleError(int, QString)));
|
||||
|
||||
icon_as_image_ = QImage(icon_.pixmap(48, 48).toImage());
|
||||
|
||||
}
|
||||
|
||||
DeezerSearch::~DeezerSearch() {}
|
||||
|
||||
QStringList DeezerSearch::TokenizeQuery(const QString &query) {
|
||||
|
||||
QStringList tokens(query.split(QRegExp("\\s+")));
|
||||
|
||||
for (QStringList::iterator it = tokens.begin(); it != tokens.end(); ++it) {
|
||||
(*it).remove('(');
|
||||
(*it).remove(')');
|
||||
(*it).remove('"');
|
||||
|
||||
const int colon = (*it).indexOf(":");
|
||||
if (colon != -1) {
|
||||
(*it).remove(0, colon + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
|
||||
}
|
||||
|
||||
bool DeezerSearch::Matches(const QStringList &tokens, const QString &string) {
|
||||
|
||||
for (const QString &token : tokens) {
|
||||
if (!string.contains(token, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
int DeezerSearch::SearchAsync(const QString &query, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
const int id = searches_next_id_++;
|
||||
|
||||
emit SearchAsyncSig(id, query, searchby);
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::SearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
const int service_id = service_->Search(query, searchby);
|
||||
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::DoSearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
int timer_id = startTimer(kDelayedSearchTimeoutMs);
|
||||
delayed_searches_[timer_id].id_ = id;
|
||||
delayed_searches_[timer_id].query_ = query;
|
||||
delayed_searches_[timer_id].searchby_ = searchby;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::SearchDone(int service_id, const SongList &songs) {
|
||||
|
||||
// Map back to the original id.
|
||||
const PendingState state = pending_searches_.take(service_id);
|
||||
const int search_id = state.orig_id_;
|
||||
|
||||
ResultList ret;
|
||||
for (const Song &song : songs) {
|
||||
Result result;
|
||||
result.metadata_ = song;
|
||||
ret << result;
|
||||
}
|
||||
|
||||
emit ResultsAvailable(search_id, ret);
|
||||
MaybeSearchFinished(search_id);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::HandleError(const int id, const QString error) {
|
||||
|
||||
emit SearchError(id, error);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::MaybeSearchFinished(int id) {
|
||||
|
||||
if (pending_searches_.keys(PendingState(id, QStringList())).isEmpty()) {
|
||||
emit SearchFinished(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::CancelSearch(int id) {
|
||||
QMap<int, DelayedSearch>::iterator it;
|
||||
for (it = delayed_searches_.begin(); it != delayed_searches_.end(); ++it) {
|
||||
if (it.value().id_ == id) {
|
||||
killTimer(it.key());
|
||||
delayed_searches_.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
service_->CancelSearch();
|
||||
}
|
||||
|
||||
void DeezerSearch::timerEvent(QTimerEvent *e) {
|
||||
QMap<int, DelayedSearch>::iterator it = delayed_searches_.find(e->timerId());
|
||||
if (it != delayed_searches_.end()) {
|
||||
SearchAsync(it.value().id_, it.value().query_, it.value().searchby_);
|
||||
delayed_searches_.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::timerEvent(e);
|
||||
}
|
||||
|
||||
void DeezerSearch::ResultsAvailableSlot(int id, DeezerSearch::ResultList results) {
|
||||
|
||||
if (results.isEmpty()) return;
|
||||
|
||||
// Limit the number of results that are used from each emission.
|
||||
if (results.count() > kMaxResultsPerEmission) {
|
||||
DeezerSearch::ResultList::iterator begin = results.begin();
|
||||
std::advance(begin, kMaxResultsPerEmission);
|
||||
results.erase(begin, results.end());
|
||||
}
|
||||
|
||||
// Load cached pixmaps into the results
|
||||
for (DeezerSearch::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
|
||||
it->pixmap_cache_key_ = PixmapCacheKey(*it);
|
||||
}
|
||||
|
||||
emit AddResults(id, results);
|
||||
|
||||
}
|
||||
|
||||
QString DeezerSearch::PixmapCacheKey(const DeezerSearch::Result &result) const {
|
||||
return "deezer:" % result.metadata_.url().toString();
|
||||
}
|
||||
|
||||
bool DeezerSearch::FindCachedPixmap(const DeezerSearch::Result &result, QPixmap *pixmap) const {
|
||||
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
||||
}
|
||||
|
||||
int DeezerSearch::LoadArtAsync(const DeezerSearch::Result &result) {
|
||||
|
||||
const int id = art_searches_next_id_++;
|
||||
|
||||
pending_art_searches_[id] = result.pixmap_cache_key_;
|
||||
|
||||
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_);
|
||||
cover_loader_tasks_[loader_id] = id;
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::ArtLoadedSlot(int id, const QImage &image) {
|
||||
HandleLoadedArt(id, image);
|
||||
}
|
||||
|
||||
void DeezerSearch::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
if (!cover_loader_tasks_.contains(id)) return;
|
||||
int orig_id = cover_loader_tasks_.take(id);
|
||||
|
||||
HandleLoadedArt(orig_id, image);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::HandleLoadedArt(int id, const QImage &image) {
|
||||
|
||||
const QString key = pending_art_searches_.take(id);
|
||||
|
||||
QPixmap pixmap = QPixmap::fromImage(image);
|
||||
pixmap_cache_.insert(key, pixmap);
|
||||
|
||||
emit ArtLoaded(id, pixmap);
|
||||
|
||||
}
|
||||
|
||||
QImage DeezerSearch::ScaleAndPad(const QImage &image) {
|
||||
|
||||
if (image.isNull()) return QImage();
|
||||
|
||||
const QSize target_size = QSize(kArtHeight, kArtHeight);
|
||||
|
||||
if (image.size() == target_size) return image;
|
||||
|
||||
// Scale the image down
|
||||
QImage copy;
|
||||
copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
// Pad the image to kHeight x kHeight
|
||||
if (copy.size() == target_size) return copy;
|
||||
|
||||
QImage padded_image(kArtHeight, kArtHeight, QImage::Format_ARGB32);
|
||||
padded_image.fill(0);
|
||||
|
||||
QPainter p(&padded_image);
|
||||
p.drawImage((kArtHeight - copy.width()) / 2, (kArtHeight - copy.height()) / 2, copy);
|
||||
p.end();
|
||||
|
||||
return padded_image;
|
||||
|
||||
}
|
||||
|
||||
MimeData *DeezerSearch::LoadTracks(const ResultList &results) {
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResultList results_copy;
|
||||
for (const Result &result : results) {
|
||||
results_copy << result;
|
||||
}
|
||||
|
||||
SongList songs;
|
||||
for (const Result &result : results) {
|
||||
songs << result.metadata_;
|
||||
}
|
||||
|
||||
InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_);
|
||||
internet_song_mime_data->songs = songs;
|
||||
MimeData *mime_data = internet_song_mime_data;
|
||||
|
||||
QList<QUrl> urls;
|
||||
for (const Result &result : results) {
|
||||
urls << result.metadata_.url();
|
||||
}
|
||||
mime_data->setUrls(urls);
|
||||
|
||||
return mime_data;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::UpdateStatusSlot(QString text) {
|
||||
emit UpdateStatus(text);
|
||||
}
|
||||
|
||||
void DeezerSearch::ProgressSetMaximumSlot(int max) {
|
||||
emit ProgressSetMaximum(max);
|
||||
}
|
||||
|
||||
void DeezerSearch::UpdateProgressSlot(int progress) {
|
||||
emit UpdateProgress(progress);
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCH_H
|
||||
#define DEEZERSEARCH_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QFuture>
|
||||
#include <QIcon>
|
||||
#include <QMetaType>
|
||||
#include <QPixmapCache>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
class Application;
|
||||
class MimeData;
|
||||
class AlbumCoverLoader;
|
||||
class InternetService;
|
||||
class DeezerService;
|
||||
|
||||
class DeezerSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerSearch(Application *app, QObject *parent = nullptr);
|
||||
~DeezerSearch();
|
||||
|
||||
struct Result {
|
||||
Song metadata_;
|
||||
QString pixmap_cache_key_;
|
||||
};
|
||||
typedef QList<Result> ResultList;
|
||||
|
||||
static const int kDelayedSearchTimeoutMs;
|
||||
static const int kMaxResultsPerEmission;
|
||||
|
||||
Application *application() const { return app_; }
|
||||
DeezerService *service() const { return service_; }
|
||||
|
||||
int SearchAsync(const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
int LoadArtAsync(const DeezerSearch::Result &result);
|
||||
|
||||
void CancelSearch(int id);
|
||||
void CancelArt(int id);
|
||||
|
||||
// Loads tracks for results that were previously emitted by ResultsAvailable.
|
||||
// The implementation creates a SongMimeData with one Song for each Result.
|
||||
MimeData *LoadTracks(const ResultList &results);
|
||||
|
||||
signals:
|
||||
void SearchAsyncSig(int id, const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void ResultsAvailable(int id, const DeezerSearch::ResultList &results);
|
||||
void AddResults(int id, const DeezerSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString error);
|
||||
void SearchFinished(int id);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int progress);
|
||||
void UpdateProgress(int max);
|
||||
|
||||
void ArtLoaded(int id, const QPixmap &pixmap);
|
||||
void ArtLoaded(int id, const QImage &image);
|
||||
|
||||
protected:
|
||||
|
||||
struct PendingState {
|
||||
PendingState() : orig_id_(-1) {}
|
||||
PendingState(int orig_id, QStringList tokens)
|
||||
: orig_id_(orig_id), tokens_(tokens) {}
|
||||
int orig_id_;
|
||||
QStringList tokens_;
|
||||
|
||||
bool operator<(const PendingState &b) const {
|
||||
return orig_id_ < b.orig_id_;
|
||||
}
|
||||
|
||||
bool operator==(const PendingState &b) const {
|
||||
return orig_id_ == b.orig_id_;
|
||||
}
|
||||
};
|
||||
|
||||
void timerEvent(QTimerEvent *e);
|
||||
|
||||
// These functions treat queries in the same way as LibraryQuery.
|
||||
// They're useful for figuring out whether you got a result because it matched in the song title or the artist/album name.
|
||||
static QStringList TokenizeQuery(const QString &query);
|
||||
static bool Matches(const QStringList &tokens, const QString &string);
|
||||
|
||||
private slots:
|
||||
void DoSearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void SearchDone(int id, const SongList &songs);
|
||||
void HandleError(const int id, const QString error);
|
||||
void ResultsAvailableSlot(int id, DeezerSearch::ResultList results);
|
||||
|
||||
void ArtLoadedSlot(int id, const QImage &image);
|
||||
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||
|
||||
void UpdateStatusSlot(QString text);
|
||||
void ProgressSetMaximumSlot(int progress);
|
||||
void UpdateProgressSlot(int max);
|
||||
|
||||
private:
|
||||
void SearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void HandleLoadedArt(int id, const QImage &image);
|
||||
bool FindCachedPixmap(const DeezerSearch::Result &result, QPixmap *pixmap) const;
|
||||
QString PixmapCacheKey(const DeezerSearch::Result &result) const;
|
||||
void MaybeSearchFinished(int id);
|
||||
void ShowConfig() {}
|
||||
static QImage ScaleAndPad(const QImage &image);
|
||||
|
||||
private:
|
||||
struct DelayedSearch {
|
||||
int id_;
|
||||
QString query_;
|
||||
DeezerSettingsPage::SearchBy searchby_;
|
||||
};
|
||||
|
||||
static const int kArtHeight;
|
||||
|
||||
Application *app_;
|
||||
DeezerService *service_;
|
||||
Song::Source source_;
|
||||
QString name_;
|
||||
QString id_;
|
||||
QIcon icon_;
|
||||
QImage icon_as_image_;
|
||||
int searches_next_id_;
|
||||
int art_searches_next_id_;
|
||||
|
||||
QMap<int, DelayedSearch> delayed_searches_;
|
||||
QMap<int, QString> pending_art_searches_;
|
||||
QPixmapCache pixmap_cache_;
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
QMap<quint64, int> cover_loader_tasks_;
|
||||
|
||||
QMap<int, PendingState> pending_searches_;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(DeezerSearch::Result)
|
||||
Q_DECLARE_METATYPE(DeezerSearch::ResultList)
|
||||
|
||||
#endif // DEEZERSEARCH_H
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "deezersearchitemdelegate.h"
|
||||
#include "deezersearchview.h"
|
||||
|
||||
DeezerSearchItemDelegate::DeezerSearchItemDelegate(DeezerSearchView* view)
|
||||
: CollectionItemDelegate(view), view_(view) {}
|
||||
|
||||
void DeezerSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
// Tell the view we painted this item so it can lazy load some art.
|
||||
const_cast<DeezerSearchView*>(view_)->LazyLoadArt(index);
|
||||
|
||||
CollectionItemDelegate::paint(painter, option, index);
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCHITEMDELEGATE_H
|
||||
#define DEEZERSEARCHITEMDELEGATE_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "collection/collectionview.h"
|
||||
|
||||
class DeezerSearchView;
|
||||
|
||||
class DeezerSearchItemDelegate : public CollectionItemDelegate {
|
||||
public:
|
||||
DeezerSearchItemDelegate(DeezerSearchView *view);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
|
||||
private:
|
||||
DeezerSearchView* view_;
|
||||
};
|
||||
|
||||
#endif // DEEZERSEARCHITEMDELEGATE_H
|
|
@ -0,0 +1,319 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardItem>
|
||||
#include <QStandardItemModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
#include <QMimeData>
|
||||
|
||||
#include "core/mimedata.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezersearchmodel.h"
|
||||
|
||||
DeezerSearchModel::DeezerSearchModel(DeezerSearch *engine, QObject *parent)
|
||||
: QStandardItemModel(parent),
|
||||
engine_(engine),
|
||||
proxy_(nullptr),
|
||||
use_pretty_covers_(true),
|
||||
artist_icon_(IconLoader::Load("folder-sound")) {
|
||||
|
||||
group_by_[0] = CollectionModel::GroupBy_Artist;
|
||||
group_by_[1] = CollectionModel::GroupBy_Album;
|
||||
group_by_[2] = CollectionModel::GroupBy_None;
|
||||
|
||||
QIcon nocover = IconLoader::Load("cdcase");
|
||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
//no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
album_icon_ = no_cover_icon_;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchModel::AddResults(const DeezerSearch::ResultList &results) {
|
||||
|
||||
int sort_index = 0;
|
||||
|
||||
for (const DeezerSearch::Result &result : results) {
|
||||
QStandardItem *parent = invisibleRootItem();
|
||||
|
||||
// Find (or create) the container nodes for this result if we can.
|
||||
ContainerKey key;
|
||||
key.provider_index_ = sort_index;
|
||||
parent = BuildContainers(result.metadata_, parent, &key);
|
||||
|
||||
// Create the item
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setText(result.metadata_.TitleWithCompilationArtist());
|
||||
item->setData(QVariant::fromValue(result), Role_Result);
|
||||
item->setData(sort_index, Role_ProviderIndex);
|
||||
|
||||
parent->appendRow(item);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QStandardItem *DeezerSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, int level) {
|
||||
|
||||
if (level >= 3) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
bool has_artist_icon = false;
|
||||
bool has_album_icon = false;
|
||||
QString display_text;
|
||||
QString sort_text;
|
||||
int unique_tag = -1;
|
||||
int year = 0;
|
||||
|
||||
switch (group_by_[level]) {
|
||||
case CollectionModel::GroupBy_Artist:
|
||||
if (s.is_compilation()) {
|
||||
display_text = tr("Various artists");
|
||||
sort_text = "aaaaaa";
|
||||
}
|
||||
else {
|
||||
display_text = CollectionModel::TextOrUnknown(s.artist());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.artist());
|
||||
}
|
||||
has_artist_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_YearAlbum:
|
||||
year = qMax(0, s.year());
|
||||
display_text = CollectionModel::PrettyYearAlbum(year, s.album());
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + s.album();
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_OriginalYearAlbum:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
display_text = CollectionModel::PrettyYearAlbum(year, s.album());
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + s.album();
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Year:
|
||||
year = qMax(0, s.year());
|
||||
display_text = QString::number(year);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_OriginalYear:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
display_text = QString::number(year);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Composer:
|
||||
display_text = s.composer();
|
||||
case CollectionModel::GroupBy_Performer:
|
||||
display_text = s.performer();
|
||||
case CollectionModel::GroupBy_Disc:
|
||||
display_text = s.disc();
|
||||
case CollectionModel::GroupBy_Grouping:
|
||||
display_text = s.grouping();
|
||||
case CollectionModel::GroupBy_Genre:
|
||||
if (display_text.isNull()) display_text = s.genre();
|
||||
case CollectionModel::GroupBy_Album:
|
||||
unique_tag = s.album_id();
|
||||
if (display_text.isNull()) {
|
||||
display_text = s.album();
|
||||
}
|
||||
// fallthrough
|
||||
case CollectionModel::GroupBy_AlbumArtist:
|
||||
if (display_text.isNull()) display_text = s.effective_albumartist();
|
||||
display_text = CollectionModel::TextOrUnknown(display_text);
|
||||
sort_text = CollectionModel::SortTextForArtist(display_text);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_FileType:
|
||||
display_text = s.TextForFiletype();
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Bitrate:
|
||||
display_text = QString(s.bitrate(), 1);
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Samplerate:
|
||||
display_text = QString(s.samplerate(), 1);
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Bitdepth:
|
||||
display_text = QString(s.bitdepth(), 1);
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_None:
|
||||
return parent;
|
||||
}
|
||||
|
||||
// Find a container for this level
|
||||
key->group_[level] = display_text + QString::number(unique_tag);
|
||||
QStandardItem *container = containers_[*key];
|
||||
if (!container) {
|
||||
container = new QStandardItem(display_text);
|
||||
container->setData(key->provider_index_, Role_ProviderIndex);
|
||||
container->setData(sort_text, CollectionModel::Role_SortText);
|
||||
container->setData(group_by_[level], CollectionModel::Role_ContainerType);
|
||||
|
||||
if (has_artist_icon) {
|
||||
container->setIcon(artist_icon_);
|
||||
}
|
||||
else if (has_album_icon) {
|
||||
if (use_pretty_covers_) {
|
||||
container->setData(no_cover_icon_, Qt::DecorationRole);
|
||||
}
|
||||
else {
|
||||
container->setIcon(album_icon_);
|
||||
}
|
||||
}
|
||||
|
||||
parent->appendRow(container);
|
||||
containers_[*key] = container;
|
||||
}
|
||||
|
||||
// Create the container for the next level.
|
||||
return BuildContainers(s, container, key, level + 1);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchModel::Clear() {
|
||||
containers_.clear();
|
||||
clear();
|
||||
}
|
||||
|
||||
DeezerSearch::ResultList DeezerSearchModel::GetChildResults(const QModelIndexList &indexes) const {
|
||||
|
||||
QList<QStandardItem*> items;
|
||||
for (const QModelIndex &index : indexes) {
|
||||
items << itemFromIndex(index);
|
||||
}
|
||||
return GetChildResults(items);
|
||||
|
||||
}
|
||||
|
||||
DeezerSearch::ResultList DeezerSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
|
||||
|
||||
DeezerSearch::ResultList results;
|
||||
QSet<const QStandardItem*> visited;
|
||||
|
||||
for (QStandardItem *item : items) {
|
||||
GetChildResults(item, &results, &visited);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchModel::GetChildResults(const QStandardItem *item, DeezerSearch::ResultList *results, QSet<const QStandardItem*> *visited) const {
|
||||
|
||||
if (visited->contains(item)) {
|
||||
return;
|
||||
}
|
||||
visited->insert(item);
|
||||
|
||||
// Does this item have children?
|
||||
if (item->rowCount()) {
|
||||
const QModelIndex parent_proxy_index = proxy_->mapFromSource(item->index());
|
||||
|
||||
// Yes - visit all the children, but do so through the proxy so we get them
|
||||
// in the right order.
|
||||
for (int i = 0; i < item->rowCount(); ++i) {
|
||||
const QModelIndex proxy_index = parent_proxy_index.child(i, 0);
|
||||
const QModelIndex index = proxy_->mapToSource(proxy_index);
|
||||
GetChildResults(itemFromIndex(index), results, visited);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No - maybe it's a song, add its result if valid
|
||||
QVariant result = item->data(Role_Result);
|
||||
if (result.isValid()) {
|
||||
results->append(result.value<DeezerSearch::Result>());
|
||||
}
|
||||
else {
|
||||
// Maybe it's a provider then?
|
||||
bool is_provider;
|
||||
const int sort_index = item->data(Role_ProviderIndex).toInt(&is_provider);
|
||||
if (is_provider) {
|
||||
// Go through all the items (through the proxy to keep them ordered) and add the ones belonging to this provider to our list
|
||||
for (int i = 0; i < proxy_->rowCount(invisibleRootItem()->index()); ++i) {
|
||||
QModelIndex child_index = proxy_->index(i, 0, invisibleRootItem()->index());
|
||||
const QStandardItem *child_item = itemFromIndex(proxy_->mapToSource(child_index));
|
||||
if (child_item->data(Role_ProviderIndex).toInt() == sort_index) {
|
||||
GetChildResults(child_item, results, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QMimeData *DeezerSearchModel::mimeData(const QModelIndexList &indexes) const {
|
||||
return engine_->LoadTracks(GetChildResults(indexes));
|
||||
}
|
||||
|
||||
namespace {
|
||||
void GatherResults(const QStandardItem *parent, DeezerSearch::ResultList *results) {
|
||||
|
||||
QVariant result_variant = parent->data(DeezerSearchModel::Role_Result);
|
||||
if (result_variant.isValid()) {
|
||||
DeezerSearch::Result result = result_variant.value<DeezerSearch::Result>();
|
||||
(*results).append(result);
|
||||
}
|
||||
|
||||
for (int i = 0; i < parent->rowCount(); ++i) {
|
||||
GatherResults(parent->child(i), results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeezerSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now) {
|
||||
|
||||
const CollectionModel::Grouping old_group_by = group_by_;
|
||||
group_by_ = grouping;
|
||||
|
||||
if (regroup_now && group_by_ != old_group_by) {
|
||||
// Walk the tree gathering the results we have already
|
||||
DeezerSearch::ResultList results;
|
||||
GatherResults(invisibleRootItem(), &results);
|
||||
|
||||
// Reset the model and re-add all the results using the new grouping.
|
||||
Clear();
|
||||
AddResults(results);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCHMODEL_H
|
||||
#define DEEZERSEARCHMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMimeData>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "deezersearch.h"
|
||||
|
||||
class DeezerSearchModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerSearchModel(DeezerSearch *engine, QObject *parent = nullptr);
|
||||
|
||||
enum Role {
|
||||
Role_Result = CollectionModel::LastRole,
|
||||
Role_LazyLoadingArt,
|
||||
Role_ProviderIndex,
|
||||
LastRole
|
||||
};
|
||||
|
||||
struct ContainerKey {
|
||||
int provider_index_;
|
||||
QString group_[3];
|
||||
};
|
||||
|
||||
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
|
||||
void set_use_pretty_covers(bool pretty) { use_pretty_covers_ = pretty; }
|
||||
void SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now);
|
||||
|
||||
void Clear();
|
||||
|
||||
DeezerSearch::ResultList GetChildResults(const QModelIndexList &indexes) const;
|
||||
DeezerSearch::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
|
||||
public slots:
|
||||
void AddResults(const DeezerSearch::ResultList &results);
|
||||
|
||||
private:
|
||||
QStandardItem *BuildContainers(const Song &metadata, QStandardItem *parent, ContainerKey *key, int level = 0);
|
||||
void GetChildResults(const QStandardItem *item, DeezerSearch::ResultList *results, QSet<const QStandardItem*> *visited) const;
|
||||
|
||||
private:
|
||||
DeezerSearch *engine_;
|
||||
QSortFilterProxyModel *proxy_;
|
||||
bool use_pretty_covers_;
|
||||
QIcon artist_icon_;
|
||||
QPixmap no_cover_icon_;
|
||||
QIcon album_icon_;
|
||||
CollectionModel::Grouping group_by_;
|
||||
QMap<ContainerKey, QStandardItem*> containers_;
|
||||
|
||||
};
|
||||
|
||||
inline uint qHash(const DeezerSearchModel::ContainerKey &key) {
|
||||
return qHash(key.provider_index_) ^ qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]);
|
||||
}
|
||||
|
||||
inline bool operator<(const DeezerSearchModel::ContainerKey &left, const DeezerSearchModel::ContainerKey &right) {
|
||||
#define CMP(field) \
|
||||
if (left.field < right.field) return true; \
|
||||
if (left.field > right.field) return false
|
||||
|
||||
CMP(provider_index_);
|
||||
CMP(group_[0]);
|
||||
CMP(group_[1]);
|
||||
CMP(group_[2]);
|
||||
return false;
|
||||
|
||||
#undef CMP
|
||||
}
|
||||
|
||||
#endif // DEEZERSEARCHMODEL_H
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "deezersearchmodel.h"
|
||||
#include "deezersearchsortmodel.h"
|
||||
|
||||
DeezerSearchSortModel::DeezerSearchSortModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent) {}
|
||||
|
||||
bool DeezerSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
||||
// Compare the provider sort index first.
|
||||
const int index_left = left.data(DeezerSearchModel::Role_ProviderIndex).toInt();
|
||||
const int index_right = right.data(DeezerSearchModel::Role_ProviderIndex).toInt();
|
||||
if (index_left < index_right) return true;
|
||||
if (index_left > index_right) return false;
|
||||
|
||||
// Dividers always go first
|
||||
if (left.data(CollectionModel::Role_IsDivider).toBool()) return true;
|
||||
if (right.data(CollectionModel::Role_IsDivider).toBool()) return false;
|
||||
|
||||
// Containers go before songs if they're at the same level
|
||||
const bool left_is_container = left.data(CollectionModel::Role_ContainerType).isValid();
|
||||
const bool right_is_container = right.data(CollectionModel::Role_ContainerType).isValid();
|
||||
if (left_is_container && !right_is_container) return true;
|
||||
if (right_is_container && !left_is_container) return false;
|
||||
|
||||
// Containers get sorted on their sort text.
|
||||
if (left_is_container) {
|
||||
return QString::localeAwareCompare(left.data(CollectionModel::Role_SortText).toString(), right.data(CollectionModel::Role_SortText).toString()) < 0;
|
||||
}
|
||||
|
||||
// Otherwise we're comparing songs. Sort by disc, track, then title.
|
||||
const DeezerSearch::Result r1 = left.data(DeezerSearchModel::Role_Result).value<DeezerSearch::Result>();
|
||||
const DeezerSearch::Result r2 = right.data(DeezerSearchModel::Role_Result).value<DeezerSearch::Result>();
|
||||
|
||||
#define CompareInt(field) \
|
||||
if (r1.metadata_.field() < r2.metadata_.field()) return true; \
|
||||
if (r1.metadata_.field() > r2.metadata_.field()) return false
|
||||
|
||||
int ret = 0;
|
||||
|
||||
#define CompareString(field) \
|
||||
ret = QString::localeAwareCompare(r1.metadata_.field(), r2.metadata_.field()); \
|
||||
if (ret < 0) return true; \
|
||||
if (ret > 0) return false
|
||||
|
||||
CompareInt(disc);
|
||||
CompareInt(track);
|
||||
CompareString(title);
|
||||
|
||||
return false;
|
||||
|
||||
#undef CompareInt
|
||||
#undef CompareString
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCHSORTMODEL_H
|
||||
#define DEEZERSEARCHSORTMODEL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class DeezerSearchSortModel : public QSortFilterProxyModel {
|
||||
public:
|
||||
DeezerSearchSortModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||
};
|
||||
|
||||
#endif // DEEZERSEARCHSORTMODEL_H
|
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QPixmap>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItem>
|
||||
#include <QSettings>
|
||||
#include <QAction>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "internet/internetsongmimedata.h"
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/groupbydialog.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezersearchitemdelegate.h"
|
||||
#include "deezersearchmodel.h"
|
||||
#include "deezersearchsortmodel.h"
|
||||
#include "deezersearchview.h"
|
||||
#include "ui_deezersearchview.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::swap;
|
||||
|
||||
const int DeezerSearchView::kSwapModelsTimeoutMsec = 250;
|
||||
|
||||
DeezerSearchView::DeezerSearchView(Application *app, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
app_(app),
|
||||
engine_(app_->deezer_search()),
|
||||
ui_(new Ui_DeezerSearchView),
|
||||
context_menu_(nullptr),
|
||||
last_search_id_(0),
|
||||
front_model_(new DeezerSearchModel(engine_, this)),
|
||||
back_model_(new DeezerSearchModel(engine_, this)),
|
||||
current_model_(front_model_),
|
||||
front_proxy_(new DeezerSearchSortModel(this)),
|
||||
back_proxy_(new DeezerSearchSortModel(this)),
|
||||
current_proxy_(front_proxy_),
|
||||
swap_models_timer_(new QTimer(this)),
|
||||
search_icon_(IconLoader::Load("search")),
|
||||
warning_icon_(IconLoader::Load("dialog-warning")),
|
||||
error_(false) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->progressbar->hide();
|
||||
ui_->progressbar->reset();
|
||||
|
||||
front_model_->set_proxy(front_proxy_);
|
||||
back_model_->set_proxy(back_proxy_);
|
||||
|
||||
ui_->search->installEventFilter(this);
|
||||
ui_->results_stack->installEventFilter(this);
|
||||
|
||||
ui_->settings->setIcon(IconLoader::Load("configure"));
|
||||
|
||||
// Must be a queued connection to ensure the DeezerSearch handles it first.
|
||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()), Qt::QueuedConnection);
|
||||
|
||||
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString)));
|
||||
connect(ui_->results, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*)));
|
||||
connect(ui_->results, SIGNAL(FocusOnFilterSignal(QKeyEvent*)), SLOT(FocusOnFilter(QKeyEvent*)));
|
||||
|
||||
// Set the appearance of the results list
|
||||
ui_->results->setItemDelegate(new DeezerSearchItemDelegate(this));
|
||||
ui_->results->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
ui_->results->setStyleSheet("QTreeView::item{padding-top:1px;}");
|
||||
|
||||
// Show the help page initially
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
ui_->help_frame->setBackgroundRole(QPalette::Base);
|
||||
|
||||
// Set the colour of the help text to the disabled window text colour
|
||||
QPalette help_palette = ui_->label_helptext->palette();
|
||||
const QColor help_color = help_palette.color(QPalette::Disabled, QPalette::WindowText);
|
||||
help_palette.setColor(QPalette::Normal, QPalette::WindowText, help_color);
|
||||
help_palette.setColor(QPalette::Inactive, QPalette::WindowText, help_color);
|
||||
ui_->label_helptext->setPalette(help_palette);
|
||||
|
||||
// Make it bold
|
||||
QFont help_font = ui_->label_helptext->font();
|
||||
help_font.setBold(true);
|
||||
ui_->label_helptext->setFont(help_font);
|
||||
|
||||
// Set up the sorting proxy model
|
||||
front_proxy_->setSourceModel(front_model_);
|
||||
front_proxy_->setDynamicSortFilter(true);
|
||||
front_proxy_->sort(0);
|
||||
|
||||
back_proxy_->setSourceModel(back_model_);
|
||||
back_proxy_->setDynamicSortFilter(true);
|
||||
back_proxy_->sort(0);
|
||||
|
||||
swap_models_timer_->setSingleShot(true);
|
||||
swap_models_timer_->setInterval(kSwapModelsTimeoutMsec);
|
||||
connect(swap_models_timer_, SIGNAL(timeout()), SLOT(SwapModels()));
|
||||
|
||||
// Add actions to the settings menu
|
||||
group_by_actions_ = CollectionFilterWidget::CreateGroupByActions(this);
|
||||
QMenu *settings_menu = new QMenu(this);
|
||||
settings_menu->addActions(group_by_actions_->actions());
|
||||
settings_menu->addSeparator();
|
||||
settings_menu->addAction(IconLoader::Load("configure"), tr("Configure Deezer..."), this, SLOT(OpenSettingsDialog()));
|
||||
ui_->settings->setMenu(settings_menu);
|
||||
|
||||
connect(ui_->radiobutton_searchbyalbums, SIGNAL(clicked(bool)), SLOT(SearchByAlbumsClicked(bool)));
|
||||
connect(ui_->radiobutton_searchbysongs, SIGNAL(clicked(bool)), SLOT(SearchBySongsClicked(bool)));
|
||||
|
||||
connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
|
||||
|
||||
// These have to be queued connections because they may get emitted before our call to Search() (or whatever) returns and we add the ID to the map.
|
||||
|
||||
connect(engine_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatus(QString)));
|
||||
connect(engine_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximum(int)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(UpdateProgress(int)), SLOT(UpdateProgress(int)), Qt::QueuedConnection);
|
||||
|
||||
connect(engine_, SIGNAL(AddResults(int, DeezerSearch::ResultList)), SLOT(AddResults(int, DeezerSearch::ResultList)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(SearchError(int, QString)), SLOT(SearchError(int, QString)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(ArtLoaded(int, QPixmap)), SLOT(ArtLoaded(int, QPixmap)), Qt::QueuedConnection);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
DeezerSearchView::~DeezerSearchView() { delete ui_; }
|
||||
|
||||
void DeezerSearchView::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
|
||||
// Collection settings
|
||||
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
const bool pretty = s.value("pretty_covers", true).toBool();
|
||||
front_model_->set_use_pretty_covers(pretty);
|
||||
back_model_->set_use_pretty_covers(pretty);
|
||||
s.endGroup();
|
||||
|
||||
// Deezer search settings
|
||||
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
searchby_ = DeezerSettingsPage::SearchBy(s.value("searchby", int(DeezerSettingsPage::SearchBy_Songs)).toInt());
|
||||
switch (searchby_) {
|
||||
case DeezerSettingsPage::SearchBy_Songs:
|
||||
ui_->radiobutton_searchbysongs->setChecked(true);
|
||||
break;
|
||||
case DeezerSettingsPage::SearchBy_Albums:
|
||||
ui_->radiobutton_searchbyalbums->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
SetGroupBy(CollectionModel::Grouping(
|
||||
CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_Artist)).toInt()),
|
||||
CollectionModel::GroupBy(s.value("group_by2", int(CollectionModel::GroupBy_Album)).toInt()),
|
||||
CollectionModel::GroupBy(s.value("group_by3", int(CollectionModel::GroupBy_None)).toInt())));
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::StartSearch(const QString &query) {
|
||||
|
||||
ui_->search->setText(query);
|
||||
TextEdited(query);
|
||||
|
||||
// Swap models immediately
|
||||
swap_models_timer_->stop();
|
||||
SwapModels();
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::TextEdited(const QString &text) {
|
||||
|
||||
const QString trimmed(text.trimmed());
|
||||
|
||||
error_ = false;
|
||||
|
||||
// Add results to the back model, switch models after some delay.
|
||||
back_model_->Clear();
|
||||
current_model_ = back_model_;
|
||||
current_proxy_ = back_proxy_;
|
||||
swap_models_timer_->start();
|
||||
|
||||
// Cancel the last search (if any) and start the new one.
|
||||
engine_->CancelSearch(last_search_id_);
|
||||
// If text query is empty, don't start a new search
|
||||
if (trimmed.isEmpty()) {
|
||||
last_search_id_ = -1;
|
||||
ui_->label_helptext->setText("Enter search terms above to find music");
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->hide();
|
||||
ui_->progressbar->reset();
|
||||
}
|
||||
else {
|
||||
ui_->progressbar->reset();
|
||||
last_search_id_ = engine_->SearchAsync(trimmed, searchby_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::AddResults(int id, const DeezerSearch::ResultList &results) {
|
||||
if (id != last_search_id_) return;
|
||||
if (results.isEmpty()) return;
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->reset();
|
||||
ui_->progressbar->hide();
|
||||
current_model_->AddResults(results);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchError(const int id, const QString error) {
|
||||
error_ = true;
|
||||
ui_->label_helptext->setText(error);
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->reset();
|
||||
ui_->progressbar->hide();
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SwapModels() {
|
||||
|
||||
art_requests_.clear();
|
||||
|
||||
std::swap(front_model_, back_model_);
|
||||
std::swap(front_proxy_, back_proxy_);
|
||||
|
||||
ui_->results->setModel(front_proxy_);
|
||||
|
||||
if (ui_->search->text().trimmed().isEmpty() || error_) {
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
}
|
||||
else {
|
||||
ui_->results_stack->setCurrentWidget(ui_->results_page);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
||||
|
||||
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Already loading art for this item?
|
||||
if (proxy_index.data(DeezerSearchModel::Role_LazyLoadingArt).isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Should we even load art at all?
|
||||
if (!app_->collection_model()->use_pretty_covers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this an album?
|
||||
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
||||
if (container_type != CollectionModel::GroupBy_Album &&
|
||||
container_type != CollectionModel::GroupBy_AlbumArtist &&
|
||||
container_type != CollectionModel::GroupBy_YearAlbum &&
|
||||
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the item as loading art
|
||||
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
||||
QStandardItem *item = front_model_->itemFromIndex(source_index);
|
||||
item->setData(true, DeezerSearchModel::Role_LazyLoadingArt);
|
||||
|
||||
// Walk down the item's children until we find a track
|
||||
while (item->rowCount()) {
|
||||
item = item->child(0);
|
||||
}
|
||||
|
||||
// Get the track's Result
|
||||
const DeezerSearch::Result result = item->data(DeezerSearchModel::Role_Result).value<DeezerSearch::Result>();
|
||||
|
||||
// Load the art.
|
||||
int id = engine_->LoadArtAsync(result);
|
||||
art_requests_[id] = source_index;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::ArtLoaded(int id, const QPixmap &pixmap) {
|
||||
|
||||
if (!art_requests_.contains(id)) return;
|
||||
QModelIndex index = art_requests_.take(id);
|
||||
|
||||
if (!pixmap.isNull()) {
|
||||
front_model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MimeData *DeezerSearchView::SelectedMimeData() {
|
||||
|
||||
if (!ui_->results->selectionModel()) return nullptr;
|
||||
|
||||
// Get all selected model indexes
|
||||
QModelIndexList indexes = ui_->results->selectionModel()->selectedRows();
|
||||
if (indexes.isEmpty()) {
|
||||
// There's nothing selected - take the first thing in the model that isn't a divider.
|
||||
for (int i = 0; i < front_proxy_->rowCount(); ++i) {
|
||||
QModelIndex index = front_proxy_->index(i, 0);
|
||||
if (!index.data(CollectionModel::Role_IsDivider).toBool()) {
|
||||
indexes << index;
|
||||
ui_->results->setCurrentIndex(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still got nothing? Give up.
|
||||
if (indexes.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get items for these indexes
|
||||
QList<QStandardItem*> items;
|
||||
for (const QModelIndex &index : indexes) {
|
||||
items << (front_model_->itemFromIndex(front_proxy_->mapToSource(index)));
|
||||
}
|
||||
|
||||
// Get a MimeData for these items
|
||||
return engine_->LoadTracks(front_model_->GetChildResults(items));
|
||||
|
||||
}
|
||||
|
||||
bool DeezerSearchView::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == ui_->search && event->type() == QEvent::KeyRelease) {
|
||||
if (SearchKeyEvent(static_cast<QKeyEvent*>(event))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (object == ui_->results_stack && event->type() == QEvent::ContextMenu) {
|
||||
if (ResultsContextMenuEvent(static_cast<QContextMenuEvent*>(event))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(object, event);
|
||||
|
||||
}
|
||||
|
||||
bool DeezerSearchView::SearchKeyEvent(QKeyEvent *event) {
|
||||
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Up:
|
||||
ui_->results->UpAndFocus();
|
||||
break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
ui_->results->DownAndFocus();
|
||||
break;
|
||||
|
||||
case Qt::Key_Escape:
|
||||
ui_->search->clear();
|
||||
break;
|
||||
|
||||
case Qt::Key_Return:
|
||||
TextEdited(ui_->search->text());
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
event->accept();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool DeezerSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
|
||||
|
||||
context_menu_ = new QMenu(this);
|
||||
context_actions_ << context_menu_->addAction( IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist()));
|
||||
context_actions_ << context_menu_->addAction( IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(LoadSelected()));
|
||||
context_actions_ << context_menu_->addAction( IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenSelectedInNewPlaylist()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddSelectedToPlaylistEnqueue()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
||||
if (ui_->results->selectionModel() && ui_->results->selectionModel()->selectedRows().length() == 1) {
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load("search"), tr("Search for this"), this, SLOT(SearchForThis()));
|
||||
}
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addMenu(tr("Group by"))->addActions(group_by_actions_->actions());
|
||||
context_menu_->addAction(IconLoader::Load("configure"), tr("Configure Deezer..."), this, SLOT(OpenSettingsDialog()));
|
||||
|
||||
const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection();
|
||||
|
||||
for (QAction *action : context_actions_) {
|
||||
action->setEnabled(enable_context_actions);
|
||||
}
|
||||
|
||||
context_menu_->popup(event->globalPos());
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::AddSelectedToPlaylist() {
|
||||
emit AddToPlaylist(SelectedMimeData());
|
||||
}
|
||||
|
||||
void DeezerSearchView::LoadSelected() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
data->clear_first_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void DeezerSearchView::AddSelectedToPlaylistEnqueue() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
data->enqueue_now_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void DeezerSearchView::OpenSelectedInNewPlaylist() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
data->open_in_new_playlist_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchForThis() {
|
||||
StartSearch(ui_->results->selectionModel()->selectedRows().first().data().toString());
|
||||
}
|
||||
|
||||
void DeezerSearchView::showEvent(QShowEvent *e) {
|
||||
QWidget::showEvent(e);
|
||||
FocusSearchField();
|
||||
}
|
||||
|
||||
void DeezerSearchView::FocusSearchField() {
|
||||
ui_->search->setFocus();
|
||||
ui_->search->selectAll();
|
||||
}
|
||||
|
||||
void DeezerSearchView::hideEvent(QHideEvent *e) {
|
||||
QWidget::hideEvent(e);
|
||||
}
|
||||
|
||||
void DeezerSearchView::FocusOnFilter(QKeyEvent *event) {
|
||||
ui_->search->setFocus();
|
||||
QApplication::sendEvent(ui_->search, event);
|
||||
}
|
||||
|
||||
void DeezerSearchView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Deezer);
|
||||
}
|
||||
|
||||
void DeezerSearchView::GroupByClicked(QAction *action) {
|
||||
|
||||
if (action->property("group_by").isNull()) {
|
||||
if (!group_by_dialog_) {
|
||||
group_by_dialog_.reset(new GroupByDialog);
|
||||
connect(group_by_dialog_.data(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping)));
|
||||
}
|
||||
|
||||
group_by_dialog_->show();
|
||||
return;
|
||||
}
|
||||
|
||||
SetGroupBy(action->property("group_by").value<CollectionModel::Grouping>());
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
|
||||
|
||||
// Clear requests: changing "group by" on the models will cause all the items to be removed/added again,
|
||||
// so all the QModelIndex here will become invalid. New requests will be created for those
|
||||
// songs when they will be displayed again anyway (when DeezerSearchItemDelegate::paint will call LazyLoadArt)
|
||||
art_requests_.clear();
|
||||
// Update the models
|
||||
front_model_->SetGroupBy(g, true);
|
||||
back_model_->SetGroupBy(g, false);
|
||||
|
||||
// Save the setting
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.setValue("group_by1", int(g.first));
|
||||
s.setValue("group_by2", int(g.second));
|
||||
s.setValue("group_by3", int(g.third));
|
||||
s.endGroup();
|
||||
|
||||
// Make sure the correct action is checked.
|
||||
for (QAction *action : group_by_actions_->actions()) {
|
||||
if (action->property("group_by").isNull()) continue;
|
||||
|
||||
if (g == action->property("group_by").value<CollectionModel::Grouping>()) {
|
||||
action->setChecked(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the advanced action
|
||||
group_by_actions_->actions().last()->setChecked(true);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchBySongsClicked(bool checked) {
|
||||
SetSearchBy(DeezerSettingsPage::SearchBy_Songs);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchByAlbumsClicked(bool checked) {
|
||||
SetSearchBy(DeezerSettingsPage::SearchBy_Albums);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SetSearchBy(DeezerSettingsPage::SearchBy searchby) {
|
||||
searchby_ = searchby;
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.setValue("searchby", int(searchby));
|
||||
s.endGroup();
|
||||
TextEdited(ui_->search->text());
|
||||
}
|
||||
|
||||
void DeezerSearchView::UpdateStatus(QString text) {
|
||||
ui_->progressbar->show();
|
||||
ui_->label_status->setText(text);
|
||||
}
|
||||
|
||||
void DeezerSearchView::ProgressSetMaximum(int max) {
|
||||
ui_->progressbar->setMaximum(max);
|
||||
}
|
||||
|
||||
void DeezerSearchView::UpdateProgress(int progress) {
|
||||
ui_->progressbar->setValue(progress);
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCHVIEW_H
|
||||
#define DEEZERSEARCHVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QMap>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QMimeData>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "deezersearch.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
class Application;
|
||||
class GroupByDialog;
|
||||
class DeezerSearchModel;
|
||||
class Ui_DeezerSearchView;
|
||||
|
||||
class DeezerSearchView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerSearchView(Application *app, QWidget *parent = nullptr);
|
||||
~DeezerSearchView();
|
||||
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
|
||||
void LazyLoadArt(const QModelIndex &index);
|
||||
|
||||
void showEvent(QShowEvent *e);
|
||||
void hideEvent(QHideEvent *e);
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
void StartSearch(const QString &query);
|
||||
void FocusSearchField();
|
||||
void OpenSettingsDialog();
|
||||
|
||||
signals:
|
||||
void AddToPlaylist(QMimeData *data);
|
||||
|
||||
private slots:
|
||||
void SwapModels();
|
||||
void TextEdited(const QString &text);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int progress);
|
||||
void UpdateProgress(int max);
|
||||
void AddResults(int id, const DeezerSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString error);
|
||||
void ArtLoaded(int id, const QPixmap &pixmap);
|
||||
|
||||
void FocusOnFilter(QKeyEvent *event);
|
||||
|
||||
void AddSelectedToPlaylist();
|
||||
void LoadSelected();
|
||||
void OpenSelectedInNewPlaylist();
|
||||
void AddSelectedToPlaylistEnqueue();
|
||||
|
||||
void SearchForThis();
|
||||
|
||||
void SearchBySongsClicked(bool);
|
||||
void SearchByAlbumsClicked(bool);
|
||||
void GroupByClicked(QAction *action);
|
||||
void SetSearchBy(DeezerSettingsPage::SearchBy searchby);
|
||||
void SetGroupBy(const CollectionModel::Grouping &g);
|
||||
|
||||
private:
|
||||
MimeData *SelectedMimeData();
|
||||
|
||||
bool SearchKeyEvent(QKeyEvent *event);
|
||||
bool ResultsContextMenuEvent(QContextMenuEvent *event);
|
||||
|
||||
Application *app_;
|
||||
DeezerSearch *engine_;
|
||||
Ui_DeezerSearchView *ui_;
|
||||
QScopedPointer<GroupByDialog> group_by_dialog_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
QList<QAction*> context_actions_;
|
||||
QActionGroup *group_by_actions_;
|
||||
|
||||
int last_search_id_;
|
||||
|
||||
// Like graphics APIs have a front buffer and a back buffer, there's a front model and a back model
|
||||
// The front model is the one that's shown in the UI and the back model is the one that lies in wait.
|
||||
// current_model_ will point to either the front or the back model.
|
||||
DeezerSearchModel *front_model_;
|
||||
DeezerSearchModel *back_model_;
|
||||
DeezerSearchModel *current_model_;
|
||||
|
||||
QSortFilterProxyModel *front_proxy_;
|
||||
QSortFilterProxyModel *back_proxy_;
|
||||
QSortFilterProxyModel *current_proxy_;
|
||||
|
||||
QMap<int, QModelIndex> art_requests_;
|
||||
|
||||
QTimer *swap_models_timer_;
|
||||
|
||||
QIcon search_icon_;
|
||||
QIcon warning_icon_;
|
||||
|
||||
DeezerSettingsPage::SearchBy searchby_;
|
||||
bool error_;
|
||||
|
||||
};
|
||||
|
||||
#endif // DEEZERSEARCHVIEW_H
|
|
@ -0,0 +1,283 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DeezerSearchView</class>
|
||||
<widget class="QWidget" name="DeezerSearchView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>660</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_search" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_top" stretch="0,0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_search">
|
||||
<item>
|
||||
<widget class="QSearchField" name="search" native="true">
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Search for anything</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="settings">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_searchby">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_searchby">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search by</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_searchbyalbums">
|
||||
<property name="text">
|
||||
<string>a&lbums</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_searchbysongs">
|
||||
<property name="text">
|
||||
<string>son&gs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_progress">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_status">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressbar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="results_stack">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="results_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AutoExpandingTreeView" name="results">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DragOnly</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="help_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="help_frame">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="help_frame_contents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>398</width>
|
||||
<height>502</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>64</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_helptext">
|
||||
<property name="text">
|
||||
<string>Enter search terms above to find music</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_helptext">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>3rdparty/qocoa/qsearchfield.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AutoExpandingTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>widgets/autoexpandingtreeview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,823 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
# include <dzmedia.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QMenu>
|
||||
#include <QDesktopServices>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mergedproxymodel.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/localredirectserver.h"
|
||||
#include "deezerservice.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezerurlhandler.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
const Song::Source DeezerService::kSource = Song::Source_Deezer;
|
||||
const char *DeezerService::kApiUrl = "https://api.deezer.com";
|
||||
const char *DeezerService::kOAuthUrl = "https://connect.deezer.com/oauth/auth.php";
|
||||
const char *DeezerService::kOAuthAccessTokenUrl = "https://connect.deezer.com/oauth/access_token.php";
|
||||
const char *DeezerService::kOAuthRedirectUrl = "https://oauth.strawbs.net";
|
||||
const int DeezerService::kAppID = 303684;
|
||||
const char *DeezerService::kSecretKey = "06911976010b9ddd7256769adf2b2e56";
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
|
||||
DeezerService::DeezerService(Application *app, InternetModel *parent)
|
||||
: InternetService(Song::Source_Deezer, "Deezer", "dzmedia", app, parent, parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
url_handler_(new DeezerUrlHandler(app, this)),
|
||||
#ifdef HAVE_DZMEDIA
|
||||
dzmedia_(new DZMedia(this)),
|
||||
#endif
|
||||
timer_searchdelay_(new QTimer(this)),
|
||||
searchdelay_(1500),
|
||||
albumssearchlimit_(1),
|
||||
songssearchlimit_(1),
|
||||
fetchalbums_(false),
|
||||
preview_(false),
|
||||
pending_search_id_(0),
|
||||
next_pending_search_id_(1),
|
||||
search_id_(0),
|
||||
albums_requested_(0),
|
||||
albums_received_(0)
|
||||
{
|
||||
|
||||
timer_searchdelay_->setSingleShot(true);
|
||||
connect(timer_searchdelay_, SIGNAL(timeout()), SLOT(StartSearch()));
|
||||
|
||||
connect(this, SIGNAL(Authenticated()), app->player(), SLOT(HandleAuthentication()));
|
||||
|
||||
app->player()->RegisterUrlHandler(url_handler_);
|
||||
|
||||
ReloadSettings();
|
||||
LoadAccessToken();
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
connect(dzmedia_, SIGNAL(StreamURLReceived(QUrl, QUrl, DZMedia::FileType)), this, SLOT(GetStreamURLFinished(QUrl, QUrl, DZMedia::FileType)));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
DeezerService::~DeezerService() {}
|
||||
|
||||
void DeezerService::ShowConfig() {
|
||||
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Deezer);
|
||||
}
|
||||
|
||||
void DeezerService::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
quality_ = s.value("quality", "FLAC").toString();
|
||||
searchdelay_ = s.value("searchdelay", 1500).toInt();
|
||||
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
||||
songssearchlimit_ = s.value("songssearchlimit", 100).toInt();
|
||||
fetchalbums_ = s.value("fetchalbums", false).toBool();
|
||||
coversize_ = s.value("coversize", "cover_big").toString();
|
||||
preview_ = s.value("preview", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::LoadAccessToken() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
if (s.contains("access_token") && s.contains("expiry_time")) {
|
||||
access_token_ = s.value("access_token").toString();
|
||||
expiry_time_ = s.value("expiry_time").toDateTime();
|
||||
}
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::Logout() {
|
||||
}
|
||||
|
||||
void DeezerService::StartAuthorisation() {
|
||||
|
||||
LocalRedirectServer *server = new LocalRedirectServer(this);
|
||||
server->Listen();
|
||||
|
||||
QUrl url = QUrl(kOAuthUrl);
|
||||
QUrlQuery url_query;
|
||||
//url_query.addQueryItem("response_type", "token");
|
||||
url_query.addQueryItem("response_type", "code");
|
||||
url_query.addQueryItem("app_id", QString::number(kAppID));
|
||||
QUrl redirect_url;
|
||||
QUrlQuery redirect_url_query;
|
||||
|
||||
const QString port = QString::number(server->url().port());
|
||||
|
||||
redirect_url = QUrl(kOAuthRedirectUrl);
|
||||
redirect_url_query.addQueryItem("port", port);
|
||||
redirect_url.setQuery(redirect_url_query);
|
||||
url_query.addQueryItem("redirect_uri", redirect_url.toString());
|
||||
url.setQuery(url_query);
|
||||
|
||||
NewClosure(server, SIGNAL(Finished()), this, &DeezerService::RedirectArrived, server, redirect_url);
|
||||
QDesktopServices::openUrl(url);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::RedirectArrived(LocalRedirectServer *server, QUrl url) {
|
||||
|
||||
server->deleteLater();
|
||||
QUrl request_url = server->request_url();
|
||||
RequestAccessToken(QUrlQuery(request_url).queryItemValue("code").toUtf8());
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::RequestAccessToken(const QByteArray &code) {
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
ArgList args = ArgList() << Arg("app_id", QString::number(kAppID))
|
||||
<< Arg("secret", kSecretKey)
|
||||
<< Arg("code", code);
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Arg &arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
QUrl url(kOAuthAccessTokenUrl);
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
QNetworkReply *reply = network_->post(request, url_query.toString(QUrl::FullyEncoded).toUtf8());
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(FetchAccessTokenFinished(QNetworkReply*)), reply);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::FetchAccessTokenFinished(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
return;
|
||||
}
|
||||
|
||||
forever {
|
||||
QByteArray line = reply->readLine();
|
||||
QString str(line);
|
||||
QStringList args = str.split("&");
|
||||
for (QString arg : args) {
|
||||
QStringList params = arg.split("=");
|
||||
if (params.count() < 2) continue;
|
||||
QString param1 = params.first();
|
||||
QString param2 = params[1];
|
||||
if (param1 == "access_token") access_token_ = param2;
|
||||
else if (param1 == "expires") SetExpiryTime(param2.toInt());
|
||||
}
|
||||
if (reply->atEnd()) break;
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.setValue("access_token", access_token_);
|
||||
s.setValue("expiry_time", expiry_time_);
|
||||
s.endGroup();
|
||||
|
||||
emit Authenticated();
|
||||
emit LoginSuccess();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::SetExpiryTime(int expires_in_seconds) {
|
||||
|
||||
// Set the expiry time with two minutes' grace.
|
||||
expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in_seconds - 120);
|
||||
qLog(Debug) << "Current oauth access token expires at:" << expiry_time_;
|
||||
|
||||
}
|
||||
|
||||
QNetworkReply *DeezerService::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms) {
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
ArgList args = ArgList() << Arg("access_token", access_token_)
|
||||
<< Arg("output", "json")
|
||||
<< params;
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Arg& arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
QUrl url(kApiUrl + QString("/") + ressource_name);
|
||||
url.setQuery(url_query);
|
||||
QNetworkRequest req(url);
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
|
||||
//qLog(Debug) << "Deezer: Sending request" << url;
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
QByteArray DeezerService::GetReplyData(QNetworkReply *reply) {
|
||||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error")) {
|
||||
QJsonValue json_value_error = json_obj["error"];
|
||||
if (json_value_error.isObject()) {
|
||||
QJsonObject json_error = json_value_error.toObject();
|
||||
int code = json_error["code"].toInt();
|
||||
if (code == 300) access_token_.clear();
|
||||
QString message = json_error["message"].toString();
|
||||
QString type = json_error["type"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
else { failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()); }
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
if (reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::ContentOperationNotPermittedError || reply->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||
// Session is probably expired
|
||||
Logout();
|
||||
Error(failure_reason);
|
||||
}
|
||||
else if (reply->error() == QNetworkReply::ContentNotFoundError) { // Ignore this error
|
||||
Error(failure_reason);
|
||||
}
|
||||
else { // Fail
|
||||
Error(failure_reason);
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
QJsonObject DeezerService::ExtractJsonObj(QByteArray &data) {
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
//qLog(Debug) << json_doc;
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
Error("Received empty Json document.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
//qLog(Debug) << json_obj;
|
||||
|
||||
return json_obj;
|
||||
|
||||
}
|
||||
|
||||
QJsonValue DeezerService::ExtractData(QByteArray &data) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) return QJsonObject();
|
||||
|
||||
if (json_obj.contains("error")) {
|
||||
QJsonValue json_value_error = json_obj["error"];
|
||||
if (!json_value_error.isObject()) {
|
||||
Error("Error missing object", json_obj);
|
||||
return QJsonValue();
|
||||
}
|
||||
QJsonObject json_error = json_value_error.toObject();
|
||||
int code = json_error["code"].toInt();
|
||||
if (code == 300) access_token_.clear();
|
||||
QString message = json_error["message"].toString();
|
||||
QString type = json_error["type"].toString();
|
||||
Error(QString("%1 (%2)").arg(message).arg(code));
|
||||
return QJsonValue();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("data") && !json_obj.contains("DATA")) {
|
||||
Error("Json reply is missing data.", json_obj);
|
||||
return QJsonValue();
|
||||
}
|
||||
|
||||
QJsonValue json_data;
|
||||
if (json_obj.contains("data")) json_data = json_obj["data"];
|
||||
else json_data = json_obj["DATA"];
|
||||
|
||||
return json_data;
|
||||
|
||||
}
|
||||
|
||||
int DeezerService::Search(const QString &text, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
pending_search_id_ = next_pending_search_id_;
|
||||
pending_search_text_ = text;
|
||||
pending_searchby_ = searchby;
|
||||
|
||||
next_pending_search_id_++;
|
||||
|
||||
if (text.isEmpty()) {
|
||||
timer_searchdelay_->stop();
|
||||
return pending_search_id_;
|
||||
}
|
||||
timer_searchdelay_->setInterval(searchdelay_);
|
||||
timer_searchdelay_->start();
|
||||
|
||||
return pending_search_id_;
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::StartSearch() {
|
||||
|
||||
if (access_token_.isEmpty()) {
|
||||
emit SearchError(pending_search_id_, "Not authenticated.");
|
||||
next_pending_search_id_ = 1;
|
||||
ShowConfig();
|
||||
return;
|
||||
}
|
||||
ClearSearch();
|
||||
search_id_ = pending_search_id_;
|
||||
search_text_ = pending_search_text_;
|
||||
|
||||
SendSearch();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::CancelSearch() {
|
||||
ClearSearch();
|
||||
}
|
||||
|
||||
void DeezerService::ClearSearch() {
|
||||
search_id_ = 0;
|
||||
search_text_.clear();
|
||||
search_error_.clear();
|
||||
albums_requested_ = 0;
|
||||
albums_received_ = 0;
|
||||
requests_album_.clear();
|
||||
requests_song_.clear();
|
||||
songs_.clear();
|
||||
}
|
||||
|
||||
void DeezerService::SendSearch() {
|
||||
|
||||
emit UpdateStatus("Searching...");
|
||||
|
||||
QList<Param> parameters;
|
||||
parameters << Param("q", search_text_);
|
||||
QString searchparam;
|
||||
switch (pending_searchby_) {
|
||||
case DeezerSettingsPage::SearchBy_Songs:
|
||||
searchparam = "search/track";
|
||||
parameters << Param("limit", QString::number(songssearchlimit_));
|
||||
break;
|
||||
case DeezerSettingsPage::SearchBy_Albums:
|
||||
default:
|
||||
searchparam = "search/album";
|
||||
parameters << Param("limit", QString::number(albumssearchlimit_));
|
||||
break;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = CreateRequest(searchparam, parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(SearchFinished(QNetworkReply*, int)), reply, search_id_);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::SearchFinished(QNetworkReply *reply, int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (id != search_id_) return;
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractData(data);
|
||||
if (!json_value.isArray()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray json_data = json_value.toArray();
|
||||
if (json_data.isEmpty()) {
|
||||
Error("No match.");
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
//qLog(Debug) << json_data;
|
||||
|
||||
for (const QJsonValue &value : json_data) {
|
||||
//qLog(Debug) << value;
|
||||
if (!value.isObject()) {
|
||||
Error("Invalid Json reply, data is not an object.", value);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
//qLog(Debug) << json_obj;
|
||||
|
||||
if (!json_obj.contains("id") || !json_obj.contains("type")) {
|
||||
Error("Invalid Json reply, item is missing ID or type.", json_obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
//int id = json_obj["id"].toInt();
|
||||
QString type = json_obj["type"].toString();
|
||||
|
||||
if (!json_obj.contains("artist")) {
|
||||
Error("Invalid Json reply, item missing artist.", json_obj);
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
if (!json_value_artist.isObject()) {
|
||||
Error("Invalid Json reply, item artist is not a object.", json_value_artist);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
|
||||
if (!json_artist.contains("name")) {
|
||||
Error("Invalid Json reply, artist data missing name.", json_artist);
|
||||
continue;
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
int album_id(0);
|
||||
QString album;
|
||||
QString cover;
|
||||
|
||||
if (type == "album") {
|
||||
album_id = json_obj["id"].toInt();
|
||||
album = json_obj["title"].toString();
|
||||
cover = json_obj[coversize_].toString();
|
||||
}
|
||||
else if (type == "track") {
|
||||
|
||||
if (!json_obj.contains("album")) {
|
||||
Error("Invalid Json reply, missing album data.", json_obj);
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_album = json_obj["album"];
|
||||
if (!json_value_album.isObject()) {
|
||||
Error("Invalid Json reply, album data is not an object.", json_value_album);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_album = json_value_album.toObject();
|
||||
if (!json_album.contains("id") || !json_album.contains("title")) {
|
||||
Error("Invalid Json reply, album data is missing ID or title.", json_album);
|
||||
continue;
|
||||
}
|
||||
album_id = json_album["id"].toInt();
|
||||
album = json_album["title"].toString();
|
||||
cover = json_album[coversize_].toString();
|
||||
if (!fetchalbums_) {
|
||||
Song song = ParseSong(album_id, album, cover, value);
|
||||
songs_ << song;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DeezerAlbumContext *album_ctx;
|
||||
if (requests_album_.contains(album_id)) {
|
||||
album_ctx = requests_album_.value(album_id);
|
||||
album_ctx->search_id = search_id_;
|
||||
continue;
|
||||
}
|
||||
album_ctx = CreateAlbum(album_id, artist, album, cover);
|
||||
GetAlbum(album_ctx);
|
||||
albums_requested_++;
|
||||
if (albums_requested_ >= albumssearchlimit_) break;
|
||||
|
||||
}
|
||||
|
||||
if (albums_requested_ > 0) {
|
||||
emit UpdateStatus(QString("Retriving %1 album%2...").arg(albums_requested_).arg(albums_requested_ == 1 ? "" : "s"));
|
||||
emit ProgressSetMaximum(albums_requested_);
|
||||
emit UpdateProgress(0);
|
||||
}
|
||||
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
DeezerAlbumContext *DeezerService::CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover) {
|
||||
|
||||
DeezerAlbumContext *album_ctx = new DeezerAlbumContext;
|
||||
album_ctx->id = album_id;
|
||||
album_ctx->artist = artist;
|
||||
album_ctx->album = album;
|
||||
album_ctx->cover = cover;
|
||||
album_ctx->cover_url.setUrl(cover);
|
||||
requests_album_.insert(album_id, album_ctx);
|
||||
|
||||
return album_ctx;
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::GetAlbum(const DeezerAlbumContext *album_ctx) {
|
||||
|
||||
QList<Param> parameters;
|
||||
QNetworkReply *reply = CreateRequest(QString("album/%1/tracks").arg(album_ctx->id), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetAlbumFinished(QNetworkReply*, int, int)), reply, search_id_, album_ctx->id);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (!requests_album_.contains(album_id)) {
|
||||
qLog(Error) << "Deezer: Got reply for cancelled album request: " << album_id;
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
DeezerAlbumContext *album_ctx = requests_album_.value(album_id);
|
||||
|
||||
if (search_id != search_id_) {
|
||||
if (album_ctx->search_id == search_id) delete requests_album_.take(album_ctx->id);
|
||||
return;
|
||||
}
|
||||
|
||||
albums_received_++;
|
||||
emit UpdateProgress(albums_received_);
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractData(data);
|
||||
if (!json_value.isArray()) {
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray json_data = json_value.toArray();
|
||||
if (json_data.isEmpty()) {
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
bool compilation = false;
|
||||
bool multidisc = false;
|
||||
Song first_song;
|
||||
SongList songs;
|
||||
for (const QJsonValue &value : json_data) {
|
||||
Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->cover, value);
|
||||
if (!song.is_valid()) continue;
|
||||
if (song.disc() >= 2) multidisc = true;
|
||||
if (song.is_compilation() || (first_song.is_valid() && song.artist() != first_song.artist())) compilation = true;
|
||||
if (!first_song.is_valid()) first_song = song;
|
||||
songs << song;
|
||||
}
|
||||
for (Song &song : songs) {
|
||||
if (compilation) song.set_compilation_detected(true);
|
||||
if (multidisc) {
|
||||
QString album_full(QString("%1 - (Disc %2)").arg(song.album()).arg(song.disc()));
|
||||
song.set_album(album_full);
|
||||
}
|
||||
songs_ << song;
|
||||
}
|
||||
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
Song DeezerService::ParseSong(const int album_id, const QString &album, const QString &album_cover, const QJsonValue &value) {
|
||||
|
||||
if (!value.isObject()) {
|
||||
Error("Invalid Json reply, track is not an object.", value);
|
||||
return Song();
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
//qLog(Debug) << json_obj;
|
||||
|
||||
if (
|
||||
!json_obj.contains("id") ||
|
||||
!json_obj.contains("title") ||
|
||||
!json_obj.contains("artist") ||
|
||||
!json_obj.contains("duration") ||
|
||||
!json_obj.contains("preview")
|
||||
) {
|
||||
Error("Invalid Json reply, track is missing one or more values.", json_obj);
|
||||
return Song();
|
||||
}
|
||||
|
||||
int song_id = json_obj["id"].toInt();
|
||||
QString title = json_obj["title"].toString();
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
QVariant q_duration = json_obj["duration"].toVariant();
|
||||
int track(0);
|
||||
if (json_obj.contains("track_position")) track = json_obj["track_position"].toInt();
|
||||
int disc(0);
|
||||
if (json_obj.contains("disk_number")) disc = json_obj["disk_number"].toInt();
|
||||
QString preview = json_obj["preview"].toString();
|
||||
|
||||
if (!json_value_artist.isObject()) {
|
||||
Error("Invalid Json reply, track artist is not an object.", json_value_artist);
|
||||
return Song();
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
if (!json_artist.contains("name")) {
|
||||
Error("Invalid Json reply, track artist is missing name.", json_artist);
|
||||
return Song();
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
|
||||
Song song;
|
||||
song.set_source(Song::Source_Deezer);
|
||||
song.set_id(song_id);
|
||||
song.set_album_id(album_id);
|
||||
song.set_artist(artist);
|
||||
song.set_album(album);
|
||||
song.set_title(title);
|
||||
song.set_disc(disc);
|
||||
song.set_track(track);
|
||||
song.set_art_automatic(album_cover);
|
||||
|
||||
QUrl url;
|
||||
if (preview_) {
|
||||
url.setUrl(preview);
|
||||
quint64 duration = (30 * kNsecPerSec);
|
||||
song.set_length_nanosec(duration);
|
||||
}
|
||||
else {
|
||||
url.setScheme(url_handler_->scheme());
|
||||
url.setPath(QString("track/%1").arg(QString::number(song_id)));
|
||||
if (q_duration.isValid()) {
|
||||
quint64 duration = q_duration.toULongLong() * kNsecPerSec;
|
||||
song.set_length_nanosec(duration);
|
||||
}
|
||||
}
|
||||
song.set_url(url);
|
||||
|
||||
song.set_valid(true);
|
||||
|
||||
return song;
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::GetStreamURL(const QUrl &original_url) {
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
stream_request_url_ = original_url;
|
||||
dzmedia_->GetStreamURL(original_url);
|
||||
#else
|
||||
stream_request_url_ = QUrl();
|
||||
emit StreamURLReceived(original_url, original_url, Song::FileType_Stream);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
void DeezerService::GetStreamURLFinished(const QUrl original_url, const QUrl media_url, const DZMedia::FileType dzmedia_filetype) {
|
||||
|
||||
Song::FileType filetype(Song::FileType_Unknown);
|
||||
|
||||
switch (dzmedia_filetype) {
|
||||
case DZMedia::FileType_FLAC:
|
||||
filetype = Song::FileType_FLAC;
|
||||
break;
|
||||
case DZMedia::FileType_MPEG:
|
||||
filetype = Song::FileType_MPEG;
|
||||
break;
|
||||
case DZMedia::FileType_Stream:
|
||||
filetype = Song::FileType_Stream;
|
||||
break;
|
||||
default:
|
||||
filetype = Song::FileType_Unknown;
|
||||
break;
|
||||
}
|
||||
stream_request_url_ = QUrl();
|
||||
emit StreamURLReceived(original_url, media_url, filetype);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void DeezerService::CheckFinish() {
|
||||
|
||||
if (search_id_ == 0) return;
|
||||
|
||||
if (albums_requested_ <= albums_received_) {
|
||||
if (songs_.isEmpty()) {
|
||||
if (search_error_.isEmpty()) emit SearchError(search_id_, "Unknown error");
|
||||
else emit SearchError(search_id_, search_error_);
|
||||
}
|
||||
else emit SearchResults(search_id_, songs_);
|
||||
ClearSearch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::Error(QString error, QVariant debug) {
|
||||
qLog(Error) << "Deezer:" << error;
|
||||
if (!debug.isValid()) qLog(Debug) << debug;
|
||||
if (search_id_ != 0) {
|
||||
if (!error.isEmpty()) {
|
||||
search_error_ += error;
|
||||
search_error_ += "<br />";
|
||||
}
|
||||
CheckFinish();
|
||||
}
|
||||
if (!stream_request_url_.isEmpty()) {
|
||||
emit StreamURLReceived(stream_request_url_, stream_request_url_, Song::FileType_Stream);
|
||||
stream_request_url_ = QUrl();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSERVICE_H
|
||||
#define DEEZERSERVICE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
# include <dzmedia.h>
|
||||
#endif
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class LocalRedirectServer;
|
||||
class DeezerUrlHandler;
|
||||
|
||||
struct DeezerAlbumContext {
|
||||
int id;
|
||||
int search_id;
|
||||
QString artist;
|
||||
QString album;
|
||||
QString cover;
|
||||
QUrl cover_url;
|
||||
};
|
||||
Q_DECLARE_METATYPE(DeezerAlbumContext);
|
||||
|
||||
class DeezerService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerService(Application *app, InternetModel *parent);
|
||||
~DeezerService();
|
||||
|
||||
static const Song::Source kSource;
|
||||
static const int kAppID;
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void CancelSearch();
|
||||
|
||||
const bool app_id() { return kAppID; }
|
||||
const bool authenticated() { return !access_token_.isEmpty(); }
|
||||
|
||||
void GetStreamURL(const QUrl &url);
|
||||
|
||||
signals:
|
||||
void Login();
|
||||
void LoginSuccess();
|
||||
void LoginFailure(QString failure_reason);
|
||||
void Authenticated();
|
||||
void SearchResults(int id, SongList songs);
|
||||
void SearchError(int id, QString message);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int max);
|
||||
void UpdateProgress(int max);
|
||||
void StreamURLReceived(const QUrl original_url, const QUrl media_url, const Song::FileType filetype);
|
||||
|
||||
public slots:
|
||||
void ShowConfig();
|
||||
|
||||
private slots:
|
||||
void StartAuthorisation();
|
||||
void FetchAccessTokenFinished(QNetworkReply *reply);
|
||||
void StartSearch();
|
||||
void SearchFinished(QNetworkReply *reply, int search_id);
|
||||
void GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id);
|
||||
#ifdef HAVE_DZMEDIA
|
||||
void GetStreamURLFinished(const QUrl original_url, const QUrl media_url, const DZMedia::FileType dzmedia_filetype);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void LoadAccessToken();
|
||||
void RedirectArrived(LocalRedirectServer *server, QUrl url);
|
||||
void RequestAccessToken(const QByteArray &code);
|
||||
void SetExpiryTime(int expires_in_seconds);
|
||||
void ClearSearch();
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<QPair<QString, QString>> ¶ms);
|
||||
QByteArray GetReplyData(QNetworkReply *reply);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||
QJsonValue ExtractData(QByteArray &data);
|
||||
void SendSearch();
|
||||
DeezerAlbumContext *CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover);
|
||||
void GetAlbum(const DeezerAlbumContext *album_ctx);
|
||||
Song ParseSong(const int album_id, const QString &album, const QString &album_cover, const QJsonValue &value);
|
||||
void CheckFinish();
|
||||
void Error(QString error, QVariant debug = QString());
|
||||
|
||||
static const char *kApiUrl;
|
||||
static const char *kOAuthUrl;
|
||||
static const char *kOAuthAccessTokenUrl;
|
||||
static const char *kOAuthRedirectUrl;
|
||||
static const char *kSecretKey;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
DeezerUrlHandler *url_handler_;
|
||||
#ifdef HAVE_DZMEDIA
|
||||
DZMedia *dzmedia_;
|
||||
#endif
|
||||
QTimer *timer_searchdelay_;
|
||||
|
||||
QString quality_;
|
||||
int searchdelay_;
|
||||
int albumssearchlimit_;
|
||||
int songssearchlimit_;
|
||||
bool fetchalbums_;
|
||||
QString coversize_;
|
||||
bool preview_;
|
||||
QString access_token_;
|
||||
QDateTime expiry_time_;
|
||||
|
||||
int pending_search_id_;
|
||||
int next_pending_search_id_;
|
||||
QString pending_search_text_;
|
||||
DeezerSettingsPage::SearchBy pending_searchby_;
|
||||
|
||||
int search_id_;
|
||||
QString search_text_;
|
||||
QHash<int, DeezerAlbumContext*> requests_album_;
|
||||
QHash<int, QUrl> requests_song_;
|
||||
int albums_requested_;
|
||||
int albums_received_;
|
||||
SongList songs_;
|
||||
QString search_error_;
|
||||
QUrl stream_request_url_;
|
||||
|
||||
};
|
||||
|
||||
#endif // DEEZERSERVICE_H
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
#include "deezerurlhandler.h"
|
||||
|
||||
DeezerUrlHandler::DeezerUrlHandler(
|
||||
Application *app, DeezerService *service)
|
||||
: UrlHandler(service), app_(app), service_(service), task_id_(-1) {
|
||||
|
||||
connect(service, SIGNAL(StreamURLReceived(QUrl, QUrl, Song::FileType)), this, SLOT(GetStreamURLFinished(QUrl, QUrl, Song::FileType)));
|
||||
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult DeezerUrlHandler::StartLoading(const QUrl &url) {
|
||||
|
||||
LoadResult ret(url);
|
||||
if (task_id_ != -1) return ret;
|
||||
last_original_url_ = url;
|
||||
task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
|
||||
service_->GetStreamURL(url);
|
||||
ret.type_ = LoadResult::WillLoadAsynchronously;
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void DeezerUrlHandler::GetStreamURLFinished(QUrl original_url, QUrl media_url, Song::FileType filetype) {
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, media_url, filetype));
|
||||
|
||||
}
|
||||
|
||||
void DeezerUrlHandler::CancelTask() {
|
||||
|
||||
app_->task_manager()->SetTaskFinished(task_id_);
|
||||
task_id_ = -1;
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERURLHANDLER_H
|
||||
#define DEEZERURLHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/urlhandler.h"
|
||||
#include "core/song.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
|
||||
class Application;
|
||||
class DeezerService;
|
||||
|
||||
class DeezerUrlHandler : public UrlHandler {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerUrlHandler(Application *app, DeezerService *service);
|
||||
|
||||
QString scheme() const { return service_->url_scheme(); }
|
||||
LoadResult StartLoading(const QUrl &url);
|
||||
|
||||
void CancelTask();
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(QUrl original_url, QUrl media_url, Song::FileType filetype);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
DeezerService *service_;
|
||||
int task_id_;
|
||||
QUrl last_original_url_;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -14,7 +14,7 @@
|
|||
<string>Device Properties</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
|
@ -453,6 +453,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
|
@ -32,7 +32,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="label_icon">
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/data.qrc">:/icons/64x64/strawberry.png</pixmap>
|
||||
<pixmap resource="../../data/icons.qrc">:/icons/64x64/strawberry.png</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
|
@ -145,6 +145,7 @@
|
|||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Edit track information</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
|
@ -861,6 +861,7 @@
|
|||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Organise Files</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
|
@ -271,6 +271,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Tag fetcher</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
|
@ -261,6 +261,7 @@
|
|||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <deezer/deezer-connect.h>
|
||||
#include <deezer/deezer-player.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/logging.h"
|
||||
#include "engine_fwd.h"
|
||||
#include "enginebase.h"
|
||||
#include "enginetype.h"
|
||||
#include "deezerengine.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
DeezerEngine::DeezerEngine(TaskManager *task_manager)
|
||||
: EngineBase(),
|
||||
state_(Engine::Empty),
|
||||
position_(0) {
|
||||
|
||||
type_ = Engine::Deezer;
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
DeezerEngine::~DeezerEngine() {
|
||||
|
||||
if (player_) {
|
||||
dz_object_release((dz_object_handle) player_);
|
||||
player_ = nullptr;
|
||||
}
|
||||
|
||||
if (connect_) {
|
||||
dz_object_release((dz_object_handle) connect_);
|
||||
connect_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DeezerEngine::Init() {
|
||||
|
||||
qLog(Debug) << "Deezer native SDK Version:" << dz_connect_get_build_id();
|
||||
|
||||
struct dz_connect_configuration config;
|
||||
memset(&config, 0, sizeof(struct dz_connect_configuration));
|
||||
config.app_id = QString::number(DeezerService::kAppID).toUtf8();
|
||||
config.product_id = QCoreApplication::applicationName().toUtf8();
|
||||
config.product_build_id = QCoreApplication::applicationVersion().toUtf8().constData();
|
||||
config.user_profile_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toUtf8().constData();
|
||||
config.connect_event_cb = ConnectEventCallback;
|
||||
|
||||
connect_ = dz_connect_new(&config);
|
||||
if (!connect_) {
|
||||
qLog(Error) << "Deezer: Failed to create connect.";
|
||||
return false;
|
||||
}
|
||||
|
||||
qLog(Debug) << "Device ID:" << dz_connect_get_device_id(connect_);
|
||||
|
||||
dz_error_t dzerr(DZ_ERROR_NO_ERROR);
|
||||
|
||||
dzerr = dz_connect_debug_log_disable(connect_);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to disable debug log.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_connect_activate(connect_, this);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to activate connect.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dz_connect_cache_path_set(connect_, nullptr, nullptr, QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toUtf8().constData());
|
||||
|
||||
player_ = dz_player_new(connect_);
|
||||
if (!player_) {
|
||||
qLog(Error) << "Deezer: Failed to create player.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_player_activate(player_, this);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to activate player.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_player_set_event_cb(player_, PlayerEventCallback);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set event callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_player_set_metadata_cb(player_, PlayerMetaDataCallback);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set metadata callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_player_set_render_progress_cb(player_, PlayerProgressCallback, 1000);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set progress callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_player_set_crossfading_duration(player_, nullptr, nullptr, 3000);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set crossfade duration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
dzerr = dz_connect_offline_mode(connect_, nullptr, nullptr, false);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set offline mode.";
|
||||
return false;
|
||||
}
|
||||
|
||||
LoadAccessToken();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool DeezerEngine::Initialised() const {
|
||||
|
||||
if (connect_ && player_) return true;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::LoadAccessToken() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
if (!s.contains("access_token") || !s.contains("expiry_time")) return;
|
||||
access_token_ = s.value("access_token").toString();
|
||||
expiry_time_ = s.value("expiry_time").toDateTime();
|
||||
s.endGroup();
|
||||
|
||||
dz_error_t dzerr = dz_connect_set_access_token(connect_, nullptr, nullptr, access_token_.toUtf8().constData());
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set access token.";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DeezerEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
if (!Initialised()) return false;
|
||||
|
||||
Engine::Base::Load(media_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
||||
dz_error_t dzerr = dz_player_load(player_, nullptr, nullptr, media_url.toString().toUtf8().constData());
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool DeezerEngine::Play(quint64 offset_nanosec) {
|
||||
|
||||
if (!Initialised()) return false;
|
||||
|
||||
dz_error_t dzerr(DZ_ERROR_NO_ERROR);
|
||||
if (state() == Engine::Paused) dzerr = dz_player_resume(player_, nullptr, nullptr);
|
||||
else dzerr = dz_player_play(player_, nullptr, nullptr, DZ_PLAYER_PLAY_CMD_START_TRACKLIST, DZ_INDEX_IN_QUEUELIST_CURRENT);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return false;
|
||||
|
||||
Seek(offset_nanosec);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::Stop(bool stop_after) {
|
||||
|
||||
if (!Initialised()) return;
|
||||
|
||||
dz_error_t dzerr = dz_player_stop(player_, nullptr, nullptr);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return;
|
||||
|
||||
state_ = Engine::Empty;
|
||||
emit TrackEnded();
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::Pause() {
|
||||
|
||||
if (!Initialised()) return;
|
||||
|
||||
dz_error_t dzerr = dz_player_pause(player_, nullptr, nullptr);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::Unpause() {
|
||||
|
||||
if (!Initialised()) return;
|
||||
dz_error_t dzerr = dz_player_resume(player_, nullptr, nullptr);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::Seek(quint64 offset_nanosec) {
|
||||
|
||||
if (!Initialised()) return;
|
||||
|
||||
int offset = (offset_nanosec / kNsecPerMsec);
|
||||
|
||||
uint len = (length_nanosec() / kNsecPerMsec);
|
||||
if (len == 0) return;
|
||||
|
||||
float pos = float(offset) / len;
|
||||
|
||||
dz_error_t dzerr = dz_player_seek(player_, nullptr, nullptr, pos);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::SetVolumeSW(uint percent) {
|
||||
|
||||
if (!Initialised()) return;
|
||||
|
||||
dz_error_t dzerr = dz_player_set_output_volume(player_, nullptr, nullptr, percent);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) qLog(Error) << "Deezer: Failed to set volume.";
|
||||
|
||||
}
|
||||
|
||||
qint64 DeezerEngine::position_nanosec() const {
|
||||
|
||||
if (state() == Engine::Empty) return 0;
|
||||
const qint64 result = (position_ * kNsecPerUsec);
|
||||
return qint64(qMax(0ll, result));
|
||||
|
||||
}
|
||||
|
||||
qint64 DeezerEngine::length_nanosec() const {
|
||||
|
||||
if (state() == Engine::Empty) return 0;
|
||||
|
||||
const qint64 result = (end_nanosec_ - beginning_nanosec_);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
EngineBase::OutputDetailsList DeezerEngine::GetOutputsList() const {
|
||||
OutputDetailsList ret;
|
||||
OutputDetails output;
|
||||
output.name = "default";
|
||||
output.description = "Default";
|
||||
output.iconname = "soundcard";
|
||||
ret << output;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool DeezerEngine::ValidOutput(const QString &output) {
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool DeezerEngine::CustomDeviceSupport(const QString &output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeezerEngine::ALSADeviceSupport(const QString &output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeezerEngine::CanDecode(const QUrl &url) {
|
||||
if (url.scheme() == "dzmedia") return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
void DeezerEngine::ConnectEventCallback(dz_connect_handle handle, dz_connect_event_handle event, void *delegate) {
|
||||
|
||||
dz_connect_event_t type = dz_connect_event_get_type(event);
|
||||
//DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(delegate);
|
||||
|
||||
switch (type) {
|
||||
case DZ_CONNECT_EVENT_USER_OFFLINE_AVAILABLE:
|
||||
qLog(Debug) << "CONNECT_EVENT USER_OFFLINE_AVAILABLE";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_ACCESS_TOKEN_OK: {
|
||||
const char* szAccessToken;
|
||||
szAccessToken = dz_connect_event_get_access_token(event);
|
||||
qLog(Debug) << "CONNECT_EVENT USER_ACCESS_TOKEN_OK Access_token :" << szAccessToken;
|
||||
}
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_ACCESS_TOKEN_FAILED:
|
||||
qLog(Debug) << "CONNECT_EVENT USER_ACCESS_TOKEN_FAILED";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_LOGIN_OK:
|
||||
qLog(Debug) << "Deezer CONNECT_EVENT USER_LOGIN_OK";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_NEW_OPTIONS:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENT USER_NEW_OPTIONS";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_NETWORK_ERROR:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_NETWORK_ERROR";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_BAD_CREDENTIALS:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_BAD_CREDENTIALS";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_USER_INFO:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_USER_INFO";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_OFFLINE_MODE:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_OFFLINE_MODE";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_ADVERTISEMENT_START:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENTADVERTISEMENT_START";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_ADVERTISEMENT_STOP:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENTADVERTISEMENT_STOP";
|
||||
break;
|
||||
|
||||
case DZ_CONNECT_EVENT_UNKNOWN:
|
||||
default:
|
||||
qLog(Debug) << "Deezer: CONNECT_EVENTUNKNOWN or default (type =" << type;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void DeezerEngine::PlayerEventCallback(dz_player_handle handle, dz_player_event_handle event, void *supervisor) {
|
||||
|
||||
DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(supervisor);
|
||||
dz_streaming_mode_t streaming_mode;
|
||||
dz_index_in_queuelist idx;
|
||||
dz_player_event_t type = dz_player_event_get_type(event);
|
||||
|
||||
if (!dz_player_event_get_queuelist_context(event, &streaming_mode, &idx)) {
|
||||
streaming_mode = DZ_STREAMING_MODE_ONDEMAND;
|
||||
idx = DZ_INDEX_IN_QUEUELIST_INVALID;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
|
||||
case DZ_PLAYER_EVENT_LIMITATION_FORCED_PAUSE:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_LIMITATION_FORCED_PAUSE";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_LOADED:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_LOADED";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_NO_RIGHT:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_NO_RIGHT";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_NEED_NATURAL_NEXT:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_NEED_NATURAL_NEXT";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_NOT_AVAILABLE_OFFLINE:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_TRACK_NOT_AVAILABLE_OFFLINE";
|
||||
engine->state_ = Engine::Error;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
emit engine->Error("Track not available offline.");
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_RIGHTS_AFTER_AUDIOADS:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_TRACK_RIGHTS_AFTER_AUDIOADS";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_SKIP_NO_RIGHT:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_SKIP_NO_RIGHT";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_SELECTED:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_MEDIASTREAM_DATA_READY:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_MEDIASTREAM_DATA_READY";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_MEDIASTREAM_DATA_READY_AFTER_SEEK:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_MEDIASTREAM_DATA_READY_AFTER_SEEK";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_START_FAILURE:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_START_FAILURE";
|
||||
engine->state_ = Engine::Error;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
emit engine->Error("Track start failure.");
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_START:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_START";
|
||||
engine->state_ = Engine::Playing;
|
||||
engine->position_ = 0;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_END:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_END";
|
||||
engine->state_ = Engine::Idle;
|
||||
engine->position_ = 0;
|
||||
emit engine->TrackEnded();
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_PAUSED:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_PAUSED";
|
||||
engine->state_ = Engine::Paused;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_UNDERFLOW:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_UNDERFLOW";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_RESUMED:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_RESUMED";
|
||||
engine->state_ = Engine::Playing;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_SEEKING:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_SEEKING";
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_REMOVED:
|
||||
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_REMOVED";
|
||||
engine->state_ = Engine::Empty;
|
||||
engine->position_ = 0;
|
||||
emit engine->TrackEnded();
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_UNKNOWN:
|
||||
default:
|
||||
qLog(Error) << "Deezer: Unknown player event" << type;
|
||||
break;
|
||||
}
|
||||
//emit engine->StateChanged(engine->state_);
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::PlayerProgressCallback(dz_player_handle handle, dz_useconds_t progress, void *userdata) {
|
||||
DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(userdata);
|
||||
engine->position_ = progress;
|
||||
}
|
||||
|
||||
void DeezerEngine::PlayerMetaDataCallback(dz_player_handle handle, dz_track_metadata_handle metadata, void *userdata) {
|
||||
//DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(userdata);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERENGINE_H
|
||||
#define DEEZERENGINE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <deezer/deezer-connect.h>
|
||||
#include <deezer/deezer-player.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "engine_fwd.h"
|
||||
#include "enginebase.h"
|
||||
|
||||
class TaskManager;
|
||||
|
||||
class DeezerEngine : public Engine::Base {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerEngine(TaskManager *task_manager);
|
||||
~DeezerEngine();
|
||||
|
||||
bool Init();
|
||||
Engine::State state() const { return state_; }
|
||||
bool Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
void Unpause();
|
||||
void Seek(quint64 offset_nanosec);
|
||||
protected:
|
||||
void SetVolumeSW(uint percent);
|
||||
public:
|
||||
virtual qint64 position_nanosec() const;
|
||||
virtual qint64 length_nanosec() const;
|
||||
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
bool ValidOutput(const QString &output);
|
||||
QString DefaultOutput() { return ""; }
|
||||
bool CustomDeviceSupport(const QString &output);
|
||||
bool ALSADeviceSupport(const QString &output);
|
||||
|
||||
private:
|
||||
Engine::State state_;
|
||||
dz_connect_handle connect_;
|
||||
dz_player_handle player_;
|
||||
QString access_token_;
|
||||
QDateTime expiry_time_;
|
||||
qint64 position_;
|
||||
|
||||
bool Initialised() const;
|
||||
bool CanDecode(const QUrl &url);
|
||||
|
||||
static void ConnectEventCallback(dz_connect_handle handle, dz_connect_event_handle event, void *delegate);
|
||||
static void PlayerEventCallback(dz_player_handle handle, dz_player_event_handle event, void *supervisor);
|
||||
static void PlayerMetaDataCallback(dz_player_handle handle, dz_track_metadata_handle metadata, void *userdata);
|
||||
static void PlayerProgressCallback(dz_player_handle handle, dz_useconds_t progress, void *userdata);
|
||||
|
||||
public slots:
|
||||
void LoadAccessToken();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -147,8 +147,7 @@ signals:
|
|||
void MetaData(const Engine::SimpleMetaBundle&);
|
||||
|
||||
// Signals that the engine's state has changed (a stream was stopped for example).
|
||||
// Always use the state from event, because it's not guaranteed that immediate
|
||||
// subsequent call to state() won't return a stale value.
|
||||
// Always use the state from event, because it's not guaranteed that immediate subsequent call to state() won't return a stale value.
|
||||
void StateChanged(Engine::State);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2014, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2014, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -28,19 +28,21 @@ namespace Engine {
|
|||
|
||||
Engine::EngineType EngineTypeFromName(QString enginename) {
|
||||
QString lower = enginename.toLower();
|
||||
if (lower == "xine") return Engine::Xine;
|
||||
else if (lower == "gstreamer") return Engine::GStreamer;
|
||||
else if (lower == "phonon") return Engine::Phonon;
|
||||
else if (lower == "vlc") return Engine::VLC;
|
||||
else return Engine::None;
|
||||
if (lower == "gstreamer") return Engine::GStreamer;
|
||||
else if (lower == "xine") return Engine::Xine;
|
||||
else if (lower == "vlc") return Engine::VLC;
|
||||
else if (lower == "phonon") return Engine::Phonon;
|
||||
else if (lower == "deezer") return Engine::Deezer;
|
||||
else return Engine::None;
|
||||
}
|
||||
|
||||
QString EngineName(Engine::EngineType enginetype) {
|
||||
switch (enginetype) {
|
||||
case Engine::Xine: return QString("xine");
|
||||
case Engine::GStreamer: return QString("gstreamer");
|
||||
case Engine::Phonon: return QString("phonon");
|
||||
case Engine::Xine: return QString("xine");
|
||||
case Engine::VLC: return QString("vlc");
|
||||
case Engine::Phonon: return QString("phonon");
|
||||
case Engine::Deezer: return QString("deezer");
|
||||
case Engine::None:
|
||||
default: return QString("None");
|
||||
}
|
||||
|
@ -48,10 +50,11 @@ QString EngineName(Engine::EngineType enginetype) {
|
|||
|
||||
QString EngineDescription(Engine::EngineType enginetype) {
|
||||
switch (enginetype) {
|
||||
case Engine::Xine: return QString("Xine");
|
||||
case Engine::GStreamer: return QString("GStreamer");
|
||||
case Engine::Phonon: return QString("Phonon");
|
||||
case Engine::Xine: return QString("Xine");
|
||||
case Engine::VLC: return QString("VLC");
|
||||
case Engine::Phonon: return QString("Phonon");
|
||||
case Engine::Deezer: return QString("Deezer");
|
||||
case Engine::None:
|
||||
default: return QString("None");
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ enum EngineType {
|
|||
GStreamer,
|
||||
VLC,
|
||||
Xine,
|
||||
Phonon
|
||||
Phonon,
|
||||
Deezer
|
||||
};
|
||||
|
||||
Engine::EngineType EngineTypeFromName(QString enginename);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Equalizer</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -158,6 +158,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Press a key</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -52,6 +52,7 @@
|
|||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "internetmodel.h"
|
||||
#include "internetservice.h"
|
||||
#include "tidal/tidalservice.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
|
||||
QMap<Song::Source, InternetService*>* InternetModel::sServices = nullptr;
|
||||
|
||||
|
@ -41,6 +42,7 @@ InternetModel::InternetModel(Application *app, QObject *parent)
|
|||
if (!sServices) sServices = new QMap<Song::Source, InternetService*>;
|
||||
Q_ASSERT(sServices->isEmpty());
|
||||
AddService(new TidalService(app, this));
|
||||
AddService(new DeezerService(app, this));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,6 @@ class InternetModel : public QStandardItemModel {
|
|||
|
||||
// Needs to be static for InternetPlaylistItem::restore
|
||||
static InternetService *ServiceBySource(const Song::Source &source);
|
||||
//static InternetService *ServiceByName(const QString &name);
|
||||
|
||||
template <typename T>
|
||||
static T *Service() {
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
* Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "localredirectserver.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <QRegExp>
|
||||
#include <QStyle>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "core/closure.h"
|
||||
|
||||
LocalRedirectServer::LocalRedirectServer(QObject* parent)
|
||||
: QObject(parent), server_(new QTcpServer(this)) {}
|
||||
|
||||
void LocalRedirectServer::Listen() {
|
||||
|
||||
server_->listen(QHostAddress::LocalHost);
|
||||
// We have to calculate this and store it now as the server port is cleared once we close the socket.
|
||||
url_.setScheme("http");
|
||||
url_.setHost("localhost");
|
||||
url_.setPort(server_->serverPort());
|
||||
url_.setPath("/");
|
||||
connect(server_, SIGNAL(newConnection()), SLOT(NewConnection()));
|
||||
|
||||
}
|
||||
|
||||
void LocalRedirectServer::NewConnection() {
|
||||
QTcpSocket* socket = server_->nextPendingConnection();
|
||||
server_->close();
|
||||
|
||||
QByteArray buffer;
|
||||
NewClosure(socket, SIGNAL(readyRead()), this, SLOT(ReadyRead(QTcpSocket*, QByteArray)), socket, buffer);
|
||||
}
|
||||
|
||||
void LocalRedirectServer::ReadyRead(QTcpSocket* socket, QByteArray buffer) {
|
||||
buffer.append(socket->readAll());
|
||||
if (socket->atEnd() || buffer.endsWith("\r\n\r\n")) {
|
||||
WriteTemplate(socket);
|
||||
socket->deleteLater();
|
||||
request_url_ = ParseUrlFromRequest(buffer);
|
||||
emit Finished();
|
||||
}
|
||||
else {
|
||||
NewClosure(socket, SIGNAL(readyRead()), this, SLOT(ReadyReady(QTcpSocket*, QByteArray)), socket, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalRedirectServer::WriteTemplate(QTcpSocket* socket) const {
|
||||
|
||||
QFile page_file(":/misc/oauthsuccess.html");
|
||||
page_file.open(QIODevice::ReadOnly);
|
||||
QString page_data = QString::fromUtf8(page_file.readAll());
|
||||
|
||||
QRegExp tr_regexp("tr\\(\"([^\"]+)\"\\)");
|
||||
int offset = 0;
|
||||
forever {
|
||||
offset = tr_regexp.indexIn(page_data, offset);
|
||||
if (offset == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
page_data.replace(offset, tr_regexp.matchedLength(), tr(tr_regexp.cap(1).toUtf8()));
|
||||
offset += tr_regexp.matchedLength();
|
||||
}
|
||||
|
||||
QBuffer image_buffer;
|
||||
image_buffer.open(QIODevice::ReadWrite);
|
||||
QApplication::style()
|
||||
->standardIcon(QStyle::SP_DialogOkButton)
|
||||
.pixmap(16)
|
||||
.toImage()
|
||||
.save(&image_buffer, "PNG");
|
||||
page_data.replace("@IMAGE_DATA@", image_buffer.data().toBase64());
|
||||
|
||||
socket->write("HTTP/1.0 200 OK\r\n");
|
||||
socket->write("Content-type: text/html;charset=UTF-8\r\n");
|
||||
socket->write("\r\n\r\n");
|
||||
socket->write(page_data.toUtf8());
|
||||
socket->flush();
|
||||
|
||||
}
|
||||
|
||||
QUrl LocalRedirectServer::ParseUrlFromRequest(const QByteArray& request) const {
|
||||
QList<QByteArray> lines = request.split('\r');
|
||||
const QByteArray& request_line = lines[0];
|
||||
QByteArray path = request_line.split(' ')[1];
|
||||
QUrl base_url = url();
|
||||
QUrl request_url(base_url.toString() + path.mid(1), QUrl::StrictMode);
|
||||
return request_url;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
* Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LOCALREDIRECTSERVER_H
|
||||
#define LOCALREDIRECTSERVER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
|
||||
class LocalRedirectServer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LocalRedirectServer(QObject* parent = nullptr);
|
||||
|
||||
// Causes the server to listen for _one_ request.
|
||||
void Listen();
|
||||
|
||||
// Returns the HTTP URL of this server.
|
||||
const QUrl& url() const { return url_; }
|
||||
|
||||
// Returns the URL requested by the OAuth redirect.
|
||||
const QUrl& request_url() const { return request_url_; }
|
||||
|
||||
signals:
|
||||
void Finished();
|
||||
|
||||
private slots:
|
||||
void NewConnection();
|
||||
void ReadyRead(QTcpSocket* socket, QByteArray buffer);
|
||||
|
||||
private:
|
||||
void WriteTemplate(QTcpSocket* socket) const;
|
||||
QUrl ParseUrlFromRequest(const QByteArray& request) const;
|
||||
|
||||
private:
|
||||
QTcpServer* server_;
|
||||
QUrl url_;
|
||||
QUrl request_url_;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -75,7 +75,7 @@ class PlaylistHeader;
|
|||
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
|
||||
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
|
||||
// This proxy style uses QCommonStyle to paint the affected elements.
|
||||
// This class is used by tidal search view as well.
|
||||
// This class is used by tidal and deezer search view as well.
|
||||
class PlaylistProxyStyle : public QProxyStyle {
|
||||
public:
|
||||
PlaylistProxyStyle(QStyle *base);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Queue Manager</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
|
@ -157,6 +157,7 @@
|
|||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -96,6 +96,9 @@ void BackendSettingsPage::Load() {
|
|||
#ifdef HAVE_PHONON
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineDescription(Engine::Phonon), Engine::Phonon);
|
||||
#endif
|
||||
#ifdef HAVE_DEEZER
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("deezer"), EngineDescription(Engine::Deezer), Engine::Deezer);
|
||||
#endif
|
||||
|
||||
enginetype_current_ = enginetype;
|
||||
output_current_ = s_.value("output", "").toString();
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QMessageBox>
|
||||
#include <QEvent>
|
||||
|
||||
#include "deezersettingspage.h"
|
||||
#include "ui_deezersettingspage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
|
||||
const char *DeezerSettingsPage::kSettingsGroup = "Deezer";
|
||||
|
||||
DeezerSettingsPage::DeezerSettingsPage(SettingsDialog *parent)
|
||||
: SettingsPage(parent),
|
||||
ui_(new Ui::DeezerSettingsPage),
|
||||
service_(dialog()->app()->internet_model()->Service<DeezerService>()) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
setWindowIcon(IconLoader::Load("deezer"));
|
||||
|
||||
connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked()));
|
||||
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked()));
|
||||
|
||||
connect(this, SIGNAL(Login()), service_, SLOT(StartAuthorisation()));
|
||||
|
||||
connect(service_, SIGNAL(LoginFailure(QString)), SLOT(LoginFailure(QString)));
|
||||
connect(service_, SIGNAL(LoginSuccess()), SLOT(LoginSuccess()));
|
||||
|
||||
dialog()->installEventFilter(this);
|
||||
|
||||
ui_->combobox_quality->addItem("AAC (64)", "AAC_64");
|
||||
ui_->combobox_quality->addItem("MP3 (64)", "MP3_64");
|
||||
ui_->combobox_quality->addItem("MP3 (128)", "MP3_128");
|
||||
ui_->combobox_quality->addItem("MP3 (256)", "MP3_256");
|
||||
ui_->combobox_quality->addItem("MP3 (320)", "MP3_320");
|
||||
ui_->combobox_quality->addItem("FLAC", "FLAC");
|
||||
|
||||
ui_->combobox_coversize->addItem("Small", "cover_small");
|
||||
ui_->combobox_coversize->addItem("Medium", "cover_medium");
|
||||
ui_->combobox_coversize->addItem("Big", "cover_big");
|
||||
ui_->combobox_coversize->addItem("XL", "cover_xl");
|
||||
|
||||
}
|
||||
|
||||
DeezerSettingsPage::~DeezerSettingsPage() { delete ui_; }
|
||||
|
||||
void DeezerSettingsPage::Load() {
|
||||
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(kSettingsGroup);
|
||||
ui_->username->setText(s.value("username").toString());
|
||||
QByteArray password = s.value("password").toByteArray();
|
||||
if (password.isEmpty()) ui_->password->clear();
|
||||
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
|
||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "FLAC");
|
||||
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
||||
ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
||||
ui_->spinbox_songssearchlimit->setValue(s.value("songssearchlimit", 100).toInt());
|
||||
ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool());
|
||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_coversize, "coversize", "cover_big");
|
||||
ui_->checkbox_preview->setChecked(s.value("preview", false).toBool());
|
||||
s.endGroup();
|
||||
|
||||
if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::Save() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("username", ui_->username->text());
|
||||
s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64()));
|
||||
s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex()));
|
||||
s.setValue("searchdelay", ui_->spinbox_searchdelay->value());
|
||||
s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value());
|
||||
s.setValue("songssearchlimit", ui_->spinbox_songssearchlimit->value());
|
||||
s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked());
|
||||
s.setValue("coversize", ui_->combobox_coversize->itemData(ui_->combobox_coversize->currentIndex()));
|
||||
s.setValue("preview", ui_->checkbox_preview->isChecked());
|
||||
s.endGroup();
|
||||
|
||||
service_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LoginClicked() {
|
||||
emit Login();
|
||||
ui_->button_login->setEnabled(false);
|
||||
}
|
||||
|
||||
bool DeezerSettingsPage::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == dialog() && event->type() == QEvent::Enter) {
|
||||
ui_->button_login->setEnabled(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return SettingsPage::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LogoutClicked() {
|
||||
service_->Logout();
|
||||
ui_->button_login->setEnabled(true);
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LoginSuccess() {
|
||||
if (!this->isVisible()) return;
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
||||
ui_->button_login->setEnabled(false);
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LoginFailure(QString failure_reason) {
|
||||
if (!this->isVisible()) return;
|
||||
QMessageBox::warning(this, tr("Authentication failed"), failure_reason);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can 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.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSETTINGSPAGE_H
|
||||
#define DEEZERSETTINGSPAGE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QEvent>
|
||||
|
||||
#include "settings/settingspage.h"
|
||||
|
||||
class DeezerService;
|
||||
class Ui_DeezerSettingsPage;
|
||||
|
||||
class DeezerSettingsPage : public SettingsPage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeezerSettingsPage(SettingsDialog* parent = nullptr);
|
||||
~DeezerSettingsPage();
|
||||
|
||||
enum SearchBy {
|
||||
SearchBy_Songs = 1,
|
||||
SearchBy_Albums = 2,
|
||||
};
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
|
||||
signals:
|
||||
void Login();
|
||||
void Login(const QString &username, const QString &password);
|
||||
|
||||
private slots:
|
||||
void LoginClicked();
|
||||
void LogoutClicked();
|
||||
void LoginSuccess();
|
||||
void LoginFailure(QString failure_reason);
|
||||
|
||||
private:
|
||||
Ui_DeezerSettingsPage* ui_;
|
||||
DeezerService *service_;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,417 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DeezerSettingsPage</class>
|
||||
<widget class="QWidget" name="DeezerSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>715</width>
|
||||
<height>547</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Deezer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="credential_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_login">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_auth">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_username">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_username">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_username">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_password">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_password">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_password">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupbox_preferences">
|
||||
<property name="title">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_quality">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_quality">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Audio quality</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="combobox_quality"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_quality">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_searchdelay">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_searchdelay">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinbox_searchdelay">
|
||||
<property name="suffix">
|
||||
<string>ms</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_searchdelay">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_albumssearchlimit">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_albumssearchlimit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Albums search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinbox_albumssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_albumssearchlimit">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_songssearchlimit">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_songssearchlimit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Songs search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinbox_songssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_songssearchlimit">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_fetchalbums">
|
||||
<property name="text">
|
||||
<string>Fetch entire albums when searching songs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_coversize">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_coversize">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Album cover size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="combobox_coversize"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_coversize">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_preview">
|
||||
<property name="text">
|
||||
<string>Use 30 seconds preview streams</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_middle">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="spacer_bottom">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_deezer">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>62</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>62</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/data.qrc">:/pictures/deezer.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LoginStateWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/loginstatewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -63,6 +63,7 @@
|
|||
#include "shortcutssettingspage.h"
|
||||
#include "transcodersettingspage.h"
|
||||
#include "tidalsettingspage.h"
|
||||
#include "deezersettingspage.h"
|
||||
|
||||
#include "ui_settingsdialog.h"
|
||||
|
||||
|
@ -114,7 +115,6 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
|
|||
ui_->list->setItemDelegate(new SettingsItemDelegate(this));
|
||||
|
||||
QTreeWidgetItem *general = AddCategory(tr("General"));
|
||||
|
||||
AddPage(Page_Behaviour, new BehaviourSettingsPage(this), general);
|
||||
AddPage(Page_Collection, new CollectionSettingsPage(this), general);
|
||||
AddPage(Page_Backend, new BackendSettingsPage(this), general);
|
||||
|
@ -124,7 +124,10 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
|
|||
#ifdef HAVE_GSTREAMER
|
||||
AddPage(Page_Transcoding, new TranscoderSettingsPage(this), general);
|
||||
#endif
|
||||
AddPage(Page_Tidal, new TidalSettingsPage(this), general);
|
||||
|
||||
QTreeWidgetItem *internet = AddCategory(tr("Internet"));
|
||||
AddPage(Page_Tidal, new TidalSettingsPage(this), internet);
|
||||
AddPage(Page_Deezer, new DeezerSettingsPage(this), internet);
|
||||
|
||||
// User interface
|
||||
QTreeWidgetItem *iface = AddCategory(tr("User interface"));
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
Page_Proxy,
|
||||
Page_Transcoding,
|
||||
Page_Tidal,
|
||||
Page_Deezer,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Settings</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
|
@ -97,6 +97,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Shortcuts</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -212,6 +212,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -76,7 +76,7 @@ void TidalSettingsPage::Load() {
|
|||
s.beginGroup(kSettingsGroup);
|
||||
ui_->username->setText(s.value("username").toString());
|
||||
QByteArray password = s.value("password").toByteArray();
|
||||
if (password.isEmpty()) ui_->password->setText("");
|
||||
if (password.isEmpty()) ui_->password->clear();
|
||||
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
|
||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "HIGH");
|
||||
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
||||
|
|
|
@ -360,7 +360,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/data.qrc">:/icons/64x64/tidal.png</pixmap>
|
||||
<pixmap resource="../../data/icons.qrc">:/icons/64x64/tidal.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -383,6 +383,7 @@
|
|||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>Transcoder Log</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<iconset resource="../../data/icons.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -36,6 +36,7 @@
|
|||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|