|  | /**************************************************************************** | 
|  | ** | 
|  | ** Copyright (C) 2016 The Qt Company Ltd. | 
|  | ** Contact: https://www.qt.io/licensing/ | 
|  | ** | 
|  | ** This file is part of the Qt Quick Dialogs module of the Qt Toolkit. | 
|  | ** | 
|  | ** $QT_BEGIN_LICENSE:LGPL$ | 
|  | ** Commercial License Usage | 
|  | ** Licensees holding valid commercial Qt licenses may use this file in | 
|  | ** accordance with the commercial license agreement provided with the | 
|  | ** Software or, alternatively, in accordance with the terms contained in | 
|  | ** a written agreement between you and The Qt Company. For licensing terms | 
|  | ** and conditions see https://www.qt.io/terms-conditions. For further | 
|  | ** information use the contact form at https://www.qt.io/contact-us. | 
|  | ** | 
|  | ** GNU Lesser General Public License Usage | 
|  | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
|  | ** General Public License version 3 as published by the Free Software | 
|  | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
|  | ** packaging of this file. Please review the following information to | 
|  | ** ensure the GNU Lesser General Public License version 3 requirements | 
|  | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
|  | ** | 
|  | ** GNU General Public License Usage | 
|  | ** Alternatively, this file may be used under the terms of the GNU | 
|  | ** General Public License version 2.0 or (at your option) the GNU General | 
|  | ** Public license version 3 or any later version approved by the KDE Free | 
|  | ** Qt Foundation. The licenses are as published by the Free Software | 
|  | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
|  | ** included in the packaging of this file. Please review the following | 
|  | ** information to ensure the GNU General Public License requirements will | 
|  | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
|  | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
|  | ** | 
|  | ** $QT_END_LICENSE$ | 
|  | ** | 
|  | ****************************************************************************/ | 
|  |  | 
|  | import QtQml 2.14 as Qml | 
|  | import QtQuick 2.2 | 
|  | import QtQuick.Controls 1.2 | 
|  | import QtQuick.Controls.Private 1.0 as ControlsPrivate | 
|  | import QtQuick.Dialogs 1.1 | 
|  | import QtQuick.Dialogs.Private 1.1 | 
|  | import QtQuick.Layouts 1.1 | 
|  | import QtQuick.Window 2.1 | 
|  | import Qt.labs.folderlistmodel 2.1 | 
|  | import Qt.labs.settings 1.0 | 
|  | import "qml" | 
|  |  | 
|  | AbstractFileDialog { | 
|  | id: root | 
|  |  | 
|  | property Component modelComponent: Component { | 
|  | FolderListModel { | 
|  | showFiles: !root.selectFolder | 
|  | nameFilters: root.selectedNameFilterExtensions | 
|  | sortField: (view.sortIndicatorColumn === 0 ? FolderListModel.Name : | 
|  | (view.sortIndicatorColumn === 1 ? FolderListModel.Type : | 
|  | (view.sortIndicatorColumn === 2 ? FolderListModel.Size : FolderListModel.LastModified))) | 
|  | sortReversed: view.sortIndicatorOrder === Qt.DescendingOrder | 
|  | } | 
|  | } | 
|  |  | 
|  | onVisibleChanged: { | 
|  | if (visible) { | 
|  | // If the TableView doesn't have a model yet, create it asynchronously to avoid a UI freeze | 
|  | if (!view.model) { | 
|  | var incubator = modelComponent.incubateObject(null, { }) | 
|  | function init(model) { | 
|  | view.model = model | 
|  | model.nameFilters = root.selectedNameFilterExtensions | 
|  | root.folder = model.folder | 
|  | } | 
|  |  | 
|  | if (incubator.status === Component.Ready) { | 
|  | init(incubator.object) | 
|  | } else { | 
|  | incubator.onStatusChanged = function(status) { | 
|  | if (status === Component.Ready) | 
|  | init(incubator.object) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | view.needsWidthAdjustment = true | 
|  | view.selection.clear() | 
|  | view.focus = true | 
|  | } | 
|  | } | 
|  |  | 
|  | Component.onCompleted: { | 
|  | filterField.currentIndex = root.selectedNameFilterIndex | 
|  | root.favoriteFolders = settings.favoriteFolders | 
|  | } | 
|  |  | 
|  | Component.onDestruction: { | 
|  | settings.favoriteFolders = root.favoriteFolders | 
|  | } | 
|  |  | 
|  | property Settings settings: Settings { | 
|  | category: "QQControlsFileDialog" | 
|  | property alias width: root.width | 
|  | property alias height: root.height | 
|  | property alias sidebarWidth: sidebar.width | 
|  | property alias sidebarSplit: shortcutsScroll.height | 
|  | property alias sidebarVisible: root.sidebarVisible | 
|  | property variant favoriteFolders: [] | 
|  | } | 
|  |  | 
|  | property bool showFocusHighlight: false | 
|  | property SystemPalette palette: SystemPalette { } | 
|  | property var favoriteFolders: [] | 
|  |  | 
|  | function dirDown(path) { | 
|  | view.selection.clear() | 
|  | root.folder = "file://" + path | 
|  | } | 
|  | function dirUp() { | 
|  | view.selection.clear() | 
|  | if (view.model.parentFolder != "") | 
|  | root.folder = view.model.parentFolder | 
|  | } | 
|  | function acceptSelection() { | 
|  | // transfer the view's selections to QQuickFileDialog | 
|  | clearSelection() | 
|  | if (selectFolder && view.selection.count === 0) | 
|  | addSelection(folder) | 
|  | else { | 
|  | view.selection.forEach(function(idx) { | 
|  | if (view.model.isFolder(idx)) { | 
|  | if (selectFolder) | 
|  | addSelection(view.model.get(idx, "fileURL")) | 
|  | } else { | 
|  | if (!selectFolder) | 
|  | addSelection(view.model.get(idx, "fileURL")) | 
|  | } | 
|  | }) | 
|  | } | 
|  | accept() | 
|  | } | 
|  |  | 
|  | property Action dirUpAction: Action { | 
|  | text: "\ue810" | 
|  | shortcut: "Ctrl+U" | 
|  | onTriggered: dirUp() | 
|  | tooltip: qsTr("Go up to the folder containing this one") | 
|  | } | 
|  |  | 
|  | Rectangle { | 
|  | id: window | 
|  | implicitWidth: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 100, splitter.implicitWidth)) | 
|  | implicitHeight: Math.min(root.__maximumDimension, Screen.pixelDensity * 80) | 
|  | color: root.palette.window | 
|  |  | 
|  | Qml.Binding { | 
|  | target: view.model | 
|  | property: "folder" | 
|  | value: root.folder | 
|  | restoreMode: Binding.RestoreBinding | 
|  | } | 
|  | Qml.Binding { | 
|  | target: currentPathField | 
|  | property: "text" | 
|  | value: root.urlToPath(root.folder) | 
|  | restoreMode: Binding.RestoreBinding | 
|  | } | 
|  | Keys.onPressed: { | 
|  | event.accepted = true | 
|  | switch (event.key) { | 
|  | case Qt.Key_Back: | 
|  | case Qt.Key_Escape: | 
|  | reject() | 
|  | break | 
|  | default: | 
|  | event.accepted = false | 
|  | break | 
|  | } | 
|  | } | 
|  | Keys.forwardTo: [view.flickableItem] | 
|  |  | 
|  | SplitView { | 
|  | id: splitter | 
|  | x: 0 | 
|  | width: parent.width | 
|  | anchors.top: titleBar.bottom | 
|  | anchors.bottom: bottomBar.top | 
|  |  | 
|  | Column { | 
|  | id: sidebar | 
|  | Component.onCompleted: if (width < 1) width = sidebarSplitter.maxShortcutWidth | 
|  | height: parent.height | 
|  | width: 0 // initial width only; settings and onCompleted will override it | 
|  | visible: root.sidebarVisible | 
|  | SplitView { | 
|  | id: sidebarSplitter | 
|  | orientation: Qt.Vertical | 
|  | property real rowHeight: 10 | 
|  | property real maxShortcutWidth: 80 | 
|  | width: parent.width | 
|  | height: parent.height - favoritesButtons.height | 
|  |  | 
|  | ScrollView { | 
|  | id: shortcutsScroll | 
|  | Component.onCompleted: { | 
|  | if (height < 1) | 
|  | height = sidebarSplitter.rowHeight * 4.65 | 
|  | Layout.minimumHeight = sidebarSplitter.rowHeight * 2.65 | 
|  | } | 
|  | height: 0 // initial width only; settings and onCompleted will override it | 
|  | ListView { | 
|  | id: shortcutsView | 
|  | model: __shortcuts.length | 
|  | anchors.bottomMargin: ControlsPrivate.Settings.hasTouchScreen ? Screen.pixelDensity * 3.5 : anchors.margins | 
|  | implicitHeight: model.count * sidebarSplitter.rowHeight | 
|  | delegate: Item { | 
|  | id: shortcutItem | 
|  | width: sidebarSplitter.width | 
|  | height: shortcutLabel.implicitHeight * 1.5 | 
|  | Text { | 
|  | id: shortcutLabel | 
|  | text: __shortcuts[index].name | 
|  | anchors { | 
|  | verticalCenter: parent.verticalCenter | 
|  | left: parent.left | 
|  | right: parent.right | 
|  | margins: 4 | 
|  | } | 
|  | elide: Text.ElideLeft | 
|  | renderType: ControlsPrivate.Settings.isMobile ? Text.QtRendering : Text.NativeRendering | 
|  | Component.onCompleted: { | 
|  | sidebarSplitter.rowHeight = parent.height | 
|  | if (implicitWidth * 1.2 > sidebarSplitter.maxShortcutWidth) | 
|  | sidebarSplitter.maxShortcutWidth = implicitWidth * 1.2 | 
|  | } | 
|  | } | 
|  | MouseArea { | 
|  | anchors.fill: parent | 
|  | onClicked: root.folder = __shortcuts[index].url | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | ScrollView { | 
|  | Layout.minimumHeight: sidebarSplitter.rowHeight * 2.5 | 
|  | ListView { | 
|  | id: favorites | 
|  | model: root.favoriteFolders | 
|  | anchors.topMargin: ControlsPrivate.Settings.hasTouchScreen ? Screen.pixelDensity * 3.5 : anchors.margins | 
|  | delegate: Item { | 
|  | width: favorites.width | 
|  | height: folderLabel.implicitHeight * 1.5 | 
|  | Text { | 
|  | id: folderLabel | 
|  | text: root.favoriteFolders[index] | 
|  | anchors { | 
|  | verticalCenter: parent.verticalCenter | 
|  | left: parent.left | 
|  | right: parent.right | 
|  | margins: 4 | 
|  | } | 
|  | elide: Text.ElideLeft | 
|  | renderType: ControlsPrivate.Settings.isMobile ? Text.QtRendering : Text.NativeRendering | 
|  | } | 
|  | Menu { | 
|  | id: favoriteCtxMenu | 
|  | title: root.favoriteFolders[index] | 
|  | MenuItem { | 
|  | text: qsTr("Remove favorite") | 
|  | onTriggered: { | 
|  | root.favoriteFolders.splice(index, 1) | 
|  | favorites.model = root.favoriteFolders | 
|  | } | 
|  | } | 
|  | } | 
|  | MouseArea { | 
|  | id: favoriteArea | 
|  | anchors.fill: parent | 
|  | acceptedButtons: Qt.LeftButton | Qt.RightButton | 
|  | hoverEnabled: true | 
|  | onClicked: { | 
|  | if (mouse.button == Qt.LeftButton) | 
|  | root.folder = root.favoriteFolders[index] | 
|  | else if (mouse.button == Qt.RightButton) | 
|  | favoriteCtxMenu.popup() | 
|  | } | 
|  | onExited: ControlsPrivate.Tooltip.hideText() | 
|  | onCanceled: ControlsPrivate.Tooltip.hideText() | 
|  | Timer { | 
|  | interval: 1000 | 
|  | running: favoriteArea.containsMouse && !favoriteArea.pressed && folderLabel.truncated | 
|  | onTriggered: ControlsPrivate.Tooltip.showText(favoriteArea, | 
|  | Qt.point(favoriteArea.mouseX, favoriteArea.mouseY), urlToPath(root.favoriteFolders[index])) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | Row { | 
|  | id: favoritesButtons | 
|  | height: plusButton.height + 1 | 
|  | anchors.right: parent.right | 
|  | anchors.rightMargin: 6 | 
|  | layoutDirection: Qt.RightToLeft | 
|  | Button { | 
|  | id: plusButton | 
|  | style: IconButtonStyle { } | 
|  | text: "\ue83e" | 
|  | tooltip: qsTr("Add the current directory as a favorite") | 
|  | width: height | 
|  | onClicked: { | 
|  | root.favoriteFolders.push(root.folder) | 
|  | favorites.model = root.favoriteFolders | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TableView { | 
|  | id: view | 
|  | sortIndicatorVisible: true | 
|  | Layout.fillWidth: true | 
|  | Layout.minimumWidth: 40 | 
|  | property bool needsWidthAdjustment: true | 
|  | selectionMode: root.selectMultiple ? | 
|  | (ControlsPrivate.Settings.hasTouchScreen ? SelectionMode.MultiSelection : SelectionMode.ExtendedSelection) : | 
|  | SelectionMode.SingleSelection | 
|  | onRowCountChanged: if (needsWidthAdjustment && rowCount > 0) { | 
|  | resizeColumnsToContents() | 
|  | needsWidthAdjustment = false | 
|  | } | 
|  | model: null | 
|  |  | 
|  | onActivated: if (view.focus) { | 
|  | if (view.selection.count > 0 && view.model.isFolder(row)) { | 
|  | dirDown(view.model.get(row, "filePath")) | 
|  | } else { | 
|  | root.acceptSelection() | 
|  | } | 
|  | } | 
|  | onClicked: currentPathField.text = view.model.get(row, "filePath") | 
|  |  | 
|  |  | 
|  | TableViewColumn { | 
|  | id: fileNameColumn | 
|  | role: "fileName" | 
|  | title: qsTr("Filename") | 
|  | delegate: Item { | 
|  | implicitWidth: pathText.implicitWidth + pathText.anchors.leftMargin + pathText.anchors.rightMargin | 
|  | IconGlyph { | 
|  | id: fileIcon | 
|  | x: 4 | 
|  | height: parent.height - 2 | 
|  | unicode: view.model.isFolder(styleData.row) ? "\ue804" : "\ue802" | 
|  | } | 
|  | Text { | 
|  | id: pathText | 
|  | text: styleData.value | 
|  | anchors { | 
|  | left: parent.left | 
|  | right: parent.right | 
|  | leftMargin: fileIcon.width + 6 | 
|  | rightMargin: 4 | 
|  | verticalCenter: parent.verticalCenter | 
|  | } | 
|  | color: styleData.textColor | 
|  | elide: Text.ElideRight | 
|  | renderType: ControlsPrivate.Settings.isMobile ? Text.QtRendering : Text.NativeRendering | 
|  | } | 
|  | } | 
|  | } | 
|  | TableViewColumn { | 
|  | role: "fileSuffix" | 
|  | title: qsTr("Type", "file type (extension)") | 
|  | // TODO should not need to create a whole new component just to customize the text value | 
|  | // something like textFormat: function(text) { return view.model.get(styleData.row, "fileIsDir") ? "folder" : text } | 
|  | delegate: Item { | 
|  | implicitWidth: sizeText.implicitWidth + sizeText.anchors.leftMargin + sizeText.anchors.rightMargin | 
|  | Text { | 
|  | id: sizeText | 
|  | text: view.model.get(styleData.row, "fileIsDir") ? "folder" : styleData.value | 
|  | anchors { | 
|  | left: parent.left | 
|  | right: parent.right | 
|  | leftMargin: 4 | 
|  | rightMargin: 4 | 
|  | verticalCenter: parent.verticalCenter | 
|  | } | 
|  | color: styleData.textColor | 
|  | elide: Text.ElideRight | 
|  | renderType: ControlsPrivate.Settings.isMobile ? Text.QtRendering : Text.NativeRendering | 
|  | } | 
|  | } | 
|  | } | 
|  | TableViewColumn { | 
|  | role: "fileSize" | 
|  | title: qsTr("Size", "file size") | 
|  | horizontalAlignment: Text.AlignRight | 
|  | } | 
|  | TableViewColumn { id: modifiedColumn; role: "fileModified" ; title: qsTr("Modified", "last-modified time") } | 
|  | TableViewColumn { id: accessedColumn; role: "fileAccessed" ; title: qsTr("Accessed", "last-accessed time") } | 
|  | } | 
|  | } | 
|  |  | 
|  | ToolBar { | 
|  | id: titleBar | 
|  | RowLayout { | 
|  | anchors.fill: parent | 
|  | ToolButton { | 
|  | action: dirUpAction | 
|  | style: IconButtonStyle { } | 
|  | Layout.maximumWidth: height * 1.5 | 
|  | } | 
|  | TextField { | 
|  | id: currentPathField | 
|  | Layout.fillWidth: true | 
|  | function doAccept() { | 
|  | root.clearSelection() | 
|  | if (root.addSelection(root.pathToUrl(text))) | 
|  | root.accept() | 
|  | else | 
|  | root.folder = root.pathFolder(text) | 
|  | } | 
|  | onAccepted: doAccept() | 
|  | } | 
|  | } | 
|  | } | 
|  | Item { | 
|  | id: bottomBar | 
|  | width: parent.width | 
|  | height: buttonRow.height + buttonRow.spacing * 2 | 
|  | anchors.bottom: parent.bottom | 
|  |  | 
|  | Row { | 
|  | id: buttonRow | 
|  | anchors.right: parent.right | 
|  | anchors.rightMargin: spacing | 
|  | anchors.verticalCenter: parent.verticalCenter | 
|  | spacing: 4 | 
|  | Button { | 
|  | id: toggleSidebarButton | 
|  | checkable: true | 
|  | style: IconButtonStyle { } | 
|  | text: "\u25E7" | 
|  | height: cancelButton.height | 
|  | width: height | 
|  | checked: root.sidebarVisible | 
|  | onClicked: { | 
|  | root.sidebarVisible = !root.sidebarVisible | 
|  | } | 
|  | } | 
|  | ComboBox { | 
|  | id: filterField | 
|  | model: root.nameFilters | 
|  | visible: !selectFolder | 
|  | width: bottomBar.width - toggleSidebarButton.width - cancelButton.width - okButton.width - parent.spacing * 6 | 
|  | anchors.verticalCenter: parent.verticalCenter | 
|  | onCurrentTextChanged: { | 
|  | root.selectNameFilter(currentText) | 
|  | if (view.model) | 
|  | view.model.nameFilters = root.selectedNameFilterExtensions | 
|  | } | 
|  | } | 
|  | Button { | 
|  | id: cancelButton | 
|  | text: qsTr("Cancel") | 
|  | onClicked: root.reject() | 
|  | } | 
|  | Button { | 
|  | id: okButton | 
|  | text: root.selectFolder ? qsTr("Choose") : (selectExisting ? qsTr("Open") : qsTr("Save")) | 
|  | onClicked: { | 
|  | if (view.model.isFolder(view.currentRow) && !selectFolder) | 
|  | dirDown(view.model.get(view.currentRow, "filePath")) | 
|  | else if (!(root.selectExisting)) | 
|  | currentPathField.doAccept() | 
|  | else | 
|  | root.acceptSelection() | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |