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