[vlc-commits] [Git][videolan/vlc][master] 18 commits: qml/ExpandGridView: Fix keyboard navigation issue

Steve Lhomme (@robUx4) gitlab at videolan.org
Mon Oct 31 06:30:53 UTC 2022



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
168f6a65 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/ExpandGridView: Fix keyboard navigation issue

When pressing down on the last row we were selecting the last item of the list.

- - - - -
5667dfff by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/ExpandGridView: Add the 'getItemY' function

Also make rowHeight and nbItemPerRow public, that's useful to determine the Grid item position.

- - - - -
cdee0d9c by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/KeyNavigableListView: Refactor the settings

- - - - -
ecb58f6c by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/KeyNavigableTableView: Add the 'getItemY' function

- - - - -
bab34f45 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/NetworkGridItem: Add the 'ExpandGridView' delegate default properties

- - - - -
67a50339 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/TextToolButton: Add Navigation support

- - - - -
1cec54ff by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml: Create BrowseDeviceHeader

- - - - -
d2b9b5a3 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml: Create BrowseDeviceView

- - - - -
b87baae8 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/NetworkHomeDisplay: Update to 'BrowseDeviceView' implementation

- - - - -
3e2a7075 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml: Remove NetworkHomeDeviceListView

- - - - -
b77ac7f9 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/NetworkDisplay: Add the 'device' page

This will display the extended view for a given device category.

- - - - -
c68b00c8 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml: Rename NetworkDisplay to BrowseDisplay

- - - - -
b46d7035 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml: Rename NetworkHomeDisplay to BrowseHomeDisplay

- - - - -
a6316701 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml: Rename NetworkBrowseDisplay to BrowseTreeDisplay

- - - - -
35c5994c by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/BrowseTreeDisplay: Update 'cancelAction' implementation

- - - - -
91ff23c1 by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/BrowseDisplay: Update 'browseComponent' cancel implementation

- - - - -
2b01be3b by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qt/sortfilterproxymodel: Remove unused m_sortOrder

- - - - -
8e16f8dd by Benjamin Arnaud at 2022-10-31T06:04:14+00:00
qml/NetworkCustomCover: Handle null model

- - - - -


17 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/qml/MainDisplay.qml
- modules/gui/qt/network/qml/NetworkHomeDeviceListView.qml → modules/gui/qt/network/qml/BrowseDeviceHeader.qml
- + modules/gui/qt/network/qml/BrowseDeviceView.qml
- modules/gui/qt/network/qml/NetworkDisplay.qml → modules/gui/qt/network/qml/BrowseDisplay.qml
- modules/gui/qt/network/qml/NetworkHomeDisplay.qml → modules/gui/qt/network/qml/BrowseHomeDisplay.qml
- modules/gui/qt/network/qml/NetworkBrowseDisplay.qml → modules/gui/qt/network/qml/BrowseTreeDisplay.qml
- modules/gui/qt/network/qml/NetworkCustomCover.qml
- modules/gui/qt/network/qml/NetworkGridItem.qml
- modules/gui/qt/network/qml/ServicesHomeDisplay.qml
- modules/gui/qt/util/sortfilterproxymodel.hpp
- modules/gui/qt/vlc.qrc
- modules/gui/qt/widgets/qml/ExpandGridView.qml
- modules/gui/qt/widgets/qml/KeyNavigableListView.qml
- modules/gui/qt/widgets/qml/KeyNavigableTableView.qml
- modules/gui/qt/widgets/qml/TextToolButton.qml
- po/POTFILES.in


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -848,15 +848,16 @@ libqt_plugin_la_QML = \
 	gui/qt/menus/qml/Menubar.qml \
 	gui/qt/menus/qml/ShortcutExt.qml \
 	gui/qt/network/qml/AddressbarButton.qml \
+	gui/qt/network/qml/BrowseDeviceHeader.qml \
+	gui/qt/network/qml/BrowseDeviceView.qml \
+	gui/qt/network/qml/BrowseDisplay.qml \
+	gui/qt/network/qml/BrowseHomeDisplay.qml \
+	gui/qt/network/qml/BrowseTreeDisplay.qml \
 	gui/qt/network/qml/DiscoverDisplay.qml \
 	gui/qt/network/qml/DiscoverUrlDisplay.qml \
 	gui/qt/network/qml/NetworkAddressbar.qml \
-	gui/qt/network/qml/NetworkBrowseDisplay.qml \
 	gui/qt/network/qml/NetworkCustomCover.qml \
-	gui/qt/network/qml/NetworkDisplay.qml \
 	gui/qt/network/qml/NetworkGridItem.qml \
-	gui/qt/network/qml/NetworkHomeDisplay.qml \
-	gui/qt/network/qml/NetworkHomeDeviceListView.qml \
 	gui/qt/network/qml/NetworkListItem.qml \
 	gui/qt/network/qml/NetworkThumbnailItem.qml \
 	gui/qt/network/qml/ServicesHomeDisplay.qml \


=====================================
modules/gui/qt/maininterface/qml/MainDisplay.qml
=====================================
@@ -161,7 +161,7 @@ FocusScope {
             displayText: I18n.qtr("Browse"),
             icon: VLCIcons.topbar_network,
             name: "network",
-            url: "qrc:///network/NetworkDisplay.qml"
+            url: "qrc:///network/BrowseDisplay.qml"
         }, {
             listed: true,
             displayText: I18n.qtr("Discover"),


=====================================
modules/gui/qt/network/qml/NetworkHomeDeviceListView.qml → modules/gui/qt/network/qml/BrowseDeviceHeader.qml
=====================================
@@ -1,6 +1,8 @@
 /*****************************************************************************
  * Copyright (C) 2020 VLC authors and VideoLAN
  *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
  * 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
@@ -15,108 +17,73 @@
  * 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.11
 
 import org.videolan.vlc 0.1
 
-import "qrc:///widgets/" as Widgets
-import "qrc:///util/" as Util
 import "qrc:///style/"
+import "qrc:///widgets/" as Widgets
 
 FocusScope {
     id: root
 
-    height: deviceListView.implicitHeight
+    // Properties
 
-    property alias ctx: deviceModel.ctx
-    property alias sd_source: deviceModel.sd_source
-    property alias model: deviceModel
-    property int leftPadding: VLCStyle.margin_xlarge
+    /* required */ property var view
 
-    property int _currentIndex: -1
+    // Aliases
 
-    signal browse(var tree, int reason)
+    property alias text: label.text
 
-    on_CurrentIndexChanged: {
-        deviceListView.currentIndex = _currentIndex
-    }
+    property alias label: label
+    property alias button: button
 
-    function setCurrentItemFocus(reason) {
-        deviceListView.setCurrentItemFocus(reason);
-    }
+    // Signals
 
-    function _actionAtIndex(index, model, selectionModel) {
-        var data = model.getDataAt(index);
+    signal clicked(int reason)
 
-        if (data.type === NetworkMediaModel.TYPE_DIRECTORY
-            ||
-            data.type === NetworkMediaModel.TYPE_NODE)
-            browse(data.tree, Qt.TabFocusReason);
-        else
-            model.addAndPlay( selectionModel.selectedIndexes);
-    }
+    // Settings
 
-    onFocusChanged: {
-        if (activeFocus && root._currentIndex === -1 && deviceModel.count > 0)
-            root._currentIndex = 0
-    }
+    width: view.width
+    height: label.height
 
-    NetworkDeviceModel {
-        id: deviceModel
+    Navigation.navigable: button.visible
 
-        source_name: "*"
-    }
+    // Children
 
-    Util.SelectableDelegateModel {
-        id: deviceSelection
-        model: deviceModel
-    }
+    RowLayout {
+        id: row
 
-    Widgets.KeyNavigableListView {
-        id: deviceListView
+        anchors.fill: parent
 
-        focus: true
+        anchors.leftMargin: root.view.contentMargin
+        anchors.rightMargin: anchors.leftMargin
 
-        currentIndex: root._currentIndex
+        Widgets.SubtitleLabel {
+            id: label
 
-        implicitHeight: VLCStyle.gridItem_network_height + VLCStyle.gridItemSelectedBorder + VLCStyle.margin_large
-        orientation: ListView.Horizontal
-        anchors.fill: parent
-        spacing: VLCStyle.column_margin_width
+            Layout.fillWidth: true
 
-        header: Item {
-            width: root.leftPadding
+            topPadding: VLCStyle.margin_large
+            bottomPadding: VLCStyle.margin_normal
         }
 
-        model: deviceModel
-        delegate: NetworkGridItem {
+        Widgets.TextToolButton {
+            id: button
+
+            Layout.preferredWidth: implicitWidth
+
             focus: true
-            x: selectedBorderWidth
-            y: selectedBorderWidth
-
-            onItemClicked : {
-                deviceSelection.updateSelection( modifier ,  deviceSelection.currentIndex, index)
-                root._currentIndex = index
-                forceActiveFocus()
-            }
-
-            onPlayClicked: deviceModel.addAndPlay( index )
-
-            onItemDoubleClicked: {
-                if (model.type === NetworkMediaModel.TYPE_NODE
-                    ||
-                    model.type === NetworkMediaModel.TYPE_DIRECTORY)
-                    browse(model.tree, Qt.MouseFocusReason);
-                else
-                    deviceModel.addAndPlay(index);
-            }
-        }
 
-        onActionAtIndex: {
-            _actionAtIndex(index, deviceModel, deviceSelection)
+            text: I18n.qtr("See All")
+
+            font.pixelSize: VLCStyle.fontSize_large
+
+            Navigation.parentItem: root
+
+            onClicked: root.clicked(focusReason)
         }
-        Navigation.parentItem: root
     }
 }


=====================================
modules/gui/qt/network/qml/BrowseDeviceView.qml
=====================================
@@ -0,0 +1,379 @@
+/*****************************************************************************
+ * Copyright (C) 2020 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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 org.videolan.vlc 0.1
+
+import "qrc:///style/"
+import "qrc:///main/"    as MainInterface
+import "qrc:///widgets/" as Widgets
+import "qrc:///util/"    as Util
+
+FocusScope {
+    id: root
+
+    // Properties
+
+    /* required */ property var model
+
+    property var parentFilter: null
+
+    readonly property int rowHeight: (_currentView) ? _currentView.rowHeight : 0
+
+    readonly property int contentHeight: (_currentView) ? _currentView.contentHeight : 0
+
+    readonly property int contentMargin: (_currentView) ? _currentView.contentMargin : 0
+
+    property int displayMarginEnd: 0
+
+    readonly property int currentIndex: (_currentView) ? _currentView.currentIndex : -1
+
+    property int maximumRows: -1
+
+    readonly property int maximumCount: (_currentView) ? _currentView.maximumCount : -1
+
+    readonly property int nbItemPerRow: (_currentView) ? _currentView.nbItemPerRow : 1
+
+    property Component header: BrowseDeviceHeader {
+        view: root
+
+        text: root.title
+
+        button.visible: root.sourceModel.hasMoreItems
+
+        Navigation.parentItem: root
+
+        Navigation.downAction: function() {
+            view.setCurrentItemFocus(Qt.TabFocusReason)
+        }
+
+        onClicked: root.seeAll(reason)
+    }
+
+    property string title
+
+    // Aliases
+
+    property alias modelFilter: modelFilter
+
+    property alias sourceModel: modelFilter.sourceModel
+
+    // Private
+
+    property alias _currentView: view.currentItem
+
+    // Signals
+
+    signal browse(var tree, int reason)
+
+    signal seeAll(int reason)
+
+    // Events
+
+    onFocusChanged: {
+        if (activeFocus === false || model.count === 0 || model.currentIndex !== -1)
+            return
+
+        model.currentIndex = 0
+    }
+
+    onParentFilterChanged: {
+        if (parentFilter === null || sourceModel === null)
+            return
+
+        sourceModel.searchRole = parentFilter.searchRole
+
+        sourceModel.searchPattern = parentFilter.searchPattern
+
+        sourceModel.sortCriteria = parentFilter.sortCriteria
+
+        sourceModel.sortOrder = parentFilter.sortOrder
+    }
+
+    // Connections
+
+    Connections {
+        target: MainCtx
+
+        onGridViewChanged: {
+            if (MainCtx.gridView) view.replace(grid)
+            else                  view.replace(list)
+        }
+    }
+
+    // NOTE: If it exists, we're applying 'parentFilter' properties to fit the sorting options.
+    Connections {
+        target: parentFilter
+
+        onSearchRoleChanged: sourceModel.searchRole = parentFilter.searchRole
+
+        onSearchPatternChanged: sourceModel.searchPattern = parentFilter.searchPattern
+
+        onSortCriteriaChanged: sourceModel.sortCriteria = parentFilter.sortCriteria
+
+        onSortOrderChanged: sourceModel.sortOrder = parentFilter.sortOrder
+    }
+
+    // Functions
+
+    function playAt(index) {
+        model.addAndPlay(modelFilter.mapIndexToSource(index))
+    }
+
+    function setCurrentItemFocus(reason) {
+        _currentView.setCurrentItemFocus(reason)
+    }
+
+    function getItemY(index) {
+        if (_currentView === null)
+            return 0
+
+        return _currentView.getItemY(index)
+    }
+
+    // Events
+
+    function onAction(index) {
+        var indexes = modelSelect.selectedIndexes
+
+        if (indexes.length > 1) {
+            model.addAndPlay(modelFilter.mapIndexesToSource(indexes))
+
+            return
+        }
+
+        var data = modelFilter.getDataAt(index)
+
+        if (data.type === NetworkMediaModel.TYPE_DIRECTORY
+            ||
+            data.type === NetworkMediaModel.TYPE_NODE)
+            browse(data.tree, Qt.TabFocusReason);
+        else
+            playAt(index);
+    }
+
+    function onClicked(model, index, modifier) {
+        modelSelect.updateSelection(modifier, model.currentIndex, index)
+
+        model.currentIndex = index
+
+        forceActiveFocus()
+    }
+
+    function onDoubleClicked(model, index) {
+        if (model.type === NetworkMediaModel.TYPE_NODE
+            ||
+            model.type === NetworkMediaModel.TYPE_DIRECTORY)
+            browse(model.tree, Qt.MouseFocusReason);
+        else
+            playAt(index);
+    }
+
+    // Children
+
+    Util.SelectableDelegateModel {
+        id: modelSelect
+
+        model: modelFilter
+    }
+
+    SortFilterProxyModel {
+        id: modelFilter
+
+        sourceModel: root.model
+
+        searchRole: "name"
+
+        // TODO: Handle the searchPattern on a partial list.
+        searchPattern: (sourceModel && maximumRows === -1) ? sourceModel.searchPattern : ""
+    }
+
+    Widgets.StackViewExt {
+        id: view
+
+        anchors.fill: parent
+
+        focus: (model.count !== 0)
+
+        initialItem: (MainCtx.gridView) ? grid : list
+    }
+
+    Component {
+        id: grid
+
+        Widgets.ExpandGridView {
+            id: gridView
+
+            readonly property int maximumCount: (root.maximumRows === -1)
+                                                ? -1
+                                                : root.maximumRows * nbItemPerRow
+
+            cellWidth: VLCStyle.gridItem_network_width
+            cellHeight: VLCStyle.gridItem_network_height
+
+            displayMarginEnd: root.displayMarginEnd
+
+            model: modelFilter
+
+            headerDelegate: root.header
+
+            selectionDelegateModel: modelSelect
+
+            Navigation.parentItem: root
+
+            Navigation.upItem: headerItem
+
+            onActionAtIndex: root.onAction(index)
+
+            delegate: NetworkGridItem {
+                onItemClicked: root.onClicked(model, index, modifier)
+
+                onItemDoubleClicked: root.onDoubleClicked(model, index)
+
+                onPlayClicked: root.playAt(index)
+            }
+        }
+    }
+
+    Component {
+        id: list
+
+        Widgets.KeyNavigableTableView {
+            id: listView
+
+            readonly property real contentMargin: VLCStyle.margin_normal
+
+            readonly property int maximumCount: root.maximumRows
+
+            readonly property int nbItemPerRow: 1
+
+            readonly property int _nbCols: VLCStyle.gridColumnsForWidth(listView.availableRowWidth)
+
+            readonly property int _nameColSpan: Math.max((_nbCols - 1) / 2, 1)
+
+            rowHeight: VLCStyle.tableCoverRow_height
+
+            displayMarginEnd: root.displayMarginEnd
+
+            model: modelFilter
+
+            sortModel: [{
+                criteria: "artwork",
+
+                width: VLCStyle.colWidth(1),
+
+                headerDelegate: artworkHeader,
+                colDelegate   : artworkColumn
+            }, {
+                isPrimary: true,
+                criteria: "name",
+
+                width: VLCStyle.colWidth(listView._nameColSpan),
+
+                text: I18n.qtr("Name")
+            }, {
+                criteria: "mrl",
+
+                width: VLCStyle.colWidth(Math.max(listView._nbCols - listView._nameColSpan - 1), 1),
+
+                text: I18n.qtr("Url"),
+
+                colDelegate: mrlColumn
+            }]
+
+            header: root.header
+
+            headerColor: VLCStyle.colors.bg
+
+            selectionDelegateModel: modelSelect
+
+            Navigation.parentItem: root
+
+            Navigation.upItem: headerItem
+
+            onActionForSelection: root.onAction(selection[0].row)
+
+            onItemDoubleClicked: root.onDoubleClicked(model, index)
+
+            Component {
+                id: artworkHeader
+
+                Item {
+                    Widgets.IconLabel {
+                        width: VLCStyle.listAlbumCover_width
+                        height: VLCStyle.listAlbumCover_height
+
+                        horizontalAlignment: Text.AlignHCenter
+
+                        color: VLCStyle.colors.caption
+                    }
+                }
+            }
+
+            Component {
+                id: artworkColumn
+
+                NetworkThumbnailItem { onPlayClicked: root.playAt(index) }
+            }
+
+            Component {
+                id: mrlColumn
+
+                Widgets.ScrollingText {
+                    id: itemText
+
+                    property var rowModel: parent.rowModel
+                    property var colModel: parent.colModel
+
+                    property color foregroundColor: parent.foregroundColor
+
+                    width: parent.width
+
+                    clip: scrolling
+
+                    label: itemLabel
+
+                    forceScroll: parent.currentlyFocused
+
+                    Widgets.ListLabel {
+                        id: itemLabel
+
+                        anchors.verticalCenter: parent.verticalCenter
+
+                        text: {
+                            if (itemText.rowModel === null)
+                                return ""
+
+                            var text = itemText.rowModel[itemText.colModel.criteria]
+
+                            if (text.toString() === "vlc://nop")
+                                return ""
+                            else
+                                return text
+                        }
+
+                        color: itemText.foregroundColor
+                    }
+                }
+            }
+        }
+    }
+}


=====================================
modules/gui/qt/network/qml/NetworkDisplay.qml → modules/gui/qt/network/qml/BrowseDisplay.qml
=====================================
@@ -40,7 +40,10 @@ Widgets.PageLoader {
 
     pageModel: [{
         name: "home",
-        url: "qrc:///network/NetworkHomeDisplay.qml"
+        url: "qrc:///network/BrowseHomeDisplay.qml"
+    }, {
+        name: "device",
+        component: browseDevice,
     }, {
         name: "browse",
         component: browseComponent,
@@ -61,29 +64,66 @@ Widgets.PageLoader {
                            ||
                            currentItem.isViewMultiView);
 
-        if (view.name === "browse")
-            localMenuDelegate = componentBar
-        else
+        if (view.name === "home")
             localMenuDelegate = null
+        else
+            localMenuDelegate = componentBar
     }
 
     // Connections
+    Connections {
+        target: (stackView.currentItem instanceof BrowseHomeDisplay) ? stackView.currentItem
+                                                                     : null
+
+        onSeeAll: {
+            History.push(["mc", "network", "device", { title: title, sd_source: sd_source }])
+
+            stackView.currentItem.setCurrentItemFocus(reason)
+        }
+    }
 
     Connections {
         target: stackView.currentItem
 
         onBrowse: {
-            History.push(["mc", "network", "browse", { tree: tree }]);
-            stackView.currentItem.setCurrentItemFocus(reason);
+            History.push(["mc", "network", "browse", { tree: tree }])
+
+            stackView.currentItem.setCurrentItemFocus(reason)
         }
     }
 
     // Children
 
+    Component {
+        id: browseDevice
+
+        BrowseDeviceView {
+            id: viewDevice
+
+            property var sd_source
+
+            property var sortModel: [
+                { text: I18n.qtr("Alphabetic"), criteria: "name" },
+                { text: I18n.qtr("Url"),        criteria: "mrl"  }
+            ]
+
+            displayMarginEnd: g_mainDisplay.displayMargin
+
+            model: modelFilter
+
+            sourceModel: NetworkDeviceModel {
+                ctx: MainCtx
+
+                sd_source: viewDevice.sd_source
+                source_name: "*"
+            }
+        }
+    }
+
     Component {
         id: browseComponent
 
-        NetworkBrowseDisplay {
+        BrowseTreeDisplay {
             providerModel: NetworkMediaModel {
                 ctx: MainCtx
             }
@@ -91,6 +131,12 @@ Widgets.PageLoader {
             contextMenu: NetworkMediaContextMenu {
                 model: providerModel
             }
+
+            Navigation.cancelAction: function() {
+                History.previous()
+
+                stackView.currentItem.setCurrentItemFocus(Qt.BacktabFocusReason)
+            }
         }
     }
 


=====================================
modules/gui/qt/network/qml/NetworkHomeDisplay.qml → modules/gui/qt/network/qml/BrowseHomeDisplay.qml
=====================================
@@ -29,10 +29,20 @@ import "qrc:///util/" as Util
 import "qrc:///style/"
 
 FocusScope {
-    id: topFocusScope
+    id: root
+
+    property int maximumRows: (MainCtx.gridView) ? 2 : 5
+
+    property var sortModel: [
+        { text: I18n.qtr("Alphabetic"), criteria: "name"},
+        { text: I18n.qtr("Url"),        criteria: "mrl" }
+    ]
+
+    property alias model: deviceSection.model
+
     focus: true
 
-    readonly property bool isViewMultiView: false
+    signal seeAll(var title, var sd_source, int reason)
 
     signal browse(var tree, int reason)
 
@@ -43,7 +53,27 @@ FocusScope {
         deviceSection.setCurrentItemFocus(reason);
     }
 
-    function _centerFlickableOnItem(minY, maxY) {
+    function _centerFlickableOnItem(item) {
+        if (item.activeFocus === false)
+            return
+
+        var minY
+        var maxY
+
+        var index = item.currentIndex
+
+        // NOTE: We want to include the header when we're on the first row.
+        if ((MainCtx.gridView && index < item.nbItemPerRow) || index < 1) {
+            minY = item.y
+
+            maxY = minY + item.getItemY(index) + item.rowHeight
+        } else {
+            minY = item.y + item.getItemY(index)
+
+            maxY = minY + item.rowHeight
+        }
+
+        // TODO: We could implement a scrolling animation like in ExpandGridView.
         if (maxY > flickable.contentItem.contentY + flickable.height) {
             flickable.contentItem.contentY = maxY - flickable.height
         } else if (minY < flickable.contentItem.contentY) {
@@ -56,7 +86,7 @@ FocusScope {
         anchors.centerIn: parent
         visible: (deviceSection.model.count === 0 && lanSection.model.count === 0 )
         font.pixelSize: VLCStyle.fontHeight_xxlarge
-        color: topFocusScope.activeFocus ? VLCStyle.colors.accent : VLCStyle.colors.text
+        color: root.activeFocus ? VLCStyle.colors.accent : VLCStyle.colors.text
         text: I18n.qtr("No network shares found")
     }
 
@@ -69,79 +99,86 @@ FocusScope {
             width: parent.width
             height: implicitHeight
 
-            topPadding: VLCStyle.margin_large
             spacing: VLCStyle.margin_small
 
-            Widgets.SubtitleLabel {
-                id: deviceLabel
-                text: I18n.qtr("My Machine")
-                width: flickable.width
-                visible: deviceSection.model.count !== 0
-                leftPadding: VLCStyle.margin_xlarge
-            }
-
-            NetworkHomeDeviceListView {
+            BrowseDeviceView {
                 id: deviceSection
-                ctx: MainCtx
-                sd_source: NetworkDeviceModel.CAT_DEVICES
 
                 width: flickable.width
-                visible: deviceSection.model.count !== 0
-                onVisibleChanged: topFocusScope.resetFocus()
+                height: contentHeight
 
-                onBrowse: topFocusScope.browse(tree, reason)
+                maximumRows: root.maximumRows
 
-                Navigation.parentItem: topFocusScope
+                visible: (model.count !== 0)
 
-                Navigation.downAction: function() {
-                    if (lanSection.visible == false)
-                        return;
+                model: NetworkDeviceModel {
+                    ctx: MainCtx
 
-                    lanSection.setCurrentItemFocus(Qt.TabFocusReason);
+                    sd_source: NetworkDeviceModel.CAT_DEVICES
+                    source_name: "*"
+
+                    maximumCount: deviceSection.maximumCount
                 }
 
-                onActiveFocusChanged: {
-                    if (activeFocus)
-                        _centerFlickableOnItem(deviceLabel.y, deviceSection.y + deviceSection.height)
+                title: I18n.qtr("My Machine")
+
+                Navigation.parentItem: root
+
+                Navigation.downAction: function() {
+                    if (lanSection.visible)
+                        lanSection.setCurrentItemFocus(Qt.TabFocusReason)
+                    else
+                        root.Navigation.defaultNavigationDown()
                 }
-            }
 
-            Widgets.SubtitleLabel {
-                id: lanLabel
-                text: I18n.qtr("My LAN")
-                width: flickable.width
-                visible: lanSection.model.count !== 0
-                leftPadding: VLCStyle.margin_xlarge
-                topPadding: deviceLabel.visible ? VLCStyle.margin_small : 0
+                onBrowse: root.browse(tree, reason)
+
+                onSeeAll: root.seeAll(title, model.sd_source, reason)
+
+                onActiveFocusChanged: _centerFlickableOnItem(deviceSection)
+                onCurrentIndexChanged: _centerFlickableOnItem(deviceSection)
             }
 
-            NetworkHomeDeviceListView {
+            BrowseDeviceView {
                 id: lanSection
-                ctx: MainCtx
-                sd_source: NetworkDeviceModel.CAT_LAN
 
                 width: flickable.width
-                visible: lanSection.model.count !== 0
-                onVisibleChanged: topFocusScope.resetFocus()
+                height: contentHeight
 
-                onBrowse: topFocusScope.browse(tree, reason)
+                maximumRows: root.maximumRows
 
-                Navigation.parentItem: topFocusScope
+                visible: (model.count !== 0)
 
-                Navigation.upAction: function() {
-                    if (deviceSection.visible == false)
-                        return;
+                model: NetworkDeviceModel {
+                    ctx: MainCtx
+
+                    sd_source: NetworkDeviceModel.CAT_LAN
+                    source_name: "*"
 
-                    deviceSection.setCurrentItemFocus(Qt.TabFocusReason);
+                    maximumCount: lanSection.maximumCount
                 }
 
-                onActiveFocusChanged: {
-                    if (activeFocus)
-                        _centerFlickableOnItem(lanLabel.y, lanSection.y + lanSection.height)
+                title: I18n.qtr("My LAN")
+
+                parentFilter: deviceSection.modelFilter
+
+                Navigation.parentItem: root
+
+                Navigation.upAction: function() {
+                    if (deviceSection.visible)
+                        deviceSection.setCurrentItemFocus(Qt.TabFocusReason)
+                    else
+                        root.Navigation.defaultNavigationUp()
                 }
+
+                onBrowse: root.browse(tree, reason)
+
+                onSeeAll: root.seeAll(title, model.sd_source, reason)
+
+                onActiveFocusChanged: _centerFlickableOnItem(lanSection)
+                onCurrentIndexChanged: _centerFlickableOnItem(lanSection)
             }
         }
-
     }
 
     function resetFocus() {


=====================================
modules/gui/qt/network/qml/NetworkBrowseDisplay.qml → modules/gui/qt/network/qml/BrowseTreeDisplay.qml
=====================================
@@ -51,6 +51,10 @@ FocusScope {
 
     signal browse(var tree, int reason)
 
+    Navigation.cancelAction: function() {
+        History.previous()
+    }
+
     onTreeChanged: providerModel.tree = tree
 
     function playSelected() {
@@ -198,9 +202,6 @@ FocusScope {
             delegate: NetworkGridItem {
                 id: delegateGrid
 
-                property var model: ({})
-                property int index: -1
-
                 subtitle: ""
                 height: VLCStyle.gridCover_network_height + VLCStyle.margin_xsmall + VLCStyle.fontHeight_normal
                 dragItem: networkDragItem
@@ -225,9 +226,6 @@ FocusScope {
 
             Navigation.parentItem: root
             Navigation.upItem: gridView.headerItem
-            Navigation.cancelAction: function() {
-                History.previous()
-            }
         }
     }
 
@@ -262,9 +260,6 @@ FocusScope {
             headerColor: VLCStyle.colors.bg
             Navigation.parentItem: root
             Navigation.upItem: tableView.headerItem
-            Navigation.cancelAction: function() {
-                History.previous()
-            }
 
             rowHeight: VLCStyle.tableCoverRow_height
 


=====================================
modules/gui/qt/network/qml/NetworkCustomCover.qml
=====================================
@@ -32,6 +32,9 @@ Widgets.ScaledImage {
 
     sourceSize: Qt.size(width, height)
     source: {
+        if (networkModel === null)
+            return ""
+
         if (!!networkModel.artwork && networkModel.artwork.length > 0)
             return networkModel.artwork
 


=====================================
modules/gui/qt/network/qml/NetworkGridItem.qml
=====================================
@@ -28,6 +28,9 @@ import "qrc:///style/"
 Widgets.GridItem {
     id: root
 
+    property var model: ({})
+    property int index: -1
+
     width: VLCStyle.gridItem_network_width
     height: VLCStyle.gridItem_network_height
 


=====================================
modules/gui/qt/network/qml/ServicesHomeDisplay.qml
=====================================
@@ -69,7 +69,7 @@ Widgets.PageLoader {
     Component {
         id: sourceRootComponent
 
-        NetworkBrowseDisplay {
+        BrowseTreeDisplay {
             property alias source_name: deviceModel.source_name
 
             property Component addressBar: NetworkAddressbar {
@@ -111,7 +111,7 @@ Widgets.PageLoader {
     Component {
         id: sourceBrowseComponent
 
-        NetworkBrowseDisplay {
+        BrowseTreeDisplay {
             property string root_name
             property string source_name
 


=====================================
modules/gui/qt/util/sortfilterproxymodel.hpp
=====================================
@@ -71,7 +71,6 @@ private slots:
 private:
     QByteArray m_searchRole;
     QString m_sortCriteria;
-    Qt::SortOrder m_sortOrder;
 };
 
 #endif // SORT_FILTER_PROXY_MODEL


=====================================
modules/gui/qt/vlc.qrc
=====================================
@@ -219,13 +219,14 @@
     </qresource>
     <qresource prefix="/network">
         <file alias="AddressbarButton.qml">network/qml/AddressbarButton.qml</file>
+        <file alias="BrowseDeviceHeader.qml">network/qml/BrowseDeviceHeader.qml</file>
+        <file alias="BrowseDeviceView.qml">network/qml/BrowseDeviceView.qml</file>
+        <file alias="BrowseDisplay.qml">network/qml/BrowseDisplay.qml</file>
+        <file alias="BrowseHomeDisplay.qml">network/qml/BrowseHomeDisplay.qml</file>
+        <file alias="BrowseTreeDisplay.qml">network/qml/BrowseTreeDisplay.qml</file>
         <file alias="DiscoverDisplay.qml">network/qml/DiscoverDisplay.qml</file>
         <file alias="DiscoverUrlDisplay.qml">network/qml/DiscoverUrlDisplay.qml</file>
         <file alias="NetworkCustomCover.qml">network/qml/NetworkCustomCover.qml</file>
-        <file alias="NetworkDisplay.qml">network/qml/NetworkDisplay.qml</file>
-        <file alias="NetworkHomeDeviceListView.qml">network/qml/NetworkHomeDeviceListView.qml</file>
-        <file alias="NetworkHomeDisplay.qml">network/qml/NetworkHomeDisplay.qml</file>
-        <file alias="NetworkBrowseDisplay.qml">network/qml/NetworkBrowseDisplay.qml</file>
         <file alias="NetworkGridItem.qml">network/qml/NetworkGridItem.qml</file>
         <file alias="NetworkListItem.qml">network/qml/NetworkListItem.qml</file>
         <file alias="NetworkThumbnailItem.qml">network/qml/NetworkThumbnailItem.qml</file>


=====================================
modules/gui/qt/widgets/qml/ExpandGridView.qml
=====================================
@@ -42,25 +42,26 @@ FocusScope {
     property int rightMargin: VLCStyle.margin_normal
 
     // NOTE: The grid margin for the item(s) horizontal positioning.
-    readonly property int contentMargin: (_contentWidth - _nbItemPerRow * _effectiveCellWidth
+    readonly property int contentMargin: (_contentWidth - nbItemPerRow * _effectiveCellWidth
                                           +
                                           horizontalSpacing) / 2
 
+    readonly property int rowHeight: cellHeight + verticalSpacing
+
     property int rowX: 0
     property int horizontalSpacing: VLCStyle.column_margin_width
     property int verticalSpacing: VLCStyle.column_margin_width
 
     property int displayMarginEnd: 0
 
+    readonly property int nbItemPerRow: Math.max(Math.floor((_contentWidth + horizontalSpacing)
+                                                            /
+                                                            _effectiveCellWidth), 1)
+
     readonly property int _effectiveCellWidth: cellWidth + horizontalSpacing
-    readonly property int _effectiveCellHeight: cellHeight + verticalSpacing
 
     readonly property int _contentWidth: width - rightMargin - leftMargin
 
-    readonly property int _nbItemPerRow: Math.max(Math.floor((_contentWidth + horizontalSpacing)
-                                                             /
-                                                             _effectiveCellWidth), 1)
-
     property Util.SelectableDelegateModel selectionDelegateModel
     property QtAbstractItemModel model
 
@@ -150,26 +151,29 @@ FocusScope {
     Keys.onPressed: {
         var newIndex = -1
         if (KeyHelper.matchRight(event)) {
-            if ((currentIndex + 1) % _nbItemPerRow !== 0) {//are we not at the end of line
+            if ((currentIndex + 1) % nbItemPerRow !== 0) {//are we not at the end of line
                 newIndex = Math.min(_count - 1, currentIndex + 1)
             }
         } else if (KeyHelper.matchLeft(event)) {
-            if (currentIndex % _nbItemPerRow !== 0) {//are we not at the beginning of line
+            if (currentIndex % nbItemPerRow !== 0) {//are we not at the beginning of line
                 newIndex = Math.max(0, currentIndex - 1)
             }
         } else if (KeyHelper.matchDown(event)) {
+            var lastIndex = _count - 1
             // we are not on the last line
-            if (Math.floor(currentIndex / _nbItemPerRow) !== Math.floor(_count / _nbItemPerRow)) {
-                newIndex = Math.min(_count - 1, currentIndex + _nbItemPerRow)
+            if (Math.floor(currentIndex / nbItemPerRow)
+                !==
+                Math.floor(lastIndex / nbItemPerRow)) {
+                newIndex = Math.min(lastIndex, currentIndex + nbItemPerRow)
             }
         } else if (KeyHelper.matchPageDown(event)) {
-            newIndex = Math.min(_count - 1, currentIndex + _nbItemPerRow * 5)
+            newIndex = Math.min(_count - 1, currentIndex + nbItemPerRow * 5)
         } else if (KeyHelper.matchUp(event)) {
-            if (Math.floor(currentIndex / _nbItemPerRow) !== 0) { //we are not on the first line
-                newIndex = Math.max(0, currentIndex - _nbItemPerRow)
+            if (Math.floor(currentIndex / nbItemPerRow) !== 0) { //we are not on the first line
+                newIndex = Math.max(0, currentIndex - nbItemPerRow)
             }
         } else if (KeyHelper.matchPageUp(event)) {
-            newIndex = Math.max(0, currentIndex - _nbItemPerRow * 5)
+            newIndex = Math.max(0, currentIndex - nbItemPerRow * 5)
         } else if (KeyHelper.matchOk(event) || event.matches(StandardKey.SelectAll) ) {
             //these events are matched on release
             event.accepted = true
@@ -324,9 +328,13 @@ FocusScope {
         flickable.retract()
     }
 
+    function getItemY(index) {
+        return Math.floor(index / nbItemPerRow) * rowHeight + headerHeight + topMargin
+    }
+
     function getItemRowCol(id) {
-        var rowId = Math.floor(id / _nbItemPerRow)
-        var colId = id % _nbItemPerRow
+        var rowId = Math.floor(id / nbItemPerRow)
+        var colId = id % nbItemPerRow
         return [colId, rowId]
     }
 
@@ -335,7 +343,7 @@ FocusScope {
 
         var x = rowCol[0] * _effectiveCellWidth + contentMargin + leftMargin;
 
-        var y = rowCol[1] * _effectiveCellHeight + headerHeight + topMargin;
+        var y = rowCol[1] * rowHeight + headerHeight + topMargin;
 
         // NOTE: Position needs to be integer based if we want to avoid visual artifacts like
         //       wrong alignments or blurry texture rendering.
@@ -351,7 +359,7 @@ FocusScope {
             return
 
         var itemTopY = getItemPos(index)[1]
-        var itemBottomY = itemTopY + _effectiveCellHeight
+        var itemBottomY = itemTopY + rowHeight
 
         var viewTopY = flickable.contentY
         var viewBottomY = viewTopY + flickable.height
@@ -431,11 +439,11 @@ FocusScope {
         }
 
         var onlyGridContentY = contentYWithoutExpand - headerHeight - topMargin
-        var rowId = Math.floor(onlyGridContentY / _effectiveCellHeight)
-        var firstId = Math.max(rowId * _nbItemPerRow, 0)
+        var rowId = Math.floor(onlyGridContentY / rowHeight)
+        var firstId = Math.max(rowId * nbItemPerRow, 0)
 
-        rowId = Math.ceil((onlyGridContentY + heightWithoutExpand) / _effectiveCellHeight)
-        var lastId = Math.min(rowId * _nbItemPerRow, _count)
+        rowId = Math.ceil((onlyGridContentY + heightWithoutExpand) / rowHeight)
+        var lastId = Math.min(rowId * nbItemPerRow, _count)
 
         return [firstId, lastId]
     }
@@ -590,7 +598,7 @@ FocusScope {
                 }
             }
         }
-        
+
         Util.FlickableScrollHandler { }
 
         Loader {
@@ -600,7 +608,7 @@ FocusScope {
             y: root.topMargin
 
             //load the header early (when the first row is visible)
-            visible: flickable.contentY < (root.headerHeight + root._effectiveCellHeight + root.topMargin)
+            visible: flickable.contentY < (root.headerHeight + root.rowHeight + root.topMargin)
 
             focus: (status === Loader.Ready) ? item.focus : false
         }
@@ -610,7 +618,7 @@ FocusScope {
 
             focus: (status === Loader.Ready) ? item.focus : false
 
-            y: root.topMargin + root.headerHeight + (root._effectiveCellHeight * (Math.ceil(model.count / root._nbItemPerRow))) +
+            y: root.topMargin + root.headerHeight + (root.rowHeight * (Math.ceil(model.count / root.nbItemPerRow))) +
                root._expandItemVerticalSpace
         }
 
@@ -669,7 +677,7 @@ FocusScope {
             if (root.expandIndex !== -1) {
                 var rowCol = root.getItemRowCol(root.expandIndex)
                 var rowId = rowCol[1] + 1
-                ret = rowId * root._nbItemPerRow
+                ret = rowId * root.nbItemPerRow
             } else {
                 ret = root._count
             }
@@ -758,10 +766,10 @@ FocusScope {
             _setupIndexes(forceRelayout, [topGridEndId, lastId], root._expandItemVerticalSpace)
 
             // update contentWidth and contentHeight
-            var gridContentWidth = root._effectiveCellWidth * root._nbItemPerRow - root.horizontalSpacing
+            var gridContentWidth = root._effectiveCellWidth * root.nbItemPerRow - root.horizontalSpacing
             contentWidth = root.leftMargin + gridContentWidth + root.rightMargin
 
-            var gridContentHeight = root.getItemPos(root._count - 1)[1] + root._effectiveCellHeight + root._expandItemVerticalSpace
+            var gridContentHeight = root.getItemPos(root._count - 1)[1] + root.rowHeight + root._expandItemVerticalSpace
             contentHeight = gridContentHeight
                     + (footerItemLoader.item ? footerItemLoader.item.height : 0)
                     + root.bottomMargin // topMargin and headerHeight is included in root.getItemPos
@@ -804,7 +812,7 @@ FocusScope {
 
             // Sliding animation
             var currentItemYPos = root.getItemPos(root.expandIndex)[1]
-            currentItemYPos += root._effectiveCellHeight / 2
+            currentItemYPos += root.rowHeight / 2
             animateFlickableContentY(currentItemYPos)
         }
 


=====================================
modules/gui/qt/widgets/qml/KeyNavigableListView.qml
=====================================
@@ -35,7 +35,7 @@ FadingEdgeListView {
 
     readonly property int scrollBarWidth: scroll_id.visible ? scroll_id.width : 0
 
-    property bool keyNavigationWraps : false
+    property bool keyNavigationWraps: false
 
     // Private
 
@@ -69,13 +69,38 @@ FadingEdgeListView {
 
     property int _currentFocusReason: Qt.OtherFocusReason
 
-
     // Settings
 
+    focus: true
+
+    //key navigation is reimplemented for item selection
+    keyNavigationEnabled: false
+
+    ScrollBar.vertical: ScrollBar { id: scroll_id; visible: root.contentHeight > root.height }
+    ScrollBar.horizontal: ScrollBar { visible: root.contentWidth > root.width }
+
+    highlightMoveDuration: 300 //ms
+    highlightMoveVelocity: 1000 //px/s
+
+    section.property: ""
+    section.criteria: ViewSection.FullString
+    section.delegate: sectionHeading
+
+    disableBeginningFade: (dragAutoScrollHandler.scrollingDirection
+                           ===
+                           Util.ViewDragAutoScrollHandler.Backward)
+
+    disableEndFade: (dragAutoScrollHandler.scrollingDirection
+                     ===
+                     Util.ViewDragAutoScrollHandler.Forward)
+
     Accessible.role: Accessible.List
 
     // Events
 
+    // NOTE: We always want a valid 'currentIndex' by default.
+    onCountChanged: if (count && currentIndex === -1) currentIndex = 0
+
     onCurrentItemChanged: {
         if (_currentFocusReason === Qt.OtherFocusReason)
             return;
@@ -116,27 +141,6 @@ FadingEdgeListView {
         root.contentX -= Math.min(root.width,root.contentX - root.originX)
     }
 
-    focus: true
-
-    //key navigation is reimplemented for item selection
-    keyNavigationEnabled: false
-
-    ScrollBar.vertical: ScrollBar { id: scroll_id; visible: root.contentHeight > root.height }
-    ScrollBar.horizontal: ScrollBar { visible: root.contentWidth > root.width }
-
-    highlightMoveDuration: 300 //ms
-    highlightMoveVelocity: 1000 //px/s
-
-    section.property: ""
-    section.criteria: ViewSection.FullString
-    section.delegate: sectionHeading
-
-    // NOTE: We always want a valid 'currentIndex' by default.
-    onCountChanged: if (count && currentIndex === -1) currentIndex = 0
-
-    disableBeginningFade: (dragAutoScrollHandler.scrollingDirection === Util.ViewDragAutoScrollHandler.Backward)
-    disableEndFade: (dragAutoScrollHandler.scrollingDirection === Util.ViewDragAutoScrollHandler.Forward)
-
     Keys.onPressed: {
         var newIndex = -1
 


=====================================
modules/gui/qt/widgets/qml/KeyNavigableTableView.qml
=====================================
@@ -146,6 +146,17 @@ FocusScope {
         view.positionViewAtBeginning()
     }
 
+    function getItemY(index) {
+        var size = index * rowHeight + topMargin
+
+        if (tableHeaderItem)
+            size += tableHeaderItem.height
+
+        return size
+    }
+
+    // Private
+
     function _qtAvoidSectionUpdate() {
         // Qt SEG. FAULT WORKAROUND
 


=====================================
modules/gui/qt/widgets/qml/TextToolButton.qml
=====================================
@@ -18,6 +18,8 @@
 import QtQuick 2.11
 import QtQuick.Templates 2.4 as T
 
+import org.videolan.vlc 0.1
+
 import "qrc:///style/"
 
 T.ToolButton {
@@ -30,6 +32,12 @@ T.ToolButton {
 
     padding: VLCStyle.margin_xxsmall
 
+    // Keys
+
+    Keys.priority: Keys.AfterItem
+
+    Keys.onPressed: Navigation.defaultKeyAction(event)
+
     contentItem: T.Label {
         text: control.text
         font: control.font


=====================================
po/POTFILES.in
=====================================
@@ -830,11 +830,14 @@ modules/gui/qt/menus/menus.hpp
 modules/gui/qt/menus/qml/Menubar.qml
 modules/gui/qt/menus/qml_menu_wrapper.cpp
 modules/gui/qt/menus/qml_menu_wrapper.hpp
+modules/gui/qt/network/qml/BrowseDeviceHeader.qml
+modules/gui/qt/network/qml/BrowseDeviceView.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/DiscoverUrlDisplay.qml
-modules/gui/qt/network/qml/NetworkBrowseDisplay.qml
 modules/gui/qt/network/qml/NetworkGridItem.qml
-modules/gui/qt/network/qml/NetworkHomeDisplay.qml
 modules/gui/qt/network/qml/NetworkListItem.qml
 modules/gui/qt/network/qml/ServicesHomeDisplay.qml
 modules/gui/qt/player/control_list_filter.cpp



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/b8a8ff969338da801a83304bb19c52ed12849a9b...8e16f8ddfc555d210acecc6b99da7fd6a38cc2c6

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/b8a8ff969338da801a83304bb19c52ed12849a9b...8e16f8ddfc555d210acecc6b99da7fd6a38cc2c6
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