From 42c113161095983446d047d71e68bee93cde85de Mon Sep 17 00:00:00 2001
From: Don Gagne <don@thegagnes.com>
Date: Mon, 7 Mar 2016 13:19:14 -0800
Subject: [PATCH] New QGCMobileFileDialog

- Convert mission load/save to it
- Allow parameter load/save on mobile
---
 qgroundcontrol.pro                             |   2 +
 qgroundcontrol.qrc                             |   5 +-
 src/MissionEditor/MissionEditor.qml            |  46 +++------
 src/MissionManager/MissionController.cc        |  55 ++--------
 src/MissionManager/MissionController.h         |  11 +-
 src/QGCApplication.cc                          |  50 ++++++----
 src/QGCApplication.h                           |   4 +
 src/QGCMobileFileDialogController.cc           |  73 ++++++++++++++
 src/QGCMobileFileDialogController.h            |  49 +++++++++
 src/QmlControls/ParameterEditor.qml            |  58 ++++++++---
 src/QmlControls/ParameterEditorController.cc   |  54 +++++-----
 src/QmlControls/ParameterEditorController.h    |   8 +-
 src/QmlControls/QGCLabel.qml                   |   1 -
 src/QmlControls/QGCMobileFileDialog.qml        | 133 +++++++++++++++++++++++++
 src/QmlControls/QGroundControl.Controls.qmldir |   1 +
 src/QmlControls/QGroundControlQmlGlobal.h      |   8 ++
 16 files changed, 402 insertions(+), 156 deletions(-)
 create mode 100644 src/QGCMobileFileDialogController.cc
 create mode 100644 src/QGCMobileFileDialogController.h
 create mode 100644 src/QmlControls/QGCMobileFileDialog.qml

diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro
index 222329f..569526c 100644
--- a/qgroundcontrol.pro
+++ b/qgroundcontrol.pro
@@ -269,6 +269,7 @@ HEADERS += \
     src/QGCGeo.h \
     src/QGCLoggingCategory.h \
     src/QGCMapPalette.h \
+    src/QGCMobileFileDialogController.h \
     src/QGCPalette.h \
     src/QGCQmlWidgetHolder.h \
     src/QGCQuickWidget.h \
@@ -397,6 +398,7 @@ SOURCES += \
     src/QGCFileDownload.cc \
     src/QGCLoggingCategory.cc \
     src/QGCMapPalette.cc \
+    src/QGCMobileFileDialogController.cc \
     src/QGCPalette.cc \
     src/QGCQuickWidget.cc \
     src/QGCQmlWidgetHolder.cpp \
diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc
index e553d7f..3bee5c3 100644
--- a/qgroundcontrol.qrc
+++ b/qgroundcontrol.qrc
@@ -64,13 +64,14 @@
         <file alias="QGroundControl/Controls/QGCButton.qml">src/QmlControls/QGCButton.qml</file>
         <file alias="QGroundControl/Controls/QGCCheckBox.qml">src/QmlControls/QGCCheckBox.qml</file>
         <file alias="QGroundControl/Controls/QGCColoredImage.qml">src/QmlControls/QGCColoredImage.qml</file>
+        <file alias="QGroundControl/Controls/QGCComboBox.qml">src/QmlControls/QGCComboBox.qml</file>
         <file alias="QGroundControl/Controls/QGCFlickable.qml">src/QmlControls/QGCFlickable.qml</file>
-        <file alias="QGroundControl/Controls/QGCPipable.qml">src/QmlControls/QGCPipable.qml</file>
         <file alias="QGroundControl/Controls/QGCFlickableVerticalIndicator.qml">src/QmlControls/QGCFlickableVerticalIndicator.qml</file>
         <file alias="QGroundControl/Controls/QGCFlickableHorizontalIndicator.qml">src/QmlControls/QGCFlickableHorizontalIndicator.qml</file>
-        <file alias="QGroundControl/Controls/QGCComboBox.qml">src/QmlControls/QGCComboBox.qml</file>
         <file alias="QGroundControl/Controls/QGCLabel.qml">src/QmlControls/QGCLabel.qml</file>
+        <file alias="QGroundControl/Controls/QGCMobileFileDialog.qml">src/QmlControls/QGCMobileFileDialog.qml</file>
         <file alias="QGroundControl/Controls/QGCMovableItem.qml">src/QmlControls/QGCMovableItem.qml</file>
+        <file alias="QGroundControl/Controls/QGCPipable.qml">src/QmlControls/QGCPipable.qml</file>
         <file alias="QGroundControl/Controls/QGCRadioButton.qml">src/QmlControls/QGCRadioButton.qml</file>
         <file alias="QGroundControl/Controls/QGCTextField.qml">src/QmlControls/QGCTextField.qml</file>
         <file alias="QGroundControl/Controls/QGCToolBarButton.qml">src/QmlControls/QGCToolBarButton.qml</file>
diff --git a/src/MissionEditor/MissionEditor.qml b/src/MissionEditor/MissionEditor.qml
index 96d7a27..9b9a867 100644
--- a/src/MissionEditor/MissionEditor.qml
+++ b/src/MissionEditor/MissionEditor.qml
@@ -92,7 +92,7 @@ QGCView {
         if (ScreenTools.isMobile) {
             _root.showDialog(mobileFilePicker, "Select Mission File", _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
         } else {
-            controller.loadMissionFromFile()
+            controller.loadMissionFromFilePicker()
             fitViewportToMissionItems()
         }
     }
@@ -191,23 +191,13 @@ QGCView {
     Component {
         id: mobileFilePicker
 
-        QGCViewDialog {
-            ListView {
-                anchors.margins:    _margin
-                anchors.fill:       parent
-                spacing:            _margin / 2
-                orientation:    ListView.Vertical
-                model: controller.getMobileMissionFiles()
-
-                delegate: QGCButton {
-                    text: modelData
+        QGCMobileFileDialog {
+            openDialog:     true
+            fileExtension:  QGroundControl.missionFileExtension
 
-                    onClicked: {
-                        hideDialog()
-                        controller.loadMobileMissionFromFile(modelData)
-                        fitViewportToMissionItems()
-                    }
-                }
+            onFilenameReturned: {
+                controller.loadMissionFromFile(filename)
+                fitViewportToMissionItems()
             }
         }
     }
@@ -215,24 +205,12 @@ QGCView {
     Component {
         id: mobileFileSaver
 
-        QGCViewDialog {
-            function accept() {
-                hideDialog()
-                controller.saveMobileMissionToFile(filenameTextField.text)
-            }
-
-            Column {
-                anchors.left:   parent.left
-                anchors.right:  parent.right
-                spacing:        ScreenTools.defaultFontPixelHeight
+        QGCMobileFileDialog {
+            openDialog:     false
+            fileExtension:  QGroundControl.missionFileExtension
 
-                QGCLabel {
-                    text: "File name:"
-                }
-
-                QGCTextField {
-                    id: filenameTextField
-                }
+            onFilenameReturned: {
+                controller.saveMissionToFile(filename)
             }
         }
     }
diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc
index 12d0c46..ecbbfc5 100644
--- a/src/MissionManager/MissionController.cc
+++ b/src/MissionManager/MissionController.cc
@@ -422,7 +422,7 @@ bool MissionController::_loadTextMissionFile(QTextStream& stream, QmlObjectListM
     return true;
 }
 
-void MissionController::_loadMissionFromFile(const QString& filename)
+void MissionController::loadMissionFromFile(const QString& filename)
 {
     QString errorString;
 
@@ -482,7 +482,7 @@ void MissionController::_loadMissionFromFile(const QString& filename)
     _initAllVisualItems();
 }
 
-void MissionController::loadMissionFromFile(void)
+void MissionController::loadMissionFromFilePicker(void)
 {
 #ifndef __mobile__
     QString filename = QGCFileDialog::getOpenFileName(NULL, "Select Mission File to load", QString(), "Mission file (*.mission);;All Files (*.*)");
@@ -490,11 +490,11 @@ void MissionController::loadMissionFromFile(void)
     if (filename.isEmpty()) {
         return;
     }
-    _loadMissionFromFile(filename);
+    loadMissionFromFile(filename);
 #endif
 }
 
-void MissionController::_saveMissionToFile(const QString& filename)
+void MissionController::saveMissionToFile(const QString& filename)
 {
     qDebug() << filename;
 
@@ -504,7 +504,7 @@ void MissionController::_saveMissionToFile(const QString& filename)
 
     QString missionFilename = filename;
     if (!QFileInfo(filename).fileName().contains(".")) {
-        missionFilename += ".mission";
+        missionFilename += QString(".%1").arg(QGCApplication::missionFileExtension);
     }
 
     QFile file(missionFilename);
@@ -566,7 +566,7 @@ void MissionController::_saveMissionToFile(const QString& filename)
     _visualItems->setDirty(false);
 }
 
-void MissionController::saveMissionToFile(void)
+void MissionController::saveMissionToFilePicker(void)
 {
 #ifndef __mobile__
     QString filename = QGCFileDialog::getSaveFileName(NULL, "Select file to save mission to", QString(), "Mission file (*.mission);;All Files (*.*)");
@@ -574,31 +574,10 @@ void MissionController::saveMissionToFile(void)
     if (filename.isEmpty()) {
         return;
     }
-    _saveMissionToFile(filename);
+    saveMissionToFile(filename);
 #endif
 }
 
-void MissionController::saveMobileMissionToFile(const QString& filename)
-{
-    QStringList docDirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
-    if (docDirs.count() <= 0) {
-        qWarning() << "No Documents location";
-        return;
-    }
-
-    _saveMissionToFile(docDirs.at(0) + QDir::separator() + filename);
-}
-
-void MissionController::loadMobileMissionFromFile(const QString& filename)
-{
-    QStringList docDirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
-    if (docDirs.count() <= 0) {
-        qWarning() << "No Documents location";
-        return;
-    }
-    _loadMissionFromFile(docDirs.at(0) + QDir::separator() + filename);
-}
-
 void MissionController::_calcPrevWaypointValues(double homeAlt, VisualMissionItem* currentItem, VisualMissionItem* prevItem, double* azimuth, double* distance, double* altDifference)
 {
     QGeoCoordinate  currentCoord =  currentItem->coordinate();
@@ -1109,26 +1088,6 @@ void MissionController::_currentMissionItemChanged(int sequenceNumber)
     }
 }
 
-QStringList MissionController::getMobileMissionFiles(void)
-{
-    QStringList missionFiles;
-
-    QStringList docDirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
-    if (docDirs.count() <= 0) {
-        qWarning() << "No Documents location";
-        return QStringList();
-    }
-    QDir missionDir = docDirs.at(0);
-
-    QFileInfoList missionFileInfoList = missionDir.entryInfoList(QStringList(QStringLiteral("*.mission")),  QDir::Files, QDir::Name);
-
-    foreach (const QFileInfo& missionFileInfo, missionFileInfoList) {
-        missionFiles << missionFileInfo.baseName() + ".mission";
-    }
-
-    return missionFiles;
-}
-
 bool MissionController::syncInProgress(void)
 {
     if (_activeVehicle) {
diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h
index 34956ec..2d6f6d2 100644
--- a/src/MissionManager/MissionController.h
+++ b/src/MissionManager/MissionController.h
@@ -51,13 +51,12 @@ public:
     Q_INVOKABLE void start(bool editMode);
     Q_INVOKABLE void getMissionItems(void);
     Q_INVOKABLE void sendMissionItems(void);
-    Q_INVOKABLE void loadMissionFromFile(void);
-    Q_INVOKABLE void saveMissionToFile(void);
-    Q_INVOKABLE void loadMobileMissionFromFile(const QString& file);
-    Q_INVOKABLE void saveMobileMissionToFile(const QString& file);
+    Q_INVOKABLE void loadMissionFromFilePicker(void);
+    Q_INVOKABLE void loadMissionFromFile(const QString& filename);
+    Q_INVOKABLE void saveMissionToFilePicker(void);
+    Q_INVOKABLE void saveMissionToFile(const QString& filename);
     Q_INVOKABLE void removeMissionItem(int index);
     Q_INVOKABLE void removeAllMissionItems(void);
-    Q_INVOKABLE QStringList getMobileMissionFiles(void);
 
     /// Add a new simple mission item to the list
     ///     @param i: index to insert at
@@ -119,8 +118,6 @@ private:
     double _normalizeLon(double lon);
     bool _loadJsonMissionFile(const QByteArray& bytes, QmlObjectListModel* visualItems, QmlObjectListModel* complexItems, QString& errorString);
     bool _loadTextMissionFile(QTextStream& stream, QmlObjectListModel* visualItems, QString& errorString);
-    void _loadMissionFromFile(const QString& file);
-    void _saveMissionToFile(const QString& file);
     int _nextSequenceNumber(void);
 
 private:
diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc
index 42816fb..a05f40d 100644
--- a/src/QGCApplication.cc
+++ b/src/QGCApplication.cc
@@ -71,6 +71,7 @@
 #include "RadioComponentController.h"
 #include "ESP8266ComponentController.h"
 #include "ScreenToolsController.h"
+#include "QGCMobileFileDialogController.h"
 #include "AutoPilotPlugin.h"
 #include "VehicleComponent.h"
 #include "FirmwarePluginManager.h"
@@ -125,6 +126,10 @@
 
 QGCApplication* QGCApplication::_app = NULL;
 
+const char* QGCApplication::parameterFileExtension =    "params";
+const char* QGCApplication::missionFileExtension =      "mission";
+const char* QGCApplication::telemetryFileExtension =     "tlog";
+
 const char* QGCApplication::_deleteAllSettingsKey           = "DeleteAllSettingsNextBoot";
 const char* QGCApplication::_settingsVersionKey             = "SettingsVersion";
 const char* QGCApplication::_promptFlightDataSave           = "PromptFLightDataSave";
@@ -449,6 +454,7 @@ void QGCApplication::_initCommon(void)
     qmlRegisterType<MissionController>                  ("QGroundControl.Controllers", 1, 0, "MissionController");
     qmlRegisterType<FlightDisplayViewController>        ("QGroundControl.Controllers", 1, 0, "FlightDisplayViewController");
     qmlRegisterType<ValuesWidgetController>             ("QGroundControl.Controllers", 1, 0, "ValuesWidgetController");
+    qmlRegisterType<QGCMobileFileDialogController>      ("QGroundControl.Controllers", 1, 0, "QGCMobileFileDialogController");
 
 #ifndef __mobile__
     qmlRegisterType<ViewWidgetController>           ("QGroundControl.Controllers", 1, 0, "ViewWidgetController");
@@ -462,28 +468,6 @@ void QGCApplication::_initCommon(void)
     qmlRegisterSingletonType<QGroundControlQmlGlobal>   ("QGroundControl",                          1, 0, "QGroundControl",         qgroundcontrolQmlGlobalSingletonFactory);
     qmlRegisterSingletonType<ScreenToolsController>     ("QGroundControl.ScreenToolsController",    1, 0, "ScreenToolsController",  screenToolsControllerSingletonFactory);
     qmlRegisterSingletonType<MavlinkQmlSingleton>       ("QGroundControl.Mavlink",                  1, 0, "Mavlink",                mavlinkQmlSingletonFactory);
-
-    // Show user an upgrade message if the settings version has been bumped up
-    bool settingsUpgraded = false;
-    if (settings.contains(_settingsVersionKey)) {
-        if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
-            settingsUpgraded = true;
-        }
-    } else if (settings.allKeys().count()) {
-        // Settings version key is missing and there are settings. This is an upgrade scenario.
-        settingsUpgraded = true;
-    } else {
-        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
-    }
-
-    if (settingsUpgraded) {
-        settings.clear();
-        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
-        showMessage("The format for QGroundControl saved settings has been modified. "
-                    "Your saved settings have been reset to defaults.");
-    }
-
-    settings.sync();
 }
 
 bool QGCApplication::_initForNormalAppBoot(void)
@@ -515,6 +499,28 @@ bool QGCApplication::_initForNormalAppBoot(void)
     // Load known link configurations
     toolbox()->linkManager()->loadLinkConfigurationList();
 
+    // Show user an upgrade message if the settings version has been bumped up
+    bool settingsUpgraded = false;
+    if (settings.contains(_settingsVersionKey)) {
+        if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
+            settingsUpgraded = true;
+        }
+    } else if (settings.allKeys().count()) {
+        // Settings version key is missing and there are settings. This is an upgrade scenario.
+        settingsUpgraded = true;
+    } else {
+        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
+    }
+
+    if (settingsUpgraded) {
+        settings.clear();
+        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
+        showMessage("The format for QGroundControl saved settings has been modified. "
+                    "Your saved settings have been reset to defaults.");
+    }
+
+    settings.sync();
+
     return true;
 }
 
diff --git a/src/QGCApplication.h b/src/QGCApplication.h
index e645e62..de5ed96 100644
--- a/src/QGCApplication.h
+++ b/src/QGCApplication.h
@@ -78,6 +78,10 @@ public:
     QGCApplication(int &argc, char* argv[], bool unitTesting);
     ~QGCApplication();
 
+    static const char* parameterFileExtension;
+    static const char* missionFileExtension;
+    static const char* telemetryFileExtension;
+
     /// @brief Sets the persistent flag to delete all settings the next time QGroundControl is started.
     void deleteAllSettingsNextBoot(void);
 
diff --git a/src/QGCMobileFileDialogController.cc b/src/QGCMobileFileDialogController.cc
new file mode 100644
index 0000000..8ad2103
--- /dev/null
+++ b/src/QGCMobileFileDialogController.cc
@@ -0,0 +1,73 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+This file is part of the QGROUNDCONTROL project
+
+    QGROUNDCONTROL 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 3 of the License, or
+    (at your option) any later version.
+
+    QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+======================================================================*/
+
+#include "QGCMobileFileDialogController.h"
+
+#include <QStandardPaths>
+#include <QDebug>
+#include <QDir>
+
+QStringList QGCMobileFileDialogController::getFiles(const QString& fileExtension)
+{
+    QStringList files;
+
+    QStringList docDirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
+    if (docDirs.count() <= 0) {
+        qWarning() << "No Documents location";
+        return QStringList();
+    }
+    QDir fileDir = docDirs.at(0);
+
+    QFileInfoList fileInfoList = fileDir.entryInfoList(QStringList(QString("*.%1").arg(fileExtension)),  QDir::Files, QDir::Name);
+
+    foreach (const QFileInfo& fileInfo, fileInfoList) {
+        files << fileInfo.baseName() + QStringLiteral(".") + fileExtension;
+    }
+
+    return files;
+}
+
+QString QGCMobileFileDialogController::fullPath(const QString& filename, const QString& fileExtension)
+{
+    QStringList docDirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
+    if (docDirs.count() <= 0) {
+        qWarning() << "No Documents location";
+        return filename;
+    }
+
+    QString fixedFilename(filename);
+    QString correctExtension = QString(".%1").arg(fileExtension);
+    if (!filename.endsWith(correctExtension)) {
+        fixedFilename += correctExtension;
+    }
+
+    QString fullPath = docDirs.at(0) + QDir::separator() + fixedFilename;
+    qDebug() << fullPath;
+    return fullPath;
+}
+
+bool QGCMobileFileDialogController::fileExists(const QString& filename, const QString& fileExtension)
+{
+    QFile file(fullPath(filename, fileExtension));
+    return file.exists();
+}
diff --git a/src/QGCMobileFileDialogController.h b/src/QGCMobileFileDialogController.h
new file mode 100644
index 0000000..62268bf
--- /dev/null
+++ b/src/QGCMobileFileDialogController.h
@@ -0,0 +1,49 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+This file is part of the QGROUNDCONTROL project
+
+    QGROUNDCONTROL 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 3 of the License, or
+    (at your option) any later version.
+
+    QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+======================================================================*/
+
+#ifndef QGCMobileFileDialogController_H
+#define QGCMobileFileDialogController_H
+
+#include <QObject>
+
+class QGCMobileFileDialogController : public QObject
+{
+    Q_OBJECT
+
+public:
+    /// Return all file in Documents location which match the specified extension
+    Q_INVOKABLE QStringList getFiles(const QString& fileExtension);
+
+    /// Return the full path for specified file in the Documents location
+    ///     @param filename File name, not fully qualified, may not have extension
+    ///     @param fileExtension Expected file extension, added if needed
+    Q_INVOKABLE QString fullPath(const QString& filename, const QString& fileExtension);
+
+    /// Check for file existance
+    ///     @param filename File name, not fully qualified, may not have extension
+    ///     @param fileExtension Expected file extension, added if needed
+    /// @return true: File exists at Documents location
+    Q_INVOKABLE bool fileExists(const QString& filename, const QString& fileExtension);
+};
+
+#endif
diff --git a/src/QmlControls/ParameterEditor.qml b/src/QmlControls/ParameterEditor.qml
index 9bc04f6..d2ce7fc 100644
--- a/src/QmlControls/ParameterEditor.qml
+++ b/src/QmlControls/ParameterEditor.qml
@@ -24,17 +24,17 @@
 /// @file
 ///     @author Don Gagne <don@thegagnes.com>
 
-import QtQuick 2.3
-import QtQuick.Controls 1.3
-import QtQuick.Controls.Styles 1.2
-import QtQuick.Dialogs 1.2
+import QtQuick                  2.5
+import QtQuick.Controls         1.3
+import QtQuick.Dialogs          1.2
 
-import QGroundControl.Controls 1.0
-import QGroundControl.Palette 1.0
-import QGroundControl.ScreenTools 1.0
-import QGroundControl.Controllers 1.0
-import QGroundControl.FactSystem 1.0
-import QGroundControl.FactControls 1.0
+import QGroundControl               1.0
+import QGroundControl.Controls      1.0
+import QGroundControl.Palette       1.0
+import QGroundControl.ScreenTools   1.0
+import QGroundControl.Controllers   1.0
+import QGroundControl.FactSystem    1.0
+import QGroundControl.FactControls  1.0
 
 QGCView {
     id:         qgcView
@@ -123,16 +123,26 @@ QGCView {
                             text:           "Search..."
                             onTriggered:    showDialog(searchDialogComponent, "Parameter Search", qgcView.showDialogDefaultWidth, StandardButton.Reset | StandardButton.Apply)
                         }
-                        MenuSeparator { visible: !ScreenTools.isMobile }
+                        MenuSeparator { }
                         MenuItem {
                             text:           "Load from file..."
-                            onTriggered:	controller.loadFromFile()
-                            visible:        !ScreenTools.isMobile
+                            onTriggered: {
+                                if (ScreenTools.isMobile) {
+                                    qgcView.showDialog(mobileFilePicker, "Select Parameter File", qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
+                                } else {
+                                    controller.loadFromFilePicker()
+                                }
+                            }
                         }
                         MenuItem {
                             text:           "Save to file..."
-                            onTriggered:	controller.saveToFile()
-                            visible:        !ScreenTools.isMobile
+                            onTriggered: {
+                                if (ScreenTools.isMobile) {
+                                    qgcView.showDialog(mobileFileSaver, "Save Parameter File", qgcView.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
+                                } else {
+                                    controller.saveToFilePicker()
+                                }
+                            }
                         }
                         MenuSeparator { visible: _showRCToParam }
                         MenuItem {
@@ -375,4 +385,22 @@ QGCView {
         }
     }
 
+    Component {
+        id: mobileFilePicker
+
+        QGCMobileFileDialog {
+            fileExtension:      QGroundControl.parameterFileExtension
+            onFilenameReturned: controller.loadFromFile(filename)
+        }
+    }
+
+    Component {
+        id: mobileFileSaver
+
+        QGCMobileFileDialog {
+            openDialog:         false
+            fileExtension:      QGroundControl.parameterFileExtension
+            onFilenameReturned: controller.saveToFile(filename)
+        }
+    }
 } // QGCView
diff --git a/src/QmlControls/ParameterEditorController.cc b/src/QmlControls/ParameterEditorController.cc
index 31f8652..028067e 100644
--- a/src/QmlControls/ParameterEditorController.cc
+++ b/src/QmlControls/ParameterEditorController.cc
@@ -95,27 +95,18 @@ void ParameterEditorController::clearRCToParam(void)
 	_uas->unsetRCToParameterMap();
 }
 
-void ParameterEditorController::saveToFile(void)
+void ParameterEditorController::saveToFile(const QString& filename)
 {
-#ifndef __mobile__
     if (!_autopilot) {
         qWarning() << "Internal error _autopilot==NULL";
         return;
     }
 
-    QString msgTitle("Save Parameters");
-    
-	QString fileName = QGCFileDialog::getSaveFileName(NULL,
-                                                      msgTitle,
-                                                      QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
-                                                      "Parameter Files (*.params)",
-                                                      "params",
-                                                      true);
-	if (!fileName.isEmpty()) {
-		QFile file(fileName);
+    if (!filename.isEmpty()) {
+        QFile file(filename);
         
 		if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
-            qgcApp()->showMessage("Unable to create file");
+            qgcApp()->showMessage(QString("Unable to create file: %1").arg(filename));
 			return;
 		}
         
@@ -123,12 +114,23 @@ void ParameterEditorController::saveToFile(void)
 		_autopilot->writeParametersToStream(stream);
 		file.close();
 	}
-#endif
 }
 
-void ParameterEditorController::loadFromFile(void)
+void ParameterEditorController::saveToFilePicker(void)
 {
 #ifndef __mobile__
+    QString fileName = QGCFileDialog::getSaveFileName(NULL,
+                                                      "Save Parameters",
+                                                      QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
+                                                      "Parameter Files (*.params)",
+                                                      "params",
+                                                      true);
+    saveToFile(fileName);
+#endif
+}
+
+void ParameterEditorController::loadFromFile(const QString& filename)
+{
     QString errors;
     
     if (!_autopilot) {
@@ -136,17 +138,11 @@ void ParameterEditorController::loadFromFile(void)
         return;
     }
 
-    QString msgTitle("Load Parameters");
-    
-	QString fileName = QGCFileDialog::getOpenFileName(NULL,
-                                                      msgTitle,
-                                                      QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
-													  "Parameter Files (*.params);;All Files (*)");
-    if (!fileName.isEmpty()) {
-        QFile file(fileName);
+    if (!filename.isEmpty()) {
+        QFile file(filename);
         
         if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
-            qgcApp()->showMessage("Unable to open file");
+            qgcApp()->showMessage(QString("Unable to open file: %1").arg(filename));
             return;
         }
         
@@ -158,6 +154,16 @@ void ParameterEditorController::loadFromFile(void)
             emit showErrorMessage(errors);
         }
     }
+}
+
+void ParameterEditorController::loadFromFilePicker(void)
+{
+#ifndef __mobile__
+    QString fileName = QGCFileDialog::getOpenFileName(NULL,
+                                                      "Load Parameters",
+                                                      QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
+                                                      "Parameter Files (*.params);;All Files (*)");
+    loadFromFile(fileName);
 #endif
 }
 
diff --git a/src/QmlControls/ParameterEditorController.h b/src/QmlControls/ParameterEditorController.h
index a1db222..0c751c0 100644
--- a/src/QmlControls/ParameterEditorController.h
+++ b/src/QmlControls/ParameterEditorController.h
@@ -49,9 +49,11 @@ public:
     Q_INVOKABLE QStringList searchParametersForComponent(int componentId, const QString& searchText, bool searchInName, bool searchInDescriptions);
 	
 	Q_INVOKABLE void clearRCToParam(void);
-	Q_INVOKABLE void saveToFile(void);
-	Q_INVOKABLE void loadFromFile(void);
-	Q_INVOKABLE void refresh(void);
+    Q_INVOKABLE void saveToFilePicker(void);
+    Q_INVOKABLE void loadFromFilePicker(void);
+    Q_INVOKABLE void saveToFile(const QString& filename);
+    Q_INVOKABLE void loadFromFile(const QString& filename);
+    Q_INVOKABLE void refresh(void);
     Q_INVOKABLE void resetAllToDefaults(void);
 	Q_INVOKABLE void setRCToParam(const QString& paramName);
 	
diff --git a/src/QmlControls/QGCLabel.qml b/src/QmlControls/QGCLabel.qml
index 2e266d8..070e7c7 100644
--- a/src/QmlControls/QGCLabel.qml
+++ b/src/QmlControls/QGCLabel.qml
@@ -11,7 +11,6 @@ Text {
     property bool enabled: true
 
     font.pixelSize: ScreenTools.defaultFontPixelSize
-    fontSizeMode:   Text.HorizontalFit
     color:          __qgcPal.text
     antialiasing:   true
 }
diff --git a/src/QmlControls/QGCMobileFileDialog.qml b/src/QmlControls/QGCMobileFileDialog.qml
new file mode 100644
index 0000000..70d947c
--- /dev/null
+++ b/src/QmlControls/QGCMobileFileDialog.qml
@@ -0,0 +1,133 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+This file is part of the QGROUNDCONTROL project
+
+    QGROUNDCONTROL 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 3 of the License, or
+    (at your option) any later version.
+
+    QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+======================================================================*/
+
+import QtQuick          2.5
+import QtQuick.Controls 1.3
+import QtQuick.Dialogs  1.2
+
+import QGroundControl               1.0
+import QGroundControl.ScreenTools   1.0
+import QGroundControl.Controls      1.0
+import QGroundControl.Controllers   1.0
+import QGroundControl.Palette       1.0
+
+/// Simple file picker for mobile
+QGCViewDialog {
+    property bool   openDialog:     true    ///< true: Show file open dialog, false: show file save dialog
+    property string fileExtension           ///< File extension for file listing
+
+    signal filenameReturned(string filename)
+
+    readonly property real _margins: ScreenTools.defaultFontPixelHeight / 2
+
+    function accept() {
+        if (!openDialog) {
+            if (!dialogLoader.item.replaceMessageShown) {
+                if (controller.fileExists(dialogLoader.item.filename, fileExtension)) {
+                    dialogLoader.item.replaceMessageShown = true
+                    return
+                }
+            }
+            filenameReturned(controller.fullPath(dialogLoader.item.filename, fileExtension))
+        }
+        hideDialog()
+    }
+
+    QGCMobileFileDialogController { id: controller }
+    QGCPalette { id: qgcPal; colorGroupEnabled: true }
+
+    Loader {
+        id:                 dialogLoader
+        anchors.fill:       parent
+        sourceComponent:    openDialog ? openDialogComponent : saveDialogComponent
+    }
+
+    Component {
+        id: saveDialogComponent
+
+        Column {
+            anchors.left:   parent.left
+            anchors.right:  parent.right
+            spacing:        ScreenTools.defaultFontPixelHeight
+
+            property alias filename:            filenameTextField.text
+            property alias replaceMessageShown: replaceMessage.visible
+
+            QGCLabel {
+                text: "File name:"
+            }
+
+            QGCTextField {
+                id:             filenameTextField
+                onTextChanged:  replaceMessage.visible = false
+            }
+
+            QGCLabel {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                wrapMode:       Text.WordWrap
+                text:           "File names must end with ." + fileExtension + " file extension. If missing it will be added."
+            }
+
+            QGCLabel {
+                id:             replaceMessage
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                wrapMode:       Text.WordWrap
+                text:           "The file " + filename + " exists. Click Save again to replace it."
+                visible:        false
+                color:          qgcPal.warningText
+            }
+        }
+    }
+
+    Component {
+        id: openDialogComponent
+
+        Item {
+            anchors.margins:    _margins
+            anchors.fill:       parent
+
+            ListView {
+                anchors.fill:   parent
+                spacing:        _margins / 2
+                orientation:    ListView.Vertical
+                model:          controller.getFiles(fileExtension)
+
+                delegate: QGCButton {
+                    text: modelData
+
+                    onClicked: {
+                        hideDialog()
+                        filenameReturned(controller.fullPath(modelData, fileExtension))
+                    }
+                }
+            }
+
+            QGCLabel {
+                text:       "No files"
+                visible:    controller.getFiles(fileExtension).length == 0
+            }
+        }
+    }
+}
diff --git a/src/QmlControls/QGroundControl.Controls.qmldir b/src/QmlControls/QGroundControl.Controls.qmldir
index 05526e6..9cb8340 100644
--- a/src/QmlControls/QGroundControl.Controls.qmldir
+++ b/src/QmlControls/QGroundControl.Controls.qmldir
@@ -20,6 +20,7 @@ QGCColoredImage         1.0 QGCColoredImage.qml
 QGCComboBox             1.0 QGCComboBox.qml
 QGCFlickable            1.0 QGCFlickable.qml
 QGCLabel                1.0 QGCLabel.qml
+QGCMobileFileDialog     1.0 QGCMobileFileDialog.qml
 QGCMovableItem          1.0 QGCMovableItem.qml
 QGCPipable              1.0 QGCPipable.qml
 QGCRadioButton          1.0 QGCRadioButton.qml
diff --git a/src/QmlControls/QGroundControlQmlGlobal.h b/src/QmlControls/QGroundControlQmlGlobal.h
index 77234f5..ae9e925 100644
--- a/src/QmlControls/QGroundControlQmlGlobal.h
+++ b/src/QmlControls/QGroundControlQmlGlobal.h
@@ -80,6 +80,10 @@ public:
     Q_PROPERTY(QGeoCoordinate flightMapPosition     MEMBER _flightMapPosition   NOTIFY flightMapPositionChanged)
     Q_PROPERTY(int            flightMapZoom         MEMBER _flightMapZoom       NOTIFY flightMapZoomChanged)
 
+    Q_PROPERTY(QString  parameterFileExtension  READ parameterFileExtension CONSTANT)
+    Q_PROPERTY(QString  missionFileExtension    READ missionFileExtension   CONSTANT)
+    Q_PROPERTY(QString  telemetryFileExtension  READ telemetryFileExtension CONSTANT)
+
     /// @ return: true: experimental survey ip code is turned on
     Q_PROPERTY(bool experimentalSurvey READ experimentalSurvey WRITE setExperimentalSurvey NOTIFY experimentalSurveyChanged)
 
@@ -140,6 +144,10 @@ public:
     bool experimentalSurvey(void) const;
     void setExperimentalSurvey(bool experimentalSurvey);
 
+    QString parameterFileExtension(void) const  { return QGCApplication::parameterFileExtension; }
+    QString missionFileExtension(void) const    { return QGCApplication::missionFileExtension; }
+    QString telemetryFileExtension(void) const  { return QGCApplication::telemetryFileExtension; }
+
     // Overrides from QGCTool
     virtual void setToolbox(QGCToolbox* toolbox);