mirror of https://code.videolan.org/videolan/vlc
512 lines
21 KiB
QML
512 lines
21 KiB
QML
/*****************************************************************************
|
|
* Copyright (C) 2019 VLC authors and VideoLAN
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* ( at your option ) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
import QtQuick 2.11
|
|
import QtQuick.Controls 2.4
|
|
import QtQuick.Templates 2.4 as T
|
|
import QtQuick.Layouts 1.11
|
|
import QtGraphicalEffects 1.0
|
|
import QtQml.Models 2.11
|
|
import QtQuick.Window 2.11
|
|
|
|
import org.videolan.vlc 0.1
|
|
import org.videolan.compat 0.1
|
|
|
|
import "qrc:///style/"
|
|
import "qrc:///playlist/" as PL
|
|
import "qrc:///widgets/" as Widgets
|
|
import "qrc:///menus/" as Menus
|
|
import "qrc:///util/Helpers.js" as Helpers
|
|
|
|
FocusScope {
|
|
id: root
|
|
|
|
height: VLCStyle.applicationVerticalMargin
|
|
+ (menubar.visible ? menubar.height : 0)
|
|
+ VLCStyle.globalToolbar_height
|
|
+ VLCStyle.localToolbar_height
|
|
|
|
property int selectedIndex: 0
|
|
property int subSelectedIndex: 0
|
|
property alias sortMenu: sortControl.menu
|
|
property alias sortModel: sortControl.model
|
|
property var contentModel
|
|
property alias isViewMultiView: list_grid_btn.visible
|
|
property alias model: pLBannerSources.model
|
|
property var extraLocalActions: undefined
|
|
property alias localMenuDelegate: localMenuGroup.sourceComponent
|
|
|
|
// For now, used for d&d functionality
|
|
// Not strictly necessary to set
|
|
property PL.PlaylistListView plListView: null
|
|
|
|
property bool _showCSD: MainCtx.clientSideDecoration && !(MainCtx.intfMainWindow.visibility === Window.FullScreen)
|
|
|
|
signal itemClicked(int index)
|
|
signal toogleMenu()
|
|
|
|
|
|
// Triggered when the toogleView button is selected
|
|
function toggleView () {
|
|
MainCtx.gridView = !MainCtx.gridView
|
|
}
|
|
|
|
function search() {
|
|
searchBox.state = "expanded"
|
|
}
|
|
|
|
ColorContext {
|
|
id: theme
|
|
colorSet: ColorContext.Window
|
|
}
|
|
|
|
BindingCompat {
|
|
property: "searchPattern"
|
|
value: searchBox.searchPattern
|
|
when: !!contentModel
|
|
|
|
Component.onCompleted: {
|
|
// restoreMode is only available in Qt >= 5.14
|
|
if ("restoreMode" in this)
|
|
this.restoreMode = Binding.RestoreBindingOrValue
|
|
|
|
target = Qt.binding(function() { return !!contentModel ? contentModel : null })
|
|
}
|
|
}
|
|
|
|
Widgets.AcrylicBackground {
|
|
tintColor: theme.bg.primary
|
|
alternativeColor: theme.bg.secondary
|
|
anchors.fill: parent
|
|
}
|
|
|
|
|
|
MouseArea {
|
|
// don't tranfer mouse to underlying components (#26274)
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
preventStealing: true
|
|
}
|
|
|
|
Item {
|
|
id: pLBannerSources
|
|
|
|
property alias model: globalMenuGroup.model
|
|
|
|
anchors.fill: parent
|
|
|
|
Column {
|
|
id: col
|
|
anchors {
|
|
fill: parent
|
|
topMargin: VLCStyle.applicationVerticalMargin
|
|
}
|
|
|
|
Item {
|
|
id: globalToolbar
|
|
width: parent.width
|
|
height: VLCStyle.globalToolbar_height
|
|
+ (menubar.visible ? menubar.height : 0)
|
|
anchors.rightMargin: VLCStyle.applicationHorizontalMargin
|
|
|
|
property bool colapseTabButtons: globalToolbar.width > (Math.max(globalToolbarLeft.width, globalToolbarRight.width) + VLCStyle.applicationHorizontalMargin)* 2
|
|
+ globalMenuGroup.model.count * VLCStyle.bannerTabButton_width_large
|
|
|
|
//drag and dbl click the titlebar in CSD mode
|
|
Loader {
|
|
anchors.fill: parent
|
|
active: root._showCSD
|
|
source: "qrc:///widgets/CSDTitlebarTapNDrapHandler.qml"
|
|
}
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: VLCStyle.applicationHorizontalMargin
|
|
anchors.rightMargin: VLCStyle.applicationHorizontalMargin
|
|
|
|
Menus.Menubar {
|
|
id: menubar
|
|
width: parent.width
|
|
height: implicitHeight
|
|
visible: MainCtx.hasToolbarMenu
|
|
}
|
|
|
|
Item {
|
|
width: parent.width
|
|
height: VLCStyle.globalToolbar_height
|
|
|
|
RowLayout {
|
|
id: globalToolbarLeft
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: VLCStyle.margin_xsmall
|
|
spacing: VLCStyle.margin_xxxsmall
|
|
|
|
Widgets.IconToolButton {
|
|
id: history_back
|
|
size: VLCStyle.icon_banner
|
|
iconText: VLCIcons.back
|
|
text: I18n.qtr("Previous")
|
|
height: VLCStyle.bannerButton_height
|
|
width: VLCStyle.bannerButton_width
|
|
onClicked: History.previous()
|
|
enabled: !History.previousEmpty
|
|
|
|
Navigation.parentItem: root
|
|
Navigation.rightItem: globalMenuGroup
|
|
Navigation.downItem: localMenuGroup.visible ? localMenuGroup : localToolbarBg
|
|
}
|
|
|
|
Widgets.BannerCone {
|
|
color: theme.accent
|
|
}
|
|
}
|
|
|
|
/* Button for the sources */
|
|
Widgets.NavigableRow {
|
|
id: globalMenuGroup
|
|
|
|
anchors {
|
|
top: parent.top
|
|
bottom: parent.bottom
|
|
horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
focus: true
|
|
|
|
indexFocus: root.selectedIndex
|
|
|
|
Navigation.parentItem: root
|
|
Navigation.leftItem: history_back.enabled ? history_back : null
|
|
Navigation.downItem: localMenuGroup.visible ? localMenuGroup : playlistGroup
|
|
|
|
delegate: Widgets.BannerTabButton {
|
|
iconTxt: model.icon
|
|
showText: globalToolbar.colapseTabButtons
|
|
selected: model.index === root.selectedIndex
|
|
onClicked: root.itemClicked(model.index)
|
|
height: globalMenuGroup.height
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: globalToolbarRight
|
|
anchors {
|
|
top: parent.top
|
|
right: parent.right
|
|
rightMargin: VLCStyle.applicationHorizontalMargin
|
|
}
|
|
height: VLCStyle.globalToolbar_height
|
|
active: root._showCSD
|
|
source: VLCStyle.palette.hasCSDImage
|
|
? "qrc:///widgets/CSDThemeButtonSet.qml"
|
|
: "qrc:///widgets/CSDWindowButtonSet.qml"
|
|
}
|
|
}
|
|
|
|
//use a raw control, we don't want platforms customisation
|
|
T.Control {
|
|
id: localToolbar
|
|
|
|
width: parent.width
|
|
height: VLCStyle.localToolbar_height
|
|
|
|
onActiveFocusChanged: {
|
|
if (activeFocus) {
|
|
// sometimes when view changes, one of the "focusable" object will become disabled
|
|
// but because of focus chainning, FocusScope still tries to force active focus on the object
|
|
// but that will fail, manually assign focus in such cases
|
|
var focusable = [localContextGroup, localMenuGroup, playlistGroup]
|
|
if (!focusable.some(function (obj) { return obj.activeFocus; })) {
|
|
// no object has focus
|
|
localToolbar.nextItemInFocusChain(true).forceActiveFocus()
|
|
}
|
|
}
|
|
}
|
|
|
|
background: Rectangle {
|
|
id: localToolbarBg
|
|
|
|
color: theme.bg.secondary
|
|
Rectangle {
|
|
anchors.left : parent.left
|
|
anchors.right: parent.right
|
|
anchors.top : parent.bottom
|
|
|
|
height: VLCStyle.border
|
|
|
|
color: theme.border
|
|
}
|
|
}
|
|
|
|
|
|
contentItem: Item {
|
|
id: localToolbarContent
|
|
|
|
Widgets.NavigableRow {
|
|
id: localContextGroup
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
left: parent.left
|
|
leftMargin: VLCStyle.applicationHorizontalMargin + VLCStyle.margin_xsmall
|
|
}
|
|
enabled: list_grid_btn.visible || sortControl.visible
|
|
|
|
model: ObjectModel {
|
|
id: localContextModel
|
|
|
|
property int countExtra: 0
|
|
|
|
Widgets.IconToolButton {
|
|
id: list_grid_btn
|
|
|
|
width: VLCStyle.bannerButton_width
|
|
height: VLCStyle.bannerButton_height
|
|
size: VLCStyle.icon_banner
|
|
iconText: MainCtx.gridView ? VLCIcons.list : VLCIcons.grid
|
|
text: I18n.qtr("List/Grid")
|
|
onClicked: MainCtx.gridView = !MainCtx.gridView
|
|
enabled: true
|
|
}
|
|
|
|
Widgets.SortControl {
|
|
id: sortControl
|
|
|
|
width: VLCStyle.bannerButton_width
|
|
height: VLCStyle.bannerButton_height
|
|
|
|
iconSize: VLCStyle.icon_banner
|
|
|
|
visible: root.sortModel !== undefined && root.sortModel.length > 1
|
|
|
|
enabled: visible
|
|
|
|
textRole: "text"
|
|
criteriaRole: "criteria"
|
|
|
|
sortKey: contentModel ? contentModel.sortCriteria
|
|
: PlaylistControllerModel.SORT_KEY_NONE
|
|
|
|
sortOrder: contentModel ? contentModel.sortOrder : undefined
|
|
|
|
onSortSelected: {
|
|
if (contentModel !== undefined)
|
|
contentModel.sortCriteria = key
|
|
}
|
|
|
|
onSortOrderSelected: {
|
|
if (contentModel !== undefined)
|
|
contentModel.sortOrder = type
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root
|
|
onExtraLocalActionsChanged : {
|
|
for (var i = 0; i < localContextModel.countExtra; i++) {
|
|
localContextModel.remove(localContextModel.count - localContextModel.countExtra, localContextModel.countExtra)
|
|
}
|
|
|
|
if (root.extraLocalActions && root.extraLocalActions instanceof ObjectModel) {
|
|
for (i = 0; i < root.extraLocalActions.count; i++)
|
|
localContextModel.append(root.extraLocalActions.get(i))
|
|
localContextModel.countExtra = root.extraLocalActions.count
|
|
} else {
|
|
localContextModel.countExtra = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Navigation.parentItem: root
|
|
Navigation.rightItem: localMenuGroup.visible ? localMenuGroup : playlistGroup
|
|
Navigation.upItem: globalMenuGroup
|
|
}
|
|
|
|
Flickable {
|
|
id: localMenuView
|
|
|
|
readonly property int availableWidth: parent.width
|
|
- (localContextGroup.width + playlistGroup.width)
|
|
- (VLCStyle.applicationHorizontalMargin * 2)
|
|
- (VLCStyle.margin_xsmall * 2)
|
|
- (VLCStyle.margin_xxsmall * 2)
|
|
readonly property bool _alignHCenter: ((localToolbarContent.width - contentWidth) / 2) + contentWidth < playlistGroup.x
|
|
|
|
width: Math.min(contentWidth, availableWidth)
|
|
height: VLCStyle.localToolbar_height
|
|
clip: contentWidth > width
|
|
contentWidth: localMenuGroup.width
|
|
contentHeight: VLCStyle.localToolbar_height // don't allow vertical flickering
|
|
|
|
anchors.right: playlistGroup.left
|
|
anchors.rightMargin: VLCStyle.margin_xxsmall // only applied when right aligned
|
|
|
|
ScrollBar.horizontal: ScrollBar { }
|
|
|
|
on_AlignHCenterChanged: {
|
|
if (_alignHCenter) {
|
|
anchors.horizontalCenter = localToolbarContent.horizontalCenter
|
|
anchors.right = undefined
|
|
} else {
|
|
anchors.horizontalCenter = undefined
|
|
anchors.right = playlistGroup.left
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: localMenuGroup
|
|
|
|
focus: true
|
|
visible: !!item
|
|
enabled: status === Loader.Ready
|
|
y: status === Loader.Ready ? (VLCStyle.localToolbar_height - item.height) / 2 : 0
|
|
width: !!item
|
|
? Helpers.clamp(localMenuView.availableWidth,
|
|
localMenuGroup.item.minimumWidth || localMenuGroup.item.implicitWidth,
|
|
localMenuGroup.item.maximumWidth || localMenuGroup.item.implicitWidth)
|
|
: 0
|
|
|
|
onVisibleChanged: {
|
|
//reset the focus on the global group when the local group is hidden,
|
|
//this avoids losing the focus if the subview changes
|
|
// FIXME: This block needs refactor for keyboard focus.
|
|
if (!visible && localMenuGroup.focus) {
|
|
localMenuGroup.focus = false
|
|
globalMenuGroup.focus = true
|
|
}
|
|
}
|
|
|
|
onItemChanged: {
|
|
if (!item)
|
|
return
|
|
item.Navigation.parentItem = root
|
|
item.Navigation.leftItem = Qt.binding(function(){ return localContextGroup.enabled ? localContextGroup : null})
|
|
item.Navigation.rightItem = Qt.binding(function(){ return playlistGroup.enabled ? playlistGroup : null})
|
|
item.Navigation.upItem = globalMenuGroup
|
|
}
|
|
}
|
|
}
|
|
|
|
Widgets.NavigableRow {
|
|
id: playlistGroup
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
right: parent.right
|
|
rightMargin: VLCStyle.applicationHorizontalMargin + VLCStyle.margin_xsmall
|
|
}
|
|
spacing: VLCStyle.margin_xxxsmall
|
|
|
|
model: ObjectModel {
|
|
|
|
Widgets.SearchBox {
|
|
id: searchBox
|
|
visible: !!root.contentModel
|
|
height: VLCStyle.bannerButton_height
|
|
buttonWidth: VLCStyle.bannerButton_width
|
|
}
|
|
|
|
Widgets.IconToolButton {
|
|
id: playlist_btn
|
|
|
|
checked: MainCtx.playlistVisible
|
|
|
|
size: VLCStyle.icon_banner
|
|
iconText: VLCIcons.playlist
|
|
text: I18n.qtr("Playlist")
|
|
width: VLCStyle.bannerButton_width
|
|
height: VLCStyle.bannerButton_height
|
|
highlighted: MainCtx.playlistVisible
|
|
|
|
onClicked: MainCtx.playlistVisible = !MainCtx.playlistVisible
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
|
|
onContainsDragChanged: {
|
|
if (containsDrag) {
|
|
_timer.restart()
|
|
|
|
if (plListView)
|
|
MainCtx.setCursor(Qt.DragCopyCursor)
|
|
} else {
|
|
_timer.stop()
|
|
|
|
if (plListView)
|
|
MainCtx.restoreCursor()
|
|
}
|
|
}
|
|
|
|
onEntered: {
|
|
if (drag.hasUrls || Helpers.isValidInstanceOf(drag.source, Widgets.DragItem)) {
|
|
drag.accept() // Not actually necessary, as it is accepted by default
|
|
} else {
|
|
drag.accepted = false
|
|
}
|
|
}
|
|
|
|
onDropped: {
|
|
if (plListView)
|
|
plListView.acceptDrop(plListView.model.count, drop)
|
|
}
|
|
|
|
Timer {
|
|
id: _timer
|
|
interval: VLCStyle.duration_humanMoment
|
|
|
|
onTriggered: {
|
|
MainCtx.playlistVisible = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Widgets.IconToolButton {
|
|
id: menu_selector
|
|
|
|
visible: !MainCtx.hasToolbarMenu
|
|
size: VLCStyle.icon_banner
|
|
iconText: VLCIcons.menu
|
|
text: I18n.qtr("Menu")
|
|
width: VLCStyle.bannerButton_width
|
|
height: VLCStyle.bannerButton_height
|
|
|
|
onClicked: contextMenu.popup(this.mapToGlobal(0, height))
|
|
|
|
QmlGlobalMenu {
|
|
id: contextMenu
|
|
ctx: MainCtx
|
|
}
|
|
}
|
|
}
|
|
|
|
Navigation.parentItem: root
|
|
Navigation.leftItem: localMenuGroup.visible ? localMenuGroup : localContextGroup
|
|
Navigation.upItem: globalMenuGroup
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Keys.priority: Keys.AfterItem
|
|
Keys.onPressed: root.Navigation.defaultKeyAction(event)
|
|
}
|
|
}
|