├── .clang-format ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── harbour-unplayer.desktop ├── harbour-unplayer.svg ├── icons └── hicolor │ ├── 108x108 │ └── apps │ │ └── harbour-unplayer.png │ ├── 128x128 │ └── apps │ │ └── harbour-unplayer.png │ ├── 172x172 │ └── apps │ │ └── harbour-unplayer.png │ ├── 256x256 │ └── apps │ │ └── harbour-unplayer.png │ └── 86x86 │ └── apps │ └── harbour-unplayer.png ├── qml ├── MediaKeysEmpty.qml ├── MediaKeysPrivate.qml ├── components │ ├── AboutPage.qml │ ├── AddToPlaylistPage.qml │ ├── AlbumDelegate.qml │ ├── AlbumPage.qml │ ├── AlbumPageHeader.qml │ ├── AlbumTracksSortPage.qml │ ├── AlbumsSortPage.qml │ ├── AllAlbumsPage.qml │ ├── AllTracksSortPage.qml │ ├── ArtistPage.qml │ ├── ArtistPageHeader.qml │ ├── ArtistsPage.qml │ ├── AsyncLoadingListView.qml │ ├── AuthorsPage.qml │ ├── BaseTrackDelegate.qml │ ├── BusyPanelBindings.qml │ ├── Cover.qml │ ├── DirectoriesPage.qml │ ├── DonatePage.qml │ ├── FilePickerDialog.qml │ ├── GenresPage.qml │ ├── LibraryDirectoriesPage.qml │ ├── LibraryPage.qml │ ├── LibraryTrackDelegate.qml │ ├── LibraryUpdateLabels.qml │ ├── LicensePage.qml │ ├── ListViewPlaceholder.qml │ ├── MainPage.qml │ ├── MainPageListItem.qml │ ├── MediaArt.qml │ ├── MediaContainerListItem.qml │ ├── MediaContainerSelectionDelegate.qml │ ├── NewPlaylistDialog.qml │ ├── NowPlayingPage.qml │ ├── NowPlayingPanel.qml │ ├── OpenUrlDialog.qml │ ├── ParentDirectoryItem.qml │ ├── PlaylistPage.qml │ ├── PlaylistsPage.qml │ ├── QueuePage.qml │ ├── RemoveFilesDialog.qml │ ├── SearchMenuItem.qml │ ├── SearchPanel.qml │ ├── SelectionMenuItem.qml │ ├── SelectionPanel.qml │ ├── SettingsPage.qml │ ├── SortModeListItem.qml │ ├── TagEditDialog.qml │ ├── TagEditDialogListItem.qml │ ├── TrackInfoPage.qml │ ├── TracksPage.qml │ └── TranslatorsPage.qml └── main.qml ├── rpm ├── harbour-unplayer.spec ├── qtmpris.patch └── taglib.patch ├── src ├── CMakeLists.txt ├── abstractlibrarymodel.cpp ├── abstractlibrarymodel.h ├── albumsmodel.cpp ├── albumsmodel.h ├── artistsmodel.cpp ├── artistsmodel.h ├── asyncloadingmodel.h ├── commandlineparser.cpp ├── commandlineparser.h ├── dbusservice.cpp ├── dbusservice.h ├── directorycontentmodel.cpp ├── directorycontentmodel.h ├── directorycontentproxymodel.cpp ├── directorycontentproxymodel.h ├── directorytracksmodel.cpp ├── directorytracksmodel.h ├── fileutils.cpp ├── fileutils.h ├── filterproxymodel.cpp ├── filterproxymodel.h ├── genresmodel.cpp ├── genresmodel.h ├── librarydirectoriesmodel.cpp ├── librarydirectoriesmodel.h ├── librarymigrator.cpp ├── librarymigrator.h ├── librarytrack.h ├── librarytracksadder.cpp ├── librarytracksadder.h ├── libraryupdaterunnable.cpp ├── libraryupdaterunnable.h ├── libraryutils.cpp ├── libraryutils.h ├── license.html ├── main.cpp ├── mediaartutils.cpp ├── mediaartutils.h ├── modelutils.h ├── org.equeim.unplayer.xml ├── org.freedesktop.Application.xml ├── player.cpp ├── player.h ├── playlistmodel.cpp ├── playlistmodel.h ├── playlistsmodel.cpp ├── playlistsmodel.h ├── playlistutils.cpp ├── playlistutils.h ├── qscopeguard.h ├── queue.cpp ├── queue.h ├── queuemodel.cpp ├── queuemodel.h ├── resources.qrc ├── settings.cpp ├── settings.h ├── signalhandler.cpp ├── signalhandler.h ├── sqlutils.h ├── stdutils.h ├── tagutils.cpp ├── tagutils.h ├── trackinfo.cpp ├── trackinfo.h ├── tracksmodel.cpp ├── tracksmodel.h ├── tracksquery.h ├── translators.html ├── utils.cpp ├── utils.h └── utilsfunctions.h └── translations ├── CMakeLists.txt ├── harbour-unplayer-ar.ts ├── harbour-unplayer-de.ts ├── harbour-unplayer-en.ts ├── harbour-unplayer-es.ts ├── harbour-unplayer-fr.ts ├── harbour-unplayer-hr.ts ├── harbour-unplayer-it.ts ├── harbour-unplayer-nb.ts ├── harbour-unplayer-nl.ts ├── harbour-unplayer-nl_BE.ts ├── harbour-unplayer-oc.ts ├── harbour-unplayer-pl.ts ├── harbour-unplayer-ru.ts ├── harbour-unplayer-sv.ts ├── harbour-unplayer-zh_CN.ts └── update.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | 4 | IndentWidth: 4 5 | NamespaceIndentation: All 6 | AccessModifierOffset: -4 7 | IndentCaseLabels: false 8 | 9 | KeepEmptyLinesAtTheStartOfBlocks: true 10 | 11 | ColumnLimit: 0 12 | AlignTrailingComments: false 13 | SpacesBeforeTrailingComments: 1 14 | BinPackArguments: false 15 | 16 | AllowShortBlocksOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | 19 | AlwaysBreakBeforeMultilineStrings: false 20 | 21 | BreakBeforeBraces: Custom 22 | BraceWrapping: 23 | AfterClass: true 24 | AfterControlStatement: false 25 | AfterEnum: true 26 | AfterFunction: true 27 | AfterNamespace: true 28 | AfterObjCDeclaration: false 29 | AfterStruct: true 30 | AfterUnion: true 31 | BeforeCatch: false 32 | BeforeElse: false 33 | IndentBraces: false 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.list 2 | .lock-waf* 3 | *.user 4 | *.user.* 5 | .waf* 6 | build-* 7 | rpm/*.spec.* 8 | RPMS 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/qtmpris"] 2 | path = 3rdparty/qtmpris 3 | url = https://git.sailfishos.org/mer-core/qtmpris.git 4 | [submodule "3rdparty/taglib"] 5 | path = 3rdparty/taglib 6 | url = https://github.com/taglib/taglib 7 | [submodule "3rdparty/cxxopts"] 8 | path = 3rdparty/cxxopts 9 | url = https://github.com/jarro2783/cxxopts 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | cmake_policy(VERSION ${CMAKE_MINIMUM_REQUIRED_VERSION}..3.17) 3 | 4 | project(harbour-unplayer VERSION 2.1.1 LANGUAGES CXX) 5 | 6 | include(GNUInstallDirs) 7 | 8 | option(HARBOUR "Build for Harbour" ON) 9 | option(QTMPRIS_STATIC "Link with qtmpris statically" OFF) 10 | option(TAGLIB_STATIC "Link with taglib statically" OFF) 11 | 12 | option(SAILFISHOS "Build for Sailfish OS" ON) 13 | 14 | add_subdirectory("src") 15 | add_subdirectory("translations") 16 | 17 | install(DIRECTORY "icons/hicolor" DESTINATION "${CMAKE_INSTALL_DATADIR}/icons") 18 | 19 | set(qml_dir "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/qml") 20 | install(FILES "qml/main.qml" DESTINATION "${qml_dir}") 21 | install(DIRECTORY "qml/components" DESTINATION "${qml_dir}") 22 | if (HARBOUR) 23 | set(media_keys "qml/MediaKeysEmpty.qml") 24 | else() 25 | set(media_keys "qml/MediaKeysPrivate.qml") 26 | endif() 27 | install(FILES "${media_keys}" 28 | DESTINATION "${qml_dir}/components" 29 | RENAME "MediaKeys.qml") 30 | 31 | install(FILES "${PROJECT_NAME}.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications") 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unplayer 2 | Simple music player for Sailfish OS 3 | 4 | ## Building 5 | 1. Clone repo: 6 | ```sh 7 | git clone https://github.com/equeim/unplayer 8 | cd unplayer 9 | git submodule update --init 10 | ``` 11 | 2. Install Sailfish SDK 12 | 3. Rad [this](https://sailfishos.org/wiki/Tutorial_-_Building_packages_-_advanced_techniques) tutorial to learn how to use `sfdk` tool. 13 | 4. Build package: 14 | ```sh 15 | cd /path/to/sources 16 | sfdk -c no-fix-version -c target= build -p -d -j 17 | ``` 18 | 5. Built RPMs will be in the `RPMS` directory. 19 | 20 | ## Translations 21 | [![Translation status](https://hosted.weblate.org/widgets/unplayer/-/svg-badge.svg)](https://hosted.weblate.org/engage/unplayer/?utm_source=widget) 22 | 23 | Translations are managed on [Hosted Weblate](https://hosted.weblate.org/projects/unplayer/translations). 24 | 25 | ## Donate 26 | I you like this app, you can support its development via 27 | [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=DDQTRHTY5YV2G&item_name=Support%20Unplayer%20development&no_note=1&item_number=1&no_shipping=1¤cy_code=EUR) or [Yandex.Money](https://yasobe.ru/na/equeim_unplayer). 28 | -------------------------------------------------------------------------------- /harbour-unplayer.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | X-Nemo-Application-Type=silica-qt5 4 | Icon=harbour-unplayer 5 | Exec=harbour-unplayer %F 6 | Name=Unplayer 7 | MimeType=audio/flac;audio/x-flac;audio/aac;audio/x-aac;audio/mp4;audio/x-m4a;audio/x-m4b;audio/mpeg;audio/x-mp3;audio/x-mpeg;audio/mp3;application/ogg;audio/ogg;audio/x-ogg;audio/x-vorbis+ogg;audio/vorbis;audio/x-vorbis;audio/x-flac+ogg;audio/x-oggflac;audio/x-ape;audio/x-matroska;audio/x-wav;audio/wav;audio/vnd.wave;audio/x-scpls;audio/x-mpegurl;application/vnd.apple.mpegurl; 8 | -------------------------------------------------------------------------------- /harbour-unplayer.svg: -------------------------------------------------------------------------------- 1 | 2 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 55 | 57 | 61 | 65 | 66 | 75 | 76 | 83 | 86 | 89 | 92 | 100 | 108 | 109 | 111 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /icons/hicolor/108x108/apps/harbour-unplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equeim/unplayer/3250b419f74211adb3e4d6f545c53a498ef77d47/icons/hicolor/108x108/apps/harbour-unplayer.png -------------------------------------------------------------------------------- /icons/hicolor/128x128/apps/harbour-unplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equeim/unplayer/3250b419f74211adb3e4d6f545c53a498ef77d47/icons/hicolor/128x128/apps/harbour-unplayer.png -------------------------------------------------------------------------------- /icons/hicolor/172x172/apps/harbour-unplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equeim/unplayer/3250b419f74211adb3e4d6f545c53a498ef77d47/icons/hicolor/172x172/apps/harbour-unplayer.png -------------------------------------------------------------------------------- /icons/hicolor/256x256/apps/harbour-unplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equeim/unplayer/3250b419f74211adb3e4d6f545c53a498ef77d47/icons/hicolor/256x256/apps/harbour-unplayer.png -------------------------------------------------------------------------------- /icons/hicolor/86x86/apps/harbour-unplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equeim/unplayer/3250b419f74211adb3e4d6f545c53a498ef77d47/icons/hicolor/86x86/apps/harbour-unplayer.png -------------------------------------------------------------------------------- /qml/MediaKeysEmpty.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | 21 | QtObject { } 22 | -------------------------------------------------------------------------------- /qml/MediaKeysPrivate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Media 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Item { 25 | MediaKey { 26 | enabled: true 27 | key: Qt.Key_MediaTogglePlayPause 28 | onReleased: Unplayer.Player.playing ? Unplayer.Player.pause() : Unplayer.Player.play() 29 | } 30 | MediaKey { 31 | enabled: true 32 | key: Qt.Key_MediaPlay 33 | onReleased: Unplayer.Player.play() 34 | } 35 | MediaKey { 36 | enabled: true 37 | key: Qt.Key_MediaPause 38 | onReleased: Unplayer.Player.pause() 39 | } 40 | MediaKey { 41 | enabled: true 42 | key: Qt.Key_MediaStop 43 | onReleased: Unplayer.Player.stop() 44 | } 45 | MediaKey { 46 | enabled: true 47 | key: Qt.Key_MediaNext 48 | onReleased: Unplayer.Player.queue.next() 49 | } 50 | MediaKey { 51 | enabled: true 52 | key: Qt.Key_MediaPrevious 53 | onReleased: Unplayer.Player.queue.previous() 54 | } 55 | MediaKey { 56 | enabled: true 57 | key: Qt.Key_ToggleCallHangup 58 | onReleased: Unplayer.Player.playing ? Unplayer.Player.pause() : Unplayer.Player.play() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /qml/components/AddToPlaylistPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Page { 25 | id: page 26 | 27 | property var tracks 28 | property bool added 29 | 30 | SearchPanel { 31 | id: searchPanel 32 | } 33 | 34 | AsyncLoadingListView { 35 | id: listView 36 | 37 | anchors { 38 | fill: parent 39 | topMargin: searchPanel.visibleSize 40 | } 41 | clip: true 42 | 43 | page: page 44 | emptyText: qsTranslate("unplayer", "No playlists") 45 | 46 | header: PageHeader { 47 | title: qsTranslate("unplayer", "Add to playlist") 48 | } 49 | delegate: MediaContainerListItem { 50 | title: Theme.highlightText(model.name, searchPanel.searchText, Theme.highlightColor) 51 | description: qsTranslate("unplayer", "%n track(s)", String(), model.tracksCount) 52 | onClicked: { 53 | if (typeof tracks.length !== "undefined") { 54 | Unplayer.PlaylistUtils.addTracksToPlaylistFromFilesystem(model.filePath, tracks) 55 | } else { 56 | Unplayer.PlaylistUtils.addTracksToPlaylistFromLibrary(model.filePath, tracks) 57 | } 58 | 59 | added = true 60 | pageStack.pop() 61 | } 62 | } 63 | model: Unplayer.FilterProxyModel { 64 | filterRole: Unplayer.PlaylistsModel.NameRole 65 | sourceModel: Unplayer.PlaylistsModel { 66 | id: playlistsModel 67 | } 68 | } 69 | 70 | PullDownMenu { 71 | MenuItem { 72 | text: qsTranslate("unplayer", "New playlist...") 73 | onClicked: pageStack.push(newPlaylistDialog, { acceptDestination: pageStack.previousPage() }) 74 | 75 | Component { 76 | id: newPlaylistDialog 77 | 78 | NewPlaylistDialog { 79 | acceptDestinationAction: PageStackAction.Pop 80 | tracks: page.tracks 81 | onAccepted: { 82 | added = true 83 | } 84 | } 85 | } 86 | } 87 | 88 | SearchMenuItem { } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /qml/components/AlbumDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | MediaContainerSelectionDelegate { 25 | id: albumDelegate 26 | 27 | property bool allArtists 28 | property int singleArtistId 29 | 30 | title: Theme.highlightText(model.displayedAlbum, searchPanel.searchText, Theme.highlightColor) 31 | secondDescription: model.year 32 | mediaArt: model.mediaArt 33 | 34 | menu: Component { 35 | ContextMenu { 36 | MenuItem { 37 | text: qsTranslate("unplayer", "Replace queue") 38 | onClicked: Unplayer.Player.queue.addTracksFromLibrary(albumsModel.getTracksForAlbum(albumsProxyModel.sourceIndex(model.index)), true) 39 | } 40 | 41 | MenuItem { 42 | text: qsTranslate("unplayer", "Add to queue") 43 | onClicked: Unplayer.Player.queue.addTracksFromLibrary(albumsModel.getTracksForAlbum(albumsProxyModel.sourceIndex(model.index))) 44 | } 45 | 46 | MenuItem { 47 | text: qsTranslate("unplayer", "Add to playlist") 48 | onClicked: pageStack.push("AddToPlaylistPage.qml", { tracks: albumsModel.getTracksForAlbum(albumsProxyModel.sourceIndex(model.index)) }) 49 | } 50 | 51 | MenuItem { 52 | text: qsTranslate("unplayer", "Edit tags") 53 | onClicked: pageStack.push("TagEditDialog.qml", {files: albumsModel.getTrackPathsForAlbum(albumsProxyModel.sourceIndex(model.index))}) 54 | } 55 | 56 | MenuItem { 57 | text: qsTranslate("unplayer", "Remove") 58 | onClicked: pageStack.push(removeAlbumDialog) 59 | } 60 | } 61 | } 62 | 63 | onClicked: { 64 | if (selectionPanel.showPanel) { 65 | listView.model.select(model.index) 66 | } else { 67 | pageStack.push(albumPageComponent) 68 | } 69 | } 70 | 71 | Component { 72 | id: albumPageComponent 73 | AlbumPage { 74 | displayedArtist: model.displayedArtist 75 | displayedAlbum: model.displayedAlbum 76 | tracksCount: model.tracksCount 77 | duration: model.duration 78 | mediaArt: albumDelegate.mediaArt 79 | 80 | albumId: model.albumId 81 | allArtists: albumDelegate.allArtists 82 | singleArtistId: albumDelegate.singleArtistId 83 | } 84 | } 85 | 86 | Component { 87 | id: filePickerDialogComponent 88 | FilePickerDialog { 89 | title: qsTranslate("unplayer", "Select Image") 90 | fileIcon: "image://theme/icon-m-image" 91 | nameFilters: Unplayer.Utils.imageNameFilters 92 | onAccepted: Unplayer.MediaArtUtils.setUserMediaArt(model.albumId, filePath) 93 | } 94 | } 95 | 96 | Component { 97 | id: removeAlbumDialog 98 | 99 | RemoveFilesDialog { 100 | title: qsTranslate("unplayer", "Are you sure you want to remove this album?") 101 | onAccepted: { 102 | albumsModel.removeAlbum(albumsProxyModel.sourceIndex(model.index), deleteFiles) 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /qml/components/AlbumPageHeader.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Item { 25 | property alias searchFieldY: pageHeader.height 26 | 27 | property int extraHeight: height - pageHeader.height 28 | 29 | width: listView.width 30 | height: listView.showSearchField ? pageHeader.height + listView.searchFieldHeight : 31 | childrenRect.height 32 | 33 | Behavior on height { PropertyAnimation { } } 34 | 35 | PageHeader { 36 | id: pageHeader 37 | title: displayedAlbum 38 | } 39 | 40 | Rectangle { 41 | anchors { 42 | left: parent.left 43 | right: parent.right 44 | top: pageHeader.bottom 45 | } 46 | height: Theme.itemSizeExtraLarge + Theme.paddingLarge * 2 47 | 48 | gradient: Gradient { 49 | GradientStop { 50 | position: 0.0 51 | color: "transparent" 52 | } 53 | GradientStop { 54 | position: 1.0 55 | color: Theme.rgba(Theme.highlightBackgroundColor, 0.2) 56 | } 57 | } 58 | 59 | opacity: listView.showSearchField ? 0.0 : 1.0 60 | 61 | Behavior on opacity { 62 | FadeAnimator { } 63 | } 64 | 65 | MediaArt { 66 | id: mediaArt 67 | 68 | anchors.verticalCenter: parent.verticalCenter 69 | x: Theme.horizontalPageMargin 70 | 71 | source: albumPage.mediaArt 72 | size: Theme.itemSizeExtraLarge 73 | } 74 | 75 | Column { 76 | anchors { 77 | left: mediaArt.right 78 | leftMargin: Theme.paddingLarge 79 | right: parent.right 80 | rightMargin: Theme.horizontalPageMargin 81 | verticalCenter: parent.verticalCenter 82 | } 83 | 84 | Label { 85 | font.pixelSize: Theme.fontSizeSmall 86 | text: displayedArtist 87 | textFormat: Text.StyledText 88 | width: parent.width 89 | truncationMode: TruncationMode.Fade 90 | } 91 | 92 | Label { 93 | font.pixelSize: Theme.fontSizeSmall 94 | text: qsTranslate("unplayer", "%n track(s)", String(), tracksCount) 95 | width: parent.width 96 | truncationMode: TruncationMode.Fade 97 | } 98 | 99 | Label { 100 | font.pixelSize: Theme.fontSizeSmall 101 | text: Unplayer.Utils.formatDuration(duration) 102 | width: parent.width 103 | truncationMode: TruncationMode.Fade 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /qml/components/AlbumTracksSortPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Page { 25 | property var tracksModel 26 | 27 | SilicaFlickable { 28 | anchors.fill: parent 29 | contentHeight: column.height 30 | 31 | Column { 32 | id: column 33 | width: parent.width 34 | 35 | PageHeader { 36 | title: qsTranslate("unplayer", "Sort") 37 | } 38 | 39 | ComboBox { 40 | label: qsTranslate("unplayer", "Order") 41 | menu: ContextMenu { 42 | MenuItem { 43 | text: qsTranslate("unplayer", "Ascending") 44 | onClicked: tracksModel.sortDescending = false 45 | } 46 | 47 | MenuItem { 48 | text: qsTranslate("unplayer", "Descending") 49 | onClicked: tracksModel.sortDescending = true 50 | } 51 | } 52 | 53 | Component.onCompleted: { 54 | if (tracksModel.sortDescending) { 55 | currentIndex = 1 56 | } 57 | } 58 | } 59 | 60 | Separator { 61 | width: parent.width 62 | color: Theme.secondaryColor 63 | } 64 | 65 | SortModeListItem { 66 | current: tracksModel.insideAlbumSortMode === Unplayer.TracksModelInsideAlbumSortMode.Title 67 | text: qsTranslate("unplayer", "Title") 68 | onClicked: tracksModel.insideAlbumSortMode = Unplayer.TracksModelInsideAlbumSortMode.Title 69 | } 70 | 71 | SortModeListItem { 72 | current: tracksModel.insideAlbumSortMode === Unplayer.TracksModelInsideAlbumSortMode.DiscNumber_Title 73 | text: qsTranslate("unplayer", "Disc number - Title") 74 | onClicked: tracksModel.insideAlbumSortMode = Unplayer.TracksModelInsideAlbumSortMode.DiscNumber_Title 75 | } 76 | 77 | SortModeListItem { 78 | current: tracksModel.insideAlbumSortMode === Unplayer.TracksModelInsideAlbumSortMode.DiscNumber_TrackNumber 79 | text: qsTranslate("unplayer", "Disc number - Track number") 80 | onClicked: tracksModel.insideAlbumSortMode = Unplayer.TracksModelInsideAlbumSortMode.DiscNumber_TrackNumber 81 | } 82 | } 83 | 84 | VerticalScrollDecorator { } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /qml/components/AlbumsSortPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Page { 25 | property var albumsModel 26 | 27 | SilicaFlickable { 28 | anchors.fill: parent 29 | contentHeight: column.height 30 | 31 | Column { 32 | id: column 33 | width: parent.width 34 | 35 | PageHeader { 36 | title: qsTranslate("unplayer", "Sort") 37 | } 38 | 39 | ComboBox { 40 | label: qsTranslate("unplayer", "Order") 41 | menu: ContextMenu { 42 | MenuItem { 43 | text: qsTranslate("unplayer", "Ascending") 44 | onClicked: albumsModel.sortDescending = false 45 | } 46 | 47 | MenuItem { 48 | text: qsTranslate("unplayer", "Descending") 49 | onClicked: albumsModel.sortDescending = true 50 | } 51 | } 52 | 53 | Component.onCompleted: { 54 | if (albumsModel.sortDescending) { 55 | currentIndex = 1 56 | } 57 | } 58 | } 59 | 60 | Separator { 61 | width: parent.width 62 | color: Theme.secondaryColor 63 | } 64 | 65 | SortModeListItem { 66 | current: albumsModel.sortMode === Unplayer.AlbumsModel.SortAlbum 67 | text: qsTranslate("unplayer", "Album title") 68 | onClicked: albumsModel.sortMode = Unplayer.AlbumsModel.SortAlbum 69 | } 70 | 71 | SortModeListItem { 72 | current: albumsModel.sortMode === Unplayer.AlbumsModel.SortYear 73 | text: qsTranslate("unplayer", "Album year") 74 | onClicked: albumsModel.sortMode = Unplayer.AlbumsModel.SortYear 75 | } 76 | 77 | SortModeListItem { 78 | visible: albumsModel.allArtists 79 | current: albumsModel.sortMode === Unplayer.AlbumsModel.SortArtistAlbum 80 | text: qsTranslate("unplayer", "Artist - Album title") 81 | onClicked: albumsModel.sortMode = Unplayer.AlbumsModel.SortArtistAlbum 82 | } 83 | 84 | SortModeListItem { 85 | visible: albumsModel.allArtists 86 | current: albumsModel.sortMode === Unplayer.AlbumsModel.SortArtistYear 87 | text: qsTranslate("unplayer", "Artist - Album year") 88 | onClicked: albumsModel.sortMode = Unplayer.AlbumsModel.SortArtistYear 89 | } 90 | } 91 | 92 | VerticalScrollDecorator { } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /qml/components/ArtistPageHeader.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Item { 25 | property alias searchFieldY: pageHeader.height 26 | 27 | property int extraHeight: height - pageHeader.height 28 | 29 | width: listView.width 30 | height: listView.showSearchField ? pageHeader.height + listView.searchFieldHeight : 31 | childrenRect.height 32 | 33 | Behavior on height { PropertyAnimation { } } 34 | 35 | PageHeader { 36 | id: pageHeader 37 | title: displayedArtist 38 | } 39 | 40 | Rectangle { 41 | anchors { 42 | left: parent.left 43 | right: parent.right 44 | top: pageHeader.bottom 45 | } 46 | height: Theme.itemSizeExtraLarge + Theme.paddingLarge * 2 47 | 48 | gradient: Gradient { 49 | GradientStop { 50 | position: 0.0 51 | color: "transparent" 52 | } 53 | GradientStop { 54 | position: 1.0 55 | color: Theme.rgba(Theme.highlightBackgroundColor, 0.2) 56 | } 57 | } 58 | 59 | opacity: listView.showSearchField ? 0 : 1 60 | 61 | Behavior on opacity { 62 | FadeAnimator { } 63 | } 64 | 65 | MediaArt { 66 | id: mediaArt 67 | 68 | anchors.verticalCenter: parent.verticalCenter 69 | x: Theme.horizontalPageMargin 70 | 71 | source: artistPage.mediaArt 72 | size: Theme.itemSizeExtraLarge 73 | } 74 | 75 | Column { 76 | anchors { 77 | left: mediaArt.right 78 | leftMargin: Theme.paddingLarge 79 | right: parent.right 80 | rightMargin: Theme.horizontalPageMargin 81 | verticalCenter: parent.verticalCenter 82 | } 83 | 84 | Label { 85 | font.pixelSize: Theme.fontSizeSmall 86 | text: qsTranslate("unplayer", "%n album(s)", String(), albumsCount) 87 | width: parent.width 88 | truncationMode: TruncationMode.Fade 89 | } 90 | 91 | Label { 92 | font.pixelSize: Theme.fontSizeSmall 93 | text: qsTranslate("unplayer", "%n track(s)", String(), tracksCount) 94 | width: parent.width 95 | truncationMode: TruncationMode.Fade 96 | } 97 | 98 | Label { 99 | font.pixelSize: Theme.fontSizeSmall 100 | text: Unplayer.Utils.formatDuration(duration) 101 | width: parent.width 102 | truncationMode: TruncationMode.Fade 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /qml/components/AsyncLoadingListView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.6 20 | import Sailfish.Silica 1.0 21 | 22 | SilicaListView { 23 | id: listView 24 | 25 | property Page page 26 | property string emptyText 27 | 28 | property int originalCacheBuffer 29 | property var sourceModel: model.sourceModel ? model.sourceModel : model 30 | 31 | // 32 | // If model is loaded before Page's push animation is completed, 33 | // delegates creation causes visible frame drop. 34 | // Set cacheBuffer to 0 until animation is complete to create only 35 | // visible delegates and reduce frame drop 36 | // 37 | 38 | Component.onCompleted: { 39 | originalCacheBuffer = cacheBuffer 40 | cacheBuffer = 0 41 | } 42 | 43 | Connections { 44 | target: page 45 | onStatusChanged: { 46 | if (status === PageStatus.Active) { 47 | if (listView.cacheBuffer !== listView.originalCacheBuffer) { 48 | listView.cacheBuffer = listView.originalCacheBuffer 49 | } 50 | } 51 | } 52 | } 53 | 54 | ListViewPlaceholder { 55 | id: placeholder 56 | listView: listView 57 | loading: sourceModel.loading 58 | text: emptyText 59 | } 60 | 61 | BusyIndicator { 62 | anchors.horizontalCenter: parent.horizontalCenter 63 | y: Math.round(page.isPortrait ? Screen.height/4 : Screen.width/4) + placeholder.verticalOffset 64 | size: BusyIndicatorSize.Large 65 | running: sourceModel.loading 66 | } 67 | 68 | VerticalScrollDecorator { } 69 | } 70 | -------------------------------------------------------------------------------- /qml/components/AuthorsPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | Page { 23 | SilicaListView { 24 | anchors.fill: parent 25 | 26 | header: PageHeader { 27 | title: qsTranslate("unplayer", "Authors") 28 | } 29 | delegate: Column { 30 | anchors { 31 | left: parent.left 32 | leftMargin: Theme.horizontalPageMargin 33 | right: parent.right 34 | rightMargin: Theme.horizontalPageMargin 35 | } 36 | 37 | Label { 38 | text: ("" + 39 | "%2 <%3>") 40 | .arg(Theme.highlightColor) 41 | .arg(model.name) 42 | .arg(model.email) 43 | 44 | textFormat: Text.RichText 45 | onLinkActivated: Qt.openUrlExternally(link) 46 | } 47 | 48 | Label { 49 | font.pixelSize: Theme.fontSizeSmall 50 | color: Theme.secondaryColor 51 | text: { 52 | switch (type) { 53 | case "maintainer": 54 | qsTranslate("unplayer", "Maintainer") 55 | break 56 | case "contributor": 57 | qsTranslate("unplayer", "Contributor") 58 | break 59 | default: 60 | String() 61 | break 62 | } 63 | } 64 | } 65 | } 66 | spacing: Theme.paddingMedium 67 | 68 | model: ListModel { 69 | ListElement { 70 | name: "Alexey Rochev" 71 | email: "equeim@gmail.com" 72 | type: "maintainer" 73 | } 74 | ListElement { 75 | name: "Rony Fadel" 76 | email: "rony.fadel@gmail.com" 77 | type: "contributor" 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /qml/components/BaseTrackDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | ListItem { 23 | id: trackDelegate 24 | 25 | property bool current 26 | 27 | property bool showArtist 28 | property bool showAlbum 29 | property bool showArtistAndAlbum 30 | property bool showUrl 31 | 32 | showMenuOnPressAndHold: !selectionPanel.showPanel 33 | 34 | ListView.onRemove: animateRemoval() 35 | 36 | Binding { 37 | target: trackDelegate 38 | property: "highlighted" 39 | value: down || menuOpen || listView.model.isSelected(model.index) 40 | } 41 | 42 | Connections { 43 | target: listView.model 44 | onSelectionChanged: trackDelegate.highlighted = listView.model.isSelected(model.index) 45 | } 46 | 47 | Column { 48 | anchors { 49 | left: parent.left 50 | leftMargin: Theme.horizontalPageMargin 51 | right: durationLabel.left 52 | rightMargin: Theme.paddingMedium 53 | verticalCenter: parent.verticalCenter 54 | } 55 | 56 | Label { 57 | color: highlighted || current ? Theme.highlightColor : Theme.primaryColor 58 | text: Theme.highlightText(model.title, searchPanel.searchText, Theme.highlightColor) 59 | textFormat: Text.StyledText 60 | truncationMode: TruncationMode.Fade 61 | width: parent.width 62 | } 63 | 64 | Label { 65 | visible: text 66 | color: highlighted || current ? Theme.secondaryHighlightColor : Theme.secondaryColor 67 | font.pixelSize: Theme.fontSizeExtraSmall 68 | text: { 69 | if (showArtistAndAlbum && model.artist && model.album) { 70 | return qsTranslate("unplayer", "%1 - %2").arg(model.artist).arg(model.album) 71 | } 72 | 73 | if (showArtist) { 74 | return model.artist 75 | } 76 | 77 | if (showAlbum) { 78 | return model.album 79 | } 80 | 81 | if (showUrl) { 82 | return model.url 83 | } 84 | 85 | return String() 86 | } 87 | 88 | textFormat: Text.StyledText 89 | truncationMode: TruncationMode.Fade 90 | width: parent.width 91 | } 92 | } 93 | 94 | Label { 95 | id: durationLabel 96 | 97 | anchors { 98 | right: parent.right 99 | rightMargin: Theme.horizontalPageMargin 100 | bottom: parent.bottom 101 | bottomMargin: Theme.paddingSmall 102 | } 103 | 104 | color: highlighted || current ? Theme.secondaryHighlightColor : Theme.secondaryColor 105 | font.pixelSize: Theme.fontSizeExtraSmall 106 | text: { 107 | if (model.duration >= 0) { 108 | return Format.formatDuration(model.duration, model.duration >= 3600? Format.DurationLong : 109 | Format.DurationShort) 110 | } 111 | return String() 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /qml/components/BusyPanelBindings.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | 21 | QtObject { 22 | id: bindings 23 | 24 | property bool when 25 | 26 | Component.onDestruction: { 27 | busyPanelActiveBinding.when = false 28 | busyPanelTextBinding.when = false 29 | } 30 | 31 | property Binding busyPanelActiveBinding: Binding { 32 | target: nowPlayingPanel 33 | property: "busyPanelActive" 34 | value: true 35 | when: bindings.when 36 | } 37 | 38 | property Binding busyPanelTextBinding: Binding { 39 | target: nowPlayingPanel 40 | property: "busyPanelText" 41 | value: qsTranslate("unplayer", "Removing files...") 42 | when: bindings.when 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /qml/components/Cover.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | CoverBackground { 25 | CoverPlaceholder { 26 | id: placeholder 27 | 28 | icon.source: "image://theme/harbour-unplayer" 29 | text: "Unplayer" 30 | visible: Unplayer.Player.queue.currentIndex === -1 31 | } 32 | 33 | Item { 34 | anchors.fill: parent 35 | visible: !placeholder.visible 36 | 37 | Image { 38 | id: mediaArtImage 39 | anchors.fill: parent 40 | fillMode: Image.PreserveAspectCrop 41 | source: Unplayer.Player.queue.currentMediaArt 42 | sourceSize.height: parent.height 43 | asynchronous: true 44 | } 45 | 46 | OpacityRampEffect { 47 | direction: OpacityRamp.BottomToTop 48 | offset: 0.15 49 | slope: 1.0 50 | sourceItem: mediaArtImage 51 | } 52 | 53 | Column { 54 | anchors { 55 | left: parent.left 56 | leftMargin: Theme.paddingMedium 57 | right: parent.right 58 | rightMargin: Theme.paddingMedium 59 | top: parent.top 60 | topMargin: Theme.paddingMedium 61 | } 62 | spacing: Theme.paddingSmall 63 | 64 | Label { 65 | property int position: Unplayer.Player.position 66 | 67 | anchors.horizontalCenter: parent.horizontalCenter 68 | color: Theme.highlightColor 69 | font.pixelSize: position >= 3600000 ? Theme.fontSizeExtraLarge : Theme.fontSizeHuge 70 | opacity: Unplayer.Player.playing ? 1.0 : 0.4 71 | text: Format.formatDuration(position / 1000, position >= 3600000? Format.DurationLong : 72 | Format.DurationShort) 73 | } 74 | 75 | Label { 76 | horizontalAlignment: implicitWidth > width ? Text.AlignLeft : Text.AlignHCenter 77 | color: Theme.highlightColor 78 | text: Unplayer.Player.queue.currentArtist 79 | truncationMode: TruncationMode.Fade 80 | width: parent.width 81 | } 82 | 83 | Label { 84 | horizontalAlignment: implicitWidth > width ? Text.AlignLeft : Text.AlignHCenter 85 | maximumLineCount: 3 86 | text: Unplayer.Player.queue.currentTitle 87 | truncationMode: TruncationMode.Elide 88 | width: parent.width 89 | wrapMode: Text.WordWrap 90 | } 91 | } 92 | } 93 | 94 | CoverActionList { 95 | enabled: !placeholder.visible 96 | iconBackground: true 97 | 98 | CoverAction { 99 | iconSource: "image://theme/icon-cover-previous-song" 100 | onTriggered: Unplayer.Player.queue.previous() 101 | } 102 | 103 | CoverAction { 104 | iconSource: Unplayer.Player.playing ? "image://theme/icon-cover-pause" : 105 | "image://theme/icon-cover-play" 106 | onTriggered: { 107 | if (Unplayer.Player.playing) { 108 | Unplayer.Player.pause() 109 | } else { 110 | Unplayer.Player.play() 111 | } 112 | } 113 | } 114 | 115 | CoverAction { 116 | iconSource: "image://theme/icon-cover-next-song" 117 | onTriggered: Unplayer.Player.queue.next() 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /qml/components/DonatePage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | Page { 23 | SilicaFlickable { 24 | anchors.fill: parent 25 | 26 | Column { 27 | width: parent.width 28 | spacing: Theme.paddingMedium 29 | 30 | PageHeader { 31 | title: qsTranslate("unplayer", "Donate") 32 | } 33 | 34 | Button { 35 | anchors.horizontalCenter: parent.horizontalCenter 36 | width: Theme.buttonWidthLarge 37 | text: "PayPal" 38 | onClicked: Qt.openUrlExternally("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=DDQTRHTY5YV2G&item_name=Support%20Unplayer%20development&no_note=1&item_number=1&no_shipping=1¤cy_code=EUR") 39 | } 40 | 41 | Button { 42 | anchors.horizontalCenter: parent.horizontalCenter 43 | width: Theme.buttonWidthLarge 44 | text: "Yandex.Money" 45 | onClicked: Qt.openUrlExternally("https://yasobe.ru/na/equeim_unplayer") 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /qml/components/LibraryPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Page { 25 | SilicaFlickable { 26 | anchors.fill: parent 27 | contentHeight: column.height 28 | 29 | PullDownMenu { 30 | MenuItem { 31 | text: qsTranslate("unplayer", "Reset Library") 32 | onClicked: Unplayer.LibraryUtils.resetDatabase() 33 | } 34 | 35 | MenuItem { 36 | text: qsTranslate("unplayer", "Update Library") 37 | onClicked: Unplayer.LibraryUtils.updateDatabase() 38 | } 39 | } 40 | 41 | Column { 42 | id: column 43 | width: parent.width 44 | 45 | PageHeader { 46 | title: qsTranslate("unplayer", "Library") 47 | } 48 | 49 | MainPageListItem { 50 | title: qsTranslate("unplayer", "Artists") 51 | description: qsTranslate("unplayer", "%n artist(s)", String(), Unplayer.LibraryUtils.artistsCount) 52 | randomMediaArt: Unplayer.RandomMediaArt {} 53 | onClicked: pageStack.push(artistsPageComponent) 54 | 55 | Component { 56 | id: artistsPageComponent 57 | ArtistsPage { } 58 | } 59 | } 60 | 61 | MainPageListItem { 62 | title: qsTranslate("unplayer", "Albums") 63 | description: qsTranslate("unplayer", "%n albums(s)", String(), Unplayer.LibraryUtils.albumsCount) 64 | randomMediaArt: Unplayer.RandomMediaArt {} 65 | onClicked: pageStack.push(allAlbumsPageComponent) 66 | 67 | Component { 68 | id: allAlbumsPageComponent 69 | AllAlbumsPage { } 70 | } 71 | } 72 | 73 | MainPageListItem { 74 | title: qsTranslate("unplayer", "Tracks") 75 | description: { 76 | var tracksCount = Unplayer.LibraryUtils.tracksCount 77 | if (!tracksCount === 0) { 78 | return qsTranslate("unplayer", "%n tracks(s)", String(), 0) 79 | } 80 | return qsTranslate("unplayer", "%1, %2") 81 | .arg(qsTranslate("unplayer", "%n tracks(s)", String(), tracksCount)) 82 | .arg(Unplayer.Utils.formatDuration(Unplayer.LibraryUtils.tracksDuration)) 83 | } 84 | randomMediaArt: Unplayer.RandomMediaArt {} 85 | 86 | onClicked: pageStack.push(tracksPageComponent) 87 | 88 | Component { 89 | id: tracksPageComponent 90 | TracksPage { 91 | pageTitle: qsTranslate("unplayer", "Tracks") 92 | queryMode: Unplayer.TracksModel.QueryAllTracks 93 | } 94 | } 95 | } 96 | 97 | MainPageListItem { 98 | title: qsTranslate("unplayer", "Genres") 99 | randomMediaArt: Unplayer.RandomMediaArt {} 100 | onClicked: pageStack.push("GenresPage.qml") 101 | } 102 | } 103 | 104 | VerticalScrollDecorator { } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /qml/components/LibraryTrackDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | BaseTrackDelegate { 25 | current: model.filePath === Unplayer.Player.queue.currentFilePath 26 | menu: Component { 27 | ContextMenu { 28 | MenuItem { 29 | text: qsTranslate("unplayer", "Track information") 30 | onClicked: pageStack.push("TrackInfoPage.qml", { filePath: model.filePath }) 31 | } 32 | 33 | MenuItem { 34 | text: qsTranslate("unplayer", "Add to queue") 35 | onClicked: Unplayer.Player.queue.addTrackFromLibrary(tracksModel.getTrack(tracksProxyModel.sourceIndex(model.index))) 36 | } 37 | 38 | MenuItem { 39 | text: qsTranslate("unplayer", "Add to playlist") 40 | onClicked: pageStack.push("AddToPlaylistPage.qml", { tracks: tracksModel.getTrack(tracksProxyModel.sourceIndex(model.index)) }) 41 | } 42 | 43 | MenuItem { 44 | text: qsTranslate("unplayer", "Edit tags") 45 | onClicked: pageStack.push("TagEditDialog.qml", {files: [model.filePath]}) 46 | } 47 | 48 | MenuItem { 49 | text: qsTranslate("unplayer", "Remove") 50 | onClicked: pageStack.push(removeTrackDialog) 51 | } 52 | } 53 | } 54 | 55 | onClicked: { 56 | if (selectionPanel.showPanel) { 57 | tracksProxyModel.select(model.index) 58 | } else { 59 | if (current) { 60 | if (!Unplayer.Player.playing) { 61 | Unplayer.Player.play() 62 | } 63 | } else { 64 | Unplayer.Player.queue.addTracksFromLibrary(tracksModel.getTracks(tracksProxyModel.sourceIndexes), true, model.index) 65 | } 66 | } 67 | } 68 | 69 | Component { 70 | id: removeTrackDialog 71 | 72 | RemoveFilesDialog { 73 | title: qsTranslate("unplayer", "Are you sure you want to remove this track?") 74 | onAccepted: { 75 | tracksModel.removeTrack(tracksProxyModel.sourceIndex(model.index), deleteFiles) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /qml/components/LibraryUpdateLabels.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Column { 25 | property bool center 26 | 27 | anchors { 28 | left: parent.left 29 | leftMargin: Theme.horizontalPageMargin 30 | right: parent.right 31 | rightMargin: Theme.horizontalPageMargin 32 | } 33 | 34 | Label { 35 | anchors { 36 | left: parent.left 37 | right: parent.right 38 | } 39 | color: Theme.highlightColor 40 | horizontalAlignment: center && implicitWidth < width ? Text.AlignHCenter : Text.AlignLeft 41 | truncationMode: TruncationMode.Fade 42 | text: { 43 | switch (Unplayer.LibraryUtils.updateStage) { 44 | case Unplayer.LibraryUtils.PreparingStage: 45 | return qsTranslate("unplayer", "Preparing") 46 | case Unplayer.LibraryUtils.ScanningStage: 47 | return qsTranslate("unplayer", "Scanning filesystem") 48 | case Unplayer.LibraryUtils.ExtractingStage: 49 | return qsTranslate("unplayer", "Extracting metadata") 50 | case Unplayer.LibraryUtils.FinishingStage: 51 | return qsTranslate("unplayer", "Finishing") 52 | default: 53 | return "" 54 | } 55 | } 56 | } 57 | 58 | Label { 59 | anchors { 60 | left: parent.left 61 | right: parent.right 62 | } 63 | visible: switch (Unplayer.LibraryUtils.updateStage) { 64 | case Unplayer.LibraryUtils.ScanningStage: 65 | case Unplayer.LibraryUtils.ExtractingStage: 66 | return true 67 | default: 68 | return false 69 | } 70 | 71 | color: Theme.highlightColor 72 | horizontalAlignment: center && implicitWidth < width ? Text.AlignHCenter : Text.AlignLeft 73 | truncationMode: TruncationMode.Fade 74 | text: { 75 | switch (Unplayer.LibraryUtils.updateStage) { 76 | case Unplayer.LibraryUtils.ScanningStage: 77 | return qsTranslate("unplayer", "%n new tracks found", "", Unplayer.LibraryUtils.foundTracks) 78 | case Unplayer.LibraryUtils.ExtractingStage: 79 | return qsTranslate("unplayer", "%1 of %n tracks processed", "", Unplayer.LibraryUtils.foundTracks).arg(Unplayer.LibraryUtils.extractedTracks) 80 | default: 81 | return "" 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /qml/components/LicensePage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Page { 25 | SilicaFlickable { 26 | anchors.fill: parent 27 | contentHeight: column.height + Theme.paddingLarge 28 | 29 | Column { 30 | id: column 31 | width: parent.width 32 | 33 | PageHeader { 34 | title: qsTranslate("unplayer", "License") 35 | } 36 | 37 | Label { 38 | anchors { 39 | left: parent.left 40 | leftMargin: Theme.horizontalPageMargin 41 | right: parent.right 42 | rightMargin: Theme.horizontalPageMargin 43 | } 44 | font.pixelSize: Theme.fontSizeExtraSmall 45 | wrapMode: Text.WordWrap 46 | text: Unplayer.Utils.license.arg(Theme.highlightColor) 47 | textFormat: Text.RichText 48 | 49 | onLinkActivated: Qt.openUrlExternally(link) 50 | } 51 | } 52 | 53 | VerticalScrollDecorator { } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /qml/components/ListViewPlaceholder.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import Sailfish.Silica 1.0 20 | 21 | ViewPlaceholder { 22 | property SilicaListView listView 23 | property bool loading 24 | enabled: !loading && !listView.count 25 | verticalOffset: listView.headerItem.extraHeight ? listView.headerItem.extraHeight : 0 26 | } 27 | -------------------------------------------------------------------------------- /qml/components/MainPageListItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 23 | 24 | ListItem { 25 | id: listItem 26 | 27 | property alias title: titleLabel.text 28 | property alias description: descriptionLabel.text 29 | property string mediaArt: randomMediaArt ? randomMediaArt.mediaArt : "" 30 | property alias fallbackIcon: mediaArt.fallbackIcon 31 | 32 | property RandomMediaArt randomMediaArt 33 | 34 | contentHeight: Theme.itemSizeExtraLarge 35 | 36 | MediaArt { 37 | id: mediaArt 38 | 39 | enabled: listItem.enabled 40 | highlighted: listItem.highlighted 41 | size: contentHeight 42 | x: Theme.horizontalPageMargin 43 | 44 | source: listItem.mediaArt 45 | } 46 | 47 | Column { 48 | anchors { 49 | left: mediaArt.right 50 | leftMargin: Theme.paddingLarge 51 | right: parent.right 52 | rightMargin: Theme.horizontalPageMargin 53 | verticalCenter: parent.verticalCenter 54 | } 55 | 56 | Label { 57 | id: titleLabel 58 | 59 | width: parent.width 60 | opacity: listItem.enabled ? 1.0 : 0.4 61 | color: highlighted ? Theme.highlightColor : Theme.primaryColor 62 | font.pixelSize: Theme.fontSizeLarge 63 | textFormat: Text.StyledText 64 | truncationMode: TruncationMode.Fade 65 | } 66 | 67 | Label { 68 | id: descriptionLabel 69 | color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor 70 | font.pixelSize: Theme.fontSizeSmall 71 | truncationMode: TruncationMode.Fade 72 | visible: text 73 | width: parent.width 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /qml/components/MediaArt.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | Item { 23 | property bool highlighted 24 | property int size 25 | property alias source: mediaArtImage.source 26 | property string fallbackIcon: "image://theme/icon-m-music" 27 | 28 | width: size 29 | height: size 30 | opacity: enabled ? 1.0 : 0.4 31 | 32 | Rectangle { 33 | anchors.fill: parent 34 | gradient: Gradient { 35 | GradientStop { 36 | position: 0.0 37 | color: Theme.rgba(Theme.primaryColor, 0.1) 38 | } 39 | GradientStop { 40 | position: 1.0 41 | color: Theme.rgba(Theme.primaryColor, 0.05) 42 | } 43 | } 44 | visible: !mediaArtImage.visible 45 | 46 | Image { 47 | anchors.centerIn: parent 48 | asynchronous: true 49 | source: highlighted ? fallbackIcon + "?" + Theme.highlightColor : 50 | fallbackIcon 51 | } 52 | } 53 | 54 | Image { 55 | id: mediaArtImage 56 | 57 | anchors.fill: parent 58 | asynchronous: true 59 | fillMode: Image.PreserveAspectCrop 60 | sourceSize.height: size 61 | visible: status === Image.Ready 62 | 63 | Rectangle { 64 | anchors.fill: parent 65 | color: Theme.highlightBackgroundColor 66 | opacity: Theme.highlightBackgroundOpacity 67 | visible: highlighted 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /qml/components/MediaContainerListItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | ListItem { 23 | id: listItem 24 | 25 | property alias title: titleLabel.text 26 | property alias description: descriptionLabel.text 27 | property alias secondDescription: secondDescriptionLabel.text 28 | property alias mediaArt: mediaArt.source 29 | 30 | contentHeight: Theme.itemSizeLarge 31 | 32 | MediaArt { 33 | id: mediaArt 34 | 35 | anchors { 36 | left: parent.left 37 | leftMargin: Theme.horizontalPageMargin 38 | } 39 | size: contentHeight 40 | highlighted: listItem.highlighted 41 | } 42 | 43 | Item { 44 | anchors { 45 | left: mediaArt.right 46 | leftMargin: Theme.paddingLarge 47 | right: parent.right 48 | rightMargin: Theme.horizontalPageMargin 49 | verticalCenter: parent.verticalCenter 50 | } 51 | height: childrenRect.height 52 | 53 | Label { 54 | id: titleLabel 55 | 56 | width: parent.width 57 | color: highlighted ? Theme.highlightColor : Theme.primaryColor 58 | textFormat: Text.StyledText 59 | truncationMode: TruncationMode.Fade 60 | } 61 | 62 | Label { 63 | id: descriptionLabel 64 | 65 | anchors { 66 | left: parent.left 67 | right: secondDescriptionLabel.left 68 | top: titleLabel.bottom 69 | } 70 | color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor 71 | font.pixelSize: Theme.fontSizeSmall 72 | truncationMode: TruncationMode.Fade 73 | } 74 | 75 | Label { 76 | id: secondDescriptionLabel 77 | 78 | anchors { 79 | right: parent.right 80 | bottom: descriptionLabel.bottom 81 | } 82 | color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor 83 | font.pixelSize: Theme.fontSizeSmall 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /qml/components/MediaContainerSelectionDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | 21 | MediaContainerListItem { 22 | id: delegate 23 | 24 | showMenuOnPressAndHold: !selectionPanel.showPanel 25 | ListView.onRemove: animateRemoval() 26 | 27 | Binding { 28 | target: delegate 29 | property: "highlighted" 30 | value: down || menuOpen || listView.model.isSelected(model.index) 31 | } 32 | 33 | Connections { 34 | target: listView.model 35 | onSelectionChanged: delegate.highlighted = listView.model.isSelected(model.index) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /qml/components/NewPlaylistDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Dialog { 25 | property var tracks 26 | 27 | canAccept: playlistNameField.text.trim() 28 | 29 | onAccepted: { 30 | if (Array.isArray(tracks)) { 31 | Unplayer.PlaylistUtils.newPlaylistFromFilesystem(playlistNameField.text.trim(), tracks) 32 | } else { 33 | Unplayer.PlaylistUtils.newPlaylistFromLibrary(playlistNameField.text.trim(), tracks) 34 | } 35 | } 36 | 37 | SilicaFlickable { 38 | anchors.fill: parent 39 | contentHeight: column.height 40 | 41 | Column { 42 | id: column 43 | width: parent.width 44 | 45 | DialogHeader { 46 | title: qsTranslate("unplayer", "Add playlist") 47 | } 48 | 49 | TextField { 50 | id: playlistNameField 51 | label: qsTranslate("unplayer", "Playlist name") 52 | placeholderText: label 53 | width: parent.width 54 | 55 | EnterKey.iconSource: "image://theme/icon-m-enter-accept" 56 | EnterKey.onClicked: accept() 57 | 58 | Component.onCompleted: forceActiveFocus() 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /qml/components/OpenUrlDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Dialog { 25 | canAccept: !urlField.errorHighlight 26 | 27 | onAccepted: Unplayer.Player.queue.addTrackFromUrl(urlField.text, false, 0) 28 | 29 | SilicaFlickable { 30 | anchors.fill: parent 31 | contentHeight: column.height 32 | 33 | Column { 34 | id: column 35 | width: parent.width 36 | 37 | DialogHeader { 38 | title: qsTranslate("unplayer", "Open URL") 39 | acceptText: qsTranslate("unplayer", "Open") 40 | } 41 | 42 | TextField { 43 | id: urlField 44 | 45 | width: parent.width 46 | 47 | label: qsTranslate("unplayer", "URL") 48 | placeholderText: label 49 | inputMethodHints: Qt.ImhNoAutoUppercase 50 | errorHighlight: !text.trim() 51 | 52 | EnterKey.iconSource: "image://theme/icon-m-enter-accept" 53 | EnterKey.onClicked: accept() 54 | 55 | Component.onCompleted: { 56 | text = Clipboard.text 57 | forceActiveFocus() 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /qml/components/ParentDirectoryItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | BackgroundItem { 23 | Row { 24 | anchors { 25 | left: parent.left 26 | leftMargin: Theme.horizontalPageMargin 27 | verticalCenter: parent.verticalCenter 28 | } 29 | spacing: Theme.paddingMedium 30 | 31 | Image { 32 | anchors.verticalCenter: parent.verticalCenter 33 | asynchronous: true 34 | source: highlighted ? "image://theme/icon-m-folder?" + Theme.highlightColor : 35 | "image://theme/icon-m-folder" 36 | } 37 | 38 | Label { 39 | anchors.verticalCenter: parent.verticalCenter 40 | text: ".." 41 | color: highlighted ? Theme.highlightColor : Theme.primaryColor 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /qml/components/RemoveFilesDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Dialog { 25 | property alias title: header.title 26 | property alias deleteFiles: deleteFilesSwitch.checked 27 | 28 | Column { 29 | id: column 30 | width: parent.width 31 | 32 | DialogHeader { 33 | id: header 34 | acceptText: qsTranslate("unplayer", "Remove") 35 | } 36 | 37 | TextSwitch { 38 | id: deleteFilesSwitch 39 | text: qsTranslate("unplayer", "Delete files from the device") 40 | checked: true 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /qml/components/SearchMenuItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import Sailfish.Silica 1.0 20 | 21 | MenuItem { 22 | enabled: listView.count || searchPanel.searchText 23 | text: qsTranslate("unplayer", "Search") 24 | 25 | onClicked: { 26 | if (searchPanel.open) { 27 | searchPanel.focusSearchField() 28 | } else { 29 | searchPanel.open = true 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /qml/components/SearchPanel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | DockedPanel { 25 | id: searchPanel 26 | 27 | property string searchText: searchField.text.trim() 28 | 29 | function focusSearchField() { 30 | searchField.forceActiveFocus() 31 | } 32 | 33 | function unfocusSearchField() { 34 | searchField.focus = false 35 | } 36 | 37 | width: parent.width 38 | height: Theme.itemSizeMedium 39 | 40 | opacity: open ? 1 : 0 41 | dock: Dock.Top 42 | 43 | onOpenChanged: { 44 | if (open) 45 | focusSearchField() 46 | else 47 | searchField.text = String() 48 | } 49 | 50 | Behavior on opacity { 51 | FadeAnimator { } 52 | } 53 | 54 | Item { 55 | anchors.horizontalCenter: parent.horizontalCenter 56 | implicitWidth: Theme.itemSizeHuge * 3 - Theme.horizontalPageMargin 57 | width: implicitWidth > parent.width ? parent.width : implicitWidth 58 | height: Math.max(searchField.height, closeButton.height) 59 | 60 | SearchField { 61 | id: searchField 62 | 63 | anchors { 64 | left: parent.left 65 | right: closeButton.left 66 | verticalCenter: parent.verticalCenter 67 | } 68 | enabled: open 69 | 70 | onTextChanged: listView.model.filterRegExp = new RegExp(Unplayer.Utils.escapeRegExp(text.trim()), "i") 71 | } 72 | 73 | IconButton { 74 | id: closeButton 75 | 76 | anchors { 77 | right: parent.right 78 | rightMargin: Theme.horizontalPageMargin 79 | verticalCenter: parent.verticalCenter 80 | } 81 | icon.source: "image://theme/icon-m-close" 82 | 83 | onClicked: open = false 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /qml/components/SelectionMenuItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import Sailfish.Silica 1.0 20 | 21 | MenuItem { 22 | enabled: listView.count || (typeof searchPanel === "undefined" ? false : searchPanel.searchText) 23 | onClicked: { 24 | if (typeof searchPanel !== "undefined") { 25 | searchPanel.unfocusSearchField() 26 | } 27 | selectionPanel.showPanel = true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /qml/components/SelectionPanel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | DockedPanel { 25 | id: selectionPanel 26 | 27 | property alias selectionText: selectionLabel.text 28 | property bool showPanel: false 29 | 30 | width: parent.width 31 | height: column.height + Theme.paddingLarge 32 | contentHeight: height 33 | 34 | opacity: open ? 1.0 : 0.0 35 | Behavior on opacity { FadeAnimator { } } 36 | 37 | Binding { 38 | target: selectionPanel 39 | property: "open" 40 | value: showPanel && !Qt.inputMethod.visible 41 | } 42 | 43 | onOpenChanged: { 44 | if (open) { 45 | visible = true 46 | } else { 47 | if (!Qt.inputMethod.visible) { 48 | showPanel = false 49 | } 50 | } 51 | 52 | } 53 | 54 | onShowPanelChanged: { 55 | if (!showPanel) { 56 | listView.model.selectionModel.clear() 57 | } 58 | } 59 | 60 | onVisibleSizeChanged: { 61 | if (!visibleSize && !showPanel) { 62 | visible = false 63 | } 64 | } 65 | 66 | Binding { 67 | target: nowPlayingPanel 68 | property: "shouldBeClosed" 69 | value: open 70 | } 71 | 72 | Column { 73 | id: column 74 | 75 | anchors { 76 | left: parent.left 77 | leftMargin: Theme.horizontalPageMargin 78 | right: parent.right 79 | rightMargin: Theme.horizontalPageMargin 80 | verticalCenter: parent.verticalCenter 81 | } 82 | 83 | Label { 84 | id: selectionLabel 85 | 86 | width: parent.width 87 | horizontalAlignment: implicitWidth > width ? Text.AlignRight : Text.AlignHCenter 88 | truncationMode: TruncationMode.Fade 89 | } 90 | 91 | Item { 92 | width: parent.width 93 | height: Math.max(selectionButtons.height, closeButton.height) 94 | 95 | Item { 96 | id: selectionButtons 97 | 98 | anchors { 99 | left: parent.left 100 | right: closeButton.left 101 | verticalCenter: parent.verticalCenter 102 | } 103 | height: childrenRect.height 104 | 105 | Button { 106 | anchors { 107 | left: parent.left 108 | right: parent.horizontalCenter 109 | rightMargin: Theme.paddingSmall 110 | } 111 | text: qsTranslate("unplayer", "All") 112 | onClicked: listView.model.selectAll() 113 | } 114 | 115 | Button { 116 | anchors { 117 | left: parent.horizontalCenter 118 | leftMargin: Theme.paddingSmall 119 | right: parent.right 120 | } 121 | text: qsTranslate("unplayer", "None") 122 | onClicked: listView.model.selectionModel.clear() 123 | } 124 | } 125 | 126 | IconButton { 127 | id: closeButton 128 | 129 | anchors { 130 | right: parent.right 131 | verticalCenter: parent.verticalCenter 132 | } 133 | icon.source: "image://theme/icon-m-close" 134 | 135 | onClicked: showPanel = false 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /qml/components/SortModeListItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import Sailfish.Silica 1.0 20 | 21 | BackgroundItem { 22 | id: item 23 | 24 | property bool current 25 | property alias text: label.text 26 | 27 | Label { 28 | id: label 29 | 30 | anchors { 31 | left: parent.left 32 | leftMargin: Theme.horizontalPageMargin 33 | right: parent.right 34 | rightMargin: Theme.horizontalPageMargin 35 | verticalCenter: parent.verticalCenter 36 | } 37 | color: (highlighted || current) ? Theme.highlightColor : Theme.primaryColor 38 | opacity: item.enabled ? 1.0 : 0.4 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /qml/components/TagEditDialogListItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | Column { 23 | id: listItemColumn 24 | objectName: "listItem" 25 | 26 | property alias checked: enableSwitch.checked 27 | property alias switchText: enableSwitch.text 28 | property string textFieldLabel 29 | property alias listModel: listModel 30 | property var values 31 | 32 | function forceActiveFocus() { 33 | if (repeater.count > 0) { 34 | repeater.itemAt(0).textField.forceActiveFocus() 35 | } 36 | } 37 | 38 | Component.onCompleted: { 39 | for (var i = 0, max = values.length; i < max; ++i) { 40 | listModel.append({"value": values[i]}) 41 | } 42 | } 43 | 44 | width: parent.width 45 | 46 | TextSwitch { 47 | id: enableSwitch 48 | } 49 | 50 | Repeater { 51 | id: repeater 52 | 53 | model: ListModel { 54 | id: listModel 55 | } 56 | 57 | delegate: Row { 58 | property alias textField: textField 59 | property bool lastItem: (index === listModel.count - 1) 60 | 61 | visible: enableSwitch.checked 62 | 63 | TextField { 64 | id: textField 65 | 66 | property bool canFocusNext: !lastItem || tagsColumn.canFocusNextField(listItemColumn.parent) 67 | 68 | width: listItemColumn.width - removeButton.width - Theme.horizontalPageMargin 69 | 70 | label: textFieldLabel 71 | placeholderText: textFieldLabel 72 | text: value 73 | 74 | onTextChanged: value = text 75 | 76 | EnterKey.iconSource: canFocusNext ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-enter-accept" 77 | EnterKey.onClicked: { 78 | if (lastItem) { 79 | tagsColumn.focusNextTextFieldOrAccept(listItemColumn.parent) 80 | } else { 81 | repeater.itemAt(index + 1).textField.forceActiveFocus() 82 | } 83 | } 84 | } 85 | 86 | IconButton { 87 | id: removeButton 88 | anchors.verticalCenter: parent.verticalCenter 89 | icon.source: "image://theme/icon-m-remove" 90 | onClicked: listModel.remove(index) 91 | } 92 | } 93 | } 94 | 95 | Button { 96 | anchors.horizontalCenter: parent.horizontalCenter 97 | visible: enableSwitch.checked 98 | preferredWidth: Theme.buttonWidthLarge 99 | text: qsTranslate("unplayer", "Add") 100 | onClicked: { 101 | listModel.append({"value": ""}) 102 | flickable.contentY += repeater.itemAt(repeater.count - 1).height 103 | repeater.itemAt(repeater.count - 1).textField.forceActiveFocus() 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /qml/components/TranslatorsPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | Page { 25 | SilicaFlickable { 26 | anchors.fill: parent 27 | contentHeight: column.height + Theme.paddingLarge 28 | 29 | Column { 30 | id: column 31 | width: parent.width 32 | 33 | PageHeader { 34 | title: qsTranslate("unplayer", "Translators") 35 | } 36 | 37 | Label { 38 | anchors { 39 | left: parent.left 40 | leftMargin: Theme.horizontalPageMargin 41 | right: parent.right 42 | rightMargin: Theme.horizontalPageMargin 43 | } 44 | font.pixelSize: Theme.fontSizeExtraSmall 45 | wrapMode: Text.WordWrap 46 | text: Unplayer.Utils.translators.arg(Theme.highlightColor) 47 | textFormat: Text.RichText 48 | 49 | onLinkActivated: Qt.openUrlExternally(link) 50 | } 51 | } 52 | 53 | VerticalScrollDecorator { } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import QtQuick 2.2 20 | import Sailfish.Silica 1.0 21 | 22 | import harbour.unplayer 0.1 as Unplayer 23 | 24 | import "components" 25 | 26 | ApplicationWindow 27 | { 28 | id: rootWindow 29 | 30 | property bool largeScreen: Screen.sizeCategory === Screen.Large || 31 | Screen.sizeCategory === Screen.ExtraLarge 32 | 33 | signal mediaArtReloadNeeded() 34 | 35 | _defaultPageOrientations: Orientation.All 36 | allowedOrientations: defaultAllowedOrientations 37 | bottomMargin: nowPlayingPanel.parent === contentItem ? 0 : nowPlayingPanel.visibleSize 38 | cover: Qt.resolvedUrl("components/Cover.qml") 39 | initialPage: Qt.resolvedUrl("components/MainPage.qml") 40 | 41 | Component.onCompleted: { 42 | // Bring panel's parent on top 43 | nowPlayingPanel.parent.z = 99 44 | 45 | if (Unplayer.LibraryUtils.databaseInitialized) { 46 | if (Unplayer.LibraryUtils.createdTables && Unplayer.Settings.hasLibraryDirectories) { 47 | Unplayer.LibraryUtils.updateDatabase() 48 | } 49 | if (Unplayer.Settings.openLibraryOnStartup && Unplayer.Settings.hasLibraryDirectories) { 50 | pageStack.push("components/LibraryPage.qml", null, PageStackAction.Immediate) 51 | } 52 | } 53 | if (commandLineArguments.length) { 54 | Unplayer.Player.queue.addTracksFromUrls(commandLineArguments, true) 55 | } else if (Unplayer.Settings.restorePlayerState) { 56 | Unplayer.Player.restoreState() 57 | } 58 | } 59 | 60 | Component.onDestruction: Unplayer.Player.saveState() 61 | 62 | MediaKeys { } 63 | 64 | NowPlayingPanel { 65 | id: nowPlayingPanel 66 | } 67 | 68 | Unplayer.DBusService { 69 | onWindowActivationRequested: activate() 70 | onFilesOpeningRequested: { 71 | activate() 72 | Unplayer.Player.queue.addTracksFromUrls(uris, true) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rpm/harbour-unplayer.spec: -------------------------------------------------------------------------------- 1 | Name: harbour-unplayer 2 | Version: 2.1.1 3 | Release: 1 4 | Summary: Simple music player for Sailfish OS 5 | Group: Applications/Music 6 | License: GPLv3 7 | URL: https://github.com/equeim/unplayer 8 | 9 | Source0: https://github.com/equeim/unplayer/archive/%{version}.tar.gz 10 | Patch0: qtmpris.patch 11 | Patch1: taglib.patch 12 | 13 | Requires: sailfishsilica-qt5 14 | BuildRequires: pkgconfig(Qt5Concurrent) 15 | BuildRequires: pkgconfig(Qt5DBus) 16 | BuildRequires: pkgconfig(Qt5Multimedia) 17 | BuildRequires: pkgconfig(Qt5Quick) 18 | BuildRequires: pkgconfig(Qt5Sql) 19 | BuildRequires: pkgconfig(sailfishapp) 20 | BuildRequires: pkgconfig(nemonotifications-qt5) 21 | BuildRequires: cmake 22 | BuildRequires: desktop-file-utils 23 | 24 | # TagLib dependencies 25 | BuildRequires: pkgconfig(zlib) 26 | BuildRequires: boost-devel 27 | 28 | %define __provides_exclude mimehandler 29 | 30 | %global debug 0 31 | 32 | %global harbour ON 33 | #%%global harbour OFF 34 | 35 | %global build_directory %{_builddir}/build-%{_target}-%(version | awk '{print $3}') 36 | 37 | %global qtmpris_source_directory %{_builddir}/3rdparty/qtmpris 38 | %global qtmpris_build_directory %{build_directory}/3rdparty/qtmpris 39 | 40 | %global taglib_source_directory %{_builddir}/3rdparty/taglib 41 | %global taglib_build_directory %{build_directory}/3rdparty/taglib 42 | 43 | %global thirdparty_install_directory %{build_directory}/3rdparty/install 44 | 45 | %define patch_if_needed() \ 46 | %if 0%(patch -p0 -R --dry-run --force --fuzz=2 --input=%{P:%1} > /dev/null 2>&1; echo $?) != 0 \ 47 | %patch%1 \ 48 | %endif 49 | 50 | %global apply_patches %{lua: \ 51 | for i, s in ipairs(patches) do \ 52 | print(rpm.expand("%patch_if_needed "..(i - 1))) \ 53 | end \ 54 | } 55 | 56 | 57 | %description 58 | %{summary} 59 | 60 | 61 | %prep 62 | %setup -q 63 | %apply_patches 64 | 65 | %build 66 | export PKG_CONFIG_PATH=%{thirdparty_install_directory}/lib/pkgconfig 67 | 68 | # Enable -O0 for debug builds 69 | # This also requires disabling _FORTIFY_SOURCE 70 | %if %{debug} 71 | export CFLAGS="${CFLAGS:-%optflags} -O0 -Wp,-U_FORTIFY_SOURCE" 72 | export CXXFLAGS="${CXXFLAGS:-%optflags} -O0 -Wp,-U_FORTIFY_SOURCE" 73 | %endif 74 | 75 | mkdir -p %{qtmpris_build_directory} 76 | cd %{qtmpris_build_directory} 77 | %qmake5 %{qtmpris_source_directory} CONFIG+=staticlib QMAKE_CXXFLAGS+="-std=c++17" PREFIX=%{thirdparty_install_directory} 78 | %make_build 79 | # not make_install, because we do not want INSTALL_ROOT here 80 | make install 81 | 82 | mkdir -p %{taglib_build_directory} 83 | cd %{taglib_build_directory} 84 | CFLAGS="$CFLAGS -fPIC" CXXFLAGS="$CXXFLAGS -fPIC" %cmake %{taglib_source_directory} \ 85 | -DCMAKE_INSTALL_PREFIX=%{thirdparty_install_directory} \ 86 | -DLIB_INSTALL_DIR=%{thirdparty_install_directory}/lib \ 87 | -DINCLUDE_INSTALL_DIR=%{thirdparty_install_directory}/include \ 88 | -DBUILD_SHARED_LIBS=OFF \ 89 | -DWITH_MP4=ON 90 | %make_build 91 | # not make_install, because we do not want DESTDIR here 92 | make install 93 | 94 | cd %{build_directory} 95 | %cmake .. \ 96 | -DHARBOUR=%{harbour} \ 97 | -DQTMPRIS_STATIC=ON \ 98 | -DTAGLIB_STATIC=ON 99 | %make_build 100 | 101 | %install 102 | cd %{build_directory} 103 | %make_install 104 | desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop 105 | 106 | 107 | %files 108 | %{_bindir}/%{name} 109 | %{_datadir}/%{name} 110 | %{_datadir}/applications/%{name}.desktop 111 | %{_datadir}/icons/hicolor/*/apps/%{name}.* 112 | -------------------------------------------------------------------------------- /rpm/qtmpris.patch: -------------------------------------------------------------------------------- 1 | --- 3rdparty/qtmpris/qtdbusextended/qtdbusextended.pro 2 | +++ 3rdparty/qtmpris/qtdbusextended/qtdbusextended.pro 3 | @@ -15,3 +15,6 @@ HEADERS += \ 4 | dbusextended.h \ 5 | dbusextendedabstractinterface.h \ 6 | dbusextendedpendingcallwatcher_p.h 7 | + 8 | +target.path = $$PREFIX/lib 9 | +INSTALLS += target 10 | --- 3rdparty/qtmpris/src/src.pro 11 | +++ 3rdparty/qtmpris/src/src.pro 12 | @@ -13,7 +13,7 @@ DEFINES += MPRIS_QT_LIBRARY 13 | 14 | DEPENDPATH += ../qtdbusextended 15 | INCLUDEPATH += ../qtdbusextended 16 | -LIBS += -L../qtdbusextended -ldbusextended-qt5 17 | +LIBS += -ldbusextended-qt5 18 | 19 | # Generate pkg-config support by default 20 | # Note that we HAVE TO also create prl config as QMake implementation 21 | @@ -54,15 +54,15 @@ INSTALL_HEADERS = \ 22 | OTHER_FILES += org.mpris.MediaPlayer2.xml \ 23 | org.mpris.MediaPlayer2.Player.xml 24 | 25 | -target.path = $$[QT_INSTALL_LIBS] 26 | +target.path = $$PREFIX/lib 27 | headers.files = $$INSTALL_HEADERS 28 | -headers.path = $$[QT_INSTALL_HEADERS]/MprisQt 29 | +headers.path = $$PREFIX/include/MprisQt 30 | prf.files = $${TARGET}.prf 31 | prf.path = $$[QMAKE_MKSPECS]/features 32 | -INSTALLS += target headers prf 33 | +INSTALLS += target headers 34 | 35 | QMAKE_PKGCONFIG_REQUIRES = Qt5Core Qt5DBus 36 | -QMAKE_PKGCONFIG_LIBDIR = $$target.path 37 | -QMAKE_PKGCONFIG_INCDIR = $$headers.path 38 | +QMAKE_PKGCONFIG_LIBDIR = \"$$target.path\" 39 | +QMAKE_PKGCONFIG_INCDIR = \"$$headers.path\" 40 | QMAKE_PKGCONFIG_DESTDIR = pkgconfig 41 | QMAKE_PKGCONFIG_NAME = MprisQt 42 | -------------------------------------------------------------------------------- /rpm/taglib.patch: -------------------------------------------------------------------------------- 1 | --- 3rdparty/taglib/taglib/CMakeLists.txt 2 | +++ 3rdparty/taglib/taglib/CMakeLists.txt 3 | @@ -335,7 +335,7 @@ set(tag_LIB_SRCS 4 | ) 5 | 6 | add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) 7 | -set_property(TARGET tag PROPERTY CXX_STANDARD 98) 8 | +set_property(TARGET tag PROPERTY CXX_STANDARD 17) 9 | 10 | if(HAVE_ZLIB AND NOT HAVE_ZLIB_SOURCE) 11 | target_link_libraries(tag ${ZLIB_LIBRARIES}) 12 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | set(CMAKE_AUTOMOC ON) 3 | set(CMAKE_AUTORCC ON) 4 | 5 | find_package(Qt5Core 5.6 REQUIRED CONFIG) 6 | find_package(Qt5Concurrent CONFIG REQUIRED) 7 | find_package(Qt5DBus CONFIG REQUIRED) 8 | find_package(Qt5Multimedia) 9 | find_package(Qt5Quick CONFIG REQUIRED) 10 | find_package(Qt5Sql CONFIG REQUIRED) 11 | 12 | find_package(PkgConfig REQUIRED) 13 | 14 | pkg_check_modules(QTMPRIS REQUIRED mpris-qt5) 15 | if (QTMPRIS_STATIC) 16 | set(qtmpris_ldflags ${QTMPRIS_STATIC_LDFLAGS}) 17 | else() 18 | set(qtmpris_ldflags ${QTMPRIS_LDFLAGS}) 19 | endif() 20 | 21 | pkg_check_modules(TAGLIB REQUIRED taglib) 22 | if (TAGLIB_STATIC) 23 | set(taglib_ldflags ${TAGLIB_STATIC_LDFLAGS}) 24 | else() 25 | set(taglib_ldflags ${TAGLIB_LDFLAGS}) 26 | endif() 27 | 28 | set_source_files_properties(org.freedesktop.Application.xml org.equeim.unplayer.xml PROPERTIES NO_NAMESPACE ON) 29 | qt5_add_dbus_interface(dbus_generated org.freedesktop.Application.xml org_freedesktop_application_interface) 30 | qt5_add_dbus_interface(dbus_generated org.equeim.unplayer.xml org_equeim_unplayer_interface) 31 | qt5_add_dbus_adaptor(dbus_generated org.freedesktop.Application.xml dbusservice.h unplayer::DBusService org_freedesktop_application_adaptor OrgFreedesktopApplicationAdaptor) 32 | qt5_add_dbus_adaptor(dbus_generated org.equeim.unplayer.xml dbusservice.h unplayer::DBusService org_equeim_unplayer_adaptor OrgEqueimUnplayerAdaptor) 33 | if (Qt5_VERSION VERSION_LESS 5.9.2) 34 | set_source_files_properties(${dbus_generated} PROPERTIES SKIP_AUTOMOC ON) 35 | endif() 36 | 37 | add_executable("${PROJECT_NAME}" 38 | abstractlibrarymodel.cpp 39 | albumsmodel.cpp 40 | artistsmodel.cpp 41 | asyncloadingmodel.h 42 | commandlineparser.cpp 43 | dbusservice.cpp 44 | directorycontentmodel.cpp 45 | directorycontentproxymodel.cpp 46 | directorytracksmodel.cpp 47 | fileutils.cpp 48 | filterproxymodel.cpp 49 | genresmodel.cpp 50 | librarydirectoriesmodel.cpp 51 | librarymigrator.cpp 52 | librarytracksadder.cpp 53 | libraryupdaterunnable.cpp 54 | libraryutils.cpp 55 | main.cpp 56 | mediaartutils.cpp 57 | player.cpp 58 | playlistmodel.cpp 59 | playlistsmodel.cpp 60 | playlistutils.cpp 61 | queue.cpp 62 | queuemodel.cpp 63 | settings.cpp 64 | signalhandler.cpp 65 | trackinfo.cpp 66 | tracksmodel.cpp 67 | utils.cpp 68 | tagutils.cpp 69 | resources.qrc 70 | ${dbus_generated} 71 | ) 72 | 73 | set_target_properties("${PROJECT_NAME}" PROPERTIES 74 | CXX_STANDARD 17 75 | CXX_STANDARD_REQUIRED ON 76 | CXX_EXTENSIONS OFF 77 | ) 78 | 79 | target_link_libraries("${PROJECT_NAME}" 80 | Qt5::Concurrent 81 | Qt5::DBus 82 | Qt5::Multimedia 83 | Qt5::Quick 84 | Qt5::Sql 85 | ${qtmpris_ldflags} 86 | ${taglib_ldflags} 87 | ) 88 | 89 | target_include_directories("${PROJECT_NAME}" PRIVATE 90 | ${QTMPRIS_INCLUDE_DIRS} 91 | ${TAGLIB_INCLUDE_DIRS} 92 | ${PROJECT_SOURCE_DIR}/3rdparty/cxxopts/include 93 | ) 94 | 95 | target_compile_definitions("${PROJECT_NAME}" PRIVATE 96 | QT_DEPRECATED_WARNINGS 97 | QT_DISABLE_DEPRECATED_BEFORE=0x050600 98 | QT_MESSAGELOGCONTEXT 99 | UNPLAYER_VERSION="${PROJECT_VERSION}" 100 | ) 101 | 102 | target_compile_options("${PROJECT_NAME}" PRIVATE 103 | -Wall 104 | -Wextra 105 | -Wpedantic 106 | -Wnon-virtual-dtor 107 | -Wcast-align 108 | -Woverloaded-virtual 109 | -Wconversion 110 | -Wsign-conversion 111 | -Wlogical-op 112 | -Wdouble-promotion 113 | -Wformat=2 114 | -Werror=format 115 | ${QTMPRIS_CFLAGS_OTHER} 116 | ${TAGLIB_CFLAGS_OTHER} 117 | ) 118 | 119 | if (SAILFISHOS) 120 | pkg_check_modules(SAILFISHAPP REQUIRED sailfishapp) 121 | pkg_check_modules(NEMONOTIFICATIONS REQUIRED nemonotifications-qt5) 122 | target_link_libraries("${PROJECT_NAME}" ${SAILFISHAPP_LDFLAGS} ${NEMONOTIFICATIONS_LDFLAGS}) 123 | target_include_directories("${PROJECT_NAME}" PRIVATE ${SAILFISHAPP_INCLUDE_DIRS} ${NEMONOTIFICATIONS_INCLUDE_DIRS}) 124 | target_compile_definitions("${PROJECT_NAME}" PRIVATE UNPLAYER_SAILFISHOS) 125 | target_compile_options("${PROJECT_NAME}" PRIVATE ${SAILFISHAPP_CFLAGS_OTHER} ${NEMONOTIFICATIONS_CFLAGS_OTHER}) 126 | endif() 127 | 128 | install(TARGETS "${PROJECT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") 129 | -------------------------------------------------------------------------------- /src/abstractlibrarymodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "abstractlibrarymodel.h" 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "libraryutils.h" 30 | #include "qscopeguard.h" 31 | #include "sqlutils.h" 32 | #include "utilsfunctions.h" 33 | 34 | namespace unplayer 35 | { 36 | template 37 | int AbstractLibraryModel::rowCount(const QModelIndex&) const 38 | { 39 | return static_cast(mItems.size()); 40 | } 41 | 42 | template 43 | bool AbstractLibraryModel::removeRows(int row, int count, const QModelIndex& parent) 44 | { 45 | if (count > 0 && (row + count) <= static_cast(mItems.size())) { 46 | beginRemoveRows(parent, row, row + count - 1); 47 | const auto first(mItems.begin() + row); 48 | mItems.erase(first, first + count); 49 | endRemoveRows(); 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | template 56 | void AbstractLibraryModel::execQuery() 57 | { 58 | removeRows(0, rowCount()); 59 | 60 | setLoading(true); 61 | 62 | auto queryString(makeQueryString()); 63 | auto itemFactory = createItemFactory(); 64 | auto future(QtConcurrent::run([itemFactory, queryString = std::move(queryString)] { 65 | auto items(std::make_shared>()); 66 | 67 | std::unique_ptr itemFactoryUnique(itemFactory); 68 | 69 | const DatabaseConnectionGuard databaseGuard(QUuid::createUuid().toString()); 70 | if (!databaseGuard.db.isOpen()) { 71 | return items; 72 | } 73 | 74 | QSqlQuery query(databaseGuard.db); 75 | if (!query.prepare(queryString)) { 76 | qWarning() << "Prepare failed:" << query.lastError(); 77 | qWarning() << "Query:" << queryString; 78 | return items; 79 | } 80 | 81 | if (query.exec()) { 82 | if (reserveFromQuery(*items, query) > 0) { 83 | while (query.next()) { 84 | items->push_back(itemFactory->itemFromQuery(query)); 85 | } 86 | } 87 | } else { 88 | qWarning() << "Exec failed: " << query.lastError(); 89 | qWarning() << "Query:" << queryString; 90 | } 91 | 92 | return items; 93 | })); 94 | 95 | onFutureFinished(future, this, [this](std::shared_ptr>&& items) { 96 | removeRows(0, rowCount()); 97 | if (!items->empty()) { 98 | beginInsertRows(QModelIndex(), 0, static_cast(items->size()) - 1); 99 | mItems = std::move(*items); 100 | endInsertRows(); 101 | } 102 | setLoading(false); 103 | }); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/abstractlibrarymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_ABSTRACTLIBRARYMODEL_H 20 | #define UNPLAYER_ABSTRACTLIBRARYMODEL_H 21 | 22 | #include 23 | 24 | #include "asyncloadingmodel.h" 25 | 26 | class QSqlQuery; 27 | 28 | namespace unplayer 29 | { 30 | template 31 | class AbstractLibraryModel : public AsyncLoadingModel 32 | { 33 | public: 34 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 35 | bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; 36 | 37 | protected: 38 | class AbstractItemFactory 39 | { 40 | public: 41 | AbstractItemFactory() = default; 42 | AbstractItemFactory(const AbstractItemFactory&) = delete; 43 | AbstractItemFactory(AbstractItemFactory&&) = delete; 44 | virtual ~AbstractItemFactory() = default; 45 | AbstractItemFactory& operator=(const AbstractItemFactory&) = delete; 46 | AbstractItemFactory& operator=(AbstractItemFactory&&) = delete; 47 | virtual Item itemFromQuery(const QSqlQuery& query) = 0; 48 | }; 49 | 50 | void execQuery(); 51 | virtual QString makeQueryString() = 0; 52 | virtual AbstractItemFactory* createItemFactory() = 0; 53 | 54 | std::vector mItems; 55 | }; 56 | } 57 | 58 | #endif // UNPLAYER_ABSTRACTLIBRARYMODEL_H 59 | -------------------------------------------------------------------------------- /src/albumsmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_ALBUMSMODEL_H 20 | #define UNPLAYER_ALBUMSMODEL_H 21 | 22 | #include 23 | #include 24 | 25 | #include "abstractlibrarymodel.h" 26 | 27 | namespace unplayer 28 | { 29 | struct LibraryTrack; 30 | 31 | struct Album 32 | { 33 | int id; 34 | QString artist; 35 | QString displayedArtist; 36 | QString album; 37 | QString displayedAlbum; 38 | int year; 39 | int tracksCount; 40 | int duration; 41 | 42 | QString mediaArt; 43 | bool requestedMediaArt; 44 | }; 45 | 46 | class AlbumsModel : public AbstractLibraryModel, public QQmlParserStatus 47 | { 48 | Q_OBJECT 49 | Q_INTERFACES(QQmlParserStatus) 50 | Q_PROPERTY(bool allArtists READ allArtists WRITE setAllArtists NOTIFY allArtistsChanged) 51 | Q_PROPERTY(int artistId READ artistId WRITE setArtistId) 52 | Q_PROPERTY(bool sortDescending READ sortDescending WRITE setSortDescending) 53 | Q_PROPERTY(SortMode sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged) 54 | public: 55 | enum Role 56 | { 57 | AlbumIdRole = Qt::UserRole, 58 | ArtistRole, 59 | DisplayedArtistRole, 60 | UnknownArtistRole, 61 | AlbumRole, 62 | DisplayedAlbumRole, 63 | UnknownAlbumRole, 64 | YearRole, 65 | TracksCountRole, 66 | DurationRole, 67 | MediaArtRole 68 | }; 69 | Q_ENUM(Role) 70 | 71 | enum SortMode 72 | { 73 | SortAlbum, 74 | SortYear, 75 | SortArtistAlbum, 76 | SortArtistYear 77 | }; 78 | Q_ENUM(SortMode) 79 | static SortMode sortModeFromInt(int value); 80 | 81 | AlbumsModel(); 82 | ~AlbumsModel() override; 83 | void classBegin() override; 84 | void componentComplete() override; 85 | 86 | QVariant data(const QModelIndex& index, int role) const override; 87 | 88 | bool allArtists() const; 89 | void setAllArtists(bool allArtists); 90 | 91 | int artistId() const; 92 | void setArtistId(int id); 93 | 94 | bool sortDescending() const; 95 | void setSortDescending(bool descending); 96 | SortMode sortMode() const; 97 | void setSortMode(SortMode mode); 98 | 99 | Q_INVOKABLE std::vector getTracksForAlbum(int index) const; 100 | Q_INVOKABLE std::vector getTracksForAlbums(const std::vector& indexes) const; 101 | Q_INVOKABLE QStringList getTrackPathsForAlbum(int index) const; 102 | Q_INVOKABLE QStringList getTrackPathsForAlbums(const std::vector& indexes) const; 103 | 104 | Q_INVOKABLE void removeAlbum(int index, bool deleteFiles); 105 | Q_INVOKABLE void removeAlbums(const std::vector& indexes, bool deleteFiles); 106 | 107 | protected: 108 | class ItemFactory final : public AbstractItemFactory 109 | { 110 | public: 111 | Album itemFromQuery(const QSqlQuery& query) override; 112 | }; 113 | 114 | QHash roleNames() const override; 115 | 116 | QString makeQueryString() override; 117 | AbstractItemFactory* createItemFactory() override; 118 | 119 | private: 120 | std::vector& mAlbums = mItems; 121 | 122 | bool mAllArtists = true; 123 | int mArtistId = 0; 124 | 125 | bool mSortDescending = false; 126 | SortMode mSortMode = SortAlbum; 127 | 128 | signals: 129 | void allArtistsChanged(); 130 | void sortModeChanged(); 131 | }; 132 | } 133 | 134 | #endif // UNPLAYER_ALBUMSMODEL_H 135 | -------------------------------------------------------------------------------- /src/artistsmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_ARTISTSMODEL_H 20 | #define UNPLAYER_ARTISTSMODEL_H 21 | 22 | #include "abstractlibrarymodel.h" 23 | 24 | namespace unplayer 25 | { 26 | struct LibraryTrack; 27 | 28 | struct Artist 29 | { 30 | int id; 31 | QString artist; 32 | QString displayedArtist; 33 | int albumsCount; 34 | int tracksCount; 35 | int duration; 36 | 37 | QString mediaArt; 38 | bool requestedMediaArt; 39 | }; 40 | 41 | class ArtistsModel : public AbstractLibraryModel 42 | { 43 | Q_OBJECT 44 | Q_PROPERTY(bool sortDescending READ sortDescending NOTIFY sortDescendingChanged) 45 | public: 46 | enum Role 47 | { 48 | ArtistIdRole = Qt::UserRole, 49 | ArtistRole, 50 | DisplayedArtistRole, 51 | AlbumsCountRole, 52 | TracksCountRole, 53 | DurationRole, 54 | MediaArtRole 55 | }; 56 | Q_ENUM(Role) 57 | 58 | ArtistsModel(); 59 | 60 | QVariant data(const QModelIndex& index, int role) const override; 61 | 62 | bool sortDescending() const; 63 | 64 | Q_INVOKABLE void toggleSortOrder(); 65 | 66 | Q_INVOKABLE std::vector getTracksForArtist(int index) const; 67 | Q_INVOKABLE std::vector getTracksForArtists(const std::vector& indexes) const; 68 | Q_INVOKABLE QStringList getTrackPathsForArtist(int index) const; 69 | Q_INVOKABLE QStringList getTrackPathsForArtists(const std::vector& indexes) const; 70 | 71 | Q_INVOKABLE void removeArtist(int index, bool deleteFiles); 72 | Q_INVOKABLE void removeArtists(const std::vector& indexes, bool deleteFiles); 73 | protected: 74 | class ItemFactory final : public AbstractItemFactory 75 | { 76 | public: 77 | Artist itemFromQuery(const QSqlQuery& query) override; 78 | }; 79 | 80 | QHash roleNames() const override; 81 | 82 | QString makeQueryString() override; 83 | AbstractItemFactory* createItemFactory() override; 84 | 85 | private: 86 | std::vector& mArtists = mItems; 87 | bool mSortDescending; 88 | 89 | signals: 90 | void sortDescendingChanged(); 91 | }; 92 | } 93 | 94 | #endif // UNPLAYER_ARTISTSMODEL_H 95 | -------------------------------------------------------------------------------- /src/asyncloadingmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_ASYNCLOADINGMODEL_H 20 | #define UNPLAYER_ASYNCLOADINGMODEL_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | class AsyncLoadingModel : public QAbstractListModel 27 | { 28 | Q_OBJECT 29 | Q_PROPERTY(bool loading READ isLoading NOTIFY loadingChanged) 30 | public: 31 | inline bool isLoading() { return mLoading; } 32 | 33 | protected: 34 | inline void setLoading(bool loading) 35 | { 36 | if (loading != mLoading) { 37 | mLoading = loading; 38 | emit loadingChanged(loading); 39 | if (!loading) { 40 | emit loaded(); 41 | } 42 | } 43 | } 44 | 45 | private: 46 | bool mLoading = true; 47 | 48 | signals: 49 | void loadingChanged(bool loading); 50 | void loaded(); 51 | }; 52 | } 53 | 54 | #endif // UNPLAYER_ASYNCLOADINGMODEL_H 55 | -------------------------------------------------------------------------------- /src/commandlineparser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "commandlineparser.h" 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #define CXXOPTS_VECTOR_DELIMITER '\0' 27 | #include 28 | 29 | #include "fileutils.h" 30 | #include "playlistutils.h" 31 | 32 | namespace unplayer 33 | { 34 | namespace 35 | { 36 | void parseFiles(const std::vector& files, CommandLineArgs& args) 37 | { 38 | if (!files.empty()) { 39 | args.files.reserve(static_cast(files.size())); 40 | for (const std::string& f : files) { 41 | const auto file(QString::fromStdString(f)); 42 | const QFileInfo info(file); 43 | if (info.isFile()) { 44 | const QString suffix(info.suffix()); 45 | if (PlaylistUtils::isPlaylistExtension(suffix) || 46 | fileutils::isExtensionSupported(suffix) || 47 | fileutils::isVideoExtensionSupported(suffix)) { 48 | 49 | args.files.push_back(QUrl::fromLocalFile(info.absoluteFilePath()).toString()); 50 | } 51 | } else { 52 | const QUrl url(file); 53 | if (url.isLocalFile()) { 54 | if (QFileInfo(url.path()).isFile()) { 55 | args.files.push_back(file); 56 | } 57 | } else { 58 | args.files.push_back(file); 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | CommandLineArgs CommandLineArgs::parse(int& argc, char**& argv) 67 | { 68 | CommandLineArgs args{}; 69 | 70 | const std::string appName(QFileInfo(*argv).fileName().toStdString()); 71 | const std::string versionString(appName + " " UNPLAYER_VERSION); 72 | 73 | cxxopts::Options opts(appName, versionString); 74 | bool version = false; 75 | bool help = false; 76 | std::vector files; 77 | opts.add_options() 78 | ("u,update-library", "update music library and exit", cxxopts::value(args.updateLibrary)) 79 | ("r,reset-library", "reset music library and exit", cxxopts::value(args.resetLibrary)) 80 | ("v,version", "display version information", cxxopts::value(version)) 81 | ("h,help", "display this help", cxxopts::value(help)) 82 | ("files", "", cxxopts::value(files)); 83 | opts.parse_positional("files"); 84 | opts.positional_help("files"); 85 | try { 86 | const auto result(opts.parse(argc, argv)); 87 | if (help) { 88 | std::cout << opts.help() << std::endl; 89 | args.exit = true; 90 | args.returnCode = EXIT_SUCCESS; 91 | return args; 92 | } 93 | if (version) { 94 | std::cout << versionString << std::endl; 95 | args.exit = true; 96 | args.returnCode = EXIT_SUCCESS; 97 | return args; 98 | } 99 | parseFiles(files, args); 100 | } catch (const cxxopts::OptionException& e) { 101 | std::cerr << e.what() << std::endl; 102 | args.exit = true; 103 | args.returnCode = EXIT_FAILURE; 104 | } 105 | 106 | return args; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/commandlineparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | namespace unplayer 22 | { 23 | struct CommandLineArgs 24 | { 25 | QStringList files; 26 | bool updateLibrary; 27 | bool resetLibrary; 28 | 29 | bool exit; 30 | int returnCode; 31 | 32 | static CommandLineArgs parse(int& argc, char**& argv); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/dbusservice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "dbusservice.h" 20 | 21 | #include "org_freedesktop_application_adaptor.h" 22 | #include "org_equeim_unplayer_adaptor.h" 23 | 24 | namespace unplayer 25 | { 26 | const QLatin1String DBusService::serviceName("org.equeim.unplayer"); 27 | const QLatin1String DBusService::objectPath("/org/equeim/unplayer"); 28 | 29 | DBusService::DBusService() 30 | { 31 | new OrgFreedesktopApplicationAdaptor(this); 32 | new OrgEqueimUnplayerAdaptor(this); 33 | 34 | auto connection(QDBusConnection::sessionBus()); 35 | if (connection.registerService(serviceName)) { 36 | qInfo("Registered D-Bus service"); 37 | if (connection.registerObject(objectPath, this)) { 38 | qInfo("Registered D-Bus object"); 39 | } else { 40 | qWarning() << "Failed to register D-Bus object" << connection.lastError(); 41 | } 42 | } else { 43 | qWarning() << "Failed toregister D-Bus service" << connection.lastError(); 44 | } 45 | } 46 | 47 | void DBusService::Activate(const QVariantMap& platform_data) 48 | { 49 | qInfo().nospace() << "Window activation requested, platform_data=" << platform_data; 50 | emit windowActivationRequested(); 51 | } 52 | 53 | void DBusService::Open(const QStringList& uris, const QVariantMap& platform_data) 54 | { 55 | qInfo().nospace() << "Files opening requested, uris=" << uris << ", platform_data=" << platform_data; 56 | emit filesOpeningRequested(uris); 57 | } 58 | 59 | void DBusService::ActivateAction(const QString& action_name, const QVariantList& parameter, const QVariantMap& platform_data) 60 | { 61 | qInfo().nospace() << "Action activated, action_name=" << action_name << ", parameter=" << parameter << ", platform_data=" << platform_data; 62 | } 63 | 64 | void DBusService::addTracksToQueue(const QStringList& tracks) 65 | { 66 | qInfo().nospace() << "Files opening requested, tracks=" << tracks; 67 | emit filesOpeningRequested(tracks); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/dbusservice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_DBUSSERVICE_H 20 | #define UNPLAYER_DBUSSERVICE_H 21 | 22 | #include 23 | 24 | class OrgFreedesktopApplicationAdaptor; 25 | class OrgEqueimUnplayerAdaptor; 26 | 27 | namespace unplayer 28 | { 29 | class DBusService : public QObject 30 | { 31 | Q_OBJECT 32 | public: 33 | static const QLatin1String serviceName; 34 | static const QLatin1String objectPath; 35 | 36 | DBusService(); 37 | 38 | private: 39 | friend OrgFreedesktopApplicationAdaptor; 40 | void Activate(const QVariantMap& platform_data); 41 | void Open(const QStringList& uris, const QVariantMap& platform_data); 42 | void ActivateAction(const QString& action_name, const QVariantList& parameter, const QVariantMap& platform_data); 43 | 44 | friend OrgEqueimUnplayerAdaptor; 45 | void addTracksToQueue(const QStringList &tracks); 46 | 47 | signals: 48 | void windowActivationRequested(); 49 | void filesOpeningRequested(const QStringList& uris); 50 | }; 51 | } 52 | 53 | #endif // UNPLAYER_DBUSSERVICE_H 54 | -------------------------------------------------------------------------------- /src/directorycontentmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_DIRECTORYCONTENTMODEL_H 20 | #define UNPLAYER_DIRECTORYCONTENTMODEL_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "asyncloadingmodel.h" 28 | 29 | namespace unplayer 30 | { 31 | struct DirectoryContentFile 32 | { 33 | QString filePath; 34 | QString name; 35 | bool isDirectory; 36 | }; 37 | 38 | class DirectoryContentModel : public AsyncLoadingModel, public QQmlParserStatus 39 | { 40 | Q_OBJECT 41 | Q_INTERFACES(QQmlParserStatus) 42 | Q_PROPERTY(QString directory READ directory WRITE setDirectory NOTIFY directoryChanged) 43 | Q_PROPERTY(QString parentDirectory READ parentDirectory NOTIFY directoryChanged) 44 | Q_PROPERTY(bool showFiles READ showFiles WRITE setShowFiles) 45 | Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters) 46 | public: 47 | enum Role 48 | { 49 | FilePathRole, 50 | NameRole, 51 | IsDirectoryRole 52 | }; 53 | Q_ENUM(Role) 54 | 55 | DirectoryContentModel(); 56 | void classBegin() override; 57 | void componentComplete() override; 58 | 59 | QVariant data(const QModelIndex& index, int role) const override; 60 | int rowCount(const QModelIndex&) const override; 61 | 62 | const QString& directory() const; 63 | void setDirectory(const QString& directory); 64 | 65 | const QString& parentDirectory() const; 66 | 67 | bool showFiles() const; 68 | void setShowFiles(bool show); 69 | 70 | const QStringList& nameFilters() const; 71 | void setNameFilters(const QStringList& filters); 72 | 73 | protected: 74 | QHash roleNames() const override; 75 | 76 | private: 77 | void loadDirectory(); 78 | 79 | bool mComponentCompleted; 80 | std::vector mFiles; 81 | QString mDirectory; 82 | QString mParentDirectory; 83 | bool mShowFiles; 84 | QStringList mNameFilters; 85 | 86 | signals: 87 | void directoryChanged(); 88 | }; 89 | } 90 | 91 | Q_DECLARE_TYPEINFO(unplayer::DirectoryContentFile, Q_MOVABLE_TYPE); 92 | 93 | #endif // UNPLAYER_DIRECTORYCONTENTMODEL_H 94 | -------------------------------------------------------------------------------- /src/directorycontentproxymodel.cpp: -------------------------------------------------------------------------------- 1 | #include "directorycontentproxymodel.h" 2 | 3 | namespace unplayer 4 | { 5 | int DirectoryContentProxyModel::isDirectoryRole() const 6 | { 7 | return mIsDirectoryRole; 8 | } 9 | 10 | void DirectoryContentProxyModel::setIsDirectoryRole(int isDirectoryRole) 11 | { 12 | mIsDirectoryRole = isDirectoryRole; 13 | } 14 | 15 | bool DirectoryContentProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const 16 | { 17 | const bool leftIsDirectory = left.data(mIsDirectoryRole).toBool(); 18 | const bool rightIsDirectory = right.data(mIsDirectoryRole).toBool(); 19 | if (leftIsDirectory != rightIsDirectory) { 20 | return leftIsDirectory; 21 | } 22 | return FilterProxyModel::lessThan(left, right); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/directorycontentproxymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_DIRECTORYCONTENTPROXYMODEL_H 20 | #define UNPLAYER_DIRECTORYCONTENTPROXYMODEL_H 21 | 22 | #include "filterproxymodel.h" 23 | 24 | namespace unplayer 25 | { 26 | class DirectoryContentProxyModel : public FilterProxyModel 27 | { 28 | Q_OBJECT 29 | Q_PROPERTY(int isDirectoryRole READ isDirectoryRole WRITE setIsDirectoryRole) 30 | public: 31 | int isDirectoryRole() const; 32 | void setIsDirectoryRole(int isDirectoryRole); 33 | 34 | protected: 35 | bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; 36 | 37 | private: 38 | int mIsDirectoryRole = 0; 39 | }; 40 | } 41 | 42 | #endif // UNPLAYER_DIRECTORYCONTENTPROXYMODEL_H 43 | -------------------------------------------------------------------------------- /src/directorytracksmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_DIRECTORYTRACKSMODEL_H 20 | #define UNPLAYER_DIRECTORYTRACKSMODEL_H 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include "asyncloadingmodel.h" 27 | #include "directorycontentproxymodel.h" 28 | 29 | namespace unplayer 30 | { 31 | struct DirectoryTrackFile 32 | { 33 | QString filePath; 34 | QString fileName; 35 | bool isDirectory; 36 | bool isPlaylist; 37 | }; 38 | 39 | class DirectoryTracksModel : public AsyncLoadingModel, public QQmlParserStatus 40 | { 41 | Q_OBJECT 42 | Q_INTERFACES(QQmlParserStatus) 43 | Q_PROPERTY(QString directory READ directory WRITE setDirectory NOTIFY directoryChanged) 44 | Q_PROPERTY(QString parentDirectory READ parentDirectory NOTIFY directoryChanged) 45 | public: 46 | enum Role 47 | { 48 | FilePathRole = Qt::UserRole, 49 | FileNameRole, 50 | IsDirectoryRole, 51 | IsPlaylistRole, 52 | }; 53 | Q_ENUM(Role) 54 | 55 | void classBegin() override; 56 | void componentComplete() override; 57 | 58 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 59 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 60 | bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; 61 | 62 | const std::vector& files() const; 63 | 64 | QString directory() const; 65 | void setDirectory(QString newDirectory); 66 | 67 | QString parentDirectory() const; 68 | 69 | Q_INVOKABLE QString getTrack(int index) const; 70 | Q_INVOKABLE QStringList getTracks(const std::vector& indexes, bool includePlaylists = true) const; 71 | 72 | Q_INVOKABLE void removeTrack(int index); 73 | Q_INVOKABLE void removeTracks(const std::vector& indexes); 74 | 75 | protected: 76 | QHash roleNames() const override; 77 | 78 | private: 79 | void loadDirectory(); 80 | void onQueryFinished(); 81 | 82 | private: 83 | std::vector mFiles; 84 | 85 | int mTracksCount = 0; 86 | 87 | QString mDirectory; 88 | 89 | bool mShowVideoFiles = false; 90 | signals: 91 | void directoryChanged(); 92 | }; 93 | 94 | class DirectoryTracksProxyModel : public DirectoryContentProxyModel 95 | { 96 | Q_OBJECT 97 | Q_PROPERTY(int directoriesCount READ directoriesCount NOTIFY countChanged) 98 | Q_PROPERTY(int tracksCount READ tracksCount NOTIFY countChanged) 99 | public: 100 | DirectoryTracksProxyModel(); 101 | void componentComplete() override; 102 | 103 | int directoriesCount() const; 104 | int tracksCount() const; 105 | 106 | Q_INVOKABLE QStringList getSelectedTracks() const; 107 | Q_INVOKABLE void selectAll(); 108 | 109 | private: 110 | int mDirectoriesCount; 111 | int mTracksCount; 112 | signals: 113 | void countChanged(); 114 | }; 115 | } 116 | 117 | //Q_DECLARE_TYPEINFO(unplayer::DirectoryTrackFile, Q_MOVABLE_TYPE); 118 | 119 | #endif // UNPLAYER_DIRECTORYTRACKSMODEL_H 120 | -------------------------------------------------------------------------------- /src/fileutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_FILEUTILS_H 20 | #define UNPLAYER_FILEUTILS_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | namespace fileutils 27 | { 28 | enum class Extension 29 | { 30 | FLAC, 31 | 32 | AAC, 33 | M4A, 34 | 35 | MP3, 36 | 37 | OGG, 38 | OPUS, 39 | SPX, 40 | 41 | APE, 42 | 43 | WAV, 44 | AIFF, 45 | 46 | Other 47 | }; 48 | 49 | enum class AudioCodec 50 | { 51 | FLAC, 52 | AAC, 53 | MP3, 54 | Vorbis, 55 | Opus, 56 | Speex, 57 | APE, 58 | LPCM, 59 | 60 | // Unsupported 61 | ALAC, 62 | AIFFC, 63 | Unknown 64 | }; 65 | 66 | Extension extensionFromSuffix(const QString& suffix); 67 | Extension extensionFromSuffixLowered(const QString& suffixLowered); 68 | bool isExtensionSupported(const QString& suffix); 69 | bool isVideoExtensionSupported(const QString& suffix); 70 | 71 | bool isAudioCodecSupported(AudioCodec audioCodec); 72 | QString audioCodecDisplayName(AudioCodec audioCodec); 73 | } 74 | } 75 | 76 | #endif // UNPLAYER_FILEUTILS_H 77 | -------------------------------------------------------------------------------- /src/filterproxymodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "filterproxymodel.h" 20 | 21 | #include 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | FilterProxyModel::FilterProxyModel() 27 | : mSortEnabled(false), 28 | mSelectionModel(new QItemSelectionModel(this)) 29 | { 30 | mCollator.setNumericMode(true); 31 | QObject::connect(mSelectionModel, &QItemSelectionModel::selectionChanged, this, &FilterProxyModel::selectionChanged); 32 | } 33 | 34 | void FilterProxyModel::classBegin() 35 | { 36 | 37 | } 38 | 39 | void FilterProxyModel::componentComplete() 40 | { 41 | if (mSortEnabled) { 42 | sort(0); 43 | } 44 | } 45 | 46 | std::vector FilterProxyModel::sourceIndexes() const 47 | { 48 | std::vector indexes; 49 | indexes.reserve(static_cast(rowCount())); 50 | for (int i = 0, max = rowCount(); i < max; ++i) { 51 | indexes.push_back(sourceIndex(i)); 52 | } 53 | return indexes; 54 | } 55 | 56 | bool FilterProxyModel::isSortEnabled() const 57 | { 58 | return mSortEnabled; 59 | } 60 | 61 | void FilterProxyModel::setSortEnabled(bool sortEnabled) 62 | { 63 | mSortEnabled = sortEnabled; 64 | } 65 | 66 | int FilterProxyModel::proxyIndex(int sourceIndex) const 67 | { 68 | return mapFromSource(sourceModel()->index(sourceIndex, 0)).row(); 69 | } 70 | 71 | int FilterProxyModel::sourceIndex(int proxyIndex) const 72 | { 73 | return mapToSource(index(proxyIndex, 0)).row(); 74 | } 75 | 76 | QItemSelectionModel* FilterProxyModel::selectionModel() const 77 | { 78 | return mSelectionModel; 79 | } 80 | 81 | bool FilterProxyModel::hasSelection() const 82 | { 83 | return mSelectionModel->hasSelection(); 84 | } 85 | 86 | int FilterProxyModel::selectedIndexesCount() const 87 | { 88 | return mSelectionModel->selectedIndexes().size(); 89 | } 90 | 91 | std::vector FilterProxyModel::selectedSourceIndexes() const 92 | { 93 | QModelIndexList modelIndexes(mSelectionModel->selectedIndexes()); 94 | std::sort(modelIndexes.begin(), modelIndexes.end()); 95 | 96 | std::vector indexes; 97 | indexes.reserve(static_cast(modelIndexes.size())); 98 | for (const QModelIndex& index : modelIndexes) { 99 | indexes.push_back(mapToSource(index).row()); 100 | } 101 | return indexes; 102 | } 103 | 104 | bool FilterProxyModel::isSelected(int row) const 105 | { 106 | return mSelectionModel->isSelected(index(row, 0)); 107 | } 108 | 109 | void FilterProxyModel::select(int row) 110 | { 111 | mSelectionModel->select(index(row, 0), QItemSelectionModel::Toggle); 112 | } 113 | 114 | void FilterProxyModel::selectAll() 115 | { 116 | mSelectionModel->select(QItemSelection(index(0, 0), index(rowCount() - 1, 0)), QItemSelectionModel::Select); 117 | } 118 | 119 | bool FilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const 120 | { 121 | const QVariant leftVariant(left.data(sortRole())); 122 | if (leftVariant.type() == QVariant::String) { 123 | return (mCollator.compare(leftVariant.toString(), right.data(sortRole()).toString()) < 0); 124 | } 125 | return QSortFilterProxyModel::lessThan(left, right); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/filterproxymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_FILTERPROXYMODEL_H 20 | #define UNPLAYER_FILTERPROXYMODEL_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | class QItemSelectionModel; 29 | 30 | namespace unplayer 31 | { 32 | class FilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus 33 | { 34 | Q_OBJECT 35 | Q_INTERFACES(QQmlParserStatus) 36 | Q_PROPERTY(bool sortEnabled READ isSortEnabled WRITE setSortEnabled) 37 | Q_PROPERTY(std::vector sourceIndexes READ sourceIndexes) 38 | Q_PROPERTY(QItemSelectionModel* selectionModel READ selectionModel CONSTANT) 39 | Q_PROPERTY(bool hasSelection READ hasSelection NOTIFY selectionChanged) 40 | Q_PROPERTY(int selectedIndexesCount READ selectedIndexesCount NOTIFY selectionChanged) 41 | Q_PROPERTY(std::vector selectedSourceIndexes READ selectedSourceIndexes NOTIFY selectionChanged) 42 | public: 43 | FilterProxyModel(); 44 | void classBegin() override; 45 | void componentComplete() override; 46 | 47 | std::vector sourceIndexes() const; 48 | 49 | bool isSortEnabled() const; 50 | void setSortEnabled(bool sortEnabled); 51 | 52 | Q_INVOKABLE int proxyIndex(int sourceIndex) const; 53 | Q_INVOKABLE int sourceIndex(int proxyIndex) const; 54 | 55 | QItemSelectionModel* selectionModel() const; 56 | bool hasSelection() const; 57 | int selectedIndexesCount() const; 58 | std::vector selectedSourceIndexes() const; 59 | Q_INVOKABLE bool isSelected(int row) const; 60 | Q_INVOKABLE void select(int row); 61 | Q_INVOKABLE void selectAll(); 62 | 63 | protected: 64 | bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; 65 | 66 | private: 67 | QCollator mCollator; 68 | bool mSortEnabled; 69 | QItemSelectionModel* mSelectionModel; 70 | signals: 71 | void selectionChanged(); 72 | }; 73 | } 74 | 75 | #endif // UNPLAYER_FILTERPROXYMODEL_H 76 | -------------------------------------------------------------------------------- /src/genresmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_GENRESMODEL_H 20 | #define UNPLAYER_GENRESMODEL_H 21 | 22 | #include "abstractlibrarymodel.h" 23 | 24 | #include 25 | 26 | namespace unplayer 27 | { 28 | struct LibraryTrack; 29 | 30 | struct Genre 31 | { 32 | int id; 33 | QString genre; 34 | int tracksCount; 35 | int duration; 36 | 37 | QString mediaArt; 38 | bool requestedMediaArt; 39 | }; 40 | 41 | class GenresModel : public AbstractLibraryModel 42 | { 43 | Q_OBJECT 44 | Q_PROPERTY(bool sortDescending READ sortDescending NOTIFY sortDescendingChanged) 45 | public: 46 | GenresModel(); 47 | 48 | QVariant data(const QModelIndex& index, int role) const override; 49 | 50 | bool sortDescending() const; 51 | Q_INVOKABLE void toggleSortOrder(); 52 | 53 | Q_INVOKABLE std::vector getTracksForGenre(int index) const; 54 | Q_INVOKABLE std::vector getTracksForGenres(const std::vector& indexes) const; 55 | Q_INVOKABLE QStringList getTrackPathsForGenre(int index) const; 56 | Q_INVOKABLE QStringList getTrackPathsForGenres(const std::vector& indexes) const; 57 | 58 | Q_INVOKABLE void removeGenre(int index, bool deleteFiles); 59 | Q_INVOKABLE void removeGenres(const std::vector& indexes, bool deleteFiles); 60 | protected: 61 | class ItemFactory final : public AbstractItemFactory 62 | { 63 | public: 64 | Genre itemFromQuery(const QSqlQuery& query) override; 65 | }; 66 | 67 | QHash roleNames() const override; 68 | 69 | QString makeQueryString() override; 70 | AbstractItemFactory* createItemFactory() override; 71 | 72 | private: 73 | std::vector& mGenres = mItems; 74 | bool mSortDescending; 75 | 76 | signals: 77 | void sortDescendingChanged(); 78 | }; 79 | } 80 | 81 | #endif // UNPLAYER_GENRESMODEL_H 82 | -------------------------------------------------------------------------------- /src/librarydirectoriesmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "librarydirectoriesmodel.h" 20 | 21 | #include "modelutils.h" 22 | #include "settings.h" 23 | 24 | namespace unplayer 25 | { 26 | void LibraryDirectoriesModel::classBegin() 27 | { 28 | 29 | } 30 | 31 | void LibraryDirectoriesModel::componentComplete() 32 | { 33 | switch (mType) { 34 | case Library: 35 | mDirectories = Settings::instance()->libraryDirectories(); 36 | break; 37 | case Blacklisted: 38 | mDirectories = Settings::instance()->blacklistedDirectories(); 39 | break; 40 | default: 41 | break; 42 | } 43 | } 44 | 45 | QVariant LibraryDirectoriesModel::data(const QModelIndex& index, int role) const 46 | { 47 | if (role == Qt::UserRole) { 48 | return mDirectories.at(index.row()); 49 | } 50 | return QVariant(); 51 | } 52 | 53 | int LibraryDirectoriesModel::rowCount(const QModelIndex&) const 54 | { 55 | return mDirectories.size(); 56 | } 57 | 58 | bool LibraryDirectoriesModel::removeRows(int row, int count, const QModelIndex& parent) 59 | { 60 | if (count > 0 && (row + count) <= static_cast(mDirectories.size())) { 61 | beginRemoveRows(parent, row, row + count - 1); 62 | const auto first(mDirectories.begin() + row); 63 | mDirectories.erase(first, first + count); 64 | endRemoveRows(); 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | LibraryDirectoriesModel::Type LibraryDirectoriesModel::type() const 71 | { 72 | return mType; 73 | } 74 | 75 | void LibraryDirectoriesModel::setType(Type type) 76 | { 77 | mType = type; 78 | } 79 | 80 | void LibraryDirectoriesModel::addDirectory(const QString& directory) 81 | { 82 | beginInsertRows(QModelIndex(), mDirectories.size(), mDirectories.size()); 83 | mDirectories.push_back(directory); 84 | endInsertRows(); 85 | 86 | switch (mType) { 87 | case Library: 88 | Settings::instance()->setLibraryDirectories(mDirectories); 89 | break; 90 | case Blacklisted: 91 | Settings::instance()->setBlacklistedDirectories(mDirectories); 92 | break; 93 | default: 94 | break; 95 | } 96 | } 97 | 98 | void LibraryDirectoriesModel::removeDirectory(int index) 99 | { 100 | beginRemoveRows(QModelIndex(), index, index); 101 | mDirectories.removeAt(index); 102 | endRemoveRows(); 103 | 104 | switch (mType) { 105 | case Library: 106 | Settings::instance()->setLibraryDirectories(mDirectories); 107 | break; 108 | case Blacklisted: 109 | Settings::instance()->setBlacklistedDirectories(mDirectories); 110 | break; 111 | default: 112 | break; 113 | } 114 | } 115 | 116 | void LibraryDirectoriesModel::removeDirectories(const std::vector& indexes) 117 | { 118 | ModelBatchRemover::removeIndexes(this, indexes); 119 | 120 | switch (mType) { 121 | case Library: 122 | Settings::instance()->setLibraryDirectories(mDirectories); 123 | break; 124 | case Blacklisted: 125 | Settings::instance()->setBlacklistedDirectories(mDirectories); 126 | break; 127 | default: 128 | break; 129 | } 130 | } 131 | 132 | QHash LibraryDirectoriesModel::roleNames() const 133 | { 134 | return {{Qt::UserRole, "directory"}}; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/librarydirectoriesmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_LIBRARYDIRECTORIESMODEL_H 20 | #define UNPLAYER_LIBRARYDIRECTORIESMODEL_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | namespace unplayer 29 | { 30 | class LibraryDirectoriesModel : public QAbstractListModel, public QQmlParserStatus 31 | { 32 | Q_OBJECT 33 | Q_INTERFACES(QQmlParserStatus) 34 | Q_PROPERTY(Type type READ type WRITE setType) 35 | public: 36 | enum Type 37 | { 38 | Library, 39 | Blacklisted 40 | }; 41 | Q_ENUM(Type) 42 | 43 | void classBegin() override; 44 | void componentComplete() override; 45 | 46 | QVariant data(const QModelIndex& index, int role) const override; 47 | int rowCount(const QModelIndex& parent) const override; 48 | bool removeRows(int row, int count, const QModelIndex& parent) override; 49 | 50 | Type type() const; 51 | void setType(Type type); 52 | 53 | Q_INVOKABLE void addDirectory(const QString& directory); 54 | Q_INVOKABLE void removeDirectory(int index); 55 | Q_INVOKABLE void removeDirectories(const std::vector& indexes); 56 | 57 | protected: 58 | QHash roleNames() const override; 59 | private: 60 | QStringList mDirectories; 61 | Type mType = Library; 62 | }; 63 | } 64 | 65 | #endif // UNPLAYER_LIBRARYDIRECTORIESMODEL_H 66 | -------------------------------------------------------------------------------- /src/librarymigrator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_LIBRARYMIGRATOR_H 20 | #define UNPLAYER_LIBRARYMIGRATOR_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | class QString; 28 | 29 | namespace unplayer 30 | { 31 | class LibraryMigrator 32 | { 33 | public: 34 | explicit LibraryMigrator(const QSqlDatabase& db); 35 | 36 | bool migrateIfNeeded(int latestVersion); 37 | 38 | private: 39 | bool migrateFrom0(); 40 | bool migrateOldTracks(std::unordered_map& userMediaArtHash); 41 | 42 | QSqlDatabase mDb; 43 | QSqlQuery mQuery{mDb}; 44 | }; 45 | } 46 | 47 | #endif // UNPLAYER_LIBRARYMIGRATOR_H 48 | -------------------------------------------------------------------------------- /src/librarytrack.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_LIBRARYTRACK_H 20 | #define UNPLAYER_LIBRARYTRACK_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | struct LibraryTrack 27 | { 28 | int id; 29 | QString filePath; 30 | QString title; 31 | QString artist; 32 | QString album; 33 | bool filteredSingleAlbum; 34 | int duration; 35 | }; 36 | } 37 | 38 | #endif // UNPLAYER_LIBRARYTRACK_H 39 | -------------------------------------------------------------------------------- /src/librarytracksadder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_LIBRARYTRACKSADDER_H 20 | #define UNPLAYER_LIBRARYTRACKSADDER_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "stdutils.h" 31 | 32 | class QSqlDatabase; 33 | 34 | using QStringIntVectorPair = QPair>; 35 | QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QStringIntVectorPair) 36 | 37 | namespace unplayer 38 | { 39 | namespace tagutils 40 | { 41 | struct Info; 42 | } 43 | 44 | class LibraryTracksAdder 45 | { 46 | public: 47 | explicit LibraryTracksAdder(const QSqlDatabase& db); 48 | 49 | void addTrackToDatabase(const QString& filePath, 50 | long long modificationTime, 51 | tagutils::Info& info, 52 | const QString& directoryMediaArt, 53 | const QString& embeddedMediaArt); 54 | 55 | int getAddedArtistId(const QString& title); 56 | int getAddedAlbumId(const QString& title, const QVector& artistIds); 57 | 58 | private: 59 | struct ArtistsOrGenres 60 | { 61 | explicit ArtistsOrGenres(QLatin1String table, const QSqlDatabase& db); 62 | 63 | QLatin1String table; 64 | QSqlQuery addNewQuery; 65 | QSqlQuery addTracksRelationshipQuery; 66 | 67 | std::unordered_map ids{}; 68 | int lastId = 0; 69 | }; 70 | 71 | struct Albums 72 | { 73 | explicit Albums(const QSqlDatabase& db); 74 | 75 | QSqlQuery addNewQuery; 76 | QSqlQuery addTracksRelationshipQuery; 77 | QSqlQuery addArtistsRelationshipQuery; 78 | 79 | std::unordered_map>, int> ids{}; 80 | int lastId = 0; 81 | }; 82 | 83 | void getArtists(); 84 | void getGenres(); 85 | void getArtistsOrGenres(ArtistsOrGenres& ids); 86 | void getAlbums(); 87 | 88 | int getArtistId(const QString& title); 89 | int getGenreId(const QString& title); 90 | int getArtistOrGenreId(const QString& title, ArtistsOrGenres& ids); 91 | int addArtistOrGenre(const QString& title, ArtistsOrGenres& ids); 92 | 93 | int getAlbumId(const QString& title, QVector&& artistIds); 94 | int addAlbum(const QString& title, QVector&& artistIds); 95 | 96 | void addRelationship(int firstId, int secondId, QSqlQuery& query); 97 | 98 | int mLastTrackId; 99 | const QSqlDatabase& mDb; 100 | QSqlQuery mAddTrackQuery{mDb}; 101 | 102 | ArtistsOrGenres mArtists{QLatin1String("artists"), mDb}; 103 | Albums mAlbums{mDb}; 104 | ArtistsOrGenres mGenres{QLatin1String("genres"), mDb}; 105 | }; 106 | } 107 | 108 | #endif // UNPLAYER_LIBRARYTRACKSADDER_H 109 | -------------------------------------------------------------------------------- /src/libraryupdaterunnable.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_LIBRARYUPDATERUNNABLE_H 20 | #define UNPLAYER_LIBRARYUPDATERUNNABLE_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "libraryutils.h" 28 | 29 | namespace unplayer 30 | { 31 | class LibraryUpdateRunnable final : public QObject, public QRunnable 32 | { 33 | Q_OBJECT 34 | public: 35 | void cancel(); 36 | void run() override; 37 | 38 | private: 39 | std::atomic_bool mCancelFlag; 40 | 41 | signals: 42 | void stageChanged(unplayer::LibraryUtils::UpdateStage newStage); 43 | void foundFilesChanged(int found); 44 | void extractedFilesChanged(int extracted); 45 | void finished(); 46 | }; 47 | } 48 | 49 | #endif // UNPLAYER_LIBRARYUPDATERUNNABLE_H 50 | -------------------------------------------------------------------------------- /src/mediaartutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_MEDIAARTPROVIDER_H 20 | #define UNPLAYER_MEDIAARTPROVIDER_H 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | class QFileInfo; 29 | class QMimeDatabase; 30 | class QThread; 31 | 32 | namespace unplayer 33 | { 34 | class MediaArtUtilsWorker; 35 | 36 | class MediaArtUtils final : public QObject 37 | { 38 | Q_OBJECT 39 | public: 40 | static MediaArtUtils* instance(); 41 | static void deleteInstance(); 42 | 43 | static const QString& mediaArtDirectory(); 44 | static QString findMediaArtForDirectory(const QString& directoryPath, std::unordered_map& directoriesMediaArtCache); 45 | static bool isMediaArtFile(const QFileInfo& fileInfo); 46 | static bool isMediaArtFile(const QFileInfo& fileInfo, const QString& suffix); 47 | static bool isMediaArtFileSuffixLowered(const QFileInfo& fileInfo, const QString& suffixLowered); 48 | static std::unordered_map getEmbeddedMediaArtFiles(); 49 | static QString saveEmbeddedMediaArt(const QByteArray& data, std::unordered_map& embeddedMediaArtFiles, const QMimeDatabase& mimeDb); 50 | 51 | Q_INVOKABLE void setUserMediaArt(int albumId, const QString& mediaArt); 52 | 53 | void getMediaArtForFile(const QString& filePath, const QString& albumForUserMediaArt, bool onlyExtractEmbedded); 54 | 55 | void getRandomMediaArt(uintptr_t requestId); 56 | void getRandomMediaArtForArtist(int artistId); 57 | void getRandomMediaArtForAlbum(int albumId); 58 | void getRandomMediaArtForArtistAndAlbum(int artistId, int albumId); 59 | void getRandomMediaArtForGenre(int genreId); 60 | 61 | private: 62 | explicit MediaArtUtils(QObject* parent); 63 | ~MediaArtUtils(); 64 | 65 | void createWorker(); 66 | 67 | QThread* mWorkerThread = nullptr; 68 | MediaArtUtilsWorker* mWorker = nullptr; 69 | 70 | signals: 71 | void gotMediaArtForFile(const QString& filePath, const QString& libraryMediaArt, const QString& directoryMediaArt, const QByteArray& embeddedMediaArtData); 72 | 73 | void gotRandomMediaArt(uintptr_t requestId, const QString& mediaArt); 74 | void gotRandomMediaArtForArtist(int artistId, const QString& mediaArt); 75 | void gotRandomMediaArtForAlbum(int albumId, const QString& mediaArt); 76 | void gotRandomMediaArtForArtistAndAlbum(int artistId, int albumId, const QString& mediaArt); 77 | void gotRandomMediaArtForGenre(int genreId, const QString& mediaArt); 78 | 79 | void mediaArtChanged(); 80 | }; 81 | 82 | class RandomMediaArt : public QObject 83 | { 84 | Q_OBJECT 85 | Q_PROPERTY(QString mediaArt READ mediaArt NOTIFY mediaArtChanged) 86 | public: 87 | RandomMediaArt(); 88 | const QString& mediaArt(); 89 | 90 | private: 91 | QString mMediaArt; 92 | bool mMediaArtRequested = false; 93 | 94 | signals: 95 | void mediaArtChanged(const QString& mediaArt); 96 | }; 97 | } 98 | 99 | #endif // UNPLAYER_MEDIAARTPROVIDER_H 100 | -------------------------------------------------------------------------------- /src/modelutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2020 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_MODELUTILS_H 20 | #define UNPLAYER_MODELUTILS_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | class ModelBatchRemover 27 | { 28 | public: 29 | inline static void removeIndexes(QAbstractItemModel* model, const std::vector& indexes) 30 | { 31 | if (!indexes.empty()) { 32 | ModelBatchRemover remover(model); 33 | for (int i = static_cast(indexes.size()) - 1; i >= 0; --i) { 34 | remover.remove(indexes[static_cast(i)]); 35 | } 36 | remover.remove(); 37 | } 38 | } 39 | 40 | inline explicit ModelBatchRemover(QAbstractItemModel* model) 41 | : model(model) {} 42 | 43 | inline void remove(int row) 44 | { 45 | if (firstRow == -1) { 46 | reset(row); 47 | } else { 48 | if (row == (firstRow - 1)) { 49 | firstRow = row; 50 | } else { 51 | remove(); 52 | reset(row); 53 | } 54 | } 55 | } 56 | 57 | inline void remove() 58 | { 59 | if (firstRow != -1) { 60 | model->removeRows(firstRow, lastRow - firstRow + 1); 61 | } 62 | } 63 | 64 | private: 65 | inline void reset(int row) 66 | { 67 | firstRow = row; 68 | lastRow = row; 69 | } 70 | 71 | QAbstractItemModel *const model; 72 | 73 | int firstRow = -1; 74 | int lastRow = -1; 75 | }; 76 | } 77 | 78 | #endif // UNPLAYER_MODELUTILS_H 79 | -------------------------------------------------------------------------------- /src/org.equeim.unplayer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/org.freedesktop.Application.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/player.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_PLAYER_H 20 | #define UNPLAYER_PLAYER_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | class Queue; 27 | 28 | class Player final : public QMediaPlayer 29 | { 30 | Q_OBJECT 31 | Q_PROPERTY(bool playing READ isPlaying NOTIFY playingChanged) 32 | Q_PROPERTY(bool stopAfterEos READ stopAfterEos WRITE setStopAfterEos NOTIFY stopAfterEosChanged) 33 | Q_PROPERTY(unplayer::Queue* queue READ queue CONSTANT) 34 | public: 35 | static Player* instance(); 36 | 37 | bool isPlaying() const; 38 | 39 | bool stopAfterEos() const; 40 | void setStopAfterEos(bool stop); 41 | 42 | Queue* queue() const; 43 | 44 | Q_INVOKABLE void saveState() const; 45 | Q_INVOKABLE void restoreState(); 46 | 47 | private: 48 | Player(QObject* parent); 49 | 50 | Queue* mQueue; 51 | bool mSettingNewTrack; 52 | 53 | bool mRestoringTracks; 54 | 55 | bool mStopAfterEos; 56 | bool mStoppedAfterEos; 57 | 58 | signals: 59 | void playingChanged(); 60 | void stopAfterEosChanged(); 61 | }; 62 | } 63 | 64 | #endif // UNPLAYER_PLAYER_H 65 | -------------------------------------------------------------------------------- /src/playlistmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_PLAYLISTMODEL_H 20 | #define UNPLAYER_PLAYLISTMODEL_H 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include "asyncloadingmodel.h" 27 | #include "playlistutils.h" 28 | 29 | namespace unplayer 30 | { 31 | class PlaylistModel : public AsyncLoadingModel 32 | { 33 | Q_OBJECT 34 | Q_PROPERTY(QString filePath READ filePath WRITE setFilePath) 35 | public: 36 | enum Role 37 | { 38 | UrlRole = Qt::UserRole, 39 | IsLocalFileRole, 40 | FilePathRole, 41 | TitleRole, 42 | DurationRole, 43 | ArtistRole, 44 | AlbumRole 45 | }; 46 | Q_ENUM(Role) 47 | 48 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 49 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 50 | bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; 51 | 52 | const QString& filePath() const; 53 | void setFilePath(const QString& playlistFilePath); 54 | 55 | Q_INVOKABLE QStringList getTracks(const std::vector& indexes); 56 | Q_INVOKABLE void removeTrack(int index); 57 | Q_INVOKABLE void removeTracks(const std::vector& indexes); 58 | protected: 59 | QHash roleNames() const override; 60 | 61 | private: 62 | std::vector mTracks; 63 | QString mFilePath; 64 | }; 65 | } 66 | 67 | #endif // UNPLAYER_PLAYLISTMODEL_H 68 | -------------------------------------------------------------------------------- /src/playlistsmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_PLAYLISTSMODEL_H 20 | #define UNPLAYER_PLAYLISTSMODEL_H 21 | 22 | #include 23 | 24 | #include "asyncloadingmodel.h" 25 | 26 | namespace unplayer 27 | { 28 | struct PlaylistsModelItem 29 | { 30 | QString filePath; 31 | QString name; 32 | int tracksCount; 33 | 34 | bool operator==(const PlaylistsModelItem& other) const; 35 | }; 36 | 37 | class PlaylistsModel : public AsyncLoadingModel 38 | { 39 | Q_OBJECT 40 | public: 41 | enum Role 42 | { 43 | FilePathRole = Qt::UserRole, 44 | NameRole, 45 | TracksCountRole 46 | }; 47 | Q_ENUM(Role) 48 | 49 | PlaylistsModel(); 50 | 51 | QVariant data(const QModelIndex& index, int role) const override; 52 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 53 | bool removeRows(int row, int count, const QModelIndex& parent) override; 54 | 55 | Q_INVOKABLE void removePlaylists(const std::vector& indexes) const; 56 | 57 | Q_INVOKABLE QStringList getTracksForPlaylists(const std::vector& indexes) const; 58 | protected: 59 | QHash roleNames() const override; 60 | 61 | private: 62 | void update(); 63 | 64 | std::vector mPlaylists; 65 | }; 66 | } 67 | 68 | Q_DECLARE_TYPEINFO(unplayer::PlaylistsModelItem, Q_MOVABLE_TYPE); 69 | 70 | #endif // UNPLAYER_PLAYLISTSMODEL_H 71 | -------------------------------------------------------------------------------- /src/playlistutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_PLAYLISTUTILS_H 20 | #define UNPLAYER_PLAYLISTUTILS_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | namespace unplayer 29 | { 30 | struct LibraryTrack; 31 | 32 | struct PlaylistTrack 33 | { 34 | QUrl url; 35 | QString title; 36 | int duration; 37 | QString artist; 38 | QString album; 39 | }; 40 | 41 | class PlaylistUtils final : public QObject 42 | { 43 | Q_OBJECT 44 | Q_PROPERTY(int playlistsCount READ playlistsCount NOTIFY playlistsChanged) 45 | public: 46 | static const QStringList& playlistsNameFilters(); 47 | static bool isPlaylistExtension(const QString& suffix); 48 | 49 | static PlaylistUtils* instance(); 50 | 51 | const QString& playlistsDirectoryPath(); 52 | int playlistsCount(); 53 | 54 | void savePlaylist(const QString& filePath, const std::vector& tracks); 55 | 56 | Q_INVOKABLE void newPlaylistFromFilesystem(const QString& name, const QStringList& trackUrls); 57 | Q_INVOKABLE void newPlaylistFromLibrary(const QString& name, const std::vector& libraryTracks); 58 | Q_INVOKABLE void newPlaylistFromLibrary(const QString& name, const unplayer::LibraryTrack& libraryTrack); 59 | 60 | Q_INVOKABLE void addTracksToPlaylistFromFilesystem(const QString& filePath, const QStringList& trackUrls); 61 | Q_INVOKABLE void addTracksToPlaylistFromLibrary(const QString& filePath, const std::vector& libraryTracks); 62 | Q_INVOKABLE void addTracksToPlaylistFromLibrary(const QString& filePath, const unplayer::LibraryTrack& libraryTrack); 63 | 64 | Q_INVOKABLE void removePlaylist(const QString& filePath); 65 | 66 | void removePlaylists(const std::vector& playlists); 67 | 68 | static std::vector parsePlaylist(const QString& filePath); 69 | Q_INVOKABLE static QStringList getPlaylistTracks(const QString& filePath); 70 | static int getPlaylistTracksCount(const QString& filePath); 71 | private: 72 | explicit PlaylistUtils(QObject* parent); 73 | 74 | void newPlaylist(const QString& name, const std::vector& tracks); 75 | void addTracksToPlaylist(const QString& filePath, std::vector&& tracks); 76 | 77 | QString mPlaylistsDirectoryPath; 78 | signals: 79 | void playlistsChanged(); 80 | }; 81 | } 82 | 83 | Q_DECLARE_TYPEINFO(unplayer::PlaylistTrack, Q_MOVABLE_TYPE); 84 | 85 | #endif // UNPLAYER_PLAYLISTUTILS_H 86 | -------------------------------------------------------------------------------- /src/qscopeguard.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sérgio Martins 4 | ** Copyright (C) 2019 The Qt Company Ltd. 5 | ** Contact: https://www.qt.io/licensing/ 6 | ** 7 | ** This file is part of the QtCore module of the Qt Toolkit. 8 | ** 9 | ** $QT_BEGIN_LICENSE:LGPL$ 10 | ** Commercial License Usage 11 | ** Licensees holding valid commercial Qt licenses may use this file in 12 | ** accordance with the commercial license agreement provided with the 13 | ** Software or, alternatively, in accordance with the terms contained in 14 | ** a written agreement between you and The Qt Company. For licensing terms 15 | ** and conditions see https://www.qt.io/terms-conditions. For further 16 | ** information use the contact form at https://www.qt.io/contact-us. 17 | ** 18 | ** GNU Lesser General Public License Usage 19 | ** Alternatively, this file may be used under the terms of the GNU Lesser 20 | ** General Public License version 3 as published by the Free Software 21 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the 22 | ** packaging of this file. Please review the following information to 23 | ** ensure the GNU Lesser General Public License version 3 requirements 24 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 25 | ** 26 | ** GNU General Public License Usage 27 | ** Alternatively, this file may be used under the terms of the GNU 28 | ** General Public License version 2.0 or (at your option) the GNU General 29 | ** Public license version 3 or any later version approved by the KDE Free 30 | ** Qt Foundation. The licenses are as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 32 | ** included in the packaging of this file. Please review the following 33 | ** information to ensure the GNU General Public License requirements will 34 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and 35 | ** https://www.gnu.org/licenses/gpl-3.0.html. 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #ifndef UNPLAYER_QSCOPEGUARD_H 42 | #define UNPLAYER_QSCOPEGUARD_H 43 | 44 | #include 45 | 46 | #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) 47 | #include 48 | #else 49 | 50 | #include 51 | #include 52 | 53 | QT_BEGIN_NAMESPACE 54 | 55 | template 56 | class 57 | QScopeGuard 58 | { 59 | public: 60 | explicit QScopeGuard(F &&f) noexcept 61 | : m_func(std::move(f)) 62 | { 63 | } 64 | 65 | explicit QScopeGuard(const F &f) noexcept 66 | : m_func(f) 67 | { 68 | } 69 | 70 | QScopeGuard(QScopeGuard &&other) noexcept 71 | : m_func(std::move(other.m_func)) 72 | , m_invoke(std::exchange(other.m_invoke, false)) 73 | { 74 | } 75 | 76 | ~QScopeGuard() noexcept 77 | { 78 | if (m_invoke) 79 | m_func(); 80 | } 81 | 82 | void dismiss() noexcept 83 | { 84 | m_invoke = false; 85 | } 86 | 87 | private: 88 | Q_DISABLE_COPY(QScopeGuard) 89 | 90 | F m_func; 91 | bool m_invoke = true; 92 | }; 93 | 94 | #ifdef __cpp_deduction_guides 95 | template QScopeGuard(F(&)()) -> QScopeGuard; 96 | #endif 97 | 98 | //! [qScopeGuard] 99 | template 100 | Q_REQUIRED_RESULT 101 | QScopeGuard::type> qScopeGuard(F &&f) 102 | { 103 | return QScopeGuard::type>(std::forward(f)); 104 | } 105 | 106 | QT_END_NAMESPACE 107 | 108 | #endif // QT_VERSION 109 | 110 | #endif // UNPLAYER_QSCOPEGUARD_H 111 | -------------------------------------------------------------------------------- /src/queuemodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "queuemodel.h" 20 | 21 | #include "queue.h" 22 | 23 | namespace unplayer 24 | { 25 | QVariant QueueModel::data(const QModelIndex& index, int role) const 26 | { 27 | if (!mQueue || !index.isValid()) { 28 | return QVariant(); 29 | } 30 | 31 | const QueueTrack* track = mQueue->tracks()[static_cast(index.row())].get(); 32 | 33 | switch (role) { 34 | case UrlRole: 35 | return track->url; 36 | case IsLocalFileRole: 37 | return track->url.isLocalFile(); 38 | case FilePathRole: 39 | return track->url.path(); 40 | case TitleRole: 41 | return track->title; 42 | case ArtistRole: 43 | return track->artist; 44 | case DisplayedArtistRole: 45 | return track->artist; 46 | case AlbumRole: 47 | return track->album; 48 | case DisplayedAlbumRole: 49 | return track->album; 50 | case DurationRole: 51 | return track->duration; 52 | default: 53 | return QVariant(); 54 | } 55 | } 56 | 57 | int QueueModel::rowCount(const QModelIndex&) const 58 | { 59 | return mQueue ? static_cast(mQueue->tracks().size()) : 0; 60 | } 61 | 62 | Queue* QueueModel::queue() const 63 | { 64 | return mQueue; 65 | } 66 | 67 | void QueueModel::setQueue(Queue* queue) 68 | { 69 | if (queue == mQueue || !queue) { 70 | return; 71 | } 72 | 73 | mQueue = queue; 74 | 75 | QObject::connect(mQueue, &Queue::tracksAboutToBeAdded, this, [=](int count) { 76 | const int first = rowCount(); 77 | beginInsertRows(QModelIndex(), first, first + count - 1); 78 | }); 79 | 80 | QObject::connect(mQueue, &Queue::tracksAdded, this, [=]() { 81 | endInsertRows(); 82 | }); 83 | 84 | QObject::connect(mQueue, &Queue::trackAboutToBeRemoved, this, [=](int index) { 85 | beginRemoveRows(QModelIndex(), index, index); 86 | }); 87 | 88 | QObject::connect(mQueue, &Queue::trackRemoved, this, [=]() { 89 | endRemoveRows(); 90 | }); 91 | 92 | QObject::connect(mQueue, &Queue::aboutToBeCleared, this, [=]() { 93 | beginRemoveRows(QModelIndex(), 0, rowCount() - 1); 94 | }); 95 | 96 | QObject::connect(mQueue, &Queue::cleared, this, [=]() { 97 | endRemoveRows(); 98 | }); 99 | } 100 | 101 | QHash QueueModel::roleNames() const 102 | { 103 | return {{UrlRole, "url"}, 104 | {IsLocalFileRole, "isLocalFile"}, 105 | {FilePathRole, "filePath"}, 106 | {TitleRole, "title"}, 107 | {ArtistRole, "artist"}, 108 | {DisplayedArtistRole, "displayedArtist"}, 109 | {AlbumRole, "album"}, 110 | {DisplayedAlbumRole, "displayedAlbum"}, 111 | {DurationRole, "duration"}}; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/queuemodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_QUEUEMODEL_H 20 | #define UNPLAYER_QUEUEMODEL_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | class Queue; 27 | 28 | class QueueModel : public QAbstractListModel 29 | { 30 | Q_OBJECT 31 | Q_PROPERTY(unplayer::Queue* queue READ queue WRITE setQueue) 32 | public: 33 | enum Role 34 | { 35 | UrlRole = Qt::UserRole, 36 | IsLocalFileRole, 37 | FilePathRole, 38 | TitleRole, 39 | ArtistRole, 40 | DisplayedArtistRole, 41 | AlbumRole, 42 | DisplayedAlbumRole, 43 | DurationRole 44 | }; 45 | Q_ENUM(Role) 46 | 47 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 48 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 49 | 50 | Queue* queue() const; 51 | void setQueue(Queue* queue); 52 | 53 | protected: 54 | QHash roleNames() const override; 55 | 56 | private: 57 | Queue* mQueue = nullptr; 58 | }; 59 | } 60 | 61 | #endif // UNPLAYER_QUEUEMODEL_H 62 | -------------------------------------------------------------------------------- /src/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | license.html 4 | translators.html 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/signalhandler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "signalhandler.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | namespace unplayer 30 | { 31 | std::atomic_bool SignalHandler::exitRequested{false}; 32 | 33 | namespace 34 | { 35 | int signalsFd[2]{}; 36 | 37 | void signalHandler(int) 38 | { 39 | SignalHandler::exitRequested = true; 40 | char tmp = 0; 41 | write(signalsFd[0], &tmp, sizeof(tmp)); 42 | } 43 | } 44 | 45 | void SignalHandler::setupHandlers() 46 | { 47 | int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, signalsFd); 48 | if (ret != 0) { 49 | qFatal("Failed to create socketpair, errno=%d", errno); 50 | } 51 | 52 | struct sigaction action{}; 53 | action.sa_handler = signalHandler; 54 | action.sa_flags |= SA_RESTART; 55 | 56 | ret = sigaction(SIGINT, &action, nullptr); 57 | if (ret != 0) { 58 | qFatal("Failed to set signal handler on SIGINT, errno=%d", errno); 59 | } 60 | ret = sigaction(SIGTERM, &action, nullptr); 61 | if (ret != 0) { 62 | qFatal("Failed to set signal handler on SIGTERM, errno=%d", errno); 63 | } 64 | ret = sigaction(SIGHUP, &action, nullptr); 65 | if (ret != 0) { 66 | qFatal("Failed to set signal handler on SIGHUP, errno=%d", errno); 67 | } 68 | ret = sigaction(SIGQUIT, &action, nullptr); 69 | if (ret != 0) { 70 | qFatal("Failed to set signal handler on SIGQUIT, errno=%d", errno); 71 | } 72 | } 73 | 74 | void SignalHandler::setupNotifier() 75 | { 76 | auto notifier = new QSocketNotifier(signalsFd[1], QSocketNotifier::Read, qApp); 77 | QObject::connect(notifier, &QSocketNotifier::activated, qApp, [notifier](int socket) { 78 | // This lambda will be executed only after calling QCoreApplication::exec() 79 | notifier->setEnabled(false); 80 | char tmp; 81 | read(socket, &tmp, sizeof(tmp)); 82 | QCoreApplication::quit(); 83 | notifier->setEnabled(true); 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/signalhandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_SIGNALHANDLER_H 20 | #define UNPLAYER_SIGNALHANDLER_H 21 | 22 | #include 23 | 24 | namespace unplayer 25 | { 26 | class SignalHandler 27 | { 28 | public: 29 | static std::atomic_bool exitRequested; 30 | 31 | static void setupHandlers(); 32 | static void setupNotifier(); 33 | 34 | SignalHandler(const SignalHandler&) = delete; 35 | SignalHandler(SignalHandler&&) = delete; 36 | SignalHandler& operator=(const SignalHandler&) = delete; 37 | SignalHandler& operator=(SignalHandler&&) = delete; 38 | }; 39 | } 40 | 41 | #endif // UNPLAYER_SIGNALHANDLER_H 42 | -------------------------------------------------------------------------------- /src/tagutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_TAGUTILS_H 20 | #define UNPLAYER_TAGUTILS_H 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "fileutils.h" 31 | 32 | template class QMap; 33 | class QMimeDatabase; 34 | class QVariant; 35 | typedef QMap QVariantMap; 36 | 37 | namespace unplayer 38 | { 39 | class Tags final : public QObject 40 | { 41 | Q_OBJECT 42 | Q_PROPERTY(QString title READ title CONSTANT) 43 | Q_PROPERTY(QString artists READ artists CONSTANT) 44 | Q_PROPERTY(QString albumArtists READ albumArtists CONSTANT) 45 | Q_PROPERTY(QString albums READ albums CONSTANT) 46 | Q_PROPERTY(QString year READ year CONSTANT) 47 | Q_PROPERTY(QString trackNumber READ trackNumber CONSTANT) 48 | Q_PROPERTY(QString genres READ genres CONSTANT) 49 | Q_PROPERTY(QString discNumber READ discNumber CONSTANT) 50 | public: 51 | static QLatin1String title(); 52 | static QLatin1String artists(); 53 | static QLatin1String albumArtists(); 54 | static QLatin1String albums(); 55 | static QLatin1String year(); 56 | static QLatin1String trackNumber(); 57 | static QLatin1String genres(); 58 | static QLatin1String discNumber(); 59 | }; 60 | 61 | namespace tagutils 62 | { 63 | struct Info 64 | { 65 | inline Info() = default; 66 | inline Info(const QString& filePath, fileutils::AudioCodec audioCodec, bool canReadTags) 67 | : filePath(filePath), audioCodec(audioCodec), canReadTags(canReadTags) {} 68 | 69 | QString filePath; 70 | fileutils::AudioCodec audioCodec = fileutils::AudioCodec::Unknown; 71 | bool canReadTags{}; 72 | 73 | QString title; 74 | QStringList artists; 75 | QStringList albumArtists; 76 | QStringList albums; 77 | int year{}; 78 | int trackNumber{}; 79 | QStringList genres; 80 | QString discNumber; 81 | 82 | int duration{}; 83 | int bitrate{}; 84 | int bitDepth{}; 85 | int sampleRate{}; 86 | int channels{}; 87 | 88 | QByteArray mediaArtData; 89 | }; 90 | 91 | struct AudioCodecInfo 92 | { 93 | inline AudioCodecInfo() = default; 94 | inline explicit AudioCodecInfo(fileutils::AudioCodec audioCodec) : audioCodec(audioCodec) {} 95 | fileutils::AudioCodec audioCodec = fileutils::AudioCodec::Unknown; 96 | int bitDepth{}; 97 | int sampleRate{}; 98 | int bitrate{}; 99 | }; 100 | 101 | std::optional getTrackInfo(const QString& filePath, fileutils::Extension extension); 102 | std::optional getTackMediaArtData(const QString& filePath, fileutils::Extension extension); 103 | std::optional getTrackAudioCodecInfo(const QString& filePath, fileutils::Extension extension); 104 | 105 | template 106 | std::vector saveTags(const QStringList& files, const QVariantMap& tags, const std::function& callback); 107 | 108 | extern template std::vector saveTags(const QStringList& files, const QVariantMap& tags, const std::function& callback); 109 | extern template std::vector saveTags(const QStringList& files, const QVariantMap& tags, const std::function& callback); 110 | } 111 | } 112 | 113 | #endif // UNPLAYER_TAGUTILS_H 114 | -------------------------------------------------------------------------------- /src/translators.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 63 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_UTILS_H 20 | #define UNPLAYER_UTILS_H 21 | 22 | #include 23 | #include 24 | 25 | namespace unplayer 26 | { 27 | class Utils final : public QObject 28 | { 29 | Q_OBJECT 30 | Q_PROPERTY(QString homeDirectory READ homeDirectory) 31 | Q_PROPERTY(QString sdcardPath READ sdcardPath) 32 | Q_PROPERTY(QStringList imageNameFilters READ imageNameFilters CONSTANT) 33 | Q_PROPERTY(QString translators READ translators CONSTANT) 34 | Q_PROPERTY(QString license READ license CONSTANT) 35 | public: 36 | static void registerTypes(); 37 | 38 | Q_INVOKABLE static QString formatDuration(int seconds); 39 | Q_INVOKABLE static QString formatByteSize(double size); 40 | 41 | Q_INVOKABLE static QString escapeRegExp(const QString& string); 42 | 43 | /** 44 | * @brief Hack to disable SilicaFlickable's overshoot fading animation 45 | * @param silicaFlickable SilicaFlickable instance 46 | */ 47 | Q_INVOKABLE static void disableSilicaFlickableBounceEffect(QObject* silicaFlickable); 48 | 49 | static QString homeDirectory(); 50 | static QString sdcardPath(bool emptyIfNotMounted = false); 51 | 52 | static QStringList imageNameFilters(); 53 | 54 | static QString translators(); 55 | static QString license(); 56 | }; 57 | } 58 | 59 | #endif // UNPLAYER_UTILS_H 60 | -------------------------------------------------------------------------------- /src/utilsfunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unplayer 3 | * Copyright (C) 2015-2019 Alexey Rochev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UNPLAYER_UTILSFUNCTIONS_H 20 | #define UNPLAYER_UTILSFUNCTIONS_H 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | namespace unplayer 31 | { 32 | inline long long getLastModifiedTime(const QString& filePath) 33 | { 34 | struct stat64 result; 35 | if (stat64(filePath.toUtf8(), &result) != 0) { 36 | return -1; 37 | } 38 | return result.st_mtim.tv_sec * 1000 + result.st_mtim.tv_nsec / 1000000; 39 | } 40 | 41 | template 42 | inline void batchedCount(size_t count, size_t batchSize, Function call) 43 | { 44 | for (size_t i = 0; i < count; i += batchSize) { 45 | const size_t nextCount = [&]() { 46 | const size_t left = count - i; 47 | if (left > batchSize) { 48 | return batchSize; 49 | } 50 | return left; 51 | }(); 52 | call(i, nextCount); 53 | } 54 | } 55 | 56 | template 57 | inline void onFutureFinished(const QFuture& future, QObject* parent, const Func& func) 58 | { 59 | auto watcher = new QFutureWatcher(parent); 60 | QObject::connect(watcher, &QFutureWatcher::finished, parent, [watcher, func]() { 61 | func(watcher->result()); 62 | watcher->deleteLater(); 63 | }); 64 | watcher->setFuture(future); 65 | } 66 | 67 | template 68 | inline void onFutureFinished(const QFuture& future, QObject* parent, const Func& func) 69 | { 70 | auto watcher = new QFutureWatcher(parent); 71 | QObject::connect(watcher, &QFutureWatcher::finished, parent, [watcher, func]() { 72 | func(); 73 | watcher->deleteLater(); 74 | }); 75 | watcher->setFuture(future); 76 | } 77 | 78 | inline QString unquote(QString str) { 79 | static const QLatin1Char quoteChar('"'); 80 | if (str.startsWith(quoteChar) && str.endsWith(quoteChar) && str.size() > 1) { 81 | str.chop(1); 82 | str.remove(0, 1); 83 | } 84 | return str; 85 | } 86 | } 87 | 88 | #endif // UNPLAYER_UTILSFUNCTIONS_H 89 | -------------------------------------------------------------------------------- /translations/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Qt5LinguistTools CONFIG REQUIRED) 2 | 3 | qt5_add_translation(qm_files 4 | harbour-unplayer-ar.ts 5 | harbour-unplayer-de.ts 6 | harbour-unplayer-en.ts 7 | harbour-unplayer-es.ts 8 | harbour-unplayer-fr.ts 9 | harbour-unplayer-hr.ts 10 | harbour-unplayer-it.ts 11 | harbour-unplayer-nb.ts 12 | harbour-unplayer-nl_BE.ts 13 | harbour-unplayer-nl.ts 14 | harbour-unplayer-oc.ts 15 | harbour-unplayer-pl.ts 16 | harbour-unplayer-ru.ts 17 | harbour-unplayer-sv.ts 18 | harbour-unplayer-zh_CN.ts 19 | ) 20 | 21 | add_custom_target(translations ALL DEPENDS ${qm_files}) 22 | 23 | install(FILES ${qm_files} DESTINATION "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/translations") 24 | -------------------------------------------------------------------------------- /translations/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if command -v lupdate-qt5 > /dev/null 2>&1; then 4 | _lupdate=lupdate-qt5 5 | else 6 | _lupdate=lupdate 7 | fi 8 | 9 | _dir="$(realpath $(dirname $0))" 10 | cd "$_dir" 11 | 12 | QT_SELECT=5 $_lupdate ../src ../qml -ts harbour-unplayer-en.ts \ 13 | harbour-unplayer-ar.ts \ 14 | harbour-unplayer-de.ts \ 15 | harbour-unplayer-es.ts \ 16 | harbour-unplayer-fr.ts \ 17 | harbour-unplayer-hr.ts \ 18 | harbour-unplayer-it.ts \ 19 | harbour-unplayer-nb.ts \ 20 | harbour-unplayer-nl.ts \ 21 | harbour-unplayer-nl_BE.ts \ 22 | harbour-unplayer-pl.ts \ 23 | harbour-unplayer-ru.ts \ 24 | harbour-unplayer-sv.ts \ 25 | harbour-unplayer-zh_CN.ts 26 | --------------------------------------------------------------------------------