QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCFileDialog.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Dialogs
4import QtQuick.Layouts
5import Qt.labs.platform as Labs
6
7import QGroundControl
8import QGroundControl.Controls
9
10/// This control is meant to be a direct replacement for the standard Qml FileDialog control.
11/// It differs for mobile builds which uses a completely custom file picker.
12Item {
13 id: _root
14 visible: false
15
16 property string folder // Due to Qt bug with file url parsing this must be an absolute path
17 property var nameFilters: [] // Important: Only name filters with simple wildcarding like *.foo are supported.
18 property string title
19 property bool selectFolder: false
20 property string defaultSuffix: ""
21
22 signal acceptedForLoad(string file)
23 signal acceptedForSave(string file)
24 signal rejected
25 signal fileImportedNotify // Emitted on a successful import so the open dialog can refresh its list.
26
27 function openForLoad() {
28 _openForLoad = true
29 if (QGCFileDialogController.testHookArmed()) {
30 // Unit test shim: bypass the native dialog and produce the armed result
31 var testFile = QGCFileDialogController.takeTestNextFile()
32 if (testFile.length === 0) {
33 _root.rejected()
34 } else {
35 _root.acceptedForLoad(testFile)
36 }
37 } else if (_mobileDlg && folder.length !== 0) {
38 mobileFileOpenDialogFactory.open()
39 } else if (selectFolder) {
40 fullFolderDialog.open()
41 } else {
42 fullFileDialog.fileMode = FileDialog.OpenFile
43 fullFileDialog.open()
44 }
45 }
46
47 function openForSave() {
48 _openForLoad = false
49 if (QGCFileDialogController.testHookArmed()) {
50 // Unit test shim: bypass the native dialog and produce the armed result
51 var testFile = QGCFileDialogController.takeTestNextFile()
52 if (testFile.length === 0) {
53 _root.rejected()
54 } else {
55 _root.acceptedForSave(testFile)
56 }
57 } else if (_mobileDlg && folder.length !== 0) {
58 mobileFileSaveDialogFactory.open()
59 } else {
60 fullFileDialog.fileMode = FileDialog.SaveFile
61 fullFileDialog.open()
62 }
63 }
64
65 function close() {
66 fullFileDialog.close()
67 }
68
69 property bool _openForLoad: true
70 property real _margins: ScreenTools.defaultFontPixelHeight / 2
71 property bool _mobileDlg: QGroundControl.corePlugin.options.useMobileFileDialog
72 property var _rgExtensions
73 property string _mobileShortPath
74 property bool _importPending: false
75
76 Component.onCompleted: {
77 _setupFileExtensions()
78 _updateMobileShortPath()
79 }
80
81 onFolderChanged: _updateMobileShortPath()
82 onNameFiltersChanged: _setupFileExtensions()
83
84 function _updateMobileShortPath() {
85 if (ScreenTools.isMobile) {
86 _mobileShortPath = QGCFileDialogController.fullFolderPathToShortMobilePath(folder);
87 }
88 }
89
90 function _setupFileExtensions() {
91 _rgExtensions = [ ]
92 for (var i=0; i<_root.nameFilters.length; i++) {
93 var filter = _root.nameFilters[i]
94 var regExp = /^.*\‍((.*)\‍)$/
95 var result = regExp.exec(filter)
96 if (result.length === 2) {
97 filter = result[1]
98 }
99 var rgFilters = filter.split(" ")
100 for (var j=0; j<rgFilters.length; j++) {
101 if (!_mobileDlg || (rgFilters[j] !== "*" && rgFilters[j] !== "*.*")) {
102 _rgExtensions.push(rgFilters[j])
103 }
104 }
105 }
106 }
107
108 QGCPalette { id: qgcPal; colorGroupEnabled: true }
109
110 Connections {
111 target: QGCFileDialogController
112 enabled: Qt.platform.os === "android" && _root._importPending
113
114 function onFileImported() {
115 _root._importPending = false
116 _root.fileImportedNotify()
117 }
118
119 function onImportFailed(errorMessage) {
120 _root._importPending = false
121 QGroundControl.showMessageDialog(_root, qsTr("Import"), errorMessage)
122 }
123 }
124
125 FileDialog {
126 id: fullFileDialog
127 currentFolder: "file:///" + _root.folder
128 nameFilters: _root.nameFilters ? _root.nameFilters : []
129 title: _root.title
130 defaultSuffix: _root.defaultSuffix
131
132 onAccepted: {
133 var fullPath = QGCFileDialogController.urlToLocalFile(selectedFile)
134 if (fileMode == FileDialog.OpenFile) {
135 _root.acceptedForLoad(fullPath)
136 } else {
137 _root.acceptedForSave(fullPath)
138 }
139 }
140 onRejected: _root.rejected()
141 }
142
143 Labs.FolderDialog {
144 id: fullFolderDialog
145 currentFolder: "file:///" + _root.folder
146 title: _root.title
147
148 onAccepted: _root.acceptedForLoad(QGCFileDialogController.urlToLocalFile(folder))
149 onRejected: _root.rejected()
150 }
151
152 QGCPopupDialogFactory {
153 id: mobileFileOpenDialogFactory
154
155 dialogComponent: mobileFileOpenDialogComponent
156 }
157
158 Component {
159 id: mobileFileOpenDialogComponent
160
161 QGCPopupDialog {
162 id: mobileFileOpenDialog
163 title: _root.title
164 buttons: Dialog.Cancel
165
166 Connections {
167 target: _root
168 function onFileImportedNotify() {
169 fileRepeater.model = QGCFileDialogController.getFiles(folder, _rgExtensions)
170 }
171 }
172
173 Column {
174 id: fileOpenColumn
175 width: 40 * ScreenTools.defaultFontPixelWidth
176 spacing: ScreenTools.defaultFontPixelHeight / 2
177
178 QGCLabel { text: qsTr("Path: %1").arg(_mobileShortPath) }
179
180 Repeater {
181 id: fileRepeater
182 model: QGCFileDialogController.getFiles(folder, _rgExtensions)
183
184 FileButton {
185 id: fileButton
186 anchors.left: parent.left
187 anchors.right: parent.right
188 text: modelData
189
190 onClicked: {
191 mobileFileOpenDialog.close()
192 _root.acceptedForLoad(QGCFileDialogController.fullyQualifiedFilename(folder, modelData))
193 }
194
195 onHamburgerClicked: {
196 highlight = true
197 hamburgerMenu.fileToDelete = QGCFileDialogController.fullyQualifiedFilename(folder, modelData)
198 hamburgerMenu.popup()
199 }
200
201 QGCMenu {
202 id: hamburgerMenu
203
204 property string fileToDelete
205
206 onAboutToHide: fileButton.highlight = false
207
208 QGCMenuItem {
209 text: qsTr("Delete")
210 onTriggered: {
211 QGCFileDialogController.deleteFile(hamburgerMenu.fileToDelete)
212 fileRepeater.model = QGCFileDialogController.getFiles(folder, _rgExtensions)
213 }
214 }
215 }
216 }
217 }
218
219 QGCLabel {
220 text: qsTr("No files")
221 visible: fileRepeater.model.length === 0
222 }
223
224 QGCButton {
225 anchors.left: parent.left
226 anchors.right: parent.right
227 text: qsTr("Import")
228 visible: Qt.platform.os === "android"
229
230 onClicked: {
231 _root._importPending = true
232 QGCFileDialogController.importFromNativePicker()
233 }
234 }
235 }
236 }
237 }
238
239 QGCPopupDialogFactory {
240 id: mobileFileSaveDialogFactory
241
242 dialogComponent: mobileFileSaveDialogComponent
243 }
244
245 Component {
246 id: mobileFileSaveDialogComponent
247
248 QGCPopupDialog {
249 id: mobileFileSaveDialog
250 title: _root.title
251 buttons: Dialog.Cancel | Dialog.Ok
252
253 onAccepted: {
254 if (filenameTextField.text == "") {
255 mobileFileSaveDialog.preventClose = true
256 return
257 }
258 if (!replaceMessage.visible) {
259 if (QGCFileDialogController.fileExists(QGCFileDialogController.fullyQualifiedFilename(folder, filenameTextField.text, _rgExtensions))) {
260 replaceMessage.visible = true
261 mobileFileSaveDialog.preventClose = true
262 return
263 }
264 }
265 _root.acceptedForSave(QGCFileDialogController.fullyQualifiedFilename(folder, filenameTextField.text, _rgExtensions))
266 }
267
268 Column {
269 id: fileSaveColumn
270 width: 40 * ScreenTools.defaultFontPixelWidth
271 spacing: ScreenTools.defaultFontPixelHeight / 2
272
273 RowLayout {
274 anchors.left: parent.left
275 anchors.right: parent.right
276 spacing: ScreenTools.defaultFontPixelWidth
277
278 QGCLabel { text: qsTr("New file name:") }
279
280 QGCTextField {
281 id: filenameTextField
282 Layout.fillWidth: true
283 onTextChanged: replaceMessage.visible = false
284 }
285 }
286
287 QGCLabel {
288 id: replaceMessage
289 anchors.left: parent.left
290 anchors.right: parent.right
291 wrapMode: Text.WordWrap
292 text: qsTr("The file %1 exists. Click Save again to replace it.").arg(filenameTextField.text)
293 visible: false
294 color: qgcPal.warningText
295 }
296
297 SectionHeader {
298 anchors.left: parent.left
299 anchors.right: parent.right
300 text: qsTr("Save to existing file:")
301 }
302
303 Repeater {
304 id: fileRepeater
305 model: QGCFileDialogController.getFiles(folder, [ _rgExtensions ])
306
307 FileButton {
308 id: fileButton
309 anchors.left: parent.left
310 anchors.right: parent.right
311 text: modelData
312
313 onClicked: {
314 mobileFileSaveDialog.close()
315 _root.acceptedForSave(QGCFileDialogController.fullyQualifiedFilename(folder, modelData))
316 }
317
318 onHamburgerClicked: {
319 highlight = true
320 hamburgerMenu.fileToDelete = QGCFileDialogController.fullyQualifiedFilename(folder, modelData)
321 hamburgerMenu.popup()
322 }
323
324 QGCMenu {
325 id: hamburgerMenu
326
327 property string fileToDelete
328
329 onAboutToHide: fileButton.highlight = false
330
331 QGCMenuItem {
332 text: qsTr("Delete")
333 onTriggered: {
334 QGCFileDialogController.deleteFile(hamburgerMenu.fileToDelete)
335 fileRepeater.model = QGCFileDialogController.getFiles(folder, [ _rgExtensions ])
336 }
337 }
338 }
339 }
340 }
341 }
342 }
343 }
344}