diff --git a/CMakeLists.txt b/CMakeLists.txt index a83b0919..35e2e742 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,155 +1,151 @@ cmake_minimum_required(VERSION 3.5) project(elisa) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) set(REQUIRED_QT_VERSION "5.9.0") find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Qml Quick Test Sql Multimedia Svg Gui Widgets QuickTest Concurrent) -set(REQUIRED_KF5_VERSION "5.32.0") +set(REQUIRED_KF5_VERSION "5.41.0") find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(FeatureSummary) include(ECMAddAppIcon) include(ECMAddTests) find_package(Qt5DBus ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt5 DBus is needed to provide MPris2 interface to allow remote control by the desktop workspace." TYPE OPTIONAL) find_package(Qt5QuickWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "Qt5 Quick Widgets is needed at runtime to provide the interface." TYPE RUNTIME) find_package(Qt5QuickControls2 ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickControls2 PROPERTIES DESCRIPTION "Qt5 Quick Controls version 2 is needed at runtime to provide the interface." TYPE RUNTIME) if (ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS AndroidExtras) endif() find_package(KF5I18n ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5I18n PROPERTIES DESCRIPTION "KF5 text internationalization library." TYPE REQUIRED) find_package(KF5Declarative ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Declarative PROPERTIES DESCRIPTION "Integration of QML and KDE work spaces." TYPE RECOMMENDED) find_package(KF5CoreAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "Qt addon library with a collection of non-GUI utilities." TYPE REQUIRED) find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "Baloo provides file searching and indexing." TYPE RECOMMENDED) find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Provides a simple library for extracting metadata." TYPE REQUIRED) -if ("${KF5FileMetaData_VERSION_MINOR}" LESS "39") - message(WARNING "KF5 FileMetaData versions prior to 5.39 are working but some crashes can happen when used by Elisa music player") -endif() - -find_package(KF5DocTools 5.39.0 CONFIG QUIET) +find_package(KF5DocTools ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Create documentation from DocBook library." TYPE OPTIONAL) find_package(KF5XmlGui ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5XmlGui PROPERTIES DESCRIPTION "Framework for managing menu and toolbar actions." TYPE RECOMMENDED) find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Config PROPERTIES DESCRIPTION "Persistent platform-independent application settings." TYPE REQUIRED) find_package(KF5ConfigWidgets ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5ConfigWidgets PROPERTIES DESCRIPTION "Widgets for configuration dialogs." TYPE RECOMMENDED) find_package(KF5Crash ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "Graceful handling of application crashes." TYPE OPTIONAL) find_package(KF5DBusAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Convenience classes for D-Bus." TYPE OPTIONAL) find_package(KF5KCMUtils ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KCMUtils PROPERTIES DESCRIPTION "KF5 Utilities for KDE System Settings modules library." TYPE RECOMMENDED) find_package(KF5Package ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Package PROPERTIES DESCRIPTION "KF5 package management library needed to get the configuration dialogs." TYPE RECOMMENDED) find_package(UPNPQT CONFIG QUIET) set_package_properties(UPNPQT PROPERTIES DESCRIPTION "UPNP layer build with Qt 5. UPnP support is currently broken. You should probably avoid this dependency." URL "https://gitlab.com/homeautomationqt/upnp-player-qt" TYPE OPTIONAL) if (UPNPQT_FOUND) message(WARNING "UPnP support is experimental and may not work.") endif() include(FeatureSummary) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) if (CMAKE_SYSTEM_NAME STREQUAL Android) set(QT_QMAKE_EXECUTABLE "$ENV{Qt5_android}/bin/qmake") endif() configure_file(config-upnp-qt.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-upnp-qt.h ) add_subdirectory(src) add_subdirectory(icons) if (BUILD_TESTING) add_subdirectory(autotests) endif() add_subdirectory(doc) install( PROGRAMS org.kde.elisa.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES org.kde.elisa.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/qml/ApplicationMenu.qml b/src/qml/ApplicationMenu.qml index 82a6d423..5e316779 100644 --- a/src/qml/ApplicationMenu.qml +++ b/src/qml/ApplicationMenu.qml @@ -1,94 +1,95 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 +//explore menu from Qt 5.10 once we can require it, but it is item-based import QtQuick.Controls 1.4 Menu { id: applicationMenu title: i18nc("open application menu", "Application Menu") property var helpAction: elisa.action("help_contents") property var quitApplication: elisa.action("file_quit") property var reportBugAction: elisa.action("help_report_bug") property var aboutAppAction: elisa.action("help_about_app") property var configureShortcutsAction: elisa.action("options_configure_keybinding") property var configureAction: elisa.action("options_configure") MenuItem { text: configureAction.text shortcut: configureAction.shortcut iconName: elisa.iconName(configureAction.icon) onTriggered: configureAction.trigger() visible: configureAction.text !== "" } MenuItem { text: configureShortcutsAction.text shortcut: configureShortcutsAction.shortcut iconName: elisa.iconName(configureShortcutsAction.icon) onTriggered: configureShortcutsAction.trigger() visible: configureShortcutsAction.text !== "" } MenuSeparator { visible: reportBugAction.text !== "" } MenuItem { text: reportBugAction.text shortcut: reportBugAction.shortcut iconName: elisa.iconName(reportBugAction.icon) onTriggered: reportBugAction.trigger() visible: reportBugAction.text !== "" } MenuSeparator { visible: helpAction.text !== "" } MenuItem { text: helpAction.text shortcut: helpAction.shortcut iconName: elisa.iconName(helpAction.icon) onTriggered: helpAction.trigger() visible: helpAction.text !== "" } MenuItem { text: aboutAppAction.text shortcut: aboutAppAction.shortcut iconName: elisa.iconName(aboutAppAction.icon) onTriggered: aboutAppAction.trigger() visible: aboutAppAction.text !== "" } MenuSeparator { visible: quitApplication.text !== "" } MenuItem { text: quitApplication.text shortcut: quitApplication.shortcut iconName: elisa.iconName(quitApplication.icon) onTriggered: quitApplication.trigger() visible: quitApplication.text !== "" } } diff --git a/src/qml/ContextView.qml b/src/qml/ContextView.qml index a9b61306..09d202b6 100644 --- a/src/qml/ContextView.qml +++ b/src/qml/ContextView.qml @@ -1,171 +1,170 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.4 +import QtQuick 2.7 import QtQuick.Window 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQml.Models 2.1 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 import org.kde.elisa 1.0 import QtQuick.Layouts 1.2 Item { id: topItem property var albumName property var artistName property var tracksCount property var albumArtUrl ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.fillHeight: true } Image { id: albumIcon source: albumArtUrl.toString() === '' ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : albumArtUrl Layout.preferredWidth: elisaTheme.coverImageSize Layout.preferredHeight: elisaTheme.coverImageSize Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.maximumWidth: elisaTheme.coverImageSize Layout.maximumHeight: elisaTheme.coverImageSize Layout.bottomMargin: elisaTheme.layoutVerticalMargin width: elisaTheme.coverImageSize height: elisaTheme.coverImageSize sourceSize.width: elisaTheme.coverImageSize sourceSize.height: elisaTheme.coverImageSize asynchronous: true fillMode: Image.PreserveAspectFit } LabelWithToolTip { id: titleLabel text: if (albumName !== undefined) albumName else '' font.weight: Font.Bold color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } LabelWithToolTip { id: artistLabel text: if (artistName !== undefined) artistName else '' font.weight: Font.Normal color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom elide: Text.ElideRight } LabelWithToolTip { id: numberLabel text: i18np("1 track", "%1 track", tracksCount) visible: tracksCount !== undefined font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } Item { Layout.fillHeight: true } RowLayout { Layout.fillWidth: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin * 2 spacing: 0 Image { id: artistJumpIcon source: Qt.resolvedUrl(elisaTheme.defaultArtistImage) Layout.preferredWidth: elisaTheme.smallImageSize Layout.preferredHeight: elisaTheme.smallImageSize Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.maximumWidth: elisaTheme.smallImageSize Layout.maximumHeight: elisaTheme.smallImageSize Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 visible: artistName !== undefined width: elisaTheme.smallImageSize height: elisaTheme.smallImageSize sourceSize.width: elisaTheme.smallImageSize sourceSize.height: elisaTheme.smallImageSize fillMode: Image.PreserveAspectFit } LabelWithToolTip { text: if (artistName !== undefined) artistName else '' font.weight: Font.Normal color: myPalette.text horizontalAlignment: Text.AlignLeft } } } } diff --git a/src/qml/DraggableItem.qml b/src/qml/DraggableItem.qml index c3446e0e..48f92554 100644 --- a/src/qml/DraggableItem.qml +++ b/src/qml/DraggableItem.qml @@ -1,266 +1,266 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * listviewdragitem * * An example of reordering items in a ListView via drag'n'drop. * * Author: Aurélien Gâteau * License: BSD */ -import QtQuick 2.5 +import QtQuick 2.7 FocusScope { id: root default property alias contentItem: dragArea.contentItem // This item will become the parent of the dragged item during the drag operation property Item draggedItemParent signal moveItemRequested(int from, int to) // Size of the area at the top and bottom of the list where drag-scrolling happens property int scrollEdgeSize: 6 // Internal: set to -1 when drag-scrolling up and 1 when drag-scrolling down property int _scrollingDirection: 0 // Internal: shortcut to access the attached ListView from everywhere. Shorter than root.ListView.view property ListView _listView: ListView.view property alias containsMouse: dragArea.containsMouse signal clicked() signal doubleClicked() width: contentItem.width height: topPlaceholder.height + wrapperParent.height + bottomPlaceholder.height property int placeholderHeight // Make contentItem a child of contentItemWrapper onContentItemChanged: { contentItem.parent = dragArea; } Rectangle { id: topPlaceholder anchors { left: parent.left right: parent.right top: parent.top } height: 0 color: myPalette.mid } Item { id: wrapperParent anchors { left: parent.left right: parent.right top: topPlaceholder.bottom } height: contentItem.height Rectangle { id: contentItemWrapper anchors.fill: parent Drag.active: dragArea.drag.active Drag.hotSpot { x: contentItem.width / 2 y: contentItem.height / 2 } MouseArea { id: dragArea anchors.fill: parent drag.target: parent // Disable smoothed so that the Item pixel from where we started the drag remains under the mouse cursor drag.smoothed: false property Item contentItem hoverEnabled: true preventStealing: true acceptedButtons: Qt.LeftButton onReleased: { if (drag.active) { emitMoveItemRequested(); } } onClicked: root.clicked() onDoubleClicked: root.doubleClicked() } } } Rectangle { id: bottomPlaceholder anchors { left: parent.left right: parent.right top: wrapperParent.bottom } height: 0 color: myPalette.mid } SmoothedAnimation { id: upAnimation target: _listView property: "contentY" to: 0 running: _scrollingDirection == -1 } SmoothedAnimation { id: downAnimation target: _listView property: "contentY" to: _listView.contentHeight - _listView.height running: _scrollingDirection == 1 } Loader { id: topDropAreaLoader active: model.index === 0 anchors { left: parent.left right: parent.right bottom: wrapperParent.verticalCenter } height: placeholderHeight sourceComponent: Component { DropArea { property int dropIndex: 0 } } } DropArea { id: bottomDropArea anchors { left: parent.left right: parent.right top: wrapperParent.verticalCenter } property bool isLast: model.index === _listView.count - 1 height: isLast ? _listView.contentHeight - y : placeholderHeight property int dropIndex: model.index + 1 } states: [ State { when: dragArea.drag.active name: "dragging" ParentChange { target: contentItemWrapper parent: draggedItemParent } PropertyChanges { target: contentItemWrapper opacity: 0.9 anchors.fill: undefined width: contentItem.width height: contentItem.height } PropertyChanges { target: wrapperParent height: 0 } PropertyChanges { target: root _scrollingDirection: { var yCoord = _listView.mapFromItem(dragArea, 0, dragArea.mouseY).y; if (yCoord < scrollEdgeSize) { -1; } else if (yCoord > _listView.height - scrollEdgeSize) { 1; } else { 0; } } } }, State { when: bottomDropArea.containsDrag name: "droppingBelow" PropertyChanges { target: bottomPlaceholder height: placeholderHeight } PropertyChanges { target: bottomDropArea height: contentItem.height } }, State { when: topDropAreaLoader.item.containsDrag name: "droppingAbove" PropertyChanges { target: topPlaceholder height: placeholderHeight } PropertyChanges { target: topDropAreaLoader height: contentItem.height } } ] function emitMoveItemRequested() { var dropArea = contentItemWrapper.Drag.target; if (!dropArea) { return; } var dropIndex = dropArea.dropIndex; // If the target item is below us, then decrement dropIndex because the target item is going to move up when // our item leaves its place if (model.index < dropIndex) { dropIndex--; } if (model.index === dropIndex) { return; } root.moveItemRequested(model.index, dropIndex); // Scroll the ListView to ensure the dropped item is visible. This is required when dropping an item after the // last item of the view. Delay the scroll using a Timer because we have to wait until the view has moved the // item before we can scroll to it. makeDroppedItemVisibleTimer.start(); } Timer { id: makeDroppedItemVisibleTimer interval: 0 onTriggered: { _listView.positionViewAtIndex(model.index, ListView.Contain); } } } diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 209c6b92..210df823 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,945 +1,943 @@ /* * Copyright 2016-2018 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: i18n("Elisa") property var goBackAction: elisa.action("go_back") - Action { - text: goBackAction.text + Controls1.Action { shortcut: goBackAction.shortcut - iconName: elisa.iconName(goBackAction.icon) onTriggered: { localAlbums.goBack() localArtists.goBack() } } - Action { + Controls1.Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } ApplicationMenu { id: applicationMenu } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1000 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false property string filterState } Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = elisa.mediaPlayList.persistentState; persistentSettings.playListControlerState = elisa.mediaPlayList.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayList playListControler: elisa.mediaPlayList audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume onVolumeChanged: headerBar.playerControl.volume = volume onMutedChanged: headerBar.playerControl.muted = muted source: manageAudioPlayer.playerSource onPlaying: { myPlayControlManager.playerPlaying() } onPaused: { myPlayControlManager.playerPaused() } onStopped: { myPlayControlManager.playerStopped() } } Connections { target: elisa.mediaPlayList onPlayListLoadFailed: { messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) } onEnsurePlay: manageAudioPlayer.ensurePlay() onPlayListFinished: manageAudioPlayer.playListFinished() } ManageHeaderBar { id: myHeaderBarManager playListModel: elisa.mediaPlayList currentTrack: elisa.mediaPlayList.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: elisa.mediaPlayList.currentTrack playListModel: elisa.mediaPlayList urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole titleRole: MediaPlayList.TitleRole artistNameRole: MediaPlayList.ArtistRole albumNameRole: MediaPlayList.AlbumRole playerStatus: audioPlayer.status playerPlaybackState: audioPlayer.playbackState playerError: audioPlayer.error audioDuration: audioPlayer.duration playerIsSeekable: audioPlayer.seekable playerPosition: audioPlayer.position persistentState: persistentSettings.audioPlayerState onPlayerPlay: audioPlayer.play() onPlayerPause: audioPlayer.pause() onPlayerStop: audioPlayer.stop() onSkipNextTrack: elisa.mediaPlayList.skipNextTrack() onSeek: audioPlayer.seek(position) onSourceInError: { elisa.mediaPlayList.trackInError(source, playerError) elisa.musicManager.playBackError(source, playerError) } onDisplayTrackError: messageNotification.showNotification(i18n("Error when playing %1", "" + fileName), 3000) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: elisa.mediaPlayList currentTrack: elisa.mediaPlayList.currentTrack } PassiveNotification { id: messageNotification } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.preferredHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true HeaderBar { id: headerBar focus: true anchors.fill: parent tracksCount: myHeaderBarManager.remainingTracks album: myHeaderBarManager.album title: myHeaderBarManager.title artist: myHeaderBarManager.artist image: myHeaderBarManager.image ratingVisible: false playerControl.duration: audioPlayer.duration playerControl.seekable: audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying playerControl.onSeek: audioPlayer.seek(position) playerControl.onPlay: manageAudioPlayer.playPause() playerControl.onPause: manageAudioPlayer.playPause() playerControl.onPlayPrevious: elisa.mediaPlayList.skipPreviousTrack() playerControl.onPlayNext: elisa.mediaPlayList.skipNextTrack() - ToolButton { + Controls1.ToolButton { id: menuButton action: applicationMenuAction z: 2 anchors { right: parent.right top: parent.top rightMargin: elisaTheme.layoutHorizontalMargin * 3 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Rectangle { anchors.fill: menuButton z: 1 radius: width / 2 color: myPalette.window } TrackImportNotification { id: importedTracksCountNotification anchors { right: menuButton.left top: menuButton.top bottom: menuButton.bottom rightMargin: elisaTheme.layoutHorizontalMargin * 3 } } Binding { target: importedTracksCountNotification property: 'musicManager' value: elisa.musicManager when: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'indexingRunning' value: elisa.musicManager.indexingRunning } active: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'importedTracksCount' value: elisa.musicManager.importedTracksCount } active: elisa.musicManager !== undefined } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 TopNotification { id: invalidBalooConfiguration Layout.fillWidth: true musicManager: elisa.musicManager focus: true } Item { Layout.fillHeight: true Layout.fillWidth: true RowLayout { anchors.fill: parent spacing: 0 id: contentZone FocusScope { id: mainContentView focus: true Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 visible: Layout.minimumWidth != 0 Rectangle { border { color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) width: 1 } radius: 3 color: myPalette.base anchors.fill: parent BusyIndicator { id: busyScanningMusic - + hoverEnabled: false anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 opacity: 0.8 z: 2 } Loader { sourceComponent: Binding { target: busyScanningMusic property: 'running' value: elisa.musicManager.indexerBusy } active: elisa.musicManager !== undefined } MediaBrowser { id: localAlbums focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allAlbumsView focus: true contentModel: elisa.allAlbumsProxyModel image: elisaTheme.albumIcon mainTitle: i18nc("Title of the view of all albums", "Albums") onOpen: { elisa.singleAlbumProxyModel.sourceModel.loadAlbumData(databaseId) localAlbums.stackView.push(albumView, { stackView: localAlbums.stackView, albumName: innerMainTitle, artistName: innerSecondaryTitle, albumArtUrl: innerImage, }) } onGoBack: localAlbums.stackView.pop() Binding { target: allAlbumsView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } MediaBrowser { id: localArtists focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allArtistsView focus: true showRating: false delegateDisplaySecondaryText: false contentModel: elisa.allArtistsProxyModel image: elisaTheme.artistIcon mainTitle: i18nc("Title of the view of all artists", "Artists") onOpen: { elisa.singleArtistProxyModel.setArtistFilterText(innerMainTitle) localArtists.stackView.push(innerAlbumView, { mainTitle: innerMainTitle, secondaryTitle: innerSecondaryTitle, image: innerImage, stackView: localArtists.stackView }) } onGoBack: localArtists.stackView.pop() Binding { target: allArtistsView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: MediaAllTracksView { id: allTracksView focus: true stackView: localTracks.stackView contentModel: elisa.allTracksProxyModel Binding { target: allTracksView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } Behavior on border.color { ColorAnimation { duration: 300 } } } } Rectangle { id: firstViewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } MediaPlayListView { id: playList playListModel: elisa.mediaPlayList randomPlayChecked: elisa.mediaPlayList.randomPlay repeatPlayChecked: elisa.mediaPlayList.repeatPlay Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width onStartPlayback: manageAudioPlayer.ensurePlay() onPausePlayback: manageAudioPlayer.playPause() onDisplayError: messageNotification.showNotification(errorText) } Rectangle { id: viewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: Layout.minimumWidth != 0 Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } ContextView { id: albumContext Layout.fillHeight: true Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width visible: Layout.minimumWidth != 0 artistName: myHeaderBarManager.artist albumName: myHeaderBarManager.album albumArtUrl: myHeaderBarManager.image } } } states: [ State { name: 'full' when: listViews.currentIndex === 0 PropertyChanges { target: mainContentView Layout.fillWidth: false Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allAlbums' when: listViews.currentIndex === 1 StateChangeScript { script: { localAlbums.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 1 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allArtists' when: listViews.currentIndex === 2 StateChangeScript { script: { localArtists.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 1 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allTracks' when: listViews.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 1 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } } } } Component { id: innerAlbumView GridBrowserView { id: innerAlbumGridView property var stackView contentModel: elisa.singleArtistProxyModel isSubPage: true onOpen: { elisa.singleAlbumProxyModel.sourceModel.loadAlbumData(databaseId) localArtists.stackView.push(albumView, { stackView: localArtists.stackView, albumName: innerMainTitle, artistName: innerSecondaryTitle, albumArtUrl: innerImage, }) } onGoBack: stackView.pop() Binding { target: innerAlbumGridView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } } Component { id: albumView MediaAlbumView { id: albumGridView property var stackView contentModel: elisa.singleAlbumProxyModel onShowArtist: { listViews.currentIndex = 2 if (localArtists.stackView.depth === 3) { localArtists.stackView.pop() } if (localArtists.stackView.depth === 2) { var artistPage = localArtists.stackView.get(1) if (artistPage.mainTitle === name) { return } else { localArtists.stackView.pop() } } allArtistsView.open(name, name, elisaTheme.defaultArtistImage, '') } onGoBack: stackView.pop() Binding { target: albumGridView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } } Component.onCompleted: { elisa.initialize() var d = new Date(); var n = d.getMilliseconds(); elisa.mediaPlayList.seedRandomGenerator(n); elisa.mediaPlayList.randomPlay = Qt.binding(function() { return playList.randomPlayChecked }) elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return playList.repeatPlayChecked }) myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return playList.randomPlayChecked || playList.repeatPlayChecked }) if (persistentSettings.playListState) { elisa.mediaPlayList.persistentState = persistentSettings.playListState } elisa.mediaPlayList.enqueue(elisa.arguments) } } diff --git a/src/qml/GridBrowserDelegate.qml b/src/qml/GridBrowserDelegate.qml index 314cb947..7aa59dd0 100644 --- a/src/qml/GridBrowserDelegate.qml +++ b/src/qml/GridBrowserDelegate.qml @@ -1,287 +1,287 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 FocusScope { id: gridEntry property var imageUrl property bool shadowForImage property alias mainText: mainLabel.text property alias secondaryText: secondaryLabel.text property var containerData property bool delegateDisplaySecondaryText: true signal enqueue(var data) signal replaceAndPlay(var data) signal open() signal selected() - Action { + Controls1.Action { id: enqueueAction text: i18nc("Add whole container to play list", "Enqueue") iconName: 'media-track-add-amarok' onTriggered: enqueue(containerData) } - Action { + Controls1.Action { id: openAction text: i18nc("Open view of the container", "Open") iconName: 'document-open-folder' onTriggered: open() } - Action { + Controls1.Action { id: replaceAndPlayAction text: i18nc("Clear play list and add whole container to play list", "Play Now and Replace Play List") iconName: 'media-playback-start' onTriggered: replaceAndPlay(containerData) } Keys.onReturnPressed: openAction.trigger(this) Keys.onEnterPressed: openAction.trigger(this) ColumnLayout { anchors.fill: parent spacing: 0 MouseArea { id: hoverHandle hoverEnabled: true acceptedButtons: Qt.LeftButton Layout.preferredHeight: gridEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + mainLabelSize.height + secondaryLabelSize.height Layout.fillWidth: true onClicked: { gridEntry.selected() } onDoubleClicked: openAction.trigger(this) TextMetrics { id: mainLabelSize font: mainLabel.font text: mainLabel.text } TextMetrics { id: secondaryLabelSize font: secondaryLabel.font text: secondaryLabel.text } ColumnLayout { id: mainData spacing: 0 anchors.fill: parent Item { Layout.preferredHeight: gridEntry.width * 0.85 Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Loader { id: hoverLoader active: false anchors.centerIn: parent z: 1 opacity: 0 sourceComponent: Row { - ToolButton { + Controls1.ToolButton { id: enqueueButton objectName: 'enqueueButton' action: enqueueAction width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } - ToolButton { + Controls1.ToolButton { id: openButton objectName: 'openButton' action: openAction width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } - ToolButton { + Controls1.ToolButton { id: replaceAndPlayButton objectName: 'replaceAndPlayButton' scale: LayoutMirroring.enabled ? -1 : 1 action: replaceAndPlayAction width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } } } Image { id: coverImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: (gridEntry.imageUrl !== undefined ? gridEntry.imageUrl : "") asynchronous: true layer.enabled: shadowForImage layer.effect: DropShadow { source: coverImage radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } LabelWithToolTip { id: mainLabel font.weight: Font.Bold color: myPalette.text horizontalAlignment: Text.AlignLeft Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom elide: Text.ElideRight } LabelWithToolTip { id: secondaryLabel font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignLeft Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom visible: delegateDisplaySecondaryText elide: Text.ElideRight } } } Item { Layout.fillHeight: true } } states: [ State { name: 'notSelected' when: !gridEntry.activeFocus && !hoverHandle.containsMouse PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: coverImage opacity: 1 } }, State { name: 'hoveredOrSelected' when: gridEntry.activeFocus || hoverHandle.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: coverImage opacity: 0.2 } } ] transitions: [ Transition { to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 100 } } }, Transition { to: 'notSelected' SequentialAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 100 } PropertyAction { properties: "active" } } } ] } diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml index e57c9339..21c2f567 100644 --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -1,144 +1,144 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: gridView property bool isSubPage: false property string mainTitle property string secondaryTitle property url image property alias contentModel: contentDirectoryView.model property bool showRating: true property bool delegateDisplaySecondaryText: true property alias filterState: navigationBar.state signal open(var innerMainTitle, var innerSecondaryTitle, var innerImage, var databaseId) signal goBack() signal filterViewChanged(string filterState) SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: gridView.mainTitle secondaryTitle: gridView.secondaryTitle image: gridView.image enableGoBack: isSubPage height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay:contentModel.replaceAndPlayOfPlayList() onGoBack: gridView.goBack() onFilterViewChanged: gridView.filterViewChanged(filterState) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true - ScrollView { + GridView { + id: contentDirectoryView + anchors.topMargin: 20 + + focus: true anchors.fill: parent - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.interactive: true - GridView { - id: contentDirectoryView - anchors.topMargin: 20 + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - focus: true + TextMetrics { + id: secondaryLabelSize + text: 'example' + } - TextMetrics { - id: secondaryLabelSize - text: 'example' - } + cellWidth: elisaTheme.gridDelegateWidth + cellHeight: (delegateDisplaySecondaryText ? elisaTheme.gridDelegateHeight : elisaTheme.gridDelegateHeight - secondaryLabelSize.height) + + delegate: GridBrowserDelegate { + width: contentDirectoryView.cellWidth + height: contentDirectoryView.cellHeight + + focus: true - cellWidth: elisaTheme.gridDelegateWidth - cellHeight: (delegateDisplaySecondaryText ? elisaTheme.gridDelegateHeight : elisaTheme.gridDelegateHeight - secondaryLabelSize.height) - - delegate: GridBrowserDelegate { - width: contentDirectoryView.cellWidth - height: contentDirectoryView.cellHeight - - focus: true - - mainText: model.display - secondaryText: model.secondaryText - imageUrl: model.imageUrl - shadowForImage: model.shadowForImage - containerData: model.containerData - delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText - - onEnqueue: elisa.mediaPlayList.enqueue(data) - onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) - onOpen: gridView.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) - onSelected: { - forceActiveFocus() - contentDirectoryView.currentIndex = model.index - } + mainText: model.display + secondaryText: model.secondaryText + imageUrl: model.imageUrl + shadowForImage: model.shadowForImage + containerData: model.containerData + delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onOpen: gridView.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index } } } } } } diff --git a/src/qml/HeaderBar.qml b/src/qml/HeaderBar.qml index aa90faf4..cb52b07d 100644 --- a/src/qml/HeaderBar.qml +++ b/src/qml/HeaderBar.qml @@ -1,389 +1,389 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 FocusScope { id: headerBar property string title property string artist property string album property string image property string newImage property string oldImage property string tracksCount property int trackRating property bool ratingVisible property alias playerControl: playControlItem onImageChanged: { if (changeBackgroundTransition.running) { changeBackgroundTransition.complete() } newImage = image changeBackgroundTransition.start() } Item { id: background anchors.fill: parent Image { id: oldBackground source: (oldImage ? oldImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) asynchronous: true anchors.fill: parent fillMode: Image.PreserveAspectCrop sourceSize.width: parent.width opacity: 1 layer.enabled: true layer.effect: HueSaturation { cached: true lightness: -0.5 saturation: 0.9 layer.enabled: true layer.effect: GaussianBlur { cached: true radius: 256 deviation: 12 samples: 129 transparentBorder: false } } } Image { id: newBackground source: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) asynchronous: true anchors.fill: parent fillMode: Image.PreserveAspectCrop sourceSize.width: parent.width visible: false opacity: 0 layer.enabled: true layer.effect: HueSaturation { cached: true lightness: -0.5 saturation: 0.9 layer.enabled: true layer.effect: GaussianBlur { cached: true radius: 256 deviation: 12 samples: 129 transparentBorder: false } } } } MediaPlayerControl { id: playControlItem focus: true anchors.left: background.left anchors.right: background.right anchors.bottom: background.bottom height: elisaTheme.mediaPlayerControlHeight } ColumnLayout { id: contentZone anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: playControlItem.top spacing: 0 Item { Layout.fillHeight: true Layout.fillWidth: true } RowLayout { spacing: 0 Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.fillHeight: true Item { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.preferredHeight: contentZone.height * 0.9 Layout.minimumHeight: contentZone.height * 0.9 Layout.maximumHeight: contentZone.height * 0.9 Layout.preferredWidth: contentZone.height * 0.9 Layout.minimumWidth: contentZone.height * 0.9 Layout.maximumWidth: contentZone.height * 0.9 Layout.leftMargin: !LayoutMirroring.enabled ? contentZone.width * 0.15 : 0 Layout.rightMargin: LayoutMirroring.enabled ? contentZone.width * 0.15 : 0 Image { id: oldMainIcon anchors.fill: parent asynchronous: true source: (oldImage ? oldImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) sourceSize { width: contentZone.height * 0.9 height: contentZone.height * 0.9 } fillMode: Image.PreserveAspectFit } Image { id: newMainIcon anchors.fill: parent asynchronous: true source: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) visible: false opacity: 0 sourceSize { width: contentZone.height * 0.9 height: contentZone.height * 0.9 } fillMode: Image.PreserveAspectFit } } ColumnLayout { spacing: 0 Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.fillHeight: true TextMetrics { id: titleFontInfo font { bold: albumLabel.font.bold pointSize: albumLabel.font.pointSize } text: albumLabel.text } LabelWithToolTip { id: mainLabel text: title Layout.fillWidth: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: myPalette.highlightedText font.pointSize: elisaTheme.defaultFontPointSize * 2.5 font.bold: true Layout.bottomMargin: titleFontInfo.height * 0.5 } LabelWithToolTip { id: authorLabel text: artist Layout.fillWidth: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: myPalette.highlightedText font.pointSize: elisaTheme.defaultFontPointSize * 1.5 layer.effect: Glow { cached: true color: myPalette.shadow radius: 4.0 samples: 9 } } LabelWithToolTip { id: albumLabel text: album Layout.fillWidth: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: myPalette.highlightedText font.weight: Font.Light font.pointSize: elisaTheme.defaultFontPointSize * 1 layer.effect: Glow { cached: true color: myPalette.shadow radius: 4.0 samples: 9 } } RatingStar { id: mainRating visible: ratingVisible starSize: elisaTheme.ratingStarSize starRating: trackRating Layout.alignment: Qt.AlignLeft } } LabelWithToolTip { id: remainingTracksLabel text: i18np("1 track remaining", "%1 tracks remaining", tracksCount) Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin elide: Text.ElideRight visible: tracksCount > 0 color: myPalette.highlightedText } } Item { Layout.fillHeight: true Layout.fillWidth: true } } SequentialAnimation { id: changeBackgroundTransition PropertyAction { targets: [newBackground, newMainIcon] property: 'opacity' value: 0 } PropertyAction { targets: [newBackground, newMainIcon] property: 'visible' value: true } PropertyAction { target: newBackground property: "source" value: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) } PropertyAction { target: newMainIcon property: "source" value: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } ParallelAnimation { NumberAnimation { targets: [newBackground, newMainIcon] property: 'opacity' from: 0 to: 1 duration: 250 easing.type: Easing.Linear } NumberAnimation { targets: [oldBackground, oldMainIcon] property: 'opacity' from: 1 to: 0 duration: 250 easing.type: Easing.Linear } } PropertyAction { target: headerBar property: "oldImage" value: image } PropertyAction { target: oldBackground property: 'source' value: (headerBar.oldImage ? headerBar.oldImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) } PropertyAction { target: oldMainIcon property: 'source' value: (headerBar.oldImage ? headerBar.oldImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } PropertyAction { targets: [oldBackground, oldMainIcon] property: 'opacity' value: 1 } PropertyAction { targets: [newBackground, newMainIcon] property: 'visible' value: false } onStopped: { oldImage = newImage } } } diff --git a/src/qml/LabelWithToolTip.qml b/src/qml/LabelWithToolTip.qml index a547c653..3583733d 100644 --- a/src/qml/LabelWithToolTip.qml +++ b/src/qml/LabelWithToolTip.qml @@ -1,78 +1,76 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Controls 2.0 as Controls2 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import QtQuick.Window 2.2 Label { id: theLabel horizontalAlignment: Text.AlignLeft - renderType: Text.NativeRendering Loader { anchors.fill: parent active: theLabel.truncated visible: theLabel.truncated sourceComponent: MouseArea { id: hoverArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton Loader { anchors.fill: parent active: hoverArea && hoverArea.containsMouse - sourceComponent: Controls2.ToolTip { + sourceComponent: ToolTip { delay: Qt.styleHints.mousePressAndHoldInterval visible: hoverArea && hoverArea.containsMouse && theLabel.truncated text: theLabel.text contentItem: Label { text: theLabel.text color: myPalette.highlightedText } enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } background: Rectangle { color: myPalette.shadow radius: elisaTheme.tooltipRadius layer.enabled: true layer.effect: DropShadow { horizontalOffset: elisaTheme.shadowOffset verticalOffset: elisaTheme.shadowOffset radius: 8 samples: 17 color: myPalette.shadow } } } } } } } diff --git a/src/qml/MediaAlbumView.qml b/src/qml/MediaAlbumView.qml index f053cdeb..d5548051 100644 --- a/src/qml/MediaAlbumView.qml +++ b/src/qml/MediaAlbumView.qml @@ -1,136 +1,135 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Window 2.2 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.2 -import QtQml.Models 2.1 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 import org.kde.elisa 1.0 import QtQuick.Layouts 1.2 FocusScope { id: topListing property var albumName property var artistName property var albumArtUrl property bool isSingleDiscAlbum property alias filterState: navigationBar.state property var albumId property alias contentModel: contentDirectoryView.model signal showArtist(var name) signal goBack(); signal filterViewChanged(string filterState) function loadAlbumData(id) { contentModel.sourceModel.loadAlbumData(id) } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true mainTitle: (topListing.artistName ? topListing.artistName : '') secondaryTitle: topListing.albumName image: (topListing.albumArtUrl ? topListing.albumArtUrl : elisaTheme.defaultAlbumImage) allowArtistNavigation: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onGoBack: topListing.goBack() onFilterViewChanged: topListing.filterViewChanged(filterState) onShowArtist: topListing.showArtist(topListing.contentModel.sourceModel.author) onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() } - ScrollView { - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.interactive: true - + ListView { + id: contentDirectoryView Layout.fillHeight: true Layout.fillWidth: true + contentWidth: parent.width + focus: true - ListView { - id: contentDirectoryView - - focus: true + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - delegate: MediaAlbumTrackDelegate { - id: entry + delegate: MediaAlbumTrackDelegate { + id: entry - height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) - width: contentDirectoryView.width + height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) + width: scrollBar.visible ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width - focus: true + focus: true - mediaTrack.isAlternateColor: (index % 2) === 1 + mediaTrack.trackData: model.containerData - mediaTrack.trackData: model.containerData + mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc - mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc + mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum - mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum + mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) - mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) + mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) - mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + mediaTrack.isAlternateColor: (index % 2) === 1 - mediaTrack.onClicked: contentDirectoryView.currentIndex = index - } + mediaTrack.onClicked: contentDirectoryView.currentIndex = index } } } } diff --git a/src/qml/MediaAllTracksView.qml b/src/qml/MediaAllTracksView.qml index 862d963b..7abbb323 100644 --- a/src/qml/MediaAllTracksView.qml +++ b/src/qml/MediaAllTracksView.qml @@ -1,128 +1,126 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 +import QtQuick.Controls 2.2 import QtQuick.Window 2.2 -import QtQml.Models 2.1 +import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: rootElement property var stackView property alias contentModel: contentDirectoryView.model property alias filterState: navigationBar.state signal filterViewChanged(string filterState) SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: i18nc("Title of the view of all tracks", "Tracks") secondaryTitle: "" image: elisaTheme.tracksIcon enableGoBack: false height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onEnqueue: contentModel.enqueueToPlayList() onFilterViewChanged: rootElement.filterViewChanged(filterState) onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true - ScrollView { + ListView { + id: contentDirectoryView anchors.fill: parent - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.interactive: true - ListView { - id: contentDirectoryView + focus: true - focus: true - - delegate: MediaTrackDelegate { - id: entry + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - width: contentDirectoryView.width - height: elisaTheme.trackDelegateHeight + delegate: MediaTrackDelegate { + id: entry - focus: true + width: scrollBar.visible ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width + height: elisaTheme.trackDelegateHeight - isAlternateColor: (index % 2) === 1 + focus: true - trackData: model.containerData + trackData: model.containerData - isFirstTrackOfDisc: false + isFirstTrackOfDisc: false - isSingleDiscAlbum: model.isSingleDiscAlbum + isSingleDiscAlbum: model.isSingleDiscAlbum - onEnqueue: elisa.mediaPlayList.enqueue(data) + onEnqueue: elisa.mediaPlayList.enqueue(data) - onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) - onClicked: contentDirectoryView.currentIndex = index - } + onClicked: contentDirectoryView.currentIndex = index } } } } } diff --git a/src/qml/MediaBrowser.qml b/src/qml/MediaBrowser.qml index 46c9b317..5651bd68 100644 --- a/src/qml/MediaBrowser.qml +++ b/src/qml/MediaBrowser.qml @@ -1,81 +1,89 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.4 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Layouts 1.1 +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import org.kde.elisa 1.0 FocusScope { id: contentDirectoryRoot - property MediaPlayList playListModel property alias firstPage: listingView.initialItem property alias stackView: listingView function goBack() { if (listingView.depth > 1) { listingView.pop() } } ColumnLayout { anchors.fill: parent spacing: 0 StackView { id: listingView Layout.fillHeight: true Layout.fillWidth: true focus: true - delegate: StackViewDelegate { - function transitionFinished(properties) - { - properties.exitItem.opacity = 1 + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 } + } + + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to: 0 + } + } + + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + } + } - pushTransition: StackViewTransition { - PropertyAnimation { - target: enterItem - property: "opacity" - from: 0 - to: 1 - } - PropertyAnimation { - target: exitItem - property: "opacity" - from: 1 - to: 0 - } + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to: 0 } } } } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onClicked: goBack() } } diff --git a/src/qml/MediaPlayListView.qml b/src/qml/MediaPlayListView.qml index f773756c..1735e3de 100644 --- a/src/qml/MediaPlayListView.qml +++ b/src/qml/MediaPlayListView.qml @@ -1,367 +1,366 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.3 as Controls1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtQml.Models 2.1 import Qt.labs.platform 1.0 as PlatformDialog import org.kde.elisa 1.0 FocusScope { property StackView parentStackView property MediaPlayList playListModel property alias randomPlayChecked: shuffleOption.checked property alias repeatPlayChecked: repeatOption.checked property int placeholderHeight: elisaTheme.dragDropPlaceholderHeight signal startPlayback() signal pausePlayback() signal displayError(var errorText) id: topItem - Action { + Controls1.Action { id: clearPlayList text: i18nc("Remove all tracks from play list", "Clear Play List") iconName: "list-remove" enabled: playListModelDelegate.items.count > 0 onTriggered: playListModel.clearPlayList() } - Action { + Controls1.Action { id: showCurrentTrack text: i18nc("Show currently played track inside playlist", "Show Current Track") iconName: 'media-show-active-track-amarok' enabled: playListModelDelegate.items.count > 0 onTriggered: { playListView.positionViewAtIndex(playListModel.currentTrackRow, ListView.Contain) playListView.currentIndex = playListModel.currentTrackRow playListView.currentItem.forceActiveFocus() } } - Action { + Controls1.Action { id: loadPlaylist text: i18nc("Load a playlist file", "Load a Playlist") iconName: 'document-open' onTriggered: { fileDialog.fileMode = PlatformDialog.FileDialog.OpenFile fileDialog.file = '' fileDialog.open() } } - Action { + Controls1.Action { id: savePlaylist text: i18nc("Save a playlist file", "Save a Playlist") iconName: 'document-save' enabled: playListModelDelegate.items.count > 0 onTriggered: { fileDialog.fileMode = PlatformDialog.FileDialog.SaveFile fileDialog.file = '' fileDialog.open() } } PlatformDialog.FileDialog { id: fileDialog defaultSuffix: 'm3u' folder: PlatformDialog.StandardPaths.writableLocation(PlatformDialog.StandardPaths.MusicLocation) nameFilters: [i18nc("file type (mime type) for m3u playlist", "Playlist (*.m3u)")] onAccepted: { if (fileMode === PlatformDialog.FileDialog.SaveFile) { if (!playListModel.savePlaylist(fileDialog.file)) { displayError(i18nc("message of passive notification when playlist load failed", "Save of playlist failed")) } } else { playListModel.loadPlaylist(fileDialog.file) } } } ColumnLayout { anchors.fill: parent spacing: 0 RowLayout { height: elisaTheme.navigationBarHeight Layout.preferredHeight: elisaTheme.navigationBarHeight Layout.minimumHeight: elisaTheme.navigationBarHeight Layout.maximumHeight: elisaTheme.navigationBarHeight Image { id: mainIcon source: elisaTheme.playlistIcon asynchronous: true sourceSize.height: elisaTheme.coverImageSize / 2 sourceSize.width: elisaTheme.coverImageSize / 2 fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 Layout.preferredWidth: elisaTheme.coverImageSize / 2 Layout.minimumWidth: elisaTheme.coverImageSize / 2 Layout.maximumWidth: elisaTheme.coverImageSize / 2 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } ColumnLayout { spacing: 0 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: titleHeight text: viewTitleHeight.text font { pointSize: viewTitleHeight.font.pointSize bold: viewTitleHeight.font.bold } } LabelWithToolTip { id: viewTitleHeight text: i18nc("Title of the view of the playlist", "Now Playing") color: myPalette.text font.pointSize: elisaTheme.defaultFontPointSize * 2 Layout.topMargin: elisaTheme.layoutVerticalMargin - } + } LabelWithToolTip { id: playListInfo text: i18np("1 track", "%1 tracks", playListModel.tracksCount) color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter } Item { Layout.fillHeight: true } RowLayout { Layout.bottomMargin: elisaTheme.layoutVerticalMargin CheckBox { id: shuffleOption text: i18n("Shuffle") } CheckBox { id: repeatOption text: i18n("Repeat") } } } } - ScrollView { - flickableItem.boundsBehavior: Flickable.StopAtBounds - + ListView { + id: playListView Layout.fillWidth: true Layout.fillHeight: true focus: true - ListView { - id: playListView + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - focus: true + TextEdit { + readOnly: true + visible: playListModelDelegate.count === 0 + wrapMode: TextEdit.Wrap - TextEdit { - readOnly: true - visible: playListModelDelegate.count === 0 - wrapMode: TextEdit.Wrap - renderType: TextEdit.NativeRendering + color: myPalette.text - color: myPalette.text + font.weight: Font.ExtraLight + font.pixelSize: elisaTheme.defaultFontPixelSize * 1.5 - font.weight: Font.ExtraLight - font.pointSize: 12 - - text: i18nc("Text shown when play list is empty", "Your play list is empty.\nIn order to start, you can explore your music library with the views on the left.\nUse the available buttons to add your selection.") - anchors.fill: parent - anchors.margins: elisaTheme.layoutHorizontalMargin - } + text: i18nc("Text shown when play list is empty", "Your play list is empty.\nIn order to start, you can explore your music library with the views on the left.\nUse the available buttons to add your selection.") + anchors.fill: parent + anchors.margins: elisaTheme.layoutHorizontalMargin + } - add: Transition { - NumberAnimation { - property: "opacity"; - from: 0; - to: 1; - duration: 100 } - } + add: Transition { + NumberAnimation { + property: "opacity"; + from: 0; + to: 1; + duration: 100 } + } - populate: Transition { - NumberAnimation { - property: "opacity"; - from: 0; - to: 1; - duration: 100 } - } + populate: Transition { + NumberAnimation { + property: "opacity"; + from: 0; + to: 1; + duration: 100 } + } - remove: Transition { - NumberAnimation { - property: "opacity"; - from: 1.0; - to: 0; - duration: 100 } - } + remove: Transition { + NumberAnimation { + property: "opacity"; + from: 1.0; + to: 0; + duration: 100 } + } - displaced: Transition { - NumberAnimation { - properties: "x,y"; - duration: 100; - easing.type: Easing.InOutQuad} - } + displaced: Transition { + NumberAnimation { + properties: "x,y"; + duration: 100; + easing.type: Easing.InOutQuad} + } - model: DelegateModel { - id: playListModelDelegate - model: playListModel + model: DelegateModel { + id: playListModelDelegate + model: playListModel - groups: [ - DelegateModelGroup { name: "selected" } - ] + groups: [ + DelegateModelGroup { name: "selected" } + ] - delegate: DraggableItem { - id: item - placeholderHeight: topItem.placeholderHeight + delegate: DraggableItem { + id: item + placeholderHeight: topItem.placeholderHeight - focus: true + focus: true - PlayListEntry { - id: entry + PlayListEntry { + id: entry - focus: true + focus: true - width: playListView.width + width: scrollBar.visible ? playListView.width - scrollBar.width : playListView.width - index: model.index + index: model.index - isAlternateColor: item.DelegateModel.itemsIndex % 2 + isAlternateColor: item.DelegateModel.itemsIndex % 2 - hasAlbumHeader: model.hasAlbumHeader + hasAlbumHeader: model.hasAlbumHeader - isSingleDiscAlbum: model.isSingleDiscAlbum + isSingleDiscAlbum: model.isSingleDiscAlbum - trackData: model.trackData + trackData: model.trackData - titleDisplay: model.title + titleDisplay: model.title - isValid: model.isValid + isValid: model.isValid - isPlaying: model.isPlaying + isPlaying: model.isPlaying - isSelected: playListView.currentIndex === index + isSelected: playListView.currentIndex === index - containsMouse: item.containsMouse + containsMouse: item.containsMouse - onStartPlayback: topItem.startPlayback() + onStartPlayback: topItem.startPlayback() - onPausePlayback: topItem.pausePlayback() + onPausePlayback: topItem.pausePlayback() - onRemoveFromPlaylist: topItem.playListModel.removeRows(trackIndex, 1) + onRemoveFromPlaylist: topItem.playListModel.removeRows(trackIndex, 1) - onSwitchToTrack: topItem.playListModel.switchTo(trackIndex) - } + onSwitchToTrack: topItem.playListModel.switchTo(trackIndex) + } - draggedItemParent: topItem + draggedItemParent: topItem - onClicked: { - playListView.currentIndex = index - entry.forceActiveFocus() - } + onClicked: { + playListView.currentIndex = index + entry.forceActiveFocus() + } - onDoubleClicked: { - if (model.isValid) { - topItem.playListModel.switchTo(model.index) - topItem.startPlayback() - } + onDoubleClicked: { + if (model.isValid) { + topItem.playListControler.switchTo(model.index) + topItem.startPlayback() } + } - onMoveItemRequested: { - playListModel.move(from, to, 1); - } + onMoveItemRequested: { + playListModel.move(from, to, 1); } } } } Rectangle { id: actionBar Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.preferredHeight: elisaTheme.delegateToolButtonSize color: myPalette.mid RowLayout { id: actionBarLayout anchors.fill: parent - ToolButton { + Controls1.ToolButton { action: clearPlayList Layout.bottomMargin: elisaTheme.layoutVerticalMargin } - ToolButton { + Controls1.ToolButton { action: showCurrentTrack Layout.bottomMargin: elisaTheme.layoutVerticalMargin } - ToolButton { + Controls1.ToolButton { action: loadPlaylist Layout.bottomMargin: elisaTheme.layoutVerticalMargin } - ToolButton { + Controls1.ToolButton { action: savePlaylist Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Item { Layout.fillWidth: true } } } } } + diff --git a/src/qml/MediaPlayerControl.qml b/src/qml/MediaPlayerControl.qml index 0f29653d..dc612309 100644 --- a/src/qml/MediaPlayerControl.qml +++ b/src/qml/MediaPlayerControl.qml @@ -1,484 +1,482 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.9 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Layouts 1.1 +import QtQuick 2.7 +import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 -import QtQuick.Controls 2.2 as Controls2 +import QtQuick.Controls 2.2 import org.kde.elisa 1.0 FocusScope { property double volume property int position property int duration property bool muted property bool isPlaying property bool seekable property bool playEnabled property bool skipForwardEnabled property bool skipBackwardEnabled signal play() signal pause() signal playPrevious() signal playNext() signal seek(int position) id: musicWidget SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Rectangle { anchors.fill: parent color: myPalette.midlight opacity: elisaTheme.mediaPlayerControlOpacity } RowLayout { anchors.fill: parent spacing: 0 - Controls2.RoundButton { + RoundButton { focus: skipBackwardEnabled Layout.preferredWidth: elisaTheme.smallControlButtonHeight Layout.preferredHeight: elisaTheme.smallControlButtonHeight Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonHeight Layout.maximumHeight: elisaTheme.smallControlButtonHeight Layout.minimumWidth: elisaTheme.smallControlButtonHeight Layout.minimumHeight: elisaTheme.smallControlButtonHeight Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 enabled: skipBackwardEnabled hoverEnabled: true onClicked: { musicWidget.playPrevious() } contentItem: Image { anchors.fill: parent source: Qt.resolvedUrl(LayoutMirroring.enabled ? elisaTheme.skipForwardIcon : elisaTheme.skipBackwardIcon) width: elisaTheme.smallControlButtonHeight height: elisaTheme.smallControlButtonHeight sourceSize.width: elisaTheme.smallControlButtonHeight sourceSize.height: elisaTheme.smallControlButtonHeight fillMode: Image.PreserveAspectFit opacity: skipBackwardEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonHeight Behavior on border.color { ColorAnimation { duration: 300 } } } } - Controls2.RoundButton { + RoundButton { focus: playEnabled Layout.preferredWidth: elisaTheme.bigControlButtonHeight Layout.preferredHeight: elisaTheme.bigControlButtonHeight Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.bigControlButtonHeight Layout.maximumHeight: elisaTheme.bigControlButtonHeight Layout.minimumWidth: elisaTheme.bigControlButtonHeight Layout.minimumHeight: elisaTheme.bigControlButtonHeight enabled: playEnabled hoverEnabled: true onClicked: { if (musicWidget.isPlaying) { musicWidget.pause() } else { musicWidget.play() } } contentItem: Image { anchors.fill: parent source: { if (musicWidget.isPlaying) Qt.resolvedUrl(elisaTheme.pauseIcon) else Qt.resolvedUrl(elisaTheme.playIcon) } width: elisaTheme.bigControlButtonHeight height: elisaTheme.bigControlButtonHeight sourceSize.width: elisaTheme.bigControlButtonHeight sourceSize.height: elisaTheme.bigControlButtonHeight fillMode: Image.PreserveAspectFit mirror: LayoutMirroring.enabled opacity: playEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.bigControlButtonHeight Behavior on border.color { ColorAnimation { duration: 300 } } } } - Controls2.RoundButton { + RoundButton { focus: skipForwardEnabled Layout.preferredWidth: elisaTheme.smallControlButtonHeight Layout.preferredHeight: elisaTheme.smallControlButtonHeight Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonHeight Layout.maximumHeight: elisaTheme.smallControlButtonHeight Layout.minimumWidth: elisaTheme.smallControlButtonHeight Layout.minimumHeight: elisaTheme.smallControlButtonHeight Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.smallControlButtonHeight : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.smallControlButtonHeight : 0 enabled: skipForwardEnabled hoverEnabled: true onClicked: { musicWidget.playNext() } contentItem: Image { anchors.fill: parent source: Qt.resolvedUrl(LayoutMirroring.enabled ? elisaTheme.skipBackwardIcon : elisaTheme.skipForwardIcon) width: elisaTheme.smallControlButtonHeight height: elisaTheme.smallControlButtonHeight sourceSize.width: elisaTheme.smallControlButtonHeight sourceSize.height: elisaTheme.smallControlButtonHeight fillMode: Image.PreserveAspectFit opacity: skipForwardEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonHeight Behavior on border.color { ColorAnimation { duration: 300 } } } } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00:00") } LabelWithToolTip { id: positionLabel text: timeIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.preferredWidth: durationTextMetrics.width+5 // be in the safe side verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight ProgressIndicator { id: timeIndicator position: musicWidget.position } } - Controls2.Slider { + Slider { property bool seekStarted: false property int seekValue id: musicProgress from: 0 to: musicWidget.duration Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 enabled: musicWidget.seekable && musicWidget.playEnabled live: true onValueChanged: { if (seekStarted) { seekValue = value } } onPressedChanged: { if (pressed) { seekStarted = true; seekValue = value } else { musicWidget.seek(seekValue) seekStarted = false; } } background: Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 6 width: musicProgress.availableWidth height: implicitHeight radius: 3 color: myPalette.mid Rectangle { x: (LayoutMirroring.enabled ? musicProgress.visualPosition * parent.width : 0) width: (LayoutMirroring.enabled ? parent.width - musicProgress.visualPosition * parent.width : musicProgress.visualPosition * parent.width) height: parent.height color: myPalette.text radius: 3 } } handle: Rectangle { x: musicProgress.leftPadding + musicProgress.visualPosition * (musicProgress.availableWidth - width) y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.button border.color: musicProgress.pressed ? myPalette.highlight : myPalette.dark } } LabelWithToolTip { id: durationLabel text: durationIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 10) : 0 Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 10) : 0 Layout.preferredWidth: durationTextMetrics.width verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft ProgressIndicator { id: durationIndicator position: musicWidget.duration } } Image { id: volumeIcon source: if (musicWidget.muted) Qt.resolvedUrl(elisaTheme.playerVolumeMutedIcon) else Qt.resolvedUrl(elisaTheme.playerVolumeIcon) Layout.preferredWidth: elisaTheme.smallControlButtonHeight Layout.preferredHeight: elisaTheme.smallControlButtonHeight Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonHeight Layout.maximumHeight: elisaTheme.smallControlButtonHeight Layout.minimumWidth: elisaTheme.smallControlButtonHeight Layout.minimumHeight: elisaTheme.smallControlButtonHeight Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 sourceSize.width: elisaTheme.smallControlButtonHeight sourceSize.height: elisaTheme.smallControlButtonHeight fillMode: Image.PreserveAspectFit visible: false } - Controls2.RoundButton { + RoundButton { focus: true Layout.preferredWidth: elisaTheme.smallControlButtonHeight Layout.preferredHeight: elisaTheme.smallControlButtonHeight Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonHeight Layout.maximumHeight: elisaTheme.smallControlButtonHeight Layout.minimumWidth: elisaTheme.smallControlButtonHeight Layout.minimumHeight: elisaTheme.smallControlButtonHeight Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 hoverEnabled: true onClicked: musicWidget.muted = !musicWidget.muted contentItem: Image { anchors.fill: parent source: if (musicWidget.muted) Qt.resolvedUrl(elisaTheme.playerVolumeMutedIcon) else Qt.resolvedUrl(elisaTheme.playerVolumeIcon) width: elisaTheme.smallControlButtonHeight height: elisaTheme.smallControlButtonHeight sourceSize.width: elisaTheme.smallControlButtonHeight sourceSize.height: elisaTheme.smallControlButtonHeight fillMode: Image.PreserveAspectFit } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonHeight Behavior on border.color { ColorAnimation { duration: 300 } } } } - Controls2.Slider { + Slider { id: volumeSlider from: 0 to: 100 value: musicWidget.volume onValueChanged: musicWidget.volume = value enabled: !muted Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: elisaTheme.volumeSliderWidth Layout.maximumWidth: elisaTheme.volumeSliderWidth Layout.minimumWidth: elisaTheme.volumeSliderWidth Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 width: elisaTheme.volumeSliderWidth background: Rectangle { x: volumeSlider.leftPadding y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 6 width: volumeSlider.availableWidth height: implicitHeight radius: 3 color: myPalette.mid Rectangle { x: (LayoutMirroring.enabled ? volumeSlider.visualPosition * parent.width : 0) width: (LayoutMirroring.enabled ? parent.width - volumeSlider.visualPosition * parent.width : volumeSlider.visualPosition * parent.width) height: parent.height color: myPalette.text radius: 3 } } handle: Rectangle { x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width) y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.button border.color: volumeSlider.pressed ? myPalette.highlight : myPalette.dark } } } onPositionChanged: { if (!musicProgress.seekStarted) { musicProgress.value = position } } onVolumeChanged: { console.log('volume of player controls changed: ' + volume) } } diff --git a/src/qml/MediaTrackDelegate.qml b/src/qml/MediaTrackDelegate.qml index ef72ca7c..94dfc352 100644 --- a/src/qml/MediaTrackDelegate.qml +++ b/src/qml/MediaTrackDelegate.qml @@ -1,417 +1,417 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2017 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.2 -import QtQuick.Controls 1.2 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: mediaTrack property alias trackData: dataHelper.trackData property alias discNumber: dataHelper.discNumber property bool isFirstTrackOfDisc property bool isSingleDiscAlbum property bool isAlternateColor property bool detailedView: true signal clicked() signal enqueue(var data) signal replaceAndPlay(var data) - Action { + Controls1.Action { id: replaceAndPlayAction text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") iconName: "media-playback-start" onTriggered: replaceAndPlay(dataHelper.databaseId) } - Action { + Controls1.Action { id: enqueueAction text: i18nc("Enqueue current track", "Enqueue") iconName: "media-track-add-amarok" onTriggered: enqueue(dataHelper.databaseId) } - Action { + Controls1.Action { id: viewDetailsAction text: i18nc("Show track metadata", "View Details") iconName: "help-about" onTriggered: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } TrackDataHelper { id: dataHelper } Keys.onReturnPressed: enqueueToPlaylist(trackData) Keys.onEnterPressed: enqueueToPlaylist(trackData) Loader { id: metadataLoader active: false - onLoaded: item.open() + onLoaded: item.show() sourceComponent: MediaTrackMetadataView { trackDataHelper: dataHelper onRejected: metadataLoader.active = false; } } Rectangle { id: rowRoot anchors.fill: parent color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) MouseArea { id: hoverArea anchors.fill: parent hoverEnabled: true focus: true acceptedButtons: Qt.LeftButton onClicked: { hoverArea.forceActiveFocus() mediaTrack.clicked() } onDoubleClicked: enqueue(trackData) RowLayout { anchors.fill: parent spacing: 0 LabelWithToolTip { id: mainLabel visible: !detailedView text: { if (dataHelper.hasValidTrackNumber()) { if (artist !== albumArtist) return i18nc("%1: track number. %2: track title. %3: artist name", - "%1-%2 - %3", + "%1 - %2 - %3", Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title, dataHelper.artist); else return i18nc("%1: track number. %2: track title.", - "%1-%2", + "%1 - %2", Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title); } else { if (dataHelper.artist !== dataHelper.albumArtist) return i18nc("%1: track title. %2: artist name", "%1 - %2", dataHelper.title, dataHelper.artist); else return i18nc("%1: track title", "%1", dataHelper.title); } } elide: Text.ElideRight horizontalAlignment: Text.AlignLeft color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true Layout.leftMargin: { if (!LayoutMirroring.enabled) return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } Layout.rightMargin: { if (LayoutMirroring.enabled) return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } } Item { Layout.preferredHeight: mediaTrack.height * 0.9 Layout.preferredWidth: mediaTrack.height * 0.9 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter visible: detailedView Image { id: coverImageElement anchors.fill: parent sourceSize.width: mediaTrack.height * 0.9 sourceSize.height: mediaTrack.height * 0.9 fillMode: Image.PreserveAspectFit smooth: true source: (dataHelper.hasValidAlbumCover() ? dataHelper.albumCover : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) asynchronous: true layer.enabled: dataHelper.hasValidAlbumCover() ? true : false layer.effect: DropShadow { source: coverImageElement radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } ColumnLayout { visible: detailedView Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignLeft spacing: 0 LabelWithToolTip { id: mainLabelDetailed text: { if (dataHelper.hasValidTrackNumber()) { return i18nc("%1: track number. %2: track title", "%1 - %2", Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title); } else { return dataHelper.title; } } horizontalAlignment: Text.AlignLeft font.weight: Font.Bold color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: artistLabel text: (isSingleDiscAlbum ? dataHelper.artist + ' - ' + dataHelper.albumName : dataHelper.artist + ' - ' + dataHelper.albumName + ' - CD ' + dataHelper.discNumber) horizontalAlignment: Text.AlignLeft font.weight: Font.Light font.italic: true color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignBottom Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } } Loader { id: hoverLoader active: false Layout.alignment: Qt.AlignVCenter | Qt.AlignRight z: 1 opacity: 0 sourceComponent: Row { anchors.centerIn: parent - ToolButton { + Controls1.ToolButton { id: detailsButton height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight action: viewDetailsAction } - ToolButton { + Controls1.ToolButton { id: enqueueButton height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight action: enqueueAction } - ToolButton { + Controls1.ToolButton { id: clearAndEnqueueButton scale: LayoutMirroring.enabled ? -1 : 1 height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight action: replaceAndPlayAction } } } RatingStar { id: ratingWidget starSize: elisaTheme.ratingStarSize starRating: dataHelper.rating Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00") } LabelWithToolTip { id: durationLabel text: dataHelper.duration font.weight: Font.Light color: myPalette.text - elide: Text.ElideRight horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.preferredWidth: durationTextMetrics.width+1 // be in the safe side + Layout.preferredWidth: durationTextMetrics.width + 1 } } } } states: [ State { name: 'notSelected' when: !hoverArea.containsMouse && !mediaTrack.activeFocus PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } }, State { name: 'hoveredOrSelected' when: hoverArea.containsMouse || mediaTrack.activeFocus PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.mid } } ] transitions: [ Transition { to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } ParallelAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 250 } ColorAnimation { properties: "color" duration: 250 } } } }, Transition { to: 'notSelected' SequentialAnimation { ParallelAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 250 } ColorAnimation { properties: "color" duration: 250 } } PropertyAction { properties: "active" } } } ] } diff --git a/src/qml/MediaTrackMetadataView.qml b/src/qml/MediaTrackMetadataView.qml index e3b559d8..018b82c7 100644 --- a/src/qml/MediaTrackMetadataView.qml +++ b/src/qml/MediaTrackMetadataView.qml @@ -1,204 +1,197 @@ /* * Copyright 2017 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.2 -import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 -Dialog { +Window { id: trackMetadata property var trackDataHelper + signal rejected() + + LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft + LayoutMirroring.childrenInherit: true + title: i18nc("Window title for track metadata", "View Details") + width: elisaTheme.trackMetadataWidth + maximumWidth: width + minimumWidth: width modality: Qt.NonModal - - standardButtons: StandardButton.Close + flags: Qt.Dialog | Qt.WindowCloseButtonHint Component.onCompleted: { if (trackDataHelper.hasValidTitle()) trackList.append({"name": i18nc("Track title for track metadata view", "Title:"), "content": trackDataHelper.title}) if (trackDataHelper.hasValidArtist()) trackList.append({"name": i18nc("Track artist for track metadata view", "Artist:"), "content": trackDataHelper.artist}) if (trackDataHelper.hasValidAlbumName()) trackList.append({"name": i18nc("Album name for track metadata view", "Album:"), "content": trackDataHelper.albumName}) if (trackDataHelper.hasValidTrackNumber()) trackList.append({"name": i18nc("Track number for track metadata view", "Track Number:"), "content": trackDataHelper.trackNumber}) if (trackDataHelper.hasValidDiscNumber()) trackList.append({"name": i18nc("Disc number for track metadata view", "Disc Number:"), "content": trackDataHelper.discNumber}) if (trackDataHelper.hasValidAlbumArtist()) trackList.append({"name": i18nc("Album artist for track metadata view", "Album Artist:"), "content": trackDataHelper.albumArtist}) trackList.append({"name": i18nc("Duration label for track metadata view", "Duration:"), "content": trackDataHelper.duration}) if (trackDataHelper.hasValidYear()) trackList.append({"name": i18nc("Year label for track metadata view", "Year:"), "content": trackDataHelper.year}) if (trackDataHelper.hasValidGenre()) trackList.append({"name": i18nc("Genre label for track metadata view", "Genre:"), "content": trackDataHelper.genre}) if (trackDataHelper.hasValidComposer()) trackList.append({"name": i18nc("Composer name for track metadata view", "Composer:"), "content": trackDataHelper.composer}) if (trackDataHelper.hasValidLyricist()) trackList.append({"name": i18nc("Lyricist label for track metadata view", "Lyricist:"), "content": trackDataHelper.lyricist}) if (trackDataHelper.hasValidBitRate()) trackList.append({"name": i18nc("Bit rate label for track metadata view", "Bit Rate:"), "content": trackDataHelper.bitRate + " " + i18nc("Unit of the bit rate in thousand", "kbit/s")}) if (trackDataHelper.hasValidSampleRate()) trackList.append({"name": i18nc("Sample Rate label for track metadata view", "Sample Rate:"), "content": trackDataHelper.sampleRate + " " + i18nc("Unit of the sample rate", "Hz")}) if (trackDataHelper.hasValidChannels()) trackList.append({"name": i18nc("Channels label for track metadata view", "Channels:"), "content": trackDataHelper.channels}) if (trackDataHelper.hasValidComment()) trackList.append({"name": i18nc("Comment label for track metadata view", "Comment:"), "content": trackDataHelper.comment}) trackData.Layout.preferredHeight = textSize.height * trackData.count + trackMetadata.height = textSize.height * (trackData.count + 1 + ( rating > -1 ? 1 : 0 )) + buttons.height + elisaTheme.layoutHorizontalMargin + trackMetadata.maximumHeight = trackMetadata.height + trackMetadata.minimumHeight = trackMetadata.height } - ColumnLayout { + Rectangle { anchors.fill: parent - spacing: 0 - // This is needed since Dialog doesn't inherit from Item - LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft - LayoutMirroring.childrenInherit: true - - RowLayout { - Layout.fillHeight: true - Layout.fillWidth: true - spacing: 0 - Image { - source: trackDataHelper.albumCover - visible: trackDataHelper.hasValidAlbumCover() - width: elisaTheme.coverImageSize - height: elisaTheme.coverImageSize - fillMode: Image.PreserveAspectFit - Layout.preferredHeight: height - Layout.preferredWidth: width - Layout.minimumHeight: height - Layout.minimumWidth: width - Layout.maximumHeight: height - Layout.maximumWidth: width - } + color: myPalette.window - ColumnLayout { - Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.fillHeight: true - Layout.preferredWidth: elisaTheme.trackMetadataWidth - spacing: 0 + ColumnLayout { + anchors.fill: parent + spacing: 0 - ListView { - id: trackData + ListView { + id: trackData - interactive: false - Layout.fillWidth: true + interactive: false + Layout.fillWidth: true - model: ListModel { - id: trackList - } + model: ListModel { + id: trackList + } - delegate: RowLayout { - id: delegateRow - spacing: 0 + delegate: RowLayout { + id: delegateRow + spacing: 0 - LabelWithToolTip { - text: name - color: myPalette.text - horizontalAlignment: Text.AlignRight + Text { + text: name + color: myPalette.text + horizontalAlignment: Text.AlignRight - Layout.preferredWidth: trackData.width * 0.3 - Layout.minimumHeight: textSize.height - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Layout.preferredWidth: trackData.width * 0.3 + Layout.minimumHeight: textSize.height + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - LabelWithToolTip { - text: content - color: myPalette.text + Text { + text: content + color: myPalette.text - horizontalAlignment: Text.AlignLeft - elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight - Layout.preferredWidth: trackData.width * 0.66 - Layout.fillWidth: true - Layout.minimumHeight: textSize.height - } + Layout.preferredWidth: trackData.width * 0.66 + Layout.fillWidth: true + Layout.minimumHeight: textSize.height } } + } - RowLayout { - anchors.margins: 0 - spacing: 0 + RowLayout { + anchors.margins: 0 + spacing: 0 - visible: trackDataHelper.hasValidRating() + visible: rating > -1 - Layout.minimumHeight: textSize.height + Layout.minimumHeight: textSize.height - LabelWithToolTip { - id: ratingText - text: i18nc("Rating label for information panel", "Rating:") - color: myPalette.text + Text { + id: ratingText + text: i18nc("Rating label for information panel", "Rating:") + color: myPalette.text - horizontalAlignment: Text.AlignRight - Layout.preferredWidth: trackData.width * 0.3 - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - } + horizontalAlignment: Text.AlignRight + Layout.preferredWidth: trackData.width * 0.3 + Layout.rightMargin: elisaTheme.layoutHorizontalMargin + } - TextMetrics { - id: textSize - font: ratingText.font - text: ratingText.text - } + TextMetrics { + id: textSize + font: ratingText.font + text: ratingText.text + } - RatingStar { - id: ratingWidget - starRating: trackDataHelper.rating - readOnly: true + RatingStar { + id: ratingWidget + starRating: rating + readOnly: true - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - Layout.fillWidth: true - } + Layout.fillWidth: true + } - ColorOverlay { - source: ratingWidget + ColorOverlay { + source: ratingWidget - z: 2 - anchors.fill: ratingWidget + z: 2 + anchors.fill: ratingWidget - color: myPalette.text - } + color: myPalette.text } } - } - LabelWithToolTip { - id: trackResource - text: trackDataHelper.resourceURI - color: myPalette.text - font.italic: true + Text { + id: trackResource + text: trackMetadata.resource + color: myPalette.text + font.italic: true - elide: Text.ElideLeft - horizontalAlignment: Text.AlignRight + elide: Text.ElideLeft - Layout.minimumHeight: textSize.height - Layout.preferredWidth: trackDataHelper.hasValidAlbumCover() ? elisaTheme.coverImageSize + elisaTheme.trackMetadataWidth : elisaTheme.trackMetadataWidth + 2 * elisaTheme.layoutHorizontalMargin - Layout.fillWidth: true - Layout.topMargin: elisaTheme.layoutVerticalMargin - } + Layout.minimumHeight: textSize.height + Layout.preferredWidth: elisaTheme.trackMetadataWidth + Layout.fillWidth: true + Layout.topMargin: elisaTheme.layoutHorizontalMargin + } + + DialogButtonBox { + id: buttons + + standardButtons: DialogButtonBox.Close + alignment: Qt.AlignRight + onRejected: trackMetadata.rejected() + Layout.fillWidth: true + } + } } } diff --git a/src/qml/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml index f99bba9e..dda0088f 100644 --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -1,353 +1,354 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQml 2.2 -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Layouts 1.3 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 import QtQuick.Window 2.0 FocusScope { id: navigationBar property string mainTitle property string secondaryTitle property url image property bool allowArtistNavigation: false property string labelText property bool showRating: true property alias filterText: filterTextInput.text property alias filterRating: ratingFilter.starRating property bool enableGoBack: true signal enqueue(); signal replaceAndPlay(); signal goBack(); signal showArtist(); signal filterViewChanged(string filterState); - Action { + Controls1.Action { id: goPreviousAction text: i18nc("navigate back in the views stack", "Back") iconName: (Qt.application.layoutDirection == Qt.RightToLeft) ? "go-next" : "go-previous" onTriggered: goBack() } - Action { + Controls1.Action { id: showFilterAction text: navigationBar.state === "" ? i18nc("Show filters in the navigation bar", "Show Search Options") : i18nc("Hide filters in the navigation bar", "Hide Search Options") iconName: navigationBar.state === "" ? "go-down-search" : "go-up-search" onTriggered: { navigationBar.state === "" ? navigationBar.state = 'expanded' : navigationBar.state = "" filterViewChanged(navigationBar.state) } } ColumnLayout { anchors.fill: parent spacing: 0 anchors.margins: { top: elisaTheme.layoutVerticalMargin bottom: elisaTheme.layoutVerticalMargin } RowLayout { spacing: 0 Layout.alignment: Qt.AlignTop - ToolButton { + Controls1.ToolButton { action: goPreviousAction Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 visible: enableGoBack } Image { id: mainIcon source: image asynchronous: true sourceSize.height: elisaTheme.coverImageSize / 2 sourceSize.width: elisaTheme.coverImageSize / 2 fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 Layout.preferredWidth: elisaTheme.coverImageSize / 2 Layout.minimumWidth: elisaTheme.coverImageSize / 2 Layout.maximumWidth: elisaTheme.coverImageSize / 2 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } ColumnLayout { Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 spacing: 0 Layout.fillWidth: true Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: albumTextSize text: albumLabel.text font.pointSize: albumLabel.font.pointSize font.bold: albumLabel.font.bold } LabelWithToolTip { id: albumLabel text: secondaryTitle Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter elide: Text.ElideRight color: myPalette.text font { pointSize: elisaTheme.defaultFontPointSize * 2 } visible: secondaryTitle !== "" } TextMetrics { id: authorTextSize text: authorLabel.text font.pointSize: authorLabel.font.pointSize font.bold: authorLabel.font.bold } LabelWithToolTip { id: authorLabel text: mainTitle color: myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter font { pointSize: (secondaryTitle !== "" ? elisaTheme.defaultFontPointSize : elisaTheme.defaultFontPointSize * 2) } elide: Text.ElideRight } Item { id: emptyBottomFiller Layout.fillWidth: true Layout.fillHeight: true } RowLayout { Layout.fillWidth: true spacing: 0 - Button { + Controls1.Button { text: i18nc("Add current list to playlist", "Enqueue") iconName: "media-track-add-amarok" onClicked: enqueue() Layout.leftMargin: 0 Layout.rightMargin: 0 } - Button { + Controls1.Button { text: i18nc("Clear playlist and play", "Replace and Play") tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") iconName: "media-playback-start" onClicked: replaceAndPlay() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } - Button { + Controls1.Button { id: showArtistButton visible: allowArtistNavigation text: i18nc("Button to navigate to the artist of the album", "Display Artist") iconName: "view-media-artist" onClicked: showArtist() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Item { Layout.fillWidth: true } - ToolButton { + Controls1.ToolButton { action: showFilterAction Layout.alignment: Qt.AlignRight Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } } RowLayout { id: filterRow spacing: 0 visible: opacity > 0.0 Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.alignment: Qt.AlignTop LabelWithToolTip { text: i18nc("before the TextField input of the filter", "Search: ") font.bold: true Layout.bottomMargin: 0 color: myPalette.text } TextField { id: filterTextInput horizontalAlignment: TextInput.AlignLeft placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") Layout.bottomMargin: 0 Layout.preferredWidth: navigationBar.width / 2 Image { anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right anchors.margins: elisaTheme.filterClearButtonMargin id: clearText fillMode: Image.PreserveAspectFit smooth: true visible: parent.text source: Qt.resolvedUrl(elisaTheme.clearIcon) height: parent.height width: parent.height sourceSize.width: parent.height sourceSize.height: parent.height mirror: LayoutMirroring.enabled MouseArea { id: clear anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } height: parent.parent.height width: parent.parent.height onClicked: { parent.parent.text = "" parent.parent.forceActiveFocus() } } } } LabelWithToolTip { text: i18nc("before the Rating widget input of the filter", "Rating: ") visible: showRating font.bold: true color: myPalette.text Layout.bottomMargin: 0 Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 } RatingStar { id: ratingFilter visible: showRating readOnly: false starSize: elisaTheme.ratingStarSize Layout.bottomMargin: 0 } } } states: [ State { name: "" PropertyChanges { target: navigationBar height: elisaTheme.navigationBarHeight } PropertyChanges { target: filterRow opacity: 0.0 } }, State { name: 'expanded' PropertyChanges { target: navigationBar height: elisaTheme.navigationBarHeight + elisaTheme.navigationBarFilterHeight } PropertyChanges { target: filterRow opacity: 1.0 } } ] transitions: Transition { PropertyAnimation { properties: "height" easing.type: Easing.Linear duration: 250 } PropertyAnimation { properties: "opacity" easing.type: Easing.Linear duration: 250 } } } diff --git a/src/qml/PassiveNotification.qml b/src/qml/PassiveNotification.qml index e67dafac..55111124 100644 --- a/src/qml/PassiveNotification.qml +++ b/src/qml/PassiveNotification.qml @@ -1,152 +1,152 @@ /* * Copyright 2015 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.5 -import QtQuick.Controls 2.0 as QQC2 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 MouseArea { id: root z: 9999999 width: background.width height: background.height opacity: 0 enabled: appearAnimation.appear anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 3 * 4 } function showNotification(message, timeout, actionText, callBack) { if (!message) { return; } appearAnimation.running = false; appearAnimation.appear = true; appearAnimation.running = true; if (timeout == "short") { timer.interval = 1000; } else if (timeout == "long") { timer.interval = 4500; } else if (timeout > 0) { timer.interval = timeout; } else { timer.interval = 4500; } messageLabel.text = message ? message : ""; actionButton.text = actionText ? actionText : ""; actionButton.callBack = callBack ? callBack : ""; timer.restart(); } function hideNotification() { appearAnimation.running = false; appearAnimation.appear = false; appearAnimation.running = true; } onClicked: { appearAnimation.appear = false; appearAnimation.running = true; } transform: Translate { id: transform y: root.height } Timer { id: timer interval: 4000 onTriggered: { appearAnimation.appear = false; appearAnimation.running = true; } } ParallelAnimation { id: appearAnimation property bool appear: true NumberAnimation { target: root properties: "opacity" to: appearAnimation.appear ? 1 : 0 duration: 1000 easing.type: Easing.InOutQuad } NumberAnimation { target: transform properties: "y" to: appearAnimation.appear ? 0 : background.height duration: 1000 easing.type: appearAnimation.appear ? Easing.OutQuad : Easing.InQuad } } Item { id: background width: backgroundRect.width + 3 height: backgroundRect.height + 3 Rectangle { id: backgroundRect anchors.centerIn: parent radius: 5 color: myPalette.button opacity: 0.6 width: mainLayout.width + Math.round((height - mainLayout.height)) height: Math.max(mainLayout.height + 5*2, 3*2) } RowLayout { id: mainLayout anchors.centerIn: parent - QQC2.Label { + Label { id: messageLabel Layout.maximumWidth: Math.min(root.parent.width - 20*2, implicitWidth) elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 4 color: myPalette.buttonText } - QQC2.Button { + Button { id: actionButton property var callBack visible: text != "" onClicked: { appearAnimation.appear = false; appearAnimation.running = true; if (callBack) { callBack(); } } } } layer.enabled: true layer.effect: DropShadow { horizontalOffset: 0 verticalOffset: 0 radius: 3 samples: 32 color: Qt.rgba(0, 0, 0, 0.5) } } } diff --git a/src/qml/PlatformIntegration.qml b/src/qml/PlatformIntegration.qml index 394a5ea6..79317463 100644 --- a/src/qml/PlatformIntegration.qml +++ b/src/qml/PlatformIntegration.qml @@ -1,45 +1,45 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.0 +import QtQuick 2.7 import org.kde.elisa 1.0 Item { id: rootItem property alias playListModel: mpris2Interface.playListModel property alias playListControler: mpris2Interface.playListControler property alias audioPlayerManager: mpris2Interface.audioPlayerManager property alias player: mpris2Interface.audioPlayer property alias headerBarManager: mpris2Interface.headerBarManager property alias manageMediaPlayerControl: mpris2Interface.manageMediaPlayerControl signal raisePlayer() Mpris2 { id: mpris2Interface playerName: 'elisa' onRaisePlayer: { rootItem.raisePlayer() } } } diff --git a/src/qml/PlayListEntry.qml b/src/qml/PlayListEntry.qml index 96a46d8e..02d49017 100644 --- a/src/qml/PlayListEntry.qml +++ b/src/qml/PlayListEntry.qml @@ -1,481 +1,481 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Layouts 1.2 -import QtQuick.Controls 1.2 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: playListEntry property var index property bool isSingleDiscAlbum property int isPlaying property bool isSelected property bool isValid property bool isAlternateColor property bool containsMouse property bool hasAlbumHeader property string titleDisplay property alias trackData: dataHelper.trackData signal startPlayback() signal pausePlayback() signal removeFromPlaylist(var trackIndex) signal switchToTrack(var trackIndex) height: (hasAlbumHeader ? elisaTheme.delegateWithHeaderHeight : elisaTheme.delegateHeight) - Action { + Controls1.Action { id: removeFromPlayList text: i18nc("Remove current track from play list", "Remove") iconName: "list-remove" onTriggered: { playListEntry.removeFromPlaylist(playListEntry.index) } } - Action { + Controls1.Action { id: playNow text: i18nc("Play now current track from play list", "Play Now") iconName: "media-playback-start" enabled: !(isPlaying == MediaPlayList.IsPlaying) && isValid onTriggered: { playListEntry.switchToTrack(playListEntry.index) playListEntry.startPlayback() } } - Action { + Controls1.Action { id: pauseNow text: i18nc("Pause current track from play list", "Pause") iconName: "media-playback-pause" enabled: isPlaying == MediaPlayList.IsPlaying && isValid onTriggered: playListEntry.pausePlayback() } - Action { + Controls1.Action { id: showInfo text: i18nc("Show track metadata", "View Details") iconName: "help-about" enabled: isValid onTriggered: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } TrackDataHelper { id: dataHelper } Loader { id: metadataLoader active: false - onLoaded: item.open() + onLoaded: item.show() sourceComponent: MediaTrackMetadataView { trackDataHelper: dataHelper onRejected: metadataLoader.active = false; } } Rectangle { id: entryBackground anchors.fill: parent color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) height: (hasAlbumHeader ? elisaTheme.delegateWithHeaderHeight : elisaTheme.delegateHeight) focus: true ColumnLayout { spacing: 0 anchors.fill: parent anchors.leftMargin: elisaTheme.layoutHorizontalMargin anchors.rightMargin: elisaTheme.layoutHorizontalMargin Item { Layout.fillWidth: true Layout.preferredHeight: elisaTheme.delegateWithHeaderHeight - elisaTheme.delegateHeight Layout.minimumHeight: elisaTheme.delegateWithHeaderHeight - elisaTheme.delegateHeight Layout.maximumHeight: elisaTheme.delegateWithHeaderHeight - elisaTheme.delegateHeight visible: hasAlbumHeader RowLayout { id: headerRow spacing: elisaTheme.layoutHorizontalMargin anchors.fill: parent Image { id: mainIcon source: (isValid ? (dataHelper.hasValidAlbumCover() ? dataHelper.albumCover : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) : Qt.resolvedUrl(elisaTheme.errorIcon)) Layout.minimumWidth: headerRow.height - 4 Layout.maximumWidth: headerRow.height - 4 Layout.preferredWidth: headerRow.height - 4 Layout.minimumHeight: headerRow.height - 4 Layout.maximumHeight: headerRow.height - 4 Layout.preferredHeight: headerRow.height - 4 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter sourceSize.width: headerRow.height - 4 sourceSize.height: parent.height - 4 fillMode: Image.PreserveAspectFit asynchronous: true visible: isValid } BrightnessContrast { source: mainIcon cached: true visible: !isValid contrast: -0.9 Layout.minimumWidth: headerRow.height - 4 Layout.maximumWidth: headerRow.height - 4 Layout.preferredWidth: headerRow.height - 4 Layout.minimumHeight: headerRow.height - 4 Layout.maximumHeight: headerRow.height - 4 Layout.preferredHeight: headerRow.height - 4 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 LabelWithToolTip { id: mainLabel text: dataHelper.albumName font.weight: Font.Bold color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignCenter Layout.topMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: authorLabel text: dataHelper.albumArtist font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignCenter Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } } } } Item { Layout.fillWidth: true Layout.fillHeight: true RowLayout { id: trackRow anchors.fill: parent spacing: elisaTheme.layoutHorizontalMargin LabelWithToolTip { id: mainCompactLabel text: { if (dataHelper.hasValidTrackNumber()) { if (dataHelper.hasValidDiscNumber() && !isSingleDiscAlbum) return i18nc("%1: disk number. %2: track number. %3: track title", "%1 - %2 - %3", Number(dataHelper.discNumber).toLocaleString(Qt.locale(), 'f', 0), Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title); else return i18nc("%1: track number. %2: track title", "%1 - %2", Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title); } else { return dataHelper.title; } } font.weight: (isPlaying ? Font.Bold : Font.Normal) color: myPalette.text Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: isValid elide: Text.ElideRight horizontalAlignment: Text.AlignLeft } LabelWithToolTip { id: mainInvalidCompactLabel text: titleDisplay font.weight: Font.Normal color: myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: !isValid elide: Text.ElideRight } Item { Layout.fillWidth: true Layout.preferredWidth: 0 } - ToolButton { + Controls1.ToolButton { id: infoButton implicitHeight: elisaTheme.smallDelegateToolButtonSize implicitWidth: elisaTheme.smallDelegateToolButtonSize opacity: 0 visible: opacity > 0.1 action: showInfo Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } - ToolButton { + Controls1.ToolButton { id: playPauseButton implicitHeight: elisaTheme.smallDelegateToolButtonSize implicitWidth: elisaTheme.smallDelegateToolButtonSize opacity: 0 scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon visible: opacity > 0.1 action: !(isPlaying == MediaPlayList.IsPlaying) ? playNow : pauseNow Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } Item { implicitHeight: elisaTheme.smallDelegateToolButtonSize implicitWidth: elisaTheme.smallDelegateToolButtonSize Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - ToolButton { + Controls1.ToolButton { id: removeButton anchors.fill: parent opacity: 0 visible: opacity > 0.1 action: removeFromPlayList } Image { id: playIcon anchors.fill: parent opacity: 0 source: (isPlaying == MediaPlayList.IsPlaying ? Qt.resolvedUrl(elisaTheme.playIcon) : Qt.resolvedUrl(elisaTheme.pauseIcon)) width: parent.height * 1. height: parent.height * 1. sourceSize.width: parent.height * 1. sourceSize.height: parent.height * 1. fillMode: Image.PreserveAspectFit mirror: LayoutMirroring.enabled visible: isPlaying == MediaPlayList.IsPlaying || isPlaying == MediaPlayList.IsPaused SequentialAnimation on opacity { running: isPlaying == MediaPlayList.IsPlaying && playListEntry.state != 'hoveredOrSelected' loops: Animation.Infinite NumberAnimation { from: 0 to: 1. duration: 1000 easing.type: Easing.InOutCubic } NumberAnimation { from: 1 to: 0 duration: 1000 easing.type: Easing.InOutCubic } } SequentialAnimation on opacity { running: isPlaying == MediaPlayList.IsPaused && playListEntry.state != 'hoveredOrSelected' NumberAnimation { from: playIcon.opacity to: 1. duration: 1000 easing.type: Easing.InOutCubic } } SequentialAnimation on opacity { running: playListEntry.state == 'hoveredOrSelected' NumberAnimation { from: playIcon.opacity to: 0 duration: 250 easing.type: Easing.InOutCubic } } } } RatingStar { id: ratingWidget starRating: dataHelper.rating starSize: elisaTheme.ratingStarSize } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00") } LabelWithToolTip { id: durationLabel text: dataHelper.duration color: myPalette.text - elide: Text.ElideRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.preferredWidth: durationTextMetrics.width+1 // be in the safe side + Layout.preferredWidth: durationTextMetrics.width + 1 horizontalAlignment: Text.AlignRight } } } } } states: [ State { name: 'notSelected' when: !containsMouse && (!playListEntry.activeFocus || !isSelected) PropertyChanges { target: removeButton opacity: 0 } PropertyChanges { target: infoButton opacity: 0 } PropertyChanges { target: playPauseButton opacity: 0 } PropertyChanges { target: entryBackground color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } }, State { name: 'hoveredOrSelected' when: containsMouse || (playListEntry.activeFocus && isSelected) PropertyChanges { target: removeButton opacity: 1 } PropertyChanges { target: playPauseButton opacity: 1 } PropertyChanges { target: infoButton opacity: 1 } PropertyChanges { target: entryBackground color: myPalette.mid } } ] transitions: Transition { ParallelAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 250 } ColorAnimation { properties: "color" duration: 250 } } } } diff --git a/src/qml/Theme.qml b/src/qml/Theme.qml index 703228ae..06a82471 100644 --- a/src/qml/Theme.qml +++ b/src/qml/Theme.qml @@ -1,90 +1,90 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 Item { function dp(pixel) { // 96 - common, "base" DPI value return Math.round(pixel * logicalDpi / 96); } property string defaultAlbumImage: 'image://icon/media-optical-audio' property string defaultArtistImage: 'image://icon/view-media-artist' property string defaultBackgroundImage: 'qrc:///background.png' property string artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/media-album-cover-manager-amarok' property string playlistIcon: 'image://icon/amarok_playlist' property string tracksIcon: 'image://icon/media-album-track' property string clearIcon: 'image://icon/edit-clear' property string skipBackwardIcon: 'image://icon/media-skip-backward' property string pauseIcon: 'image://icon/media-playback-pause' property string playIcon: 'image://icon/media-playback-start' property string skipForwardIcon: 'image://icon/media-skip-forward' property string playerVolumeMutedIcon: 'image://icon/player-volume-muted' property string playerVolumeIcon: 'image://icon/player-volume' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' property int layoutHorizontalMargin: dp(8) property int layoutVerticalMargin: dp(6) property int delegateHeight: dp(28) property int delegateWithHeaderHeight: dp(68) property int trackDelegateHeight: dp(45) property int coverImageSize: dp(180) property int smallImageSize: dp(32) property int trackMetadataWidth: dp(300) property int tooltipRadius: dp(3) property int shadowOffset: dp(2) property int delegateToolButtonSize: dp(34) property int smallDelegateToolButtonSize: dp(20) property int ratingStarSize: dp(15) property int mediaPlayerControlHeight: dp(48) property real mediaPlayerControlOpacity: 0.6 property int smallControlButtonHeight: dp(32) property int bigControlButtonHeight: dp(44) property int volumeSliderWidth: dp(140) property int dragDropPlaceholderHeight: dp(28) property int navigationBarHeight: dp(100) property int navigationBarFilterHeight: dp(44) property int gridDelegateHeight: dp(100) + layoutVerticalMargin + fontSize.height * 2 property int gridDelegateWidth: dp(100) property int viewSelectorDelegateHeight: dp(32) property int filterClearButtonMargin: layoutVerticalMargin property alias defaultFontPointSize: fontSize.font.pointSize Label { id: fontSize } } diff --git a/src/qml/TopNotification.qml b/src/qml/TopNotification.qml index 6bf1c8ee..c3616c45 100644 --- a/src/qml/TopNotification.qml +++ b/src/qml/TopNotification.qml @@ -1,340 +1,342 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Layouts 1.3 -import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 import QtQml.Models 2.2 import org.kde.elisa 1.0 FocusScope { id: topItem property var musicManager property bool isViewExpanded: false property int rowHeight: elisaTheme.delegateHeight * 2 visible: Layout.preferredHeight > 0 Rectangle { anchors.fill: parent color: myPalette.mid } Component { id: highlightBar Rectangle { width: notificationColumn.width height: rowHeight color: (topItem.state === 'expanded' ? myPalette.highlight : myPalette.mid) Behavior on color { ColorAnimation { duration: 300 } } } } Rectangle { id: notificationCounter anchors { top: parent.top left: parent.left margins: elisaTheme.layoutVerticalMargin } width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize radius: width / 2 color: myPalette.window opacity: 0.0 visible: opacity > 0 z: 3 ToolButton { anchors.centerIn: parent text: manager.countNotifications onClicked: topItem.isViewExpanded = !topItem.isViewExpanded } } ScrollView { id: expandedView - flickableItem.boundsBehavior: Flickable.StopAtBounds - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOff anchors { top: parent.top right: parent.right left: notificationCounter.right bottom: parent.bottom } opacity: 0 visible: opacity > 0 focus: true z: 2 ListView { id: notificationColumn focus: true populate: Transition { NumberAnimation { properties: "opacity"; from: 0; to: 1.0; duration: 300 } } add: Transition { NumberAnimation { properties: "opacity"; from: 0; to: 1.0; duration: 300 } } displaced: Transition { NumberAnimation { properties: "x,y"; duration: 300 } } highlight: highlightBar model: DelegateModel { model: manager delegate: TopNotificationItem { id: currentDelegate focus: true width: ListView.view.width height: rowHeight onEntered: notificationColumn.currentIndex = index onClose: manager.closeNotification(index) onMainButtonClicked: manager.triggerMainButton(index) onSecondaryButtonClicked: manager.triggerSecondaryButton(index) itemMessage: message itemMainButtonText: mainButtonText itemMainButtonIconName: mainButtonIconName itemSecondaryButtonText: secondaryButtonText itemSecondaryButtonIconName: secondaryButtonIconName ListView.onRemove: SequentialAnimation { PropertyAction { target: currentDelegate; property: "ListView.delayRemove"; value: true } ParallelAnimation { NumberAnimation { target: currentDelegate; properties: "height"; from: elisaTheme.delegateHeight * 2; to: 0; duration: 300 } NumberAnimation { target: currentDelegate; properties: "opacity"; from: 1.0; to: 0; duration: 300 } } PropertyAction { target: currentDelegate; property: "ListView.delayRemove"; value: false } } } } function gotoBeginning() { anim.from = notificationColumn.contentY; anim.to = 0; anim.running = true; } NumberAnimation { id: anim; target: notificationColumn; property: "contentY"; duration: 300 } } } TopNotificationManager { id: manager } Connections { target: elisa.musicManager onNewNotification: manager.addNotification(notification) onCloseNotification: manager.closeNotificationById(notificationId) } states: [ State { name: "empty" when: manager.countNotifications === 0 PropertyChanges { target: topItem Layout.preferredHeight: 0 } PropertyChanges { target: notificationCounter opacity: 0.0 } PropertyChanges { target: notificationCounter width: 0 } PropertyChanges { target: notificationColumn interactive: false } PropertyChanges { target: expandedView opacity: 0.0 - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOff } StateChangeScript { script: notificationColumn.gotoBeginning() } }, State { name: "oneNotification" when: manager.countNotifications === 1 PropertyChanges { target: topItem Layout.preferredHeight: elisaTheme.delegateHeight * 2 } PropertyChanges { target: notificationCounter opacity: 0.0 } PropertyChanges { target: notificationCounter width: 0 } PropertyChanges { target: notificationColumn interactive: false } PropertyChanges { target: expandedView opacity: 1.0 - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOff } StateChangeScript { script: notificationColumn.gotoBeginning() } }, State { name: "multipleNotifications" when: manager.countNotifications > 1 && !isViewExpanded PropertyChanges { target: topItem Layout.preferredHeight: elisaTheme.delegateHeight * 2 } PropertyChanges { target: notificationCounter opacity: 1.0 } PropertyChanges { target: notificationCounter width: elisaTheme.delegateToolButtonSize } PropertyChanges { target: notificationColumn interactive: false } PropertyChanges { target: expandedView opacity: 1.0 - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded } StateChangeScript { script: notificationColumn.gotoBeginning() } }, State { name: "expanded" when: manager.countNotifications > 1 && isViewExpanded PropertyChanges { target: topItem Layout.preferredHeight: elisaTheme.delegateHeight * 4 } PropertyChanges { target: notificationCounter opacity: 1.0 } PropertyChanges { target: notificationCounter width: elisaTheme.delegateToolButtonSize } PropertyChanges { target: notificationColumn interactive: true } PropertyChanges { target: expandedView opacity: 1.0 - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + ScrollBar.vertical.policy: ScrollBar.AsNeeded } } ] transitions: [ Transition { SequentialAnimation { - PropertyAction { target: expandedView; property: "verticalScrollBarPolicy"; value: Qt.ScrollBarAlwaysOff } - + PropertyAction { + target: expandedView + property: "ScrollBar.vertical.policy" + value: ScrollBar.AlwaysOff + } ParallelAnimation { NumberAnimation { target: topItem duration: 300 property: "Layout.preferredHeight" } NumberAnimation { target: notificationCounter duration: 300 property: "opacity" } NumberAnimation { target: notificationCounter duration: 300 property: "width" } NumberAnimation { target: expandedView duration: 300 property: "opacity" } } } } ] } diff --git a/src/qml/TopNotificationItem.qml b/src/qml/TopNotificationItem.qml index 6f6ea9df..a72ebbd7 100644 --- a/src/qml/TopNotificationItem.qml +++ b/src/qml/TopNotificationItem.qml @@ -1,126 +1,127 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Layouts 1.3 -import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 import org.kde.elisa 1.0 FocusScope { id: topItem signal close() signal mainButtonClicked() signal secondaryButtonClicked() signal entered() property alias itemMessage: notificationText.text property alias itemMainButtonText: mainButton.text property alias itemMainButtonIconName: mainButton.iconName property alias itemSecondaryButtonText: secondaryButton.text property alias itemSecondaryButtonIconName: secondaryButton.iconName property var parentList MouseArea { anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton onEntered: topItem.entered() } RowLayout { id: content anchors.fill: parent Label { id: notificationText font.pointSize: elisaTheme.defaultFontPoinPoint * 1.5 Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.alignment: Qt.AlignHCenter visible: topItem.height > height opacity: (topItem.height - height) / height } - Button { + Controls1.Button { id: mainButton Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.alignment: Qt.AlignHCenter Layout.maximumHeight: elisaTheme.delegateHeight visible: text !== "" && topItem.height > height opacity: (topItem.height - height) / height onClicked: { mainButton.enabled = false enableAgainMainButtonTimer.start() mainButtonClicked() } Timer { id: enableAgainMainButtonTimer interval: 500 onTriggered: mainButton.enabled = true } } - Button { + Controls1.Button { id: secondaryButton Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.alignment: Qt.AlignHCenter Layout.maximumHeight: elisaTheme.delegateHeight visible: text !== "" && topItem.height > height opacity: (topItem.height - height) / height onClicked: { secondaryButton.enabled = false enableAgainSecondaryButtonTimer.start() secondaryButtonClicked() } Timer { id: enableAgainSecondaryButtonTimer interval: 500 onTriggered: secondaryButton.enabled = true } } Item { Layout.fillWidth: true } } - ToolButton { + Controls1.ToolButton { anchors.top: parent.top anchors.right: parent.right visible: topItem.height > height opacity: (topItem.height - height) / height iconName: 'dialog-close' onClicked: close() } } diff --git a/src/qml/TrackImportNotification.qml b/src/qml/TrackImportNotification.qml index 79370f68..6b767178 100644 --- a/src/qml/TrackImportNotification.qml +++ b/src/qml/TrackImportNotification.qml @@ -1,72 +1,71 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 +import QtQuick.Controls 2.2 import org.kde.elisa 1.0 Rectangle { id: rootComponent property bool indexingRunning property int importedTracksCount property MusicListenersManager musicManager color: myPalette.highlight width: elisaTheme.gridDelegateWidth * 1.5 visible: opacity > 0 opacity: (indexingRunning ? 1 : 0) Label { anchors.centerIn: parent text: i18ncp("number of imported tracks", "Imported one track", "Imported %1 tracks", importedTracksCount) color: myPalette.highlightedText } Timer { id: hideTimer interval: 6000 repeat: false onTriggered: { rootComponent.opacity = 0 musicManager.resetImportedTracksCounter() } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } onIndexingRunningChanged: if (indexingRunning) { hideTimer.stop() opacity = 1 } else { hideTimer.start() } } diff --git a/src/qml/ViewSelector.qml b/src/qml/ViewSelector.qml index 76104172..c936fe93 100644 --- a/src/qml/ViewSelector.qml +++ b/src/qml/ViewSelector.qml @@ -1,200 +1,197 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 -import QtQml.Models 2.1 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 import QtGraphicalEffects 1.0 FocusScope { id: rootFocusScope property alias currentIndex: viewModeView.currentIndex Rectangle { anchors.fill: parent color: myPalette.window border { color: (rootFocusScope.activeFocus ? myPalette.highlight : myPalette.window) width: 1 } ScrollView { - flickableItem.boundsBehavior: Flickable.StopAtBounds - focus: true anchors.fill: parent ListView { id: viewModeView focus: true z: 2 highlight: Rectangle { id: item height: elisaTheme.viewSelectorDelegateHeight * 1.4 width: viewModeView.width color: myPalette.highlight } model: DelegateModel { id: pageDelegateModel model: ListModel { id: pageModel } delegate: MouseArea { id: item height: elisaTheme.viewSelectorDelegateHeight * 1.4 width: viewModeView.width hoverEnabled: true acceptedButtons: Qt.LeftButton Rectangle { anchors.fill: parent z: 1 color: ((item.containsMouse && index !== viewModeView.currentIndex) ? myPalette.mid : "transparent") Behavior on color { ColorAnimation { duration: 200 } } } Image { id: viewIcon z: 2 anchors { verticalCenter: parent.verticalCenter leftMargin: elisaTheme.layoutHorizontalMargin left: parent.left } height: elisaTheme.viewSelectorDelegateHeight width: elisaTheme.viewSelectorDelegateHeight sourceSize { width: elisaTheme.viewSelectorDelegateHeight height: elisaTheme.viewSelectorDelegateHeight } source: iconName visible: false } ColorOverlay { source: viewIcon z: 2 anchors { verticalCenter: parent.verticalCenter leftMargin: elisaTheme.layoutHorizontalMargin left: parent.left } height: elisaTheme.viewSelectorDelegateHeight width: elisaTheme.viewSelectorDelegateHeight color: (index === viewModeView.currentIndex ? myPalette.highlightedText : "transparent") Behavior on color { ColorAnimation { duration: 300 } } } LabelWithToolTip { id: nameLabel z: 2 anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: elisaTheme.layoutHorizontalMargin anchors.left: viewIcon.right anchors.right: parent.right anchors.rightMargin: elisaTheme.layoutHorizontalMargin verticalAlignment: "AlignVCenter" font.pointSize: elisaTheme.defaultFontPointSize * 1.4 text: model.name color: (viewModeView.currentIndex === index ? myPalette.highlightedText : myPalette.text) Behavior on color { ColorAnimation { duration: 300 } } } onClicked: { viewModeView.currentIndex = index rootFocusScope.focus = true } } Component.onCompleted: { pageModel.insert(0, {"name": i18nc("Title of the view of the playlist", "Now Playing"), "iconName": elisaTheme.playlistIcon}) pageModel.insert(1, {"name": i18nc("Title of the view of all albums", "Albums"), "iconName": elisaTheme.albumIcon}) pageModel.insert(2, {"name": i18nc("Title of the view of all artists", "Artists"), "iconName": elisaTheme.artistIcon}) pageModel.insert(3, {"name": i18nc("Title of the view of all tracks", "Tracks"), "iconName": elisaTheme.tracksIcon}) viewModeView.currentIndex = 1 } } footer: MouseArea { width: viewModeView.width height: viewModeView.height - y acceptedButtons: Qt.LeftButton onClicked: { rootFocusScope.focus = true } } } } Behavior on border.color { ColorAnimation { duration: 300 } } } } diff --git a/src/qtquickcontrols2.conf b/src/qtquickcontrols2.conf new file mode 100644 index 00000000..1eba912c --- /dev/null +++ b/src/qtquickcontrols2.conf @@ -0,0 +1,7 @@ +; This file can be edited to change the style of the application +; Read "Qt Quick Controls 2 Configuration File" for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html + +[Controls] +Style=org.kde.desktop + diff --git a/src/resources.qrc b/src/resources.qrc index c02e4de7..a920a161 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,41 +1,42 @@ qml/MediaPlayerControl.qml qml/RatingStar.qml qml/MediaPlayListView.qml qml/MediaAlbumView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/HeaderBar.qml qml/ContextView.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/NavigationActionBar.qml qml/PlayListEntry.qml qml/MediaBrowser.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/MediaAllTracksView.qml qml/TopNotification.qml qml/TrackImportNotification.qml qml/TopNotificationItem.qml qml/ViewSelector.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml + qtquickcontrols2.conf background.png windows/WindowsTheme.qml windows/PlatformIntegration.qml windows/LabelWithToolTip.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml diff --git a/src/windows/LabelWithToolTip.qml b/src/windows/LabelWithToolTip.qml index b022404a..8adeef77 100644 --- a/src/windows/LabelWithToolTip.qml +++ b/src/windows/LabelWithToolTip.qml @@ -1,51 +1,48 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Controls 2.0 as Controls2 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import QtQuick.Window 2.2 Label { id: theLabel - renderType: Text.NativeRendering - Loader { anchors.fill: parent active: theLabel.truncated visible: theLabel.truncated MouseArea { anchors.fill: parent hoverEnabled: true - Controls2.ToolTip { + ToolTip { delay: Qt.styleHints.mousePressAndHoldInterval visible: parent.containsMouse && theLabel.truncated text: theLabel.text enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } } } } }