[vlc-devel] [RFC 75/82] qml: add views to browse music indexed by the medialibrary

Pierre Lamot pierre at videolabs.io
Fri Feb 1 14:02:19 CET 2019


---
 modules/gui/qt/Makefile.am                    |   9 +
 .../gui/qt/qml/mediacenter/ArtistGridView.qml |  61 +++++
 .../gui/qt/qml/mediacenter/ArtistListView.qml |  56 ++++
 .../qt/qml/mediacenter/ArtistTopBanner.qml    | 100 +++++++
 .../gui/qt/qml/mediacenter/MCMusicDisplay.qml | 242 ++++++++++++++++
 .../qt/qml/mediacenter/MusicAlbumsDisplay.qml | 258 ++++++++++++++++++
 .../MusicAlbumsGridExpandDelegate.qml         | 226 +++++++++++++++
 .../qml/mediacenter/MusicArtistsDisplay.qml   | 252 +++++++++++++++++
 .../qt/qml/mediacenter/MusicGenresDisplay.qml | 224 +++++++++++++++
 .../qml/mediacenter/MusicTrackListDisplay.qml |  54 ++++
 .../qt/qml/mediacenter/MusicTracksDisplay.qml |  39 +++
 modules/gui/qt/vlc.qrc                        |  12 +
 12 files changed, 1533 insertions(+)
 create mode 100644 modules/gui/qt/qml/mediacenter/ArtistGridView.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/ArtistListView.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/ArtistTopBanner.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MCMusicDisplay.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MusicAlbumsDisplay.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MusicAlbumsGridExpandDelegate.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MusicArtistsDisplay.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MusicGenresDisplay.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MusicTrackListDisplay.qml
 create mode 100644 modules/gui/qt/qml/mediacenter/MusicTracksDisplay.qml

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index 1036ee6f68..08e18ba9f3 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -561,6 +561,15 @@ libqt_plugin_la_RES = \
 	gui/qt/qml/menus/ToolsMenu.qml \
 	gui/qt/qml/menus/ViewMenu.qml \
 	gui/qt/qml/menus/VideoMenu.qml \
+	gui/qt/qml/mediacenter/ArtistListView.qml \
+	gui/qt/qml/mediacenter/ArtistTopBanner.qml \
+	gui/qt/qml/mediacenter/MCMusicDisplay.qml \
+	gui/qt/qml/mediacenter/MusicAlbumsDisplay.qml \
+	gui/qt/qml/mediacenter/MusicAlbumsGridExpandDelegate.qml \
+	gui/qt/qml/mediacenter/MusicArtistsDisplay.qml \
+	gui/qt/qml/mediacenter/MusicGenresDisplay.qml \
+	gui/qt/qml/mediacenter/MusicTracksDisplay.qml \
+	gui/qt/qml/mediacenter/MusicTrackListDisplay.qml \
 	gui/qt/qml/playlist/PlaylistListView.qml \
 	gui/qt/qml/playlist/PLItem.qml \
 	gui/qt/qml/playlist/PLItemFooter.qml \
diff --git a/modules/gui/qt/qml/mediacenter/ArtistGridView.qml b/modules/gui/qt/qml/mediacenter/ArtistGridView.qml
new file mode 100644
index 0000000000..b10100e7b6
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/ArtistGridView.qml
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+import org.videolan.medialib 0.1
+
+
+GridView {
+    id: artistGridView
+
+    cellWidth: (VLCStyle.cover_normal) + VLCStyle.margin_small
+    cellHeight: (VLCStyle.cover_normal + VLCStyle.fontHeight_normal)  + VLCStyle.margin_small
+    clip: true
+    ScrollBar.vertical: ScrollBar { }
+
+    delegate : Utils.GridItem {
+        id: gridItem
+        width: VLCStyle.cover_normal
+        height: VLCStyle.cover_normal + VLCStyle.fontHeight_normal
+
+        cover: Utils.MultiCoverPreview {
+            albums: MLAlbumModel {
+                ml: medialib
+                parentId: model.id
+                maxItems: 4
+            }
+        }
+        name: model.name || "Unknown Artist"
+
+        onItemClicked: {
+            console.log('Clicked on details : '+model.name);
+        }
+        onPlayClicked: {
+            console.log('Clicked on play : '+model.name);
+            medialib.addAndPlay( model.id )
+        }
+        onAddToPlaylistClicked: {
+            console.log('Clicked on addToPlaylist : '+model.name);
+            medialib.addToPlaylist( model.id );
+        }
+    }
+}
diff --git a/modules/gui/qt/qml/mediacenter/ArtistListView.qml b/modules/gui/qt/qml/mediacenter/ArtistListView.qml
new file mode 100644
index 0000000000..ee0a3d9a6a
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/ArtistListView.qml
@@ -0,0 +1,56 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+ListView {
+    id: artistListView
+    property var onItemClicked
+
+    spacing: 2
+    delegate : Utils.ListItem {
+        height: VLCStyle.icon_normal
+        width: parent.width
+
+        cover: Image {
+            id: cover_obj
+            fillMode: Image.PreserveAspectFit
+            source: model.cover || VLCStyle.noArtCover
+        }
+        line1: model.name || qsTr("Unknown artist")
+
+
+        onItemClicked: {
+            artistListView.onItemClicked( model.id )
+        }
+
+        onPlayClicked: {
+            console.log('Clicked on play : '+model.name);
+            medialib.addAndPlay( model.id )
+        }
+        onAddToPlaylistClicked: {
+            console.log('Clicked on addToPlaylist : '+model.name);
+            medialib.addToPlaylist( model.id );
+        }
+    }
+
+    ScrollBar.vertical: ScrollBar { }
+}
diff --git a/modules/gui/qt/qml/mediacenter/ArtistTopBanner.qml b/modules/gui/qt/qml/mediacenter/ArtistTopBanner.qml
new file mode 100644
index 0000000000..1e48e14eff
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/ArtistTopBanner.qml
@@ -0,0 +1,100 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.3
+import QtGraphicalEffects 1.0
+
+import org.videolan.medialib 0.1
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Rectangle {
+    id: root
+    property var artist: null
+    color: VLCStyle.colors.bg
+
+    property int contentY: 0
+    height: VLCStyle.heightBar_xlarge
+
+    RowLayout {
+        anchors.fill: parent
+        spacing: VLCStyle.margin_large
+
+        Image {
+            id: artistImage
+            source: artist.cover || VLCStyle.noArtCover
+
+            Layout.leftMargin: VLCStyle.margin_large
+            Layout.preferredWidth: VLCStyle.cover_small
+            Layout.preferredHeight: VLCStyle.cover_small
+
+            layer.enabled: true
+            layer.effect: OpacityMask {
+                maskSource: Rectangle {
+                    width: artistImage.width
+                    height: artistImage.height
+                    radius: artistImage.width
+                }
+            }
+        }
+
+        Text {
+            id: main_artist
+            text: artist.name
+            Layout.fillWidth: true
+            font.pixelSize: VLCStyle.fontSize_xxxlarge
+            font.bold: true
+            wrapMode: Text.WordWrap
+            maximumLineCount: 2
+            elide: Text.ElideRight
+            color: VLCStyle.colors.text
+        }
+
+    }
+    states: [
+        State {
+            name: "full"
+            PropertyChanges {
+                target: artistImage
+                width: VLCStyle.cover_small
+                height: VLCStyle.cover_small
+            }
+            PropertyChanges {
+                target: main_artist
+                font.pixelSize: VLCStyle.fontSize_xxxlarge
+            }
+            when: contentY < VLCStyle.heightBar_large
+        },
+        State {
+            name: "small"
+            PropertyChanges {
+                target: artistImage
+                width: VLCStyle.icon_normal
+                height: VLCStyle.icon_normal
+            }
+            PropertyChanges {
+                target: main_artist
+                font.pixelSize: VLCStyle.fontSize_large
+                anchors.leftMargin: VLCStyle.margin_small
+            }
+            when: contentY >= VLCStyle.heightBar_large
+        }
+    ]
+}
diff --git a/modules/gui/qt/qml/mediacenter/MCMusicDisplay.qml b/modules/gui/qt/qml/mediacenter/MCMusicDisplay.qml
new file mode 100644
index 0000000000..3c9d9abafc
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MCMusicDisplay.qml
@@ -0,0 +1,242 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.3
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+import org.videolan.medialib 0.1
+
+Utils.NavigableFocusScope {
+    id: root
+
+    //name and properties of the tab to be initially loaded
+    property string view: "albums"
+    property var viewProperties: ({})
+
+    Component { id: albumComp; MusicAlbumsDisplay{ } }
+    Component { id: artistComp; MusicArtistsDisplay{ } }
+    Component { id: genresComp; MusicGenresDisplay{ } }
+    Component { id: tracksComp; MusicTrackListDisplay{ } }
+
+    readonly property var pageModel: [{
+            displayText: qsTr("Albums"),
+            name: "albums",
+            component: albumComp
+        }, {
+            displayText: qsTr("Artists"),
+            name: "artists",
+            component: artistComp
+        }, {
+            displayText: qsTr("Genres"),
+            name: "genres" ,
+            component: genresComp
+        }, {
+            displayText: qsTr("Tracks"),
+            name: "tracks" ,
+            component: tracksComp
+        }
+    ]
+
+    property var tabModel: ListModel {
+        Component.onCompleted: {
+            pageModel.forEach(function(e) {
+                append({
+                    displayText: e.displayText,
+                    name: e.name,
+                    selected: (e.name === root.view)
+                })
+            })
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill : parent
+
+        Utils.NavigableFocusScope {
+            id: toobar
+
+            Layout.fillWidth: true
+            Layout.preferredHeight: VLCStyle.icon_normal + VLCStyle.margin_small
+
+            Rectangle {
+                anchors.fill: parent
+                color: VLCStyle.colors.banner
+
+                RowLayout {
+                    anchors.fill: parent
+
+                    TabBar {
+                        id: bar
+
+                        focus: true
+
+                        Layout.preferredHeight: parent.height - VLCStyle.margin_small
+                        Layout.alignment: Qt.AlignVCenter
+
+                        background: Rectangle {
+                            color: VLCStyle.colors.banner
+                        }
+                        Component.onCompleted: {
+                            bar.contentItem.focus= true
+                        }
+
+                        /* List of sub-sources for Music */
+                        Repeater {
+                            id: model_music_id
+
+                            model: tabModel
+
+                            //Column {
+                            TabButton {
+                                id: control
+                                text: model.displayText
+                                font.pixelSize: VLCStyle.fontSize_normal
+                                background: Rectangle {
+                                    color: control.hovered ? VLCStyle.colors.bannerHover : VLCStyle.colors.banner
+                                }
+                                contentItem: Label {
+                                    text: control.text
+                                    font: control.font
+                                    color:  control.hovered ?  VLCStyle.colors.textActiveSource : VLCStyle.colors.text
+                                    verticalAlignment: Text.AlignVCenter
+                                    horizontalAlignment: Text.AlignHCenter
+
+                                    Rectangle {
+                                        anchors {
+                                            left: parent.left
+                                            right: parent.right
+                                            bottom: parent.bottom
+                                        }
+                                        height: 2
+                                        visible: control.activeFocus || control.checked
+                                        color: control.activeFocus ? VLCStyle.colors.accent  : VLCStyle.colors.bgHover
+                                    }
+                                }
+                                onClicked: {
+                                    stackView.replace(pageModel[index].component)
+                                    history.push(["mc", "music", model.name ], History.Stay)
+                                    stackView.focus = true
+                                }
+                                checked: (model.name === root.view)
+                                activeFocusOnTab: true
+                                Component.onCompleted: {
+                                    if (model.selected)
+                                        bar.currentIndex = index
+                                }
+                            }
+                        }
+
+                        KeyNavigation.right: searchBox
+                    }
+
+                    /* Spacer */
+                    Item {
+                        Layout.fillWidth: true
+                    }
+
+                    TextField {
+                        Layout.preferredWidth: VLCStyle.widthSearchInput
+                        Layout.preferredHeight: parent.height - VLCStyle.margin_small
+                        Layout.alignment: Qt.AlignVCenter  | Qt.AlignRight
+
+                        id: searchBox
+                        font.pixelSize: VLCStyle.fontSize_normal
+
+                        color: VLCStyle.colors.buttonText
+                        placeholderText: qsTr("filter")
+                        hoverEnabled: true
+
+                        background: Rectangle {
+                            radius: 5 //fixme
+                            color: VLCStyle.colors.button
+                            border.color: {
+                                if ( searchBox.text.length < 3 && searchBox.text.length !== 0 )
+                                    return VLCStyle.colors.alert
+                                else if ( searchBox.hovered || searchBox.activeFocus )
+                                    return VLCStyle.colors.accent
+                                else
+                                    return VLCStyle.colors.buttonBorder
+                           }
+                        }
+
+                        onTextChanged: {
+                            stackView.currentItem.model.searchPattern = text;
+                        }
+
+                        KeyNavigation.right: combo
+                    }
+
+                    /* Selector to choose a specific sorting operation */
+                    Utils.ComboBoxExt {
+                        id: combo
+
+                        //Layout.fillHeight: true
+                        Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
+                        Layout.preferredWidth: VLCStyle.widthSortBox
+                        Layout.preferredHeight: parent.height - VLCStyle.margin_small
+                        textRole: "text"
+                        model: stackView.currentItem.sortModel
+                        onCurrentIndexChanged: {
+                            var sorting = model.get(currentIndex);
+                            stackView.currentItem.model.sortCriteria = sorting.criteria
+                        }
+                    }
+                }
+            }
+
+            onActionLeft:   root.actionLeft(index)
+            onActionRight:  root.actionRight(index)
+            onActionDown:   stackView.focus = true
+            onActionUp:     root.actionUp( index )
+            onActionCancel: root.actionCancel( index )
+
+            Keys.priority: Keys.AfterItem
+            Keys.onPressed: {
+                if (!event.accepted)
+                    defaultKeyAction(event, 0)
+            }
+        }
+
+        /* The data elements */
+        Utils.StackViewExt  {
+            id: stackView
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            focus: true
+
+            Component.onCompleted: {
+                var found = stackView.loadView(root.pageModel, view, viewProperties)
+                if (!found)
+                    replace(pageModel[0].component)
+            }
+        }
+
+        Connections {
+            target: stackView.currentItem
+            ignoreUnknownSignals: true
+            onActionLeft:   root.actionLeft(index)
+            onActionRight:  root.actionRight(index)
+            onActionDown:   root.actionDown(index)
+            onActionUp:     toobar.focus = true
+            onActionCancel: toobar.focus = true
+        }
+    }
+}
diff --git a/modules/gui/qt/qml/mediacenter/MusicAlbumsDisplay.qml b/modules/gui/qt/qml/mediacenter/MusicAlbumsDisplay.qml
new file mode 100644
index 0000000000..d85da2f9f5
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MusicAlbumsDisplay.qml
@@ -0,0 +1,258 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.3
+import QtQml.Models 2.2
+import org.videolan.medialib 0.1
+
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Utils.NavigableFocusScope {
+    id: root
+
+    property var sortModel: ListModel {
+        ListElement { text: qsTr("Alphabetic");  criteria: "title";}
+        ListElement { text: qsTr("Duration");    criteria: "duration"; }
+        ListElement { text: qsTr("Date");        criteria: "release_year"; }
+        ListElement { text: qsTr("Artist");      criteria: "main_artist"; }
+    }
+
+    property alias model: delegateModel.model
+    property alias parentId: delegateModel.parentId
+    property var currentIndex: view.currentItem.currentIndex
+
+    Utils.SelectableDelegateModel {
+        id: delegateModel
+        property alias parentId: albumModelId.parentId
+
+        model: MLAlbumModel {
+            id: albumModelId
+            ml: medialib
+        }
+
+        delegate: Package {
+            id: element
+
+            Utils.GridItem {
+                Package.name: "gridTop"
+                image: model.cover || VLCStyle.noArtCover
+                title: model.title || qsTr("Unknown title")
+                subtitle: model.main_artist || qsTr("Unknown artist")
+                selected: element.DelegateModel.inSelected || view.currentItem.currentIndex === index
+                shiftX: view.currentItem.shiftX(model.index)
+
+                onItemClicked : {
+                    view._switchExpandItem( index )
+                    delegateModel.updateSelection( modifier , view.currentItem.currentIndex, index)
+                    view.currentItem.currentIndex = index
+                    view.currentItem.forceActiveFocus()
+
+                }
+                onPlayClicked: medialib.addAndPlay( model.id )
+                onAddToPlaylistClicked : medialib.addToPlaylist( model.id )
+            }
+
+            Utils.GridItem {
+                Package.name: "gridBottom"
+                image: model.cover || VLCStyle.noArtCover
+                title: model.title || qsTr("Unknown title")
+                subtitle: model.main_artist || qsTr("Unknown artist")
+                selected: element.DelegateModel.inSelected || view.currentItem.currentIndex === index
+                shiftX: view.currentItem.shiftX(model.index)
+
+                onItemClicked : {
+                    view._switchExpandItem( index )
+                    delegateModel.updateSelection( modifier , view.currentItem.currentIndex, index)
+                    view.currentItem.currentIndex = index
+                    view.currentItem.forceActiveFocus()
+                }
+                onPlayClicked: medialib.addAndPlay( model.id )
+                onAddToPlaylistClicked : medialib.addToPlaylist( model.id )
+            }
+
+            Utils.ListItem {
+                Package.name: "list"
+                width: root.width
+                height: VLCStyle.icon_normal
+
+                color: VLCStyle.colors.getBgColor(element.DelegateModel.inSelected, this.hovered, this.activeFocus)
+
+                cover: Image {
+                    id: cover_obj
+                    fillMode: Image.PreserveAspectFit
+                    source: model.cover || VLCStyle.noArtCover
+                }
+                line1: (model.title || qsTr("Unknown title"))+" ["+model.duration+"]"
+                line2: model.main_artist || qsTr("Unknown artist")
+
+                onItemClicked : {
+                    delegateModel.updateSelection( modifier, view.currentItem.currentIndex, index )
+                    view.currentItem.currentIndex = index
+                    this.forceActiveFocus()
+                }
+                onPlayClicked: medialib.addAndPlay( model.id )
+                onAddToPlaylistClicked : medialib.addToPlaylist( model.id )
+            }
+        }
+
+        function actionAtIndex(index) {
+            if (delegateModel.selectedGroup.count > 1) {
+                var list = []
+                for (var i = 0; i < delegateModel.selectedGroup.count; i++)
+                    list.push(delegateModel.selectedGroup.get(i).model.id)
+                medialib.addAndPlay( list )
+            } else {
+                medialib.addAndPlay( delegateModel.items.get(index).model.id )
+            }
+        }
+    }
+
+    /*
+     *define the intial position/selection
+     * This is done on activeFocus rather than Component.onCompleted because delegateModel.
+     * selectedGroup update itself after this event
+     */
+    onActiveFocusChanged: {
+        if (activeFocus && delegateModel.items.count > 0 && delegateModel.selectedGroup.count === 0) {
+            var initialIndex = 0
+            if (view.currentItem.currentIndex !== -1)
+                initialIndex = view.currentItem.currentIndex
+            delegateModel.items.get(initialIndex).inSelected = true
+            view.currentItem.currentIndex = initialIndex
+        }
+    }
+
+    Component {
+        id: gridComponent
+
+        Utils.ExpandGridView {
+            id: gridView_id
+
+            activeFocusOnTab:true
+
+            cellWidth: VLCStyle.cover_normal + VLCStyle.margin_small
+            cellHeight: VLCStyle.cover_normal + VLCStyle.fontHeight_normal * 2 + VLCStyle.margin_small
+
+            expandDelegate:  Rectangle {
+                id: expandDelegateId
+                height: albumDetail.implicitHeight
+                width: root.width
+                color: VLCStyle.colors.bgAlt
+                property int currentId: -1
+                property alias model : albumDetail.model
+
+                MusicAlbumsGridExpandDelegate {
+                    id: albumDetail
+                    anchors.fill: parent
+                    visible: true
+                    focus: true
+                    model: delegateModel.items.get(gridView_id.expandIndex).model
+                    onActionCancel:  gridView_id.expandIndex = -1
+                    onActionUp:  gridView_id.expandIndex = -1
+                    onActionDown: gridView_id.expandIndex = -1
+                    onActionLeft: root.actionLeft(index)
+                    onActionRight: root.actionRight(index)
+                }
+
+                Connections {
+                    target: gridView_id
+                    onExpandIndexChanged: {
+                        if (gridView_id.expandIndex !== -1)
+                        {
+                            expandDelegateId.model = delegateModel.items.get(gridView_id.expandIndex).model
+                        }
+                    }
+                }
+            }
+
+            modelTop: delegateModel.parts.gridTop
+            modelBottom: delegateModel.parts.gridBottom
+            modelCount: delegateModel.items.count
+
+            onActionAtIndex: {
+                if (delegateModel.selectedGroup.count === 1) {
+                    view._switchExpandItem(index)
+                } else {
+                    delegateModel.actionAtIndex(index)
+                }
+            }
+            onSelectAll: delegateModel.selectAll()
+            onSelectionUpdated: delegateModel.updateSelection( keyModifiers, oldIndex, newIndex )
+
+            onActionLeft: root.actionLeft(index)
+            onActionRight: root.actionRight(index)
+            onActionDown: root.actionDown(index)
+            onActionUp: root.actionUp(index)
+            onActionCancel: root.actionCancel(index)
+        }
+    }
+
+    Component {
+        id: listComponent
+        /* ListView */
+        Utils.KeyNavigableListView {
+            id: listView_id
+
+            interactive: root.interactive
+
+            spacing: VLCStyle.margin_xxxsmall
+
+            model: delegateModel.parts.list
+            modelCount: delegateModel.items.count
+
+            onActionAtIndex: delegateModel.actionAtIndex(index)
+            onSelectAll: delegateModel.selectAll()
+            onSelectionUpdated: delegateModel.updateSelection( keyModifiers, oldIndex, newIndex )
+
+            onActionLeft: root.actionLeft(index)
+            onActionRight: root.actionRight(index)
+            onActionDown: root.actionDown(index)
+            onActionUp: root.actionUp(index)
+            onActionCancel: root.actionCancel(index)
+        }
+    }
+
+    Utils.StackViewExt {
+        id: view
+
+        anchors.fill: parent
+        focus: true
+
+        initialItem: medialib.gridView ? gridComponent : listComponent
+
+        Connections {
+            target: medialib
+            onGridViewChanged: {
+                if (medialib.gridView)
+                    view.replace(gridComponent)
+                else
+                    view.replace(listComponent)
+            }
+        }
+
+        function _switchExpandItem(index) {
+            if (view.currentItem.expandIndex === index)
+                view.currentItem.expandIndex = -1
+            else
+                view.currentItem.expandIndex = index
+        }
+    }
+}
diff --git a/modules/gui/qt/qml/mediacenter/MusicAlbumsGridExpandDelegate.qml b/modules/gui/qt/qml/mediacenter/MusicAlbumsGridExpandDelegate.qml
new file mode 100644
index 0000000000..529fe4957d
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MusicAlbumsGridExpandDelegate.qml
@@ -0,0 +1,226 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.3
+
+import org.videolan.medialib 0.1
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Utils.NavigableFocusScope {
+    id: root
+    property var model: []
+    //color: VLCStyle.colors.bg
+    implicitHeight: layout.height
+
+    Menu {
+        id: moreactions_menu
+        MenuItem {
+            text: qsTr("view artist")
+            onTriggered: console.log("view artist checked")
+        }
+        MenuItem {
+            text: qsTr("view artist")
+            onTriggered: console.log("view artist checked")
+        }
+        MenuItem {
+            text: qsTr("view artist")
+            onTriggered: console.log("view artist checked")
+        }
+        MenuItem {
+            text: qsTr("view artist")
+            onTriggered: console.log("view artist checked")
+        }
+    }
+
+    Row {
+        id: layout
+        spacing: VLCStyle.margin_xsmall
+        width: parent.width
+        height: Math.max(expand_infos_id.height, artAndControl.height)
+
+        FocusScope {
+            id: artAndControl
+            //width: VLCStyle.cover_large + VLCStyle.margin_small * 2
+            //height: VLCStyle.cover_xsmall + VLCStyle.cover_large
+            width:  artAndControlLayout.implicitWidth
+            height:  artAndControlLayout.implicitHeight
+
+            Column {
+                id: artAndControlLayout
+                anchors.margins: VLCStyle.margin_small
+                spacing: VLCStyle.margin_small
+
+                Item {
+                    //dummy item for margins
+                    width: parent.width
+                    height: 1
+                }
+
+                /* A bigger cover for the album */
+                Image {
+                    id: expand_cover_id
+                    height: VLCStyle.cover_large
+                    width: VLCStyle.cover_large
+                    source: model.cover || VLCStyle.noArtCover
+                }
+
+                RowLayout {
+                    anchors {
+                        left: parent.left
+                        right: parent.right
+                    }
+
+                    Utils.IconToolButton {
+                        id: addButton
+                        focus: true
+
+                        Layout.preferredWidth: VLCStyle.icon_normal
+                        Layout.preferredHeight: VLCStyle.icon_normal
+                        size: VLCStyle.icon_normal
+                        Layout.alignment: Qt.AlignHCenter
+
+                        text: VLCIcons.add
+
+                        onClicked: medialib.addToPlaylist(model.id)
+
+                        KeyNavigation.right: playButton
+                    }
+                    Utils.IconToolButton {
+                        id: playButton
+
+                        Layout.preferredWidth: VLCStyle.icon_normal
+                        Layout.preferredHeight: VLCStyle.icon_normal
+                        Layout.alignment: Qt.AlignHCenter
+                        size: VLCStyle.icon_normal
+
+                        text: VLCIcons.play
+
+                        onClicked: medialib.addAndPlay(model.id)
+
+                        Keys.onRightPressed: {
+                            expand_track_id.focus = true
+                            event.accepted = true
+                        }
+                    }
+                }
+
+                Item {
+                    //dummy item for margins
+                    width: parent.width
+                    height: 1
+                }
+            }
+        }
+
+
+        Column {
+            id: expand_infos_id
+
+            spacing: VLCStyle.margin_xsmall
+            width: root.width - x
+
+            /* The title of the albums */
+            // Needs a rectangle too prevent the tracks from overlapping the title when scrolled
+            Rectangle {
+                id: expand_infos_titleRect_id
+                height: expand_infos_title_id.implicitHeight
+                anchors {
+                    left: parent.left
+                    right: parent.right
+                    topMargin: VLCStyle.margin_small
+                    leftMargin: VLCStyle.margin_small
+                    rightMargin: VLCStyle.margin_small
+                }
+                color: "transparent"
+                Text {
+                    id: expand_infos_title_id
+                    text: "<b>"+(model.title || qsTr("Unknown title") )+"</b>"
+                    font.pixelSize: VLCStyle.fontSize_xxlarge
+                    color: VLCStyle.colors.text
+                }
+            }
+
+            Rectangle {
+                id: expand_infos_subtitleRect_id
+                height: expand_infos_subtitle_id.implicitHeight
+                anchors {
+                    left: parent.left
+                    right: parent.right
+                    topMargin: VLCStyle.margin_xxsmall
+                    leftMargin: VLCStyle.margin_small
+                    rightMargin: VLCStyle.margin_small
+                }
+
+                color: "transparent"
+                Text {
+                    id: expand_infos_subtitle_id
+                    text: qsTr("By %1 - %2 - %3")
+                    .arg(model.main_artist || qsTr("Unknown title"))
+                    .arg(model.release_year || "")
+                    .arg(model.duration || "")
+                    font.pixelSize: VLCStyle.fontSize_large
+                    color: VLCStyle.colors.text
+                }
+            }
+
+            /* The list of the tracks available */
+            MusicTrackListDisplay {
+                id: expand_track_id
+
+                height: expand_track_id.contentHeight
+                anchors {
+                    left: parent.left
+                    right: parent.right
+                    topMargin: VLCStyle.margin_xxsmall
+                    leftMargin: VLCStyle.margin_small
+                    rightMargin: VLCStyle.margin_small
+                    bottomMargin: VLCStyle.margin_small
+                }
+
+                interactive: false
+
+                parentId : root.model.id
+                sortModel: ListModel {
+                    ListElement{ criteria: "track_number";  width:0.10; visible: true; text: qsTr("#"); showSection: "" }
+                    ListElement{ criteria: "title";         width:0.70; visible: true; text: qsTr("TITLE"); showSection: "" }
+                    ListElement{ criteria: "duration";      width:0.20; visible: true; text: qsTr("DURATION"); showSection: "" }
+                }
+                focus: true
+
+                onActionLeft:  artAndControl.focus = true
+                onActionRight: root.actionRight(index)
+                onActionUp: root.actionUp(index)
+                onActionDown: root.actionDown(index)
+                onActionCancel: root.actionCancel(index)
+            }
+
+            Item {
+                //dummy item for margins
+                width: parent.width
+                height: 1
+            }
+        }
+    }
+
+
+    Keys.priority:  KeyNavigation.AfterItem
+    Keys.onPressed:  defaultKeyAction(event, 0)
+}
diff --git a/modules/gui/qt/qml/mediacenter/MusicArtistsDisplay.qml b/modules/gui/qt/qml/mediacenter/MusicArtistsDisplay.qml
new file mode 100644
index 0000000000..9b16d7c9c5
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MusicArtistsDisplay.qml
@@ -0,0 +1,252 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick.Controls 2.4
+import QtQuick 2.11
+import QtQml.Models 2.2
+import QtQuick.Layouts 1.3
+
+import org.videolan.medialib 0.1
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Utils.NavigableFocusScope {
+    id: root
+    property alias model: delegateModel.model
+    property var sortModel: ListModel {
+        ListElement { text: qsTr("Alphabetic");  criteria: "title" }
+    }
+
+    property int currentArtistIndex: -1
+    onCurrentArtistIndexChanged: {
+        if (currentArtistIndex == -1)
+            view.replace(artistGridComponent)
+        else
+            view.replace(albumComponent)
+    }
+    property var artistId: null
+
+    Utils.SelectableDelegateModel {
+        id: delegateModel
+        model: MLArtistModel {
+            ml: medialib
+        }
+        delegate: Package {
+            id: element
+            Utils.ListItem {
+                Package.name: "list"
+                height: VLCStyle.icon_normal
+                width: parent.width
+
+                color: VLCStyle.colors.getBgColor(element.DelegateModel.inSelected, this.hovered, this.activeFocus)
+
+                cover: Image {
+                    id: cover_obj
+                    fillMode: Image.PreserveAspectFit
+                    source: model.cover || VLCStyle.noArtCover
+                }
+                line1: model.name || qsTr("Unknown artist")
+
+                onItemClicked: {
+                    currentArtistIndex = index
+                    artistId = model.id
+                    delegateModel.updateSelection( modifier , artistList.currentIndex, index)
+                    artistList.currentIndex = index
+                    artistList.forceActiveFocus()
+                }
+
+                onItemDoubleClicked: {
+                    delegateModel.actionAtIndex(index)
+                }
+
+                onPlayClicked: {
+                    console.log('Clicked on play : '+model.name);
+                    medialib.addAndPlay( model.id )
+                }
+                onAddToPlaylistClicked: {
+                    console.log('Clicked on addToPlaylist : '+model.name);
+                    medialib.addToPlaylist( model.id );
+                }
+            }
+
+            Utils.GridItem {
+                Package.name: "grid"
+                id: gridItem
+
+                image: VLCStyle.noArtCover
+                title: model.name || "Unknown Artist"
+                selected: element.DelegateModel.inSelected
+
+                //shiftX: view.currentItem.shiftX(index)
+
+                onItemClicked: {
+                    delegateModel.updateSelection( modifier , artistList.currentIndex, index)
+                    artistList.currentIndex = index
+                    artistList.forceActiveFocus()
+                }
+
+                onItemDoubleClicked: {
+                    delegateModel.actionAtIndex(index)
+                }
+
+                onPlayClicked: {
+                    medialib.addAndPlay( model.id )
+                }
+                onAddToPlaylistClicked: {
+                    console.log('Clicked on addToPlaylist : '+model.name);
+                    medialib.addToPlaylist( model.id );
+                }
+
+                //replace image with a mutlicovers preview
+                Utils.MultiCoverPreview {
+                    id: multicover
+                    visible: false
+                    width: VLCStyle.cover_normal
+                    height: VLCStyle.cover_normal
+
+                    albums: MLAlbumModel {
+                        ml: medialib
+                        parentId: model.id
+                        maxItems: 4
+                    }
+                }
+                Component.onCompleted: {
+                    multicover.grabToImage(function(result) {
+                        gridItem.image = result.url
+                        //multicover.destroy()
+                    })
+                }
+            }
+        }
+
+        function actionAtIndex(index) {
+            console.log("actionAtIndex", index)
+            if (delegateModel.selectedGroup.count > 1) {
+                var list = []
+                for (var i = 0; i < delegateModel.selectedGroup.count; i++)
+                    list.push(delegateModel.selectedGroup.get(i).model.id)
+                medialib.addAndPlay( list )
+            } else if (delegateModel.selectedGroup.count === 1) {
+                root.artistId =  delegateModel.selectedGroup.get(0).model.id
+                root.currentArtistIndex = index
+                artistList.currentIndex = index
+            }
+        }
+    }
+
+    /*
+     *define the intial position/selection
+     * This is done on activeFocus rather than Component.onCompleted because delegateModel.
+     * selectedGroup update itself after this event
+     */
+    onActiveFocusChanged: {
+        if (activeFocus && delegateModel.items.count > 0 && delegateModel.selectedGroup.count === 0) {
+            var initialIndex = 0
+            if (view.currentItem.currentIndex !== -1)
+                initialIndex = view.currentItem.currentIndex
+            delegateModel.items.get(initialIndex).inSelected = true
+            view.currentItem.currentIndex = initialIndex
+        }
+    }
+
+    Component {
+        id: artistGridComponent
+        Utils.KeyNavigableGridView {
+            cellWidth: (VLCStyle.cover_normal) + VLCStyle.margin_small
+            cellHeight: (VLCStyle.cover_normal + VLCStyle.fontHeight_normal)  + VLCStyle.margin_small
+
+            model: delegateModel.parts.grid
+            modelCount: delegateModel.items.count
+
+            onSelectAll: delegateModel.selectAll()
+            onActionAtIndex: delegateModel.actionAtIndex(index)
+            onSelectionUpdated: delegateModel.updateSelection( keyModifiers, oldIndex, newIndex )
+
+            onActionLeft: artistList.focus = true
+            onActionRight: root.actionRight(index)
+            onActionUp: root.actionUp(index)
+            onActionDown: root.actionDown(index)
+            onActionCancel: root.actionCancel(index)
+        }
+    }
+
+    Component {
+        id: albumComponent
+        // Display selected artist albums
+
+        FocusScope {
+            property alias currentIndex: albumSubView.currentIndex
+            ColumnLayout {
+                anchors.fill: parent
+                ArtistTopBanner {
+                    id: artistBanner
+                    Layout.fillWidth: true
+                    focus: false
+                    //contentY: albumsView.contentY
+                    contentY: 0
+                    artist: delegateModel.items.get(currentArtistIndex).model
+                }
+                MusicAlbumsDisplay {
+                    id: albumSubView
+                    Layout.fillWidth: true
+                    Layout.fillHeight: true
+                    focus: true
+                    parentId: artistId
+                    onActionLeft: artistList.focus = true
+
+                    onActionRight: root.actionRight(index)
+                    onActionUp: root.actionUp(index)
+                    onActionDown: root.actionDown(index)
+                    onActionCancel: root.actionCancel(index)
+                }
+            }
+        }
+    }
+
+    Row {
+        anchors.fill: parent
+        Utils.KeyNavigableListView {
+            width: parent.width * 0.25
+            height: parent.height
+
+            id: artistList
+            spacing: 2
+            model: delegateModel.parts.list
+            modelCount: delegateModel.items.count
+
+            onSelectAll: delegateModel.selectAll()
+            onSelectionUpdated: delegateModel.updateSelection( keyModifiers, oldIndex, newIndex )
+            onActionAtIndex: delegateModel.actionAtIndex(index)
+
+            onActionRight:  view.focus = true
+            onActionLeft: root.actionLeft(index)
+            onActionUp: root.actionUp(index)
+            onActionDown: root.actionDown(index)
+            onActionCancel: root.actionCancel(index)
+        }
+
+        Utils.StackViewExt {
+            id: view
+            width: parent.width * 0.75
+            height: parent.height
+            focus: true
+
+            initialItem: artistGridComponent
+        }
+    }
+}
diff --git a/modules/gui/qt/qml/mediacenter/MusicGenresDisplay.qml b/modules/gui/qt/qml/mediacenter/MusicGenresDisplay.qml
new file mode 100644
index 0000000000..9ae1957b0a
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MusicGenresDisplay.qml
@@ -0,0 +1,224 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQml.Models 2.2
+import org.videolan.vlc 0.1
+import org.videolan.medialib 0.1
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Utils.NavigableFocusScope {
+    id: root
+    property alias model: delegateModel.model
+    property var sortModel: ListModel {
+        ListElement { text: qsTr("Alphabetic"); criteria: "title" }
+    }
+
+    function goToView( parent ) {
+        history.push([ "mc", "music", "albums", { parentId: parent } ], History.Go)
+    }
+
+    Utils.SelectableDelegateModel {
+        id: delegateModel
+        model: MLGenreModel {
+            ml: medialib
+        }
+
+        delegate: Package {
+            id: element
+            Utils.GridItem {
+                Package.name: "grid"
+                id: gridItem
+                image: VLCStyle.noArtCover
+                title: model.name || "Unknown genre"
+                selected: element.DelegateModel.inSelected
+
+                shiftX: view.currentItem.shiftX(model.index)
+
+                onItemClicked: {
+                    delegateModel.updateSelection( modifier , view.currentItem.currentIndex, index)
+                    view.currentItem.currentIndex = index
+                    view.currentItem.forceActiveFocus()
+                }
+                onPlayClicked: {
+                    medialib.addAndPlay( model.id )
+                }
+                onItemDoubleClicked: {
+                    history.push(["mc", "music", "albums", { parentId: model.id } ], History.Go)
+                }
+                onAddToPlaylistClicked: {
+                    medialib.addToPlaylist( model.id );
+                }
+
+                //replace image with a mutlicovers preview
+                Utils.MultiCoverPreview {
+                    id: multicover
+                    visible: false
+                    width: VLCStyle.cover_normal
+                    height: VLCStyle.cover_normal
+
+                    albums: MLAlbumModel {
+                        ml: medialib
+                        parentId: model.id
+                    }
+                }
+
+                Component.onCompleted: {
+                    multicover.grabToImage(function(result) {
+                        gridItem.image = result.url
+                        //multicover.destroy()
+                    })
+                }
+            }
+
+            Utils.ListItem {
+                Package.name: "list"
+                width: root.width
+                height: VLCStyle.icon_normal
+
+                color: VLCStyle.colors.getBgColor(element.DelegateModel.inSelected, this.hovered, this.activeFocus)
+
+                cover:  Utils.MultiCoverPreview {
+                    albums: MLAlbumModel {
+                        ml: medialib
+                        parentId: model.id
+                    }
+                }
+
+                line1: (model.name || "Unknown genre")+" - "+model.nb_tracks+" tracks"
+
+                onItemClicked: {
+                    console.log("Clicked on : "+model.name);
+                    delegateModel.updateSelection( modifier, view.currentItem.currentIndex, index )
+                    view.currentItem.currentIndex = index
+                    this.forceActiveFocus()
+                }
+                onPlayClicked: {
+                    console.log('Clicked on play : '+model.name);
+                    medialib.addAndPlay( model.id )
+                }
+                onItemDoubleClicked: {
+                    history.push([ "mc", "music", "albums", { parentId: model.id } ], History.Go)
+                }
+                onAddToPlaylistClicked: {
+                    console.log('Clicked on addToPlaylist : '+model.name);
+                    medialib.addToPlaylist( model.id );
+                }
+            }
+        }
+
+        function actionAtIndex(index) {
+            if (delegateModel.selectedGroup.count > 1) {
+                var list = []
+                for (var i = 0; i < delegateModel.selectedGroup.count; i++)
+                    list.push(delegateModel.selectedGroup.get(i).model.id)
+                medialib.addAndPlay( list )
+            } else if (delegateModel.selectedGroup.count === 1) {
+                goToView(delegateModel.selectedGroup.get(0).model.id)
+            }
+        }
+    }
+
+    /*
+     *define the intial position/selection
+     * This is done on activeFocus rather than Component.onCompleted because delegateModel.
+     * selectedGroup update itself after this event
+     */
+    onActiveFocusChanged: {
+        if (activeFocus && delegateModel.items.count > 0 && delegateModel.selectedGroup.count === 0) {
+            var initialIndex = 0
+            if (view.currentItem.currentIndex !== -1)
+                initialIndex = view.currentItem.currentIndex
+            delegateModel.items.get(initialIndex).inSelected = true
+            view.currentItem.currentIndex = initialIndex
+        }
+    }
+
+    /* Grid View */
+    Component {
+        id: gridComponent
+        Utils.KeyNavigableGridView {
+            id: gridView_id
+
+            model: delegateModel.parts.grid
+            modelCount: delegateModel.items.count
+
+            focus: true
+
+            cellWidth: (VLCStyle.cover_normal) + VLCStyle.margin_small
+            cellHeight: (VLCStyle.cover_normal + VLCStyle.fontHeight_normal) + VLCStyle.margin_small
+
+            onSelectAll: delegateModel.selectAll()
+            onSelectionUpdated:  delegateModel.updateSelection( keyModifiers, oldIndex, newIndex )
+            onActionAtIndex: delegateModel.actionAtIndex(index)
+
+            onActionLeft: root.actionLeft(index)
+            onActionRight: root.actionRight(index)
+            onActionUp: root.actionUp(index)
+            onActionDown: root.actionDown(index)
+            onActionCancel: root.actionCancel(index)
+        }
+    }
+
+
+    Component {
+        id: listComponent
+        /* List View */
+        Utils.KeyNavigableListView {
+            id: listView_id
+
+            model: delegateModel.parts.list
+            modelCount: delegateModel.items.count
+
+            focus: true
+            spacing: VLCStyle.margin_xxxsmall
+
+            onSelectAll: delegateModel.selectAll()
+            onSelectionUpdated: delegateModel.updateSelection( keyModifiers, oldIndex, newIndex )
+            onActionAtIndex: delegateModel.actionAtIndex(index)
+
+            onActionLeft: root.actionLeft(index)
+            onActionRight: root.actionRight(index)
+            onActionUp: root.actionUp(index)
+            onActionDown: root.actionDown(index)
+            onActionCancel: root.actionCancel(index)
+        }
+    }
+
+
+    Utils.StackViewExt {
+        id: view
+
+        anchors.fill: parent
+        focus: true
+
+        initialItem: medialib.gridView ? gridComponent : listComponent
+
+        Connections {
+            target: medialib
+            onGridViewChanged: {
+                if (medialib.gridView)
+                    view.replace(gridComponent)
+                else
+                    view.replace(listComponent)
+            }
+        }
+    }
+}
diff --git a/modules/gui/qt/qml/mediacenter/MusicTrackListDisplay.qml b/modules/gui/qt/qml/mediacenter/MusicTrackListDisplay.qml
new file mode 100644
index 0000000000..435af625ac
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MusicTrackListDisplay.qml
@@ -0,0 +1,54 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQml.Models 2.2
+import QtQuick.Layouts 1.3
+
+import org.videolan.medialib 0.1
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Utils.KeyNavigableTableView {
+    id: root
+
+    sortModel: ListModel {
+        ListElement{ criteria: "track_number";width:0.15; text: qsTr("TRACK NB"); showSection: "" }
+        ListElement{ criteria: "disc_number"; width:0.15; text: qsTr("DISC NB");  showSection: "" }
+        ListElement{ criteria: "title";       width:0.15; text: qsTr("TITLE");    showSection: "title" }
+        ListElement{ criteria: "main_artist"; width:0.15; text: qsTr("ARTIST");   showSection: "main_artist" }
+        ListElement{ criteria: "album_title"; width:0.15; text: qsTr("ALBUM");    showSection: "album_title" }
+        ListElement{ criteria: "duration";    width:0.15; text: qsTr("DURATION"); showSection: "" }
+    }
+
+    model: MLAlbumTrackModel {
+        id: rootmodel
+        ml: medialib
+    }
+
+    property alias parentId: rootmodel.parentId
+
+    onActionForSelection: {
+        var list = []
+        for (var i = 0; i < selection.count; i++ ) {
+            list.push(selection.get(i).model.id)
+        }
+        medialib.addAndPlay(list)
+    }
+}
diff --git a/modules/gui/qt/qml/mediacenter/MusicTracksDisplay.qml b/modules/gui/qt/qml/mediacenter/MusicTracksDisplay.qml
new file mode 100644
index 0000000000..bee67e240a
--- /dev/null
+++ b/modules/gui/qt/qml/mediacenter/MusicTracksDisplay.qml
@@ -0,0 +1,39 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import org.videolan.medialib 0.1
+
+import "qrc:///utils/" as Utils
+import "qrc:///style/"
+
+Loader {
+    id: viewLoader
+    property var model: null
+
+    sourceComponent: listViewComponent_id
+
+    /* List View */
+    Component {
+        id: listViewComponent_id
+        MusicTrackListDisplay {
+            id: tracklistdisplay_id
+            model: viewLoader.model
+        }
+    }
+}
diff --git a/modules/gui/qt/vlc.qrc b/modules/gui/qt/vlc.qrc
index 0255e1ee79..889409b498 100644
--- a/modules/gui/qt/vlc.qrc
+++ b/modules/gui/qt/vlc.qrc
@@ -179,6 +179,18 @@
         <file alias="MenuItemExt.qml">qml/utils/MenuItemExt.qml</file>
         <file alias="ExpandGridView.qml">qml/utils/ExpandGridView.qml</file>
     </qresource>
+    <qresource prefix="/mediacenter">
+        <file alias="MCMusicDisplay.qml">qml/mediacenter/MCMusicDisplay.qml</file>
+        <file alias="MusicAlbumsDisplay.qml">qml/mediacenter/MusicAlbumsDisplay.qml</file>
+        <file alias="MusicAlbumsGridExpandDelegate.qml">qml/mediacenter/MusicAlbumsGridExpandDelegate.qml</file>
+        <file alias="MusicArtistsDisplay.qml">qml/mediacenter/MusicArtistsDisplay.qml</file>
+        <file alias="MusicGenresDisplay.qml">qml/mediacenter/MusicGenresDisplay.qml</file>
+        <file alias="MusicTracksDisplay.qml">qml/mediacenter/MusicTracksDisplay.qml</file>
+        <file alias="ArtistGridView.qml">qml/mediacenter/ArtistGridView.qml</file>
+        <file alias="ArtistListView.qml">qml/mediacenter/ArtistListView.qml</file>
+        <file alias="MusicTrackListDisplay.qml">qml/mediacenter/MusicTrackListDisplay.qml</file>
+        <file alias="ArtistTopBanner.qml">qml/mediacenter/ArtistTopBanner.qml</file>
+    </qresource>
     <qresource prefix="/style">
         <file alias="qmldir">qml/style/qmldir</file>
         <file alias="VLCStyle.qml">qml/style/VLCStyle.qml</file>
-- 
2.19.1



More information about the vlc-devel mailing list