[vlc-commits] [Git][videolan/vlc][master] 9 commits: qt: change History from a dictionary structure to a flat list + property dictionary

Steve Lhomme (@robUx4) gitlab at videolan.org
Mon Oct 16 12:52:39 UTC 2023



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
a02a0727 by Pierre Lamot at 2023-10-16T10:44:43+00:00
qt: change History from a dictionary structure to a flat list + property dictionary

exposing History properties as a single dictionary simplifies how view can
retrieve or alter its properties, by directly writing to the History.viewProp object

Recursive nature of the history path was underused, and we can achieve the same
behavior by having unique names for intermediates view properties

- - - - -
e13f4cda by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: move loadview function from StackViewExt to PageLoader

the component that we use to load view is PageLoader

- - - - -
56bac5ff by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: don't force the focus in subviews when the focus isn't a strong focus reason

- - - - -
e36f887b by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: update PageLoader loadView implementation

* page model is now a property of PageLoader
* page are loaded recursively and imperatively
* function parameters reflects updated History implementation
* page name is exposed in place of the former view property

- - - - -
ed08a23b by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: prefer altering History.viewProp rather than updating the history path

- - - - -
4222eedf by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: update navigation in Service and Browse sections

- - - - -
7fbe9dfe by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: use pagePrefix to build sub-page path

- - - - -
3ccda38f by Pierre Lamot at 2023-10-16T10:44:43+00:00
qml: bind focus color state to visualFocus

- - - - -
317de4df by Pierre Lamot at 2023-10-16T10:44:43+00:00
qt: remove temporary property viewPropLegacy in History

this property was here for compatibility before updating the views

- - - - -


23 changed files:

- modules/gui/qt/maininterface/qml/MainDisplay.qml
- modules/gui/qt/maininterface/qml/MainInterface.qml
- modules/gui/qt/medialibrary/qml/MusicAlbumsDisplay.qml
- modules/gui/qt/medialibrary/qml/MusicArtistsDisplay.qml
- modules/gui/qt/medialibrary/qml/MusicDisplay.qml
- modules/gui/qt/medialibrary/qml/MusicGenresDisplay.qml
- modules/gui/qt/medialibrary/qml/MusicPlaylistsDisplay.qml
- modules/gui/qt/medialibrary/qml/VideoAllDisplay.qml
- modules/gui/qt/medialibrary/qml/VideoDisplay.qml
- modules/gui/qt/medialibrary/qml/VideoPlaylistsDisplay.qml
- modules/gui/qt/network/qml/BrowseDisplay.qml
- modules/gui/qt/network/qml/BrowseHomeDisplay.qml
- modules/gui/qt/network/qml/BrowseTreeDisplay.qml
- modules/gui/qt/network/qml/DiscoverDisplay.qml
- modules/gui/qt/network/qml/ServicesHomeDisplay.qml
- modules/gui/qt/network/qml/ServicesSources.qml
- modules/gui/qt/util/navigation_history.cpp
- modules/gui/qt/util/navigation_history.hpp
- modules/gui/qt/widgets/qml/BannerTabButton.qml
- modules/gui/qt/widgets/qml/GridItem.qml
- modules/gui/qt/widgets/qml/LocalTabBar.qml
- modules/gui/qt/widgets/qml/PageLoader.qml
- modules/gui/qt/widgets/qml/StackViewExt.qml


Changes:

=====================================
modules/gui/qt/maininterface/qml/MainDisplay.qml
=====================================
@@ -36,12 +36,6 @@ import "qrc:///dialogs/" as DG
 FocusScope {
     id: g_mainDisplay
 
-    //name and properties of the tab to be initially loaded
-    property var view: ({
-        "name": "",
-        "properties": {}
-    })
-
     // Properties
 
     property bool hasMiniPlayer: miniPlayer.visible
@@ -49,6 +43,9 @@ FocusScope {
     // NOTE: The main view must be above the indexing bar and the mini player.
     property real displayMargin: (height - miniPlayer.y) + (loaderProgress.active ? loaderProgress.height : 0)
 
+    //MainDisplay behave as a PageLoader
+    property alias pagePrefix: stackView.pagePrefix
+
     readonly property int positionSliderY: {
         var size = miniPlayer.y + miniPlayer.sliderY
 
@@ -61,22 +58,16 @@ FocusScope {
     property bool _showMiniPlayer: false
     property var _oldViewProperties: ({}) // saves last state of the views
 
-    onViewChanged: {
-        _oldViewProperties[view.name] = view.properties
-        loadView()
-    }
-
-    Component.onCompleted: {
-        loadView()
-    }
+    // functions
 
-    function loadView() {
-        const found = stackView.loadView(g_mainDisplay.pageModel, g_mainDisplay.view.name, g_mainDisplay.view.properties)
+    //MainDisplay behave as a PageLoader
+    function loadView(path, properties, focusReason) {
+        const found = stackView.loadView(path, properties, focusReason)
+        if (!found)
+            return
 
         const item = stackView.currentItem
 
-        item.Navigation.parentItem = stackView
-
         sourcesBanner.localMenuDelegate = Qt.binding(function () {
             return !!item.localMenuDelegate ? item.localMenuDelegate : null
         })
@@ -95,13 +86,6 @@ FocusScope {
         MainCtx.sort.model = Qt.binding(function () { return item.sortModel })
         MainCtx.sort.available = Qt.binding(function () { return Array.isArray(item.sortModel) && item.sortModel.length > 0 })
 
-        // Restore sourcesBanner state
-        sourcesBanner.selectedIndex = pageModel.filter(function (e) {
-            return e.listed
-        }).findIndex(function (e) {
-            return e.name === g_mainDisplay.view.name
-        })
-
         if (Player.hasVideoOutput && MainCtx.hasEmbededVideo)
             _showMiniPlayer = true
     }
@@ -242,7 +226,7 @@ FocusScope {
                         if (_oldViewProperties[name] === undefined)
                             History.push(["mc", name])
                         else
-                            History.push(["mc", name, _oldViewProperties[name]])
+                            History.push(["mc", name], _oldViewProperties[name])
                     }
 
                     Navigation.parentItem: medialibId
@@ -254,7 +238,7 @@ FocusScope {
                     Layout.fillHeight: true
                     z: 0
 
-                    Widgets.StackViewExt {
+                    Widgets.PageLoader {
                         id: stackView
 
                         focus: true
@@ -271,6 +255,8 @@ FocusScope {
                                    : parent.right
                         }
 
+                        pageModel: g_mainDisplay.pageModel
+
                         leftPadding: VLCStyle.applicationHorizontalMargin
 
                         rightPadding: (MainCtx.playlistDocked && MainCtx.playlistVisible)


=====================================
modules/gui/qt/maininterface/qml/MainInterface.qml
=====================================
@@ -31,6 +31,7 @@ import "qrc:///util/" as Util
 import "qrc:///playlist/" as PL
 
 Item {
+    id: root
 
     property bool _interfaceReady: false
     property bool _playlistReady: false
@@ -49,15 +50,16 @@ Item {
         //set the initial view
         const loadPlayer = !MainPlaylistController.empty;
 
-        if (MainCtx.mediaLibraryAvailable)
-            History.push(["mc", "video"],
-                         Qt.OtherFocusReason, loadPlayer ? History.Stay : History.Go)
-        else
-            History.push(["mc", "home"],
-                         Qt.OtherFocusReason, loadPlayer ? History.Stay : History.Go)
-
         if (loadPlayer)
+        {
+            if (MainCtx.mediaLibraryAvailable)
+                History.update(["mc", "video"])
+            else
+                History.update(["mc", "home"])
             History.push(["player"])
+        }
+        else
+            _pushHome()
     }
 
     function _pushHome() {
@@ -67,20 +69,13 @@ Item {
             History.push(["mc", "home"])
     }
 
-    function loadCurrentHistoryView() {
-        const current = History.current
-        if ( !current || !current.name  || !current.properties ) {
-            console.warn("unable to load requested view, undefined")
-            return
-        }
+    function loadCurrentHistoryView(focusReason) {
         contextSaver.save(_oldHistoryPath)
 
-        stackView.loadView(_pageModel, current.name, current.properties)
+        stackView.loadView(History.viewPath, History.viewProp, focusReason)
 
         contextSaver.restore(History.viewPath)
         _oldHistoryPath = History.viewPath
-
-        MainCtx.mediaLibraryVisible = !History.match(History.viewPath, ["player"])
     }
 
     Util.ModelSortSettingHandler {
@@ -162,7 +157,10 @@ Item {
 
         Connections {
             target: History
-            onCurrentChanged: loadCurrentHistoryView()
+            onNavigate: (focusReason) => {
+                loadCurrentHistoryView(focusReason)
+                MainCtx.mediaLibraryVisible = !History.match(History.viewPath, ["player"])
+            }
         }
 
         Connections {
@@ -239,12 +237,14 @@ Item {
                 }
             }
 
-            Widgets.StackViewExt {
+            Widgets.PageLoader {
                 id: stackView
                 anchors.fill: parent
                 focus: true
                 clip: _extendedFrameVisible
 
+                pageModel: _pageModel
+
                 Connections {
                     target: Player
                     onPlayingStateChanged: {


=====================================
modules/gui/qt/medialibrary/qml/MusicAlbumsDisplay.qml
=====================================
@@ -26,11 +26,9 @@ import "qrc:///widgets/" as Widgets
 MusicAlbums {
     id: root
 
-    onCurrentIndexChanged: {
-        History.update(["mc","music", "albums", {"initialIndex": currentIndex}])
-    }
-
     searchPattern: MainCtx.search.pattern
     sortCriteria: MainCtx.sort.criteria
     sortOrder: MainCtx.sort.order
+
+    onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
 }


=====================================
modules/gui/qt/medialibrary/qml/MusicArtistsDisplay.qml
=====================================
@@ -34,40 +34,28 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "all",
+        default: true,
         component: allArtistsComponent
     }, {
         name: "albums",
         component: artistAlbumsComponent
     }]
 
-    loadDefaultView: function () {
-        History.update(["mc", "music", "artists", "all"])
-        loadPage("all")
-    }
-
-    function _updateArtistsAllHistory(currentIndex) {
-        History.update(["mc", "music", "artists", "all", { "initialIndex": currentIndex }])
-    }
-
-    function _updateArtistsAlbumsHistory(currentIndex, initialAlbumIndex) {
-        History.update(["mc","music", "artists", "albums", {
-            "initialIndex": currentIndex,
-            "initialAlbumIndex": initialAlbumIndex,
-        }])
-    }
 
     Component {
         id: allArtistsComponent
 
         MusicAllArtists {
-            onCurrentIndexChanged: _updateArtistsAllHistory(currentIndex)
 
             searchPattern: MainCtx.search.pattern
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onRequestArtistAlbumView: History.push(["mc", "music", "artists", "albums",
-                                                    { initialIndex: currentIndex }], reason)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
+
+            onRequestArtistAlbumView: (reason) => {
+                History.push([...root.pagePrefix, "albums"], { initialIndex: currentIndex }, reason)
+            }
         }
     }
 
@@ -75,15 +63,12 @@ Widgets.PageLoader {
         id: artistAlbumsComponent
 
         MusicArtistsAlbums {
-
-            Navigation.parentItem: root
-
             searchPattern: MainCtx.search.pattern
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onCurrentIndexChanged: _updateArtistsAlbumsHistory(currentIndex, currentAlbumIndex)
-            onCurrentAlbumIndexChanged: _updateArtistsAlbumsHistory(currentIndex, currentAlbumIndex)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
+            onCurrentAlbumIndexChanged: History.viewProp.initialAlbumIndex = currentAlbumIndex
         }
     }
 }


=====================================
modules/gui/qt/medialibrary/qml/MusicDisplay.qml
=====================================
@@ -35,6 +35,7 @@ Widgets.PageLoader {
     pageModel: [{
             displayText: I18n.qtr("Artists"),
             name: "artists",
+            default: true,
             url: "qrc:///medialibrary/MusicArtistsDisplay.qml"
         }, {
             displayText: I18n.qtr("Albums"),
@@ -55,13 +56,8 @@ Widgets.PageLoader {
         }
     ]
 
-    loadDefaultView: function () {
-        History.update(["mc", "music", "artists"])
-        loadPage("artists")
-    }
-
     function loadIndex(index) {
-        History.push(["mc", "music", root.pageModel[index].name])
+        History.push([...root.pagePrefix, root.pageModel[index].name])
     }
 
     property ListModel tabModel: ListModel {
@@ -75,14 +71,11 @@ Widgets.PageLoader {
         }
     }
 
-    property Component localMenuDelegate: Widgets.LocalTabBar {
-        currentView: root.view
+    localMenuDelegate: Widgets.LocalTabBar {
+        currentView: root.pageName
         model: tabModel
 
         onClicked: {
-            if (root.pageModel[index].name === currentView.name)
-                return
-
             root.loadIndex(index)
         }
     }


=====================================
modules/gui/qt/medialibrary/qml/MusicGenresDisplay.qml
=====================================
@@ -30,42 +30,26 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "all",
+        default: true,
         component: genresComponent
     }, {
         name: "albums",
         component: albumGenreComponent
     }]
 
-    loadDefaultView: function () {
-        History.update(["mc", "music", "genres", "all"])
-        loadPage("all")
-    }
-
-
-    function _updateGenresAllHistory(currentIndex) {
-        History.update(["mc", "music", "genres", "all", { "initialIndex": currentIndex }])
-    }
-
-    function _updateGenresAlbumsHistory(currentIndex, parentId, genreName) {
-        History.update(["mc","music", "genres", "albums", {
-            "initialIndex": currentIndex,
-            "parentId": parentId,
-            "genreName": genreName,
-        }])
-    }
-
     Component {
         id: genresComponent
         /* List View */
         MusicGenres {
-            onCurrentIndexChanged: _updateGenresAllHistory(currentIndex)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
 
             searchPattern: MainCtx.search.pattern
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onShowAlbumView: History.push(["mc", "music", "genres", "albums",
-                                           { parentId: id, genreName: name }], reason)
+            onShowAlbumView: (id, name, reason) => {
+                History.push([...root.pagePrefix, "albums"], { parentId: id, genreName: name }, reason)
+            }
         }
     }
 
@@ -93,9 +77,7 @@ Widgets.PageLoader {
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onParentIdChanged: _updateGenresAlbumsHistory(currentIndex, parentId, genreName)
-            onGenreNameChanged: _updateGenresAlbumsHistory(currentIndex, parentId, genreName)
-            onCurrentIndexChanged: _updateGenresAlbumsHistory(currentIndex, parentId, genreName)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 }


=====================================
modules/gui/qt/medialibrary/qml/MusicPlaylistsDisplay.qml
=====================================
@@ -36,34 +36,13 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "all",
+        default: true,
         component: componentAll
     }, {
         name: "list",
         component: componentList
     }]
 
-    loadDefaultView: function () {
-        History.update(["mc", "music", "playlists", "all"])
-        loadPage("all")
-    }
-
-    //---------------------------------------------------------------------------------------------
-    // Functions
-    //---------------------------------------------------------------------------------------------
-    // Private
-
-    function _updateHistoryList(index) {
-        History.update(["mc", "music", "playlists", "all", { "initialIndex": index }]);
-    }
-
-    function _updateHistoryPlaylist(playlist) {
-        History.update(["mc", "music", "playlists", "list", {
-                            "initialIndex": playlist.currentIndex,
-                            "parentId"   : playlist.parentId,
-                            "name" : playlist.name
-                        }]);
-    }
-
     //---------------------------------------------------------------------------------------------
     // Childs
     //---------------------------------------------------------------------------------------------
@@ -74,16 +53,15 @@ Widgets.PageLoader {
         PlaylistMediaList {
             isMusic: true
 
-            onCurrentIndexChanged: _updateHistoryList(currentIndex)
-
-            onShowList: (model, reason) => {
-                History.push(["mc", "music", "playlists", "list",
-                    { parentId: model.id, name: model.name }], reason)
-            }
-
             searchPattern: MainCtx.search.pattern
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
+
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
+
+            onShowList: (model, reason) => {
+                History.push([...root.pagePrefix, "list"], { parentId: model.id, name: model.name }, reason)
+            }
         }
     }
 
@@ -99,9 +77,7 @@ Widgets.PageLoader {
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onCurrentIndexChanged: _updateHistoryPlaylist(playlist)
-            onParentIdChanged    : _updateHistoryPlaylist(playlist)
-            onNameChanged        : _updateHistoryPlaylist(playlist)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 }


=====================================
modules/gui/qt/medialibrary/qml/VideoAllDisplay.qml
=====================================
@@ -38,6 +38,7 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "base",
+        default: true,
         component: componentBase
     }, {
         name: "group",
@@ -47,29 +48,11 @@ Widgets.PageLoader {
         component: componentRecentVideos
     }]
 
-    loadDefaultView: function () {
-        History.update(["mc", "video", "all", "base"])
-        loadPage("base")
-    }
-
     // Events
 
     onCurrentItemChanged: {
         sortMenu  = currentItem.sortMenu
     }
-    // Functions private
-
-    function _updateHistoryAll(index) {
-        History.update(["mc", "video", "all", "base", { "initialIndex": index }])
-    }
-
-    function _updateHistoryGroup(group) {
-        History.update(["mc", "video", "all", "group", {
-                            "initialIndex": group.currentIndex,
-                            "parentId" : group.parentId,
-                            "title" : group.title
-                        }])
-    }
 
     // Children
 
@@ -80,12 +63,10 @@ Widgets.PageLoader {
             // Events
 
             onShowList: (model, reason) => {
-                History.push(["mc", "video", "all", "group",
-                    { parentId: model.id, title: model.title }], reason)
+                History.push([...root.pagePrefix, "group"], { parentId: model.id, title: model.title }, reason)
             }
 
-
-            onCurrentIndexChanged: root._updateHistoryAll(currentIndex)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 
@@ -95,9 +76,7 @@ Widgets.PageLoader {
         MediaGroupDisplay {
             id: group
 
-            onCurrentIndexChanged: root._updateHistoryGroup(group)
-            onParentIdChanged    : root._updateHistoryGroup(group)
-            onTitleChanged       : root._updateHistoryGroup(group)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
 
             function isInfoExpandPanelAvailable(/* modelIndexData */) {
                 return true


=====================================
modules/gui/qt/medialibrary/qml/VideoDisplay.qml
=====================================
@@ -46,17 +46,12 @@ Widgets.PageLoader {
         }
     }
 
-    property Component localMenuDelegate: Widgets.LocalTabBar {
-        currentView: root.view
+    localMenuDelegate: Widgets.LocalTabBar {
+        currentView: root.pageName
 
         model: tabModel
 
-        onClicked: {
-            if (root.pageModel[index].name === currentView.name)
-                return
-
-            root.loadIndex(index)
-        }
+        onClicked: (index) => History.push([...root.pagePrefix, root.pageModel[index].name])
     }
 
     //---------------------------------------------------------------------------------------------
@@ -65,6 +60,7 @@ Widgets.PageLoader {
 
     pageModel: [{
             name: "all",
+            default: true,
             displayText: I18n.qtr("All"),
             url: "qrc:///medialibrary/VideoAllDisplay.qml"
         },{
@@ -74,11 +70,6 @@ Widgets.PageLoader {
         }
     ]
 
-    loadDefaultView: function () {
-        History.update(["mc", "video", "all"])
-        loadPage("all")
-    }
-
     Accessible.role: Accessible.Client
     Accessible.name: I18n.qtr("Video view")
 
@@ -86,12 +77,4 @@ Widgets.PageLoader {
         // NOTE: We need bindings because the VideoAll model can change over time.
         sortMenu     = Qt.binding(function () { return currentItem.sortMenu; })
     }
-
-    //---------------------------------------------------------------------------------------------
-    // Functions
-    //---------------------------------------------------------------------------------------------
-
-    function loadIndex(index) {
-        History.push(["mc", "video", root.pageModel[index].name]);
-    }
 }


=====================================
modules/gui/qt/medialibrary/qml/VideoPlaylistsDisplay.qml
=====================================
@@ -36,34 +36,13 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "all",
+        default: true,
         component: componentAll
     }, {
         name: "list",
         component: componentList
     }]
 
-    loadDefaultView: function () {
-        History.update(["mc", "video", "playlists", "all"])
-        loadPage("all")
-    }
-
-    //---------------------------------------------------------------------------------------------
-    // Functions
-    //---------------------------------------------------------------------------------------------
-    // Private
-
-    function _updateHistoryList(index) {
-        History.update(["mc", "video", "playlists", "all", { "initialIndex": index }]);
-    }
-
-    function _updateHistoryPlaylist(playlist) {
-        History.update(["mc", "video", "playlists", "list", {
-                            "currentIndex": playlist.currentIndex,
-                            "parentId"   : playlist.parentId,
-                            "name" : playlist.name
-                        }]);
-    }
-
     //---------------------------------------------------------------------------------------------
     // Childs
     //---------------------------------------------------------------------------------------------
@@ -79,11 +58,10 @@ Widgets.PageLoader {
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onCurrentIndexChanged: _updateHistoryList(currentIndex)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
 
             onShowList: (model, reason) => {
-                History.push(["mc", "video", "playlists", "list",
-                             { parentId: model.id, name: model.name }], reason);
+                History.push([...root.pagePrefix, "list"], { parentId: model.id, name: model.name }, reason)
             }
         }
     }
@@ -100,9 +78,7 @@ Widgets.PageLoader {
             sortOrder: MainCtx.sort.order
             sortCriteria: MainCtx.sort.criteria
 
-            onCurrentIndexChanged: _updateHistoryPlaylist(playlist)
-            onParentIdChanged    : _updateHistoryPlaylist(playlist)
-            onNameChanged        : _updateHistoryPlaylist(playlist)
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 }


=====================================
modules/gui/qt/network/qml/BrowseDisplay.qml
=====================================
@@ -33,7 +33,8 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "home",
-        url: "qrc:///network/BrowseHomeDisplay.qml"
+        default: true,
+        component: browseHome
     }, {
         name: "folders",
         component: browseFolders,
@@ -46,37 +47,49 @@ Widgets.PageLoader {
         guard: function (prop) { return !!prop.tree }
     }]
 
-    loadDefaultView: function() {
-        History.update(["mc", "network", "home"])
-        loadPage("home")
-    }
-
-    localMenuDelegate: (view.name !== "home") ? componentBar : null
+    localMenuDelegate: (pageName !== "home") ? componentBar : null
 
     Accessible.role: Accessible.Client
     Accessible.name: I18n.qtr("Browse view")
 
-    // Connections
-    Connections {
-        target: (Helpers.isValidInstanceOf(currentItem, BrowseHomeDisplay)) ? currentItem
-                                                                              : null
-
-        onSeeAll: {
-            if (sd_source === -1)
-                History.push(["mc", "network", "folders", { title: title }], reason)
-            else
-                History.push(["mc", "network", "device", { title: title, sd_source: sd_source }],
-                             reason)
-        }
+    //functions
+
+    function _showBrowseNode(tree, reason) {
+        History.push([...root.pagePrefix, "browse"], { tree: tree }, reason)
     }
 
-    Connections {
-        target: root.currentItem
+    function _showHome(reason) {
+        History.push([...root.pagePrefix, "home"], reason)
+    }
+
+
+    function _showBrowseFolder(title, reason) {
+        History.push([...root.pagePrefix, "folders"], { title: title }, reason)
+    }
 
-        onBrowse: History.push(["mc", "network", "browse", { tree: tree }], reason)
+    function _showBrowseDevices(title, sd_source, reason) {
+        History.push([...root.pagePrefix, "device"], { title: title, sd_source: sd_source }, reason)
     }
 
     // Children
+    Component {
+        id: browseHome
+
+        BrowseHomeDisplay {
+            onSeeAllDevices: (title, sd_source, reason) => {
+                root._showBrowseDevices(title, sd_source, reason)
+            }
+
+            onSeeAllFolders:(title, reason) => {
+                root._showBrowseFolder(title, reason)
+            }
+
+            onBrowse: (tree, reason) => {
+                root._showBrowseNode(tree, reason)
+            }
+        }
+    }
+
 
     Component {
         id: browseFolders
@@ -94,6 +107,10 @@ Widgets.PageLoader {
                 sortOrder: MainCtx.sort.order
                 searchPattern: MainCtx.search.pattern
             }
+
+            onBrowse: (tree, reason) => { root._showBrowseNode(tree, reason) }
+
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 
@@ -103,7 +120,7 @@ Widgets.PageLoader {
         BrowseDeviceView {
             id: viewDevice
 
-            property var sd_source
+            /*required*/ property var sd_source
 
             property var sortModel: [
                 { text: I18n.qtr("Alphabetic"), criteria: "name" },
@@ -122,6 +139,12 @@ Widgets.PageLoader {
                 sortOrder: MainCtx.sort.order
                 searchPattern: MainCtx.search.pattern
             }
+
+            onBrowse: (tree, reason) => {
+                root._showBrowseNode(tree, reason)
+            }
+
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 
@@ -129,6 +152,9 @@ Widgets.PageLoader {
         id: browseComponent
 
         BrowseTreeDisplay {
+
+            property alias tree: mediaModel.tree
+
             model: NetworkMediaModel {
                 id: mediaModel
 
@@ -146,6 +172,12 @@ Widgets.PageLoader {
             Navigation.cancelAction: function() {
                 History.previous(Qt.BacktabFocusReason)
             }
+
+            onBrowse: (tree, reason) => {
+                root._showBrowseNode(tree, reason)
+            }
+
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
         }
     }
 
@@ -153,11 +185,11 @@ Widgets.PageLoader {
         id: componentBar
 
         NetworkAddressbar {
-            path: view.name === "browse" ? root.currentItem.model.path : []
+            path: root.pageName === "browse" ? root.currentItem.model.path : []
 
-            onHomeButtonClicked: History.push(["mc", "network", "home"], reason)
+            onHomeButtonClicked: root._showHome(reason)
 
-            onBrowse: History.push(["mc", "network", "browse", { "tree": tree }], reason)
+            onBrowse:  (tree, reason) => { root._showBrowseNode(tree, reason) }
         }
     }
 }


=====================================
modules/gui/qt/network/qml/BrowseHomeDisplay.qml
=====================================
@@ -59,7 +59,8 @@ FocusScope {
 
     // Signals
 
-    signal seeAll(var title, var sd_source, int reason)
+    signal seeAllDevices(var title, var sd_source, int reason)
+    signal seeAllFolders(var title, int reason)
 
     signal browse(var tree, int reason)
 
@@ -171,7 +172,7 @@ FocusScope {
 
                 onBrowse: root.browse(tree, reason)
 
-                onSeeAll: root.seeAll(title, -1, reason)
+                onSeeAll: root.seeAllFolders(title, reason)
 
                 onActiveFocusChanged: _centerFlickableOnItem(foldersSection)
                 onCurrentIndexChanged: _centerFlickableOnItem(foldersSection)
@@ -221,7 +222,7 @@ FocusScope {
 
                 onBrowse: root.browse(tree, reason)
 
-                onSeeAll: root.seeAll(title, model.sd_source, reason)
+                onSeeAll: root.seeAllDevices(title, model.sd_source, reason)
 
                 onActiveFocusChanged: _centerFlickableOnItem(deviceSection)
                 onCurrentIndexChanged: _centerFlickableOnItem(deviceSection)
@@ -265,7 +266,7 @@ FocusScope {
 
                 onBrowse: root.browse(tree, reason)
 
-                onSeeAll: root.seeAll(title, model.sd_source, reason)
+                onSeeAll: root.seeAllDevices(title, model.sd_source, reason)
 
                 onActiveFocusChanged: _centerFlickableOnItem(lanSection)
                 onCurrentIndexChanged: _centerFlickableOnItem(lanSection)


=====================================
modules/gui/qt/network/qml/BrowseTreeDisplay.qml
=====================================
@@ -32,7 +32,6 @@ MainInterface.MainViewLoader {
     // Properties
 
     property var contextMenu
-    property var tree
 
     readonly property var currentIndex: _currentView.currentIndex
 
@@ -66,8 +65,6 @@ MainInterface.MainViewLoader {
         History.previous(Qt.BacktabFocusReason)
     }
 
-    onTreeChanged: model.tree = tree
-
     function playSelected() {
         model.addAndPlay(selectionModel.selectedIndexes)
     }


=====================================
modules/gui/qt/network/qml/DiscoverDisplay.qml
=====================================
@@ -31,6 +31,7 @@ Widgets.PageLoader {
 
     pageModel: [{
             displayText: I18n.qtr("Services"),
+            default: true,
             name: "services",
             url: "qrc:///network/ServicesHomeDisplay.qml"
         }, {
@@ -40,18 +41,13 @@ Widgets.PageLoader {
         }
     ]
 
-    loadDefaultView: function () {
-        History.update(["mc", "discover", "services"])
-        loadPage("services")
-    }
-
     localMenuDelegate: menuDelegate
 
     Accessible.role: Accessible.Client
     Accessible.name: I18n.qtr("Discover view")
 
     function loadIndex(index) {
-        History.push(["mc", "discover", root.pageModel[index].name])
+        History.push([...root.pagePrefix, root.pageModel[index].name])
     }
 
 
@@ -70,11 +66,11 @@ Widgets.PageLoader {
         id: menuDelegate
 
         Widgets.LocalTabBar {
-            currentView: root.view
+            currentView: root.pageName
             model: tabModel
 
             onClicked: {
-                if (root.pageModel[index].name === currentView.name)
+                if (root.pageModel[index].name === root.pageName)
                     return
 
                 root.loadIndex(index)


=====================================
modules/gui/qt/network/qml/ServicesHomeDisplay.qml
=====================================
@@ -32,7 +32,8 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "all",
-        url: "qrc:///network/ServicesSources.qml"
+        default: true,
+        component: serviceSourceComponent
     }, {
         name: "services_manage",
         url: "qrc:///network/ServicesManage.qml"
@@ -45,15 +46,39 @@ Widgets.PageLoader {
         guard: function (prop) { return !!prop.tree }
     }]
 
-    loadDefaultView: function() {
-        History.update(["mc", "discover", "services", "all"])
-        loadPage("all")
+
+    function _showServiceHome(reason) {
+        History.push([...root.pagePrefix, "services"], reason)
+    }
+
+    function _showServiceManage(reason) {
+        History.push([...root.pagePrefix, "services_manage"], reason)
+    }
+
+    function _showServiceRoot(source_name, reason) {
+        History.push([...root.pagePrefix, "source_root"], { source_name: source_name }, reason)
+    }
+
+    function _showServiceNode(tree, source_name, reason) {
+        History.push(
+            [...root.pagePrefix, "source_browse"],
+            {
+                tree: tree,
+                source_name: source_name
+            },
+            reason)
     }
 
-    function setCurrentItemFocus(reason) {
-        stackView.currentItem.setCurrentItemFocus(reason);
+    Component {
+        id: serviceSourceComponent
+
+        ServicesSources {
+            onBrowseServiceManage:  (reason) => root._showServiceManage(reason)
+            onBrowseSourceRoot: (name, reason) => root. _showServiceRoot(name, reason)
+        }
     }
 
+
     Component {
         id: sourceRootComponent
 
@@ -63,15 +88,17 @@ Widgets.PageLoader {
             property Component localMenuDelegate: NetworkAddressbar {
                 path: [{display: deviceModel.name, tree: {}}]
 
-                onHomeButtonClicked: History.push(["mc", "discover", "services"], reason)
+                onHomeButtonClicked: _showServiceHome(reason)
             }
 
             model: deviceModel
             contextMenu: contextMenu
 
-            onBrowse: History.push(["mc", "discover", "services", "source_browse",
-                                    { tree: tree, "root_name": deviceModel.name,
-                                      "source_name": source_name }], reason)
+            onBrowse: (tree, reason) => {
+                root._showServiceNode(tree, deviceModel.source_name, reason)
+            }
+
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
 
             NetworkDeviceModel {
                 id: deviceModel
@@ -92,7 +119,7 @@ Widgets.PageLoader {
         id: sourceBrowseComponent
 
         BrowseTreeDisplay {
-            property string root_name
+            property alias tree: mediaModel.tree
             property string source_name
 
             property Component localMenuDelegate: NetworkAddressbar {
@@ -102,20 +129,19 @@ Widgets.PageLoader {
                     return _path
                 }
 
-                onHomeButtonClicked: History.push(["mc", "discover", "services"], reason)
+                onHomeButtonClicked: root._showServiceHome(reason)
 
-                onBrowse: {
-                    if (!!tree.isRoot)
-                        History.push(["mc", "discover", "services", "source_root",
-                                      { source_name: tree.source_name }], reason)
+                onBrowse: (tree, reason) => {
+                    if (tree.isRoot)
+                        root._showServiceRoot(source_name, reason)
                     else
-                        History.push(["mc", "discover", "services", "source_browse",
-                                      { tree: tree, "root": root_name }], reason)
+                        root._showServiceNode(tree, source_name, reason)
                 }
             }
 
-            onBrowse: History.push(["mc", "discover", "services", "source_browse",
-                                    { tree: tree, "root": root_name }], reason)
+            onBrowse:  root._showServiceNode(tree, source_name, reason)
+
+            onCurrentIndexChanged: History.viewProp.initialIndex = currentIndex
 
             model: NetworkMediaModel {
                 id: mediaModel


=====================================
modules/gui/qt/network/qml/ServicesSources.qml
=====================================
@@ -29,9 +29,18 @@ import "qrc:///style/"
 MainInterface.MainGridView {
     id: root
 
+    //properties
+
     readonly property bool hasGridListMode: false
     readonly property bool isSearchable: true
 
+    //signals
+
+    signal browseServiceManage(int reason)
+    signal browseSourceRoot(string sourceName, int reason)
+
+    //settings
+
     selectionDelegateModel: selectionModel
     model: sourcesModel
     topMargin: VLCStyle.margin_large
@@ -82,11 +91,9 @@ MainInterface.MainGridView {
 
         onItemDoubleClicked: {
             if (is_dummy)
-                History.push(["mc", "discover", "services", "services_manage"],
-                             Qt.MouseFocusReason)
+                root.browseServiceManage(Qt.MouseFocusReason)
             else
-                History.push(["mc", "discover", "services", "source_root",
-                              { source_name: model.name }], Qt.MouseFocusReason)
+                root.browseSourceRoot(model.name, Qt.TabFocusReason)
         }
 
         onItemClicked : {
@@ -100,10 +107,9 @@ MainInterface.MainGridView {
         const itemData = sourcesModel.getDataAt(index);
 
         if (itemData.type === NetworkSourcesModel.TYPE_DUMMY)
-            History.push(["mc", "discover", "services", "services_manage"], Qt.TabFocusReason)
+            browseServiceManage(Qt.TabFocusReason)
         else
-            History.push(["mc", "discover", "services", "source_root",
-                          { source_name: itemData.name }], Qt.TabFocusReason)
+            browseSourceRoot(itemData.name, Qt.TabFocusReason)
     }
 
     Navigation.cancelAction: function() {


=====================================
modules/gui/qt/util/navigation_history.cpp
=====================================
@@ -3,96 +3,46 @@
 #include "network/networkmediamodel.hpp"
 #include "medialibrary/mlqmltypes.hpp"
 
-
 NavigationHistory::NavigationHistory(QObject *parent)
-    : QObject(parent),
-      m_reason(Qt::OtherFocusReason)
+    : QObject(parent)
 {
 }
 
-QVariant NavigationHistory::getCurrent()
-{
-    assert(m_history.isEmpty() == false);
-
-    return m_history.back();
-}
-
 bool NavigationHistory::isPreviousEmpty()
 {
-    return m_history.count() <= 1;
+    return m_history.size() <= 1;
 }
 
-void NavigationHistory::push(QVariantMap item, Qt::FocusReason reason, PostAction postAction)
+void NavigationHistory::push(QStringList path, const QVariantMap& properties, Qt::FocusReason focusReason)
 {
-    m_history.push_back(item);
-    emit previousEmptyChanged(false);
-    if (postAction == PostAction::Go)
-    {
-        updateViewPath();
+    auto prop = std::make_unique<QQmlPropertyMap>();
+    for (auto it =  properties.keyValueBegin(); it != properties.keyValueEnd(); ++it)
+        prop->insert((*it).first, (*it).second);
 
-        m_reason = reason;
+    m_history.emplace_back(path, std::move(prop));
 
-        emit currentChanged(m_history.back());
-    }
-}
+    updateCurrent();
 
-static void pushListRec(QVariantMap& itemMap, QVariantList::const_iterator it, QVariantList::const_iterator end )
-{
-    if (it == end)
-        return;
-    if(it->canConvert<QString>())
-    {
-        QVariantMap subViewMap;
-        subViewMap["name"] = it->toString();
-        QVariantMap subViewProperties;
-        pushListRec(subViewProperties, ++it, end);
-        subViewMap["properties"] = subViewProperties;
-        itemMap["view"] = subViewMap;
-    }
-    else if ( it->canConvert<QVariantMap>() )
-    {
-        QVariantMap varMap = it->toMap();
-        for (auto kv = varMap.constBegin(); kv != varMap.constEnd(); ++kv )
-            itemMap[kv.key()] = kv.value();
-        pushListRec(itemMap, ++it, end);
-    }
+    if (m_history.size() == 2)
+        emit previousEmptyChanged(false);
+
+    emit navigate(focusReason);
 }
 
-static void addLeafRec(QVariant &item, const QVariantMap &leaf)
+void NavigationHistory::push(QStringList path, Qt::FocusReason focusReason)
 {
-    auto itemMap = item.toMap();
-    if (itemMap.contains("view"))
-    {
-        QVariant viewProps = itemMap.value("view");
-        addLeafRec(viewProps, leaf);
-        itemMap["view"] = viewProps;
-    }
-    else if (itemMap.contains("properties"))
-    {
-        QVariant propsVar = itemMap.value("properties");
-        const auto propsMap = propsVar.toMap();
-        if (propsMap.empty())
-        {
-            itemMap["properties"] = leaf;
-        }
-        else
-        {
-            addLeafRec(propsVar, leaf);
-            itemMap["properties"] = propsVar;
-        }
-    }
-    else
-    {
-        // invalid node?
-        return;
-    }
+    m_history.emplace_back(path, std::make_unique<QQmlPropertyMap>());
 
-    //overwrite item QVariant
-    item = itemMap;
-}
+    updateCurrent();
+
+    if (m_history.size() == 2)
+        emit previousEmptyChanged(false);
 
+    emit navigate(focusReason);
+
+}
 
-static bool isNodeValid(QVariant& value)
+static bool isNodeValid(const QVariant& value)
 {
     if (value.canConvert(QVariant::StringList)
         || value.canConvert(QVariant::StringList)
@@ -107,7 +57,8 @@ static bool isNodeValid(QVariant& value)
     else if ( value.canConvert(QVariant::List) )
     {
         QVariantList valueList = value.toList();
-        for (QVariant& v : valueList) {
+        for (QVariant& v : valueList)
+        {
             if (!isNodeValid(v))
                 return false;
         }
@@ -117,18 +68,17 @@ static bool isNodeValid(QVariant& value)
     {
         NetworkTreeItem item = value.value<NetworkTreeItem>();
         if ( ! item.isValid() )
-        {
             return false;
-        }
+
         return true;
     }
     else if ( value.canConvert(QVariant::Map) )
     {
         QVariantMap valueList = value.toMap();
-        for (QVariant& v : valueList.values()) {
-            if (!isNodeValid(v)) {
+        for (QVariant& v : valueList.values())
+        {
+            if (!isNodeValid(v))
                 return false;
-            }
         }
         return true;
     }
@@ -137,91 +87,81 @@ static bool isNodeValid(QVariant& value)
     return false;
 }
 
-static QStringList getViewPath(QVariantMap map)
+static bool isNodeValid(const QQmlPropertyMap& value)
 {
-    QStringList r;
-    if (map.contains("view"))
-        return getViewPath(map.value("view").toMap());
-    else if (map.contains("name"))
+    for (auto it: value.keys())
     {
-        r = QStringList( map.value("name").toString() );
-        r.append(std::move(getViewPath(map.value("properties").toMap())));
+        if (!isNodeValid(value[it]))
+            return false;
     }
-    return r;
+    return true;
 }
 
-void NavigationHistory::push(QVariantList itemList, Qt::FocusReason reason,
-                             NavigationHistory::PostAction postAction)
-{
-    QVariantMap itemMap;
-    pushListRec(itemMap, itemList.cbegin(), itemList.cend());
-    if (!itemMap.contains("view"))
-        return;
-    QVariant rootView = itemMap["view"];
-    if (!rootView.canConvert(QVariant::Map))
-        return;
-    push(rootView.toMap(), reason, postAction);
-}
 
 
-void NavigationHistory::update(QVariantMap item)
+void NavigationHistory::update(QStringList path)
 {
-    assert(m_history.size() >= 1);
-    m_history.back() = item;
-    updateViewPath();
+    if (m_history.size() == 0)
+    {
+        m_history.emplace_back(path, std::make_unique<QQmlPropertyMap>());
+    }
+    else
+    {
+        auto& last = *m_history.rbegin();
+        last.first = path;
+    }
+    updateCurrent();
 }
 
-void NavigationHistory::update(QVariantList itemList)
+void NavigationHistory::update(QStringList path, const QVariantMap& properties)
 {
-    QVariantMap itemMap;
-    pushListRec(itemMap, itemList.cbegin(), itemList.cend());
-    if (!itemMap.contains("view"))
-        return;
-    QVariant rootView = itemMap["view"];
-    if (!rootView.canConvert(QVariant::Map))
-        return;
-    update(rootView.toMap());
+    if (m_history.size() == 0)
+    {
+        auto prop = std::make_unique<QQmlPropertyMap>();
+        for (auto it =  properties.keyValueBegin(); it != properties.keyValueEnd(); ++it)
+        {
+            prop->insert((*it).first, (*it).second);
+        }
+        m_history.emplace_back(path, std::move(prop));
+    }
+    else
+    {
+        auto& last = *m_history.rbegin();
+        last.first = path;
+    }
+    updateCurrent();
 }
 
-void NavigationHistory::addLeaf(QVariantMap itemMap)
-{
-    assert(m_history.size() >= 1);
-    addLeafRec(m_history.back(), itemMap);
-    updateViewPath();
-}
 
-void NavigationHistory::previous(Qt::FocusReason reason, PostAction postAction)
+void NavigationHistory::previous(Qt::FocusReason reason)
 {
-    if (m_history.count() == 1)
+    if (m_history.size() == 1)
         return;
 
     m_history.pop_back();
-    while (!isNodeValid(m_history.back())) {
+
+    while (!isNodeValid(*m_history.back().second)) {
         m_history.pop_back();
-        if (m_history.count() == 1)
+        if (m_history.size() == 1)
             break;
     }
 
-    if (m_history.count() == 1)
+    if (m_history.size() == 1)
         emit previousEmptyChanged(true);
 
-    if (postAction == PostAction::Go) {
-        updateViewPath();
-
-        m_reason = reason;
-
-        emit currentChanged( m_history.back() );
-    }
+    updateCurrent();
+    emit navigate(reason);
 }
 
-void NavigationHistory::updateViewPath()
+void NavigationHistory::updateCurrent()
 {
-    const auto viewPath = getViewPath(getCurrent().toMap());
-    if (viewPath == m_viewPath)
-        return;
+    assert(m_history.size() >= 1);
+    const auto it = m_history.rbegin();
 
-    m_viewPath = viewPath;
-    emit viewPathChanged( m_viewPath );
+    m_viewPath = it->first;
+    m_viewProperties = it->second.get();
+    emit viewPathChanged(m_viewPath);
+    emit viewPropChanged(m_viewProperties);
 }
 
 QStringList NavigationHistory::viewPath() const
@@ -229,13 +169,9 @@ QStringList NavigationHistory::viewPath() const
     return m_viewPath;
 }
 
-Qt::FocusReason NavigationHistory::takeFocusReason()
+QQmlPropertyMap* NavigationHistory::viewProp() const
 {
-    Qt::FocusReason reason = m_reason;
-
-    m_reason = Qt::OtherFocusReason;
-
-    return reason;
+    return m_viewProperties;
 }
 
 Q_INVOKABLE bool NavigationHistory::match(const QStringList& path,  const QStringList& pattern)


=====================================
modules/gui/qt/util/navigation_history.hpp
=====================================
@@ -1,6 +1,8 @@
 #ifndef NAVIGATION_HISTORY_HPP
 #define NAVIGATION_HISTORY_HPP
 
+#include <memory>
+
 #include <QObject>
 #include <QtQml/QQmlPropertyMap>
 
@@ -8,9 +10,17 @@ class NavigationHistory : public QObject
 {
     Q_OBJECT
 public:
-    Q_PROPERTY(QVariant current READ getCurrent NOTIFY currentChanged FINAL)
     Q_PROPERTY(bool previousEmpty READ isPreviousEmpty NOTIFY previousEmptyChanged FINAL)
+    /**
+     * current path
+     */
     Q_PROPERTY(QStringList viewPath READ viewPath NOTIFY viewPathChanged FINAL)
+    /**
+     * properties of the current view,
+     * * properties that are pushed will be accessible thru this property
+     * * views may store values in this property, they will be restored when navigating back
+     */
+    Q_PROPERTY(QQmlPropertyMap* viewProp READ viewProp NOTIFY viewPropChanged FINAL)
 
     enum class PostAction{
         Stay,
@@ -24,90 +34,57 @@ public:
     QVariant getCurrent();
     bool isPreviousEmpty();
     QStringList viewPath() const;
-
-    // NOTE: The first item to call this takes ownership over the focus reason.
-    Q_INVOKABLE Qt::FocusReason takeFocusReason();
+    QQmlPropertyMap* viewProp() const;
 
     Q_INVOKABLE bool match(const QStringList& path,  const QStringList& pattern);
     Q_INVOKABLE bool exactMatch(const QStringList& path,  const QStringList& pattern);
 
 signals:
-    void currentChanged(QVariant current);
+    void navigate(Qt::FocusReason);
     void previousEmptyChanged(bool empty);
     void viewPathChanged(const QStringList& viewPath);
+    void viewPropChanged(QQmlPropertyMap*);
 
 public slots:
     /**
-     * Push a
      *
-     * \code
-     * push({
-     *   name: "foo", //push the view foo
-     *   properties: {
-     *      view: { //the sub view "bar"
-     *          name: "bar",
-     *          properties: {
-     *              baz: "plop" //the property baz will be set in the view "bar"
-     *          }
-     *      }
-     *   }
-     * }, History.Go)
-     * \endcode
-     */
-    Q_INVOKABLE void push( QVariantMap, Qt::FocusReason = Qt::OtherFocusReason,
-                           PostAction = PostAction::Go );
-
-    /**
-     * provide a short version of the history push({k:v}), which implicitly create a dictonnary tree from the input list
-     *
-     * List items are interpreted as
-     *   * strings will push a dict with "view" key to the value of the string and
-     *     a "viewProperties" dict configured with the tail of the list
+     * navigate to a new page
      *
-     *   * dict: values will be added to the current viewProperty
+     * @param path: path of the page to load
+     * @param properties: values that will be set to the current viewProp
      *
      * example:
      * \code
      * //push the view foo, then bar, set baz to plop in the view "bar"
-     *  push(["foo", "bar", {baz: "plop"} ], History.Go)
+     *  push(["foo", "bar"], {baz: "plop"})
      * \endcode
      */
-    Q_INVOKABLE void push(QVariantList itemList, Qt::FocusReason = Qt::OtherFocusReason,
-                          PostAction = PostAction::Go );
+    Q_INVOKABLE void push(QStringList path, const QVariantMap& properties, Qt::FocusReason focusReason = Qt::OtherFocusReason);
+    Q_INVOKABLE void push(QStringList path, Qt::FocusReason focusReason = Qt::OtherFocusReason);
 
 
-    /**
-     * @brief same as @a push(QVariantMap) but modify the last (current) item instead of insterting a new one
-     *
-     * @see push
-     */
-    Q_INVOKABLE void update(QVariantMap itemList);
 
     /**
-     * @brief same as @a push(QVariantList) but modify the last (current) item instead of insterting a new one
+     * @brief modify the current history path
      *
-     * @see push
+     * @note:
+     * * invoking update won't cause page to be reloaded, this mainly affects history
+     * * invoking update when there is no path will create a node (the initial history node)
      */
-    Q_INVOKABLE void update(QVariantList itemList);
+    Q_INVOKABLE void update(QStringList path);
 
-    /**
-     * @brief same as @a push(QVariantList) but modify the last (current) item's tail instead of insterting a new one
-     *
-     * @see push
-     */
-    Q_INVOKABLE void addLeaf(QVariantMap itemMap);
+    Q_INVOKABLE void update(QStringList path, const QVariantMap& properties);
 
 
-    // Go to previous page
-    void previous( Qt::FocusReason = Qt::OtherFocusReason, PostAction = PostAction::Go );
+    /// Go to previous page
+    void previous(Qt::FocusReason = Qt::OtherFocusReason);
 
 private:
-    void updateViewPath();
+    void updateCurrent();
 
-    QVariantList m_history;
+    std::vector<std::pair<QStringList, std::unique_ptr<QQmlPropertyMap>>> m_history;
     QStringList m_viewPath;
-
-    Qt::FocusReason m_reason;
+    QQmlPropertyMap* m_viewProperties = nullptr;
 };
 
 #endif // NAVIGATION_HISTORY_HPP


=====================================
modules/gui/qt/widgets/qml/BannerTabButton.qml
=====================================
@@ -77,7 +77,7 @@ T.TabButton {
         id: theme
         colorSet: ColorContext.TabButton
 
-        focused: control.activeFocus
+        focused: control.visualFocus
         hovered: control.hovered
         pressed: control.down
         enabled: control.enabled


=====================================
modules/gui/qt/widgets/qml/GridItem.qml
=====================================
@@ -158,7 +158,7 @@ T.ItemDelegate {
         id: theme
         colorSet: ColorContext.Item
 
-        focused: root.activeFocus
+        focused: root.visualFocus
         hovered: root.hovered
     }
 


=====================================
modules/gui/qt/widgets/qml/LocalTabBar.qml
=====================================
@@ -32,7 +32,7 @@ NavigableRow {
 
     delegate: BannerTabButton {
         text: model.displayText
-        selected: model.name === row.currentView.name
+        selected: model.name === row.currentView
         showCurrentIndicator: false
         height: VLCStyle.localToolbar_height
         onClicked: row.clicked(index)


=====================================
modules/gui/qt/widgets/qml/PageLoader.qml
=====================================
@@ -18,20 +18,21 @@
 import QtQuick 2.12
 import org.videolan.vlc 0.1
 
+import "qrc:///util/Helpers.js" as Helpers
+
 StackViewExt {
     id: root
 
     // Properties
 
-    property var view: null
+    //name of the loaded page
+    property string pageName: ""
 
-    property var pageModel: []
+    //path of the current page loader
+    property var pagePrefix: []
 
-    // loadDefaultView - function ()
-    // a function that loads the default page,
-    // must be implemented by the user of the class
-    // one may use `loadPage(string pageName)` to load the page from 'pageModel'
-    property var loadDefaultView: null
+    //list of available pages
+    property var pageModel: []
 
     //indicates whether the subview support grid/list mode
     readonly property bool hasGridListMode: (currentItem
@@ -50,66 +51,118 @@ StackViewExt {
                                     && currentItem.localMenuDelegate
                                     && (currentItem.localMenuDelegate instanceof Component)) ? currentItem.localMenuDelegate : null
 
-    // Private
-
-    property bool _ready: false
-
-
-    // Signals
-
-    signal pageChanged(string page)
-
-    // Events
-
-    Component.onCompleted: {
-        _ready = true
-
-        _loadView()
-    }
-
-    onViewChanged: _loadView()
 
     // Functions
 
-    function _loadView() {
-        // NOTE: We wait for the item to be fully loaded to avoid size glitches.
-        if (_ready === false)
-            return
-
-        if (view === null) {
-            if (!loadDefaultView)
-                console.error("both 'view' and 'loadDefaultView' is null, history -", JSON.stringify(History.current))
-            else
-                loadDefaultView()
-            return
+    /**
+     * @arg {string[]} path - the (sub) path to load
+     * @arg {Object.<string, Object>} properties - the properties to apply to the loaded view
+     * @arg {number} focusReason - the initial focus reason
+     */
+    function loadView(path, properties, focusReason)
+    {
+        if (currentItem && typeof currentItem.dismiss === "function")
+            currentItem.dismiss()
+
+        if (path.length === 0) {
+            path = _getDefaultPage()
+            if (path.length === 0) {
+                console.assert("trying to load an empty view path")
+                return false
+            }
         }
 
-        if (view.name === "") {
-            console.error("view is not defined")
-            return
-        }
-        if (pageModel.length === 0) {
-            console.error("pageModel is not defined")
-            return
-        }
+        const head = path[0]
 
-        const reason = History.takeFocusReason()
+        //We always reload if the last node even if this is the same page, as initial properties may differ
+        //for the intermediary pages, we can just forward the request down the tree
+        if (pageName === head && path.length > 1) {
+            return _reloadPage(path, properties, focusReason)
+        }
 
-        const found = root.loadView(root.pageModel, view.name, view.properties)
+        let found = false
+        for (let tab = 0; tab < pageModel.length; tab++ ) {
+
+            const model = pageModel[tab]
+            if (model.name === head) {
+                if (model.guard !== undefined && typeof model.guard === "function" && !model.guard(properties)) {
+                    continue //we're not allowed to load this page
+                }
+
+                //we can't use push(url, properties) as Qt interprets viewProperties
+                //as a second component to load
+                let component = undefined
+                if (model.component) {
+                    component = model.component
+                } else if ( model.url ) {
+                    component = Qt.createComponent(model.url)
+                } else {
+                    console.warn( "you should define either component or url of the view to load" )
+                    return false
+                }
+
+                if (component.status === Component.Ready ) {
+
+                    let pageProp = {
+                        pagePrefix:[...pagePrefix, head]
+                    }
+                    for (const key of properties.keys()) {
+                        pageProp[key] = properties[key]
+                    }
+
+                    root.replace(null, component, pageProp)
+                    found = true
+                    break;
+                } else {
+                    console.warn("component is not ready: " + component.errorString())
+                }
+            }
+        }
         if (!found) {
-            console.error("failed to load", JSON.stringify(History.current))
-            return
+            console.warn("unable to load view " + head)
+            return false
         }
 
+        pageName = head
         currentItem.Navigation.parentItem = root
+        //pages like MainDisplay are not page PageLoader, so just check for the loadView function
+        if (typeof currentItem.loadView === "function") {
+            currentItem.loadView(path.slice(1), properties, focusReason)
+        } else {
+            setCurrentItemFocus(focusReason)
+        }
 
-        if (reason !== Qt.OtherFocusReason)
-            setCurrentItemFocus(reason)
+        return true
+    }
 
-        currentItemChanged(currentItem)
+    function _getDefaultPage() {
+        for (let tab = 0; tab < pageModel.length; tab++ ) {
+            if (pageModel[tab].default) {
+                return [pageModel[tab].name]
+            }
+        }
+        console.assert("no default page set")
+        return []
     }
 
-    function loadPage(page) {
-        view = {"name": page, "properties": {}}
+    function _reloadPage(path, properties, focusReason)
+    {
+        if (!currentItem) {
+            console.warn("try to update subpage, but page isn't loaded")
+            return false
+        }
+
+        for (const key of properties.keys()) {
+            if (currentItem.hasOwnProperty(key))
+                currentItem[key] = properties[key]
+        }
+
+        if (typeof currentItem.loadView === "function") {
+            currentItem.loadView(path.slice(1), properties, focusReason)
+        } else if (path.length > 1) {
+            console.warn("unable to load subpath", path.slice(1))
+            return false
+        }
+        return true
     }
 }


=====================================
modules/gui/qt/widgets/qml/StackViewExt.qml
=====================================
@@ -26,9 +26,17 @@ import "qrc:///style/"
 StackView {
     id: root
 
-    // Private
+    // Functions
 
-    property string _currentView: ""
+    function setCurrentItemFocus(reason) {
+        if (reason === Qt.OtherFocusReason)
+            return
+        focus = true
+        focusReason = reason
+        if (typeof currentItem.setCurrentItemFocus === "function")
+            currentItem.setCurrentItemFocus(reason)
+
+    }
 
     // Settings
 
@@ -61,74 +69,4 @@ StackView {
             })
         }
     }
-
-    // Functions
-
-    /**
-     * viewModel: model with the definition of the available view
-     *            elements should contains at least :
-     *     name: name of the view
-     *     url or component: the url of the Component or the component to load
-     * view: string (name of the view to load)
-     * viewProperties: map of the propertes to apply to the view
-     */
-    function loadView(viewModel, view, viewProperties)
-    {
-        if (root.currentItem && root.currentItem.hasOwnProperty("dismiss"))
-            root.currentItem.dismiss()
-
-        if (view === _currentView) {
-            if (Object.keys(viewProperties).length === 0 && root.currentItem.hasOwnProperty("loadDefaultView") ) {
-                root.currentItem.loadDefaultView()
-            } else {
-                for ( let viewProp in viewProperties ) {
-                    if ( root.currentItem.hasOwnProperty(viewProp) ) {
-                        root.currentItem[viewProp] = viewProperties[viewProp]
-                    }
-                }
-            }
-            return true
-        }
-
-        let found = false
-        for (let tab = 0; tab < viewModel.length; tab++ )
-        {
-            const model = viewModel[tab]
-            if (model.name === view) {
-                if (model.guard !== undefined && typeof model.guard === "function" && !model.guard(viewProperties)) {
-                    continue //we're not allowed to load this page
-                }
-
-                //we can't use push(url, properties) as Qt interprets viewProperties
-                //as a second component to load
-                let component = undefined
-                if (model.component) {
-                    component = model.component
-                } else if ( model.url ) {
-                    component = Qt.createComponent(model.url)
-                } else {
-                    console.warn( "you should define either component or url of the view to load" )
-                    return false
-                }
-
-                if (component.status === Component.Ready ) {
-                    //note doesn't work with qt 5.9
-                    root.replace(null, component, viewProperties)
-                    found = true
-                    break;
-                } else {
-                    console.warn("component is not ready: " + component.errorString())
-                }
-            }
-        }
-        if (!found)
-            console.warn("unable to load view " + view)
-        else
-            _currentView = view
-        return found
-    }
-
-    function setCurrentItemFocus(reason) {
-        currentItem.setCurrentItemFocus(reason);
-    }
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/8b1f9fb457805168d1faa618cdac19cdcc0dfbca...317de4dfab14a0e1264a338602ad73ccca31867b

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/8b1f9fb457805168d1faa618cdac19cdcc0dfbca...317de4dfab14a0e1264a338602ad73ccca31867b
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list