QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCFileDialogController.cc
Go to the documentation of this file.
3#include "SettingsManager.h"
4#include "AppSettings.h"
5
6#include <QtCore/QDir>
7
8#ifdef Q_OS_ANDROID
9#include <QtCore/QPointer>
10#include "AndroidInterface.h"
11#endif
12
13QGC_LOGGING_CATEGORY(QGCFileDialogControllerLog, "QMLControls.QGCFileDialogController")
14
15#ifdef QGC_UNITTEST_BUILD
16static bool s_testHookArmed = false;
17static QString s_testNextFile;
18
20{
21 return s_testHookArmed;
22}
23
25{
26 s_testHookArmed = false;
27 const QString file = s_testNextFile;
28 s_testNextFile.clear();
29 return file;
30}
31
32void QGCFileDialogController::setTestNextFileForAccept(const QString &file)
33{
34 s_testHookArmed = true;
35 s_testNextFile = file;
36}
37
38void QGCFileDialogController::setTestRejectNext()
39{
40 s_testHookArmed = true;
41 s_testNextFile.clear();
42}
43#else
45{
46 return false;
47}
48
50{
51 return QString();
52}
53#endif
54
56 : QObject(parent)
57{
58 qCDebug(QGCFileDialogControllerLog) << this;
59}
60
62{
63 qCDebug(QGCFileDialogControllerLog) << this;
64}
65
66QStringList QGCFileDialogController::getFiles(const QString &directoryPath, const QStringList &nameFilters)
67{
68 qCDebug(QGCFileDialogControllerLog) << "getFiles" << directoryPath << nameFilters;
69
70 QDir fileDir(directoryPath);
71 const QFileInfoList fileInfoList = fileDir.entryInfoList(nameFilters, QDir::Files, QDir::Name);
72
73 QStringList files;
74 for (const QFileInfo &fileInfo: fileInfoList) {
75 qCDebug(QGCFileDialogControllerLog) << "getFiles found" << fileInfo.fileName();
76 files << fileInfo.fileName();
77 }
78
79 return files;
80}
81
82bool QGCFileDialogController::fileExists(const QString &filename)
83{
84 return QFile(filename).exists();
85}
86
87QString QGCFileDialogController::fullyQualifiedFilename(const QString& directoryPath, const QString& filename, const QStringList& nameFilters)
88{
89 QString firstFileExtention;
90
91 // Check that the filename has one of the specified file extensions
92
93 bool extensionFound = true;
94 if (nameFilters.count()) {
95 extensionFound = false;
96 for (const QString& nameFilter: nameFilters) {
97 if (nameFilter.startsWith("*.")) {
98 const QString fileExtension = nameFilter.right(nameFilter.length() - 2);
99 if (fileExtension != "*") {
100 if (firstFileExtention.isEmpty()) {
101 firstFileExtention = fileExtension;
102 }
103 if (filename.endsWith(fileExtension)) {
104 extensionFound = true;
105 break;
106 }
107 }
108 } else if (nameFilter != "*") {
109 qCWarning(QGCFileDialogControllerLog) << "unsupported name filter format" << nameFilter;
110 }
111 }
112 }
113
114 // Add the extension if it is missing
115 QString filenameWithExtension = filename;
116 if (!extensionFound) {
117 filenameWithExtension = QStringLiteral("%1.%2").arg(filename).arg(firstFileExtention);
118 }
119
120 return (directoryPath + QStringLiteral("/") + filenameWithExtension);
121}
122
123void QGCFileDialogController::deleteFile(const QString &filename)
124{
125 QFile::remove(filename);
126}
127
129{
130#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
131 const QString defaultSavePath = SettingsManager::instance()->appSettings()->savePath()->rawValueString();
132 if (fullFolderPath.startsWith(defaultSavePath)) {
133 const int lastDirSepIndex = fullFolderPath.lastIndexOf(QStringLiteral("/"));
134 return (QCoreApplication::applicationName() + QStringLiteral("/") + fullFolderPath.right(fullFolderPath.length() - lastDirSepIndex));
135 }
136#else
137 qCWarning(QGCFileDialogControllerLog) << Q_FUNC_INFO << "should only be used in mobile builds";
138#endif
139 return fullFolderPath;
140}
141
143{
144 // For some strange reason on Qt6 running on Linux files returned by FileDialog are not returned as local file urls.
145 // Seems to be new behavior with Qt6.
146 QString result;
147 if (url.isLocalFile()) {
148 result = url.toLocalFile();
149 } else {
150 result = url.toString();
151 }
152
153#ifndef Q_OS_WIN
154 // Qt6 Linux FileDialog can return file URLs missing the third slash
155 // (file://path instead of file:///path). Qt parses that as host=first-segment,
156 // path=rest, and toLocalFile() yields "//host/rest" (UNC-style). On non-Windows
157 // platforms drop the spurious leading slash so it becomes a normal absolute path.
158 if (result.startsWith(QStringLiteral("//"))) {
159 result = result.mid(1);
160 }
161#endif
162
163 return result;
164}
165
167{
168#ifdef Q_OS_ANDROID
169 const QString missionPath = SettingsManager::instance()->appSettings()->missionSavePath();
170 if (missionPath.isEmpty()) {
171 qCWarning(QGCFileDialogControllerLog) << "Missions save path is empty";
172 emit importFailed(tr("Missions directory is not configured"));
173 return;
174 }
175
176 QDir dir(missionPath);
177 if (!dir.exists()) {
178 qCWarning(QGCFileDialogControllerLog) << "Missions save path does not exist";
179 emit importFailed(tr("Missions save path does not exist"));
180 return;
181 }
182
183 QPointer<QGCFileDialogController> self = this;
184 AndroidInterface::openFileImportDialog(missionPath, [self](const QString& filePath) {
185 if (self) {
186 QMetaObject::invokeMethod(
187 self,
188 [filePath, self]() { self->_handleImportResult(filePath); },
189 Qt::QueuedConnection);
190 }
191 });
192#else
193 qCWarning(QGCFileDialogControllerLog) << Q_FUNC_INFO << "only supported on Android";
194#endif
195}
196
197#ifdef Q_OS_ANDROID
198
199void QGCFileDialogController::_handleImportResult(const QString& filePath)
200{
201 if (filePath.isEmpty()) {
202 qCWarning(QGCFileDialogControllerLog) << "Import failed: empty file path received from Java";
203 emit importFailed(tr("Failed to import file"));
204 return;
205 }
206
207 qCDebug(QGCFileDialogControllerLog) << "File imported successfully to:" << filePath;
208 emit fileImported(filePath);
209}
210
211#endif // Q_OS_ANDROID
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QString missionSavePath()
void fileImported(const QString &filePath)
static Q_INVOKABLE bool fileExists(const QString &filename)
Check for file existence of specified fully qualified file name.
static Q_INVOKABLE QString fullFolderPathToShortMobilePath(const QString &fullFolderPath)
static Q_INVOKABLE QString takeTestNextFile()
Returns the armed file path (empty for reject) and disarms the hook.
static Q_INVOKABLE QString urlToLocalFile(QUrl url)
static Q_INVOKABLE bool testHookArmed()
Returns true if a test result has been armed for the next dialog open.
static Q_INVOKABLE void deleteFile(const QString &filename)
Deletes the file specified by the fully qualified file name.
static Q_INVOKABLE QStringList getFiles(const QString &directoryPath, const QStringList &nameFilters)
Return all file in the specified path which match the specified extension.
Q_INVOKABLE void importFromNativePicker()
static Q_INVOKABLE QString fullyQualifiedFilename(const QString &directoryPath, const QString &filename, const QStringList &nameFilters=QStringList())
void importFailed(const QString &errorMessage)
QGCFileDialogController(QObject *parent=nullptr)
static SettingsManager * instance()
AppSettings * appSettings() const
void openFileImportDialog(const QString &destPath, std::function< void(const QString &)> callback)