diff --git a/src/QtLocationPlugin/QGCMapEngineData.h b/src/QtLocationPlugin/QGCMapEngineData.h index 5765aa8560d6b9fd3fa08388b7a25690d54b9641..eb0ff7974c60acb64155b25b3f66a41572c0fb0e 100644 --- a/src/QtLocationPlugin/QGCMapEngineData.h +++ b/src/QtLocationPlugin/QGCMapEngineData.h @@ -119,7 +119,9 @@ public: taskUpdateTileDownloadState, taskDeleteTileSet, taskPruneCache, - taskReset + taskReset, + taskExport, + taskImport }; QGCMapTask(TaskType type) @@ -365,5 +367,80 @@ signals: void resetCompleted(); }; +//----------------------------------------------------------------------------- +class QGCExportTileTask : public QGCMapTask +{ + Q_OBJECT +public: + QGCExportTileTask(QVector sets, QString path) + : QGCMapTask(QGCMapTask::taskExport) + , _sets(sets) + , _path(path) + {} + + ~QGCExportTileTask() + { + } + + QVector sets() { return _sets; } + QString path() { return _path; } + + void setExportCompleted() + { + emit actionCompleted(); + } + + void setProgress(int percentage) + { + emit actionProgress(percentage); + } + +private: + QVector _sets; + QString _path; + +signals: + void actionCompleted (); + void actionProgress (int percentage); + +}; + +//----------------------------------------------------------------------------- +class QGCImportTileTask : public QGCMapTask +{ + Q_OBJECT +public: + QGCImportTileTask(QString path, bool replace) + : QGCMapTask(QGCMapTask::taskImport) + , _path(path) + , _replace(replace) + {} + + ~QGCImportTileTask() + { + } + + QString path () { return _path; } + bool replace () { return _replace; } + + void setImportCompleted() + { + emit actionCompleted(); + } + + void setProgress(int percentage) + { + emit actionProgress(percentage); + } + +private: + QString _path; + bool _replace; + +signals: + void actionCompleted (); + void actionProgress (int percentage); + +}; #endif // QGC_MAP_ENGINE_DATA_H diff --git a/src/QtLocationPlugin/QGCMapTileSet.cpp b/src/QtLocationPlugin/QGCMapTileSet.cpp index f5c0e10821e2294cf372102081fe090263121d69..70e70f2e8cd2c0ea5ec6c9e260e775f0b594aa29 100644 --- a/src/QtLocationPlugin/QGCMapTileSet.cpp +++ b/src/QtLocationPlugin/QGCMapTileSet.cpp @@ -52,6 +52,7 @@ QGCCachedTileSet::QGCCachedTileSet(const QString& name) , _noMoreTiles(false) , _batchRequested(false) , _manager(NULL) + , _selected(false) { } @@ -349,3 +350,14 @@ QGCCachedTileSet::setManager(QGCMapEngineManager* mgr) { _manager = mgr; } + +//----------------------------------------------------------------------------- +void +QGCCachedTileSet::setSelected(bool sel) +{ + _selected = sel; + emit selectedChanged(); + if(_manager) { + emit _manager->selectedCountChanged(); + } +} diff --git a/src/QtLocationPlugin/QGCMapTileSet.h b/src/QtLocationPlugin/QGCMapTileSet.h index dc8217e6177e314d370cee0a879b2f50414827fb..36241b7f547de13e749a88fab10820af87c47531 100644 --- a/src/QtLocationPlugin/QGCMapTileSet.h +++ b/src/QtLocationPlugin/QGCMapTileSet.h @@ -72,6 +72,8 @@ public: Q_PROPERTY(quint32 errorCount READ errorCount NOTIFY errorCountChanged) Q_PROPERTY(QString errorCountStr READ errorCountStr NOTIFY errorCountChanged) + Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged) + Q_INVOKABLE void createDownloadTask (); Q_INVOKABLE void resumeDownloadTask (); Q_INVOKABLE void cancelDownloadTask (); @@ -109,7 +111,9 @@ public: bool downloading () { return _downloading; } quint32 errorCount () { return _errorCount; } QString errorCountStr (); + bool selected () { return _selected; } + void setSelected (bool sel); void setName (QString name) { _name = name; } void setMapTypeStr (QString typeStr) { _mapTypeStr = typeStr; } void setTopleftLat (double lat) { _topleftLat = lat; } @@ -142,6 +146,7 @@ signals: void savedTileSizeChanged (); void completeChanged (); void errorCountChanged (); + void selectedChanged (); private slots: void _tileListFetched (QList tiles); @@ -181,6 +186,7 @@ private: bool _noMoreTiles; bool _batchRequested; QGCMapEngineManager* _manager; + bool _selected; }; #endif // QGC_MAP_TILE_SET_H diff --git a/src/QtLocationPlugin/QGCTileCacheWorker.cpp b/src/QtLocationPlugin/QGCTileCacheWorker.cpp index 254e4c9e29a3aabe4390ece38e1657eaa9fc096f..0e4691497e51bd4ce38b9d0f189c438704232215 100644 --- a/src/QtLocationPlugin/QGCTileCacheWorker.cpp +++ b/src/QtLocationPlugin/QGCTileCacheWorker.cpp @@ -30,7 +30,8 @@ #include "time.h" const char* kDefaultSet = "Default Tile Set"; -const QString kSession = QLatin1String("QGeoTileWorkerSession"); +const QString kSession = QLatin1String("QGeoTileWorkerSession"); +const QString kExportSession = QLatin1String("QGeoTileExportSession"); QGC_LOGGING_CATEGORY(QGCTileCacheLog, "QGCTileCacheLog") @@ -153,6 +154,12 @@ QGCCacheWorker::run() case QGCMapTask::taskReset: _resetCacheDatabase(task); break; + case QGCMapTask::taskExport: + _exportSets(task); + break; + case QGCMapTask::taskImport: + _importSets(task); + break; case QGCMapTask::taskTestInternet: _testInternet(); break; @@ -262,8 +269,7 @@ QGCCacheWorker::_saveTile(QGCMapTask *mtask) void QGCCacheWorker::_getTile(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } bool found = false; @@ -291,8 +297,7 @@ QGCCacheWorker::_getTile(QGCMapTask* mtask) void QGCCacheWorker::_getTileSets(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } QGCFetchTileSetTask* task = static_cast(mtask); @@ -506,8 +511,7 @@ QGCCacheWorker::_createTileSet(QGCMapTask *mtask) void QGCCacheWorker::_getTileDownloadList(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } QList tiles; @@ -538,8 +542,7 @@ QGCCacheWorker::_getTileDownloadList(QGCMapTask* mtask) void QGCCacheWorker::_updateTileDownloadState(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } QGCUpdateTileDownloadStateTask* task = static_cast(mtask); @@ -563,8 +566,7 @@ QGCCacheWorker::_updateTileDownloadState(QGCMapTask* mtask) void QGCCacheWorker::_pruneCache(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } QGCPruneCacheTask* task = static_cast(mtask); @@ -594,8 +596,7 @@ QGCCacheWorker::_pruneCache(QGCMapTask* mtask) void QGCCacheWorker::_deleteTileSet(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } QGCDeleteTileSetTask* task = static_cast(mtask); @@ -613,12 +614,12 @@ QGCCacheWorker::_deleteTileSet(QGCMapTask* mtask) _updateTotals(); task->setTileSetDeleted(); } + //----------------------------------------------------------------------------- void QGCCacheWorker::_resetCacheDatabase(QGCMapTask* mtask) { - if(!_valid) { - mtask->setError("No Cache Database"); + if(!_testTask(mtask)) { return; } QGCResetTask* task = static_cast(mtask); @@ -632,10 +633,288 @@ QGCCacheWorker::_resetCacheDatabase(QGCMapTask* mtask) query.exec(s); s = QString("DROP TABLE TilesDownload"); query.exec(s); - _createDB(); + _valid = _createDB(_db); task->setResetCompleted(); } +//----------------------------------------------------------------------------- +void +QGCCacheWorker::_importSets(QGCMapTask* mtask) +{ + if(!_testTask(mtask)) { + return; + } + QGCImportTileTask* task = static_cast(mtask); + //-- If replacing, simply copy over it + if(task->replace()) { + //-- Close and delete old database + if(_db) { + delete _db; + _db = NULL; + QSqlDatabase::removeDatabase(kSession); + } + QFile file(_databasePath); + file.remove(); + //-- Copy given database + QFile::copy(task->path(), _databasePath); + task->setProgress(25); + _init(); + if(_valid) { + task->setProgress(50); + _db = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", kSession)); + _db->setDatabaseName(_databasePath); + _db->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); + _valid = _db->open(); + } + task->setProgress(100); + } else { + //-- Open imported set + QSqlDatabase* dbImport = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", kExportSession)); + dbImport->setDatabaseName(task->path()); + dbImport->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); + if (dbImport->open()) { + QSqlQuery query(*dbImport); + //-- Prepare progress report + quint64 tileCount = 0; + quint64 currentCount = 0; + QString s; + s = QString("SELECT COUNT(tileID) FROM Tiles"); + if(query.exec(s)) { + if(query.next()) { + tileCount = query.value(0).toULongLong(); + } + } + if(!tileCount) { + qWarning() << "No tiles found in imported database"; + tileCount = 1; //-- Let it run through + } + //-- Iterate Tile Sets + s = QString("SELECT * FROM TileSets ORDER BY defaultSet DESC, name ASC"); + if(query.exec(s)) { + while(query.next()) { + QString name = query.value("name").toString(); + quint64 setID = query.value("setID").toULongLong(); + QString mapType = query.value("typeStr").toString(); + double topleftLat = query.value("topleftLat").toDouble(); + double topleftLon = query.value("topleftLon").toDouble(); + double bottomRightLat = query.value("bottomRightLat").toDouble(); + double bottomRightLon = query.value("bottomRightLon").toDouble(); + int minZoom = query.value("minZoom").toInt(); + int maxZoom = query.value("maxZoom").toInt(); + int type = query.value("type").toInt(); + quint32 numTiles = query.value("numTiles").toUInt(); + int defaultSet = query.value("defaultSet").toInt(); + quint64 insertSetID = _getDefaultTileSet(); + //-- If not default set, create new one + if(!defaultSet) { + //-- Check if we have this tile set already + int testCount = 0; + while (true) { + QString testName; + testName.sprintf("%s %03d", name.toLatin1().data(), ++testCount); + if(!_findTileSetID(testName, insertSetID) || testCount > 99) { + if(testCount > 1) { + name = testName; + } + break; + } + } + //-- Create new set + QSqlQuery cQuery(*_db); + cQuery.prepare("INSERT INTO TileSets(" + "name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, defaultSet, date" + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + cQuery.addBindValue(name); + cQuery.addBindValue(mapType); + cQuery.addBindValue(topleftLat); + cQuery.addBindValue(topleftLon); + cQuery.addBindValue(bottomRightLat); + cQuery.addBindValue(bottomRightLon); + cQuery.addBindValue(minZoom); + cQuery.addBindValue(maxZoom); + cQuery.addBindValue(type); + cQuery.addBindValue(numTiles); + cQuery.addBindValue(defaultSet); + cQuery.addBindValue(QDateTime::currentDateTime().toTime_t()); + if(!cQuery.exec()) { + task->setError("Error adding imported tile set to database"); + break; + } else { + //-- Get just created (auto-incremented) setID + insertSetID = cQuery.lastInsertId().toULongLong(); + } + } + //-- Find set tiles + QSqlQuery cQuery(*_db); + QSqlQuery subQuery(*dbImport); + QString sb = QString("SELECT * FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A JOIN SetTiles B ON A.tileID = B.tileID WHERE B.setID = %1 GROUP BY A.tileID HAVING COUNT(A.tileID) = 1)").arg(setID); + if(subQuery.exec(sb)) { + _db->transaction(); + while(subQuery.next()) { + QString hash = subQuery.value("hash").toString(); + QString format = subQuery.value("format").toString(); + QByteArray img = subQuery.value("tile").toByteArray(); + int type = subQuery.value("type").toInt(); + //-- Save tile + cQuery.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); + cQuery.addBindValue(hash); + cQuery.addBindValue(format); + cQuery.addBindValue(img); + cQuery.addBindValue(img.size()); + cQuery.addBindValue(type); + cQuery.addBindValue(QDateTime::currentDateTime().toTime_t()); + if(cQuery.exec()) { + quint64 importTileID = cQuery.lastInsertId().toULongLong(); + QString s = QString("INSERT INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(importTileID).arg(insertSetID); + cQuery.prepare(s); + cQuery.exec(); + currentCount++; + task->setProgress((int)((double)currentCount / (double)tileCount * 100.0)); + } + } + _db->commit(); + //-- Update tile count + s = QString("SELECT COUNT(size) FROM Tiles A INNER JOIN SetTiles B on A.tileID = B.tileID WHERE B.setID = %1").arg(insertSetID); + if(cQuery.exec(s)) { + if(cQuery.next()) { + quint64 count = cQuery.value(0).toULongLong(); + s = QString("UPDATE TileSets SET numTiles = %1 WHERE setID = %2").arg(count).arg(insertSetID); + cQuery.exec(s); + } + } + } + } + } else { + task->setError("No tile set in database"); + } + delete dbImport; + QSqlDatabase::removeDatabase(kExportSession); + } else { + task->setError("Error opening import database"); + } + } + task->setImportCompleted(); +} + +//----------------------------------------------------------------------------- +void +QGCCacheWorker::_exportSets(QGCMapTask* mtask) +{ + if(!_testTask(mtask)) { + return; + } + QGCExportTileTask* task = static_cast(mtask); + //-- Delete target if it exists + QFile file(task->path()); + file.remove(); + //-- Create exported database + QSqlDatabase *dbExport = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", kExportSession)); + dbExport->setDatabaseName(task->path()); + dbExport->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); + if (dbExport->open()) { + if(_createDB(dbExport, false)) { + //-- Prepare progress report + quint64 tileCount = 0; + quint64 currentCount = 0; + for(int i = 0; i < task->sets().count(); i++) { + QGCCachedTileSet* set = task->sets()[i]; + //-- Default set has no unique tiles + if(set->defaultSet()) { + tileCount += set->totalTileCount(); + } else { + tileCount += set->uniqueTileCount(); + } + } + if(!tileCount) { + tileCount = 1; + } + //-- Iterate sets to save + for(int i = 0; i < task->sets().count(); i++) { + QGCCachedTileSet* set = task->sets()[i]; + //-- Create Tile Exported Set + QSqlQuery exportQuery(*dbExport); + exportQuery.prepare("INSERT INTO TileSets(" + "name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, defaultSet, date" + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + exportQuery.addBindValue(set->name()); + exportQuery.addBindValue(set->mapTypeStr()); + exportQuery.addBindValue(set->topleftLat()); + exportQuery.addBindValue(set->topleftLon()); + exportQuery.addBindValue(set->bottomRightLat()); + exportQuery.addBindValue(set->bottomRightLon()); + exportQuery.addBindValue(set->minZoom()); + exportQuery.addBindValue(set->maxZoom()); + exportQuery.addBindValue(set->type()); + exportQuery.addBindValue(set->totalTileCount()); + exportQuery.addBindValue(set->defaultSet()); + exportQuery.addBindValue(QDateTime::currentDateTime().toTime_t()); + if(!exportQuery.exec()) { + task->setError("Error adding tile set to exported database"); + break; + } else { + //-- Get just created (auto-incremented) setID + quint64 exportSetID = exportQuery.lastInsertId().toULongLong(); + //-- Find set tiles + QString s = QString("SELECT * FROM SetTiles WHERE setID = %1").arg(set->id()); + QSqlQuery query(*_db); + if(query.exec(s)) { + dbExport->transaction(); + while(query.next()) { + quint64 tileID = query.value("tileID").toULongLong(); + //-- Get tile + QString s = QString("SELECT * FROM Tiles WHERE tileID = \"%1\"").arg(tileID); + QSqlQuery subQuery(*_db); + if(subQuery.exec(s)) { + if(subQuery.next()) { + QString hash = subQuery.value("hash").toString(); + QString format = subQuery.value("format").toString(); + QByteArray img = subQuery.value("tile").toByteArray(); + int type = subQuery.value("type").toInt(); + //-- Save tile + exportQuery.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); + exportQuery.addBindValue(hash); + exportQuery.addBindValue(format); + exportQuery.addBindValue(img); + exportQuery.addBindValue(img.size()); + exportQuery.addBindValue(type); + exportQuery.addBindValue(QDateTime::currentDateTime().toTime_t()); + if(exportQuery.exec()) { + quint64 exportTileID = exportQuery.lastInsertId().toULongLong(); + QString s = QString("INSERT INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(exportTileID).arg(exportSetID); + exportQuery.prepare(s); + exportQuery.exec(); + currentCount++; + task->setProgress((int)((double)currentCount / (double)tileCount * 100.0)); + } + } + } + } + } + dbExport->commit(); + } + } + } else { + task->setError("Error creating export database"); + } + } else { + qCritical() << "Map Cache SQL error (create export database):" << dbExport->lastError(); + task->setError("Error opening export database"); + } + delete dbExport; + QSqlDatabase::removeDatabase(kExportSession); + task->setExportCompleted(); +} + +//----------------------------------------------------------------------------- +bool QGCCacheWorker::_testTask(QGCMapTask* mtask) +{ + if(!_valid) { + mtask->setError("No Cache Database"); + return false; + } + return true; +} + //----------------------------------------------------------------------------- bool QGCCacheWorker::_init() @@ -648,7 +927,10 @@ QGCCacheWorker::_init() _db->setDatabaseName(_databasePath); _db->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); if (_db->open()) { - _createDB(); + _valid = _createDB(_db); + if(!_valid) { + _failed = true; + } } else { qCritical() << "Map Cache SQL error (init() open db):" << _db->lastError(); _failed = true; @@ -665,10 +947,11 @@ QGCCacheWorker::_init() } //----------------------------------------------------------------------------- -void -QGCCacheWorker::_createDB() +bool +QGCCacheWorker::_createDB(QSqlDatabase* db, bool createDefault) { - QSqlQuery query(*_db); + bool res = false; + QSqlQuery query(*db); if(!query.exec( "CREATE TABLE IF NOT EXISTS Tiles (" "tileID INTEGER PRIMARY KEY NOT NULL, " @@ -719,13 +1002,13 @@ QGCCacheWorker::_createDB() qWarning() << "Map Cache SQL error (create TilesDownload db):" << query.lastError().text(); } else { //-- Database it ready for use - _valid = true; + res = true; } } } } //-- Create default tile set - if(_valid) { + if(res && createDefault) { QString s = QString("SELECT name FROM TileSets WHERE name = \"%1\"").arg(kDefaultSet); if(query.exec(s)) { if(!query.next()) { @@ -734,19 +1017,19 @@ QGCCacheWorker::_createDB() query.addBindValue(1); query.addBindValue(QDateTime::currentDateTime().toTime_t()); if(!query.exec()) { - qWarning() << "Map Cache SQL error (Creating default tile set):" << _db->lastError(); - _valid = false; + qWarning() << "Map Cache SQL error (Creating default tile set):" << db->lastError(); + res = false; } } } else { - qWarning() << "Map Cache SQL error (Looking for default tile set):" << _db->lastError(); + qWarning() << "Map Cache SQL error (Looking for default tile set):" << db->lastError(); } } - if(!_valid) { + if(!res) { QFile file(_databasePath); file.remove(); } - _failed = !_valid; + return res; } //----------------------------------------------------------------------------- diff --git a/src/QtLocationPlugin/QGCTileCacheWorker.h b/src/QtLocationPlugin/QGCTileCacheWorker.h index 1b87e1d4fa24305f538c4ef1bc0f4f3864510faa..72fe8ea634cf8dfee8d393d757fc163b7c1ce712 100644 --- a/src/QtLocationPlugin/QGCTileCacheWorker.h +++ b/src/QtLocationPlugin/QGCTileCacheWorker.h @@ -59,13 +59,16 @@ private: void _deleteTileSet (QGCMapTask* mtask); void _resetCacheDatabase (QGCMapTask* mtask); void _pruneCache (QGCMapTask* mtask); + void _exportSets (QGCMapTask* mtask); + void _importSets (QGCMapTask* mtask); + bool _testTask (QGCMapTask* mtask); void _testInternet (); quint64 _findTile (const QString hash); bool _findTileSetID (const QString name, quint64& setID); void _updateSetTotals (QGCCachedTileSet* set); bool _init (); - void _createDB (); + bool _createDB (QSqlDatabase *db, bool createDefault = true); quint64 _getDefaultTileSet (); void _updateTotals (); diff --git a/src/QtLocationPlugin/QMLControl/OfflineMap.qml b/src/QtLocationPlugin/QMLControl/OfflineMap.qml index e0512dd9775e0855c7693754fca1426cb0e51a75..6fc91a610daff0b2c9884fc8cad0a68a75e41139 100644 --- a/src/QtLocationPlugin/QMLControl/OfflineMap.qml +++ b/src/QtLocationPlugin/QMLControl/OfflineMap.qml @@ -15,11 +15,12 @@ import QtQuick.Layouts 1.2 import QtLocation 5.3 import QtPositioning 5.3 -import QGroundControl 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.FlightMap 1.0 +import QGroundControl 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.FlightMap 1.0 +import QGroundControl.QGCMapEngineManager 1.0 QGCView { id: offlineMapView @@ -37,7 +38,9 @@ QGCView { property string savedMapType: "" property bool _showPreview: true property bool _defaultSet: offlineMapView && offlineMapView._currentSelection && offlineMapView._currentSelection.defaultSet - property real _margins: ScreenTools.defaultFontPixelWidth / 2 + property real _margins: ScreenTools.defaultFontPixelWidth * 0.5 + property real _buttonSize: ScreenTools.defaultFontPixelWidth * 12 + property real _bigButtonSize: ScreenTools.defaultFontPixelWidth * 16 property bool _saveRealEstate: ScreenTools.isTinyScreen || ScreenTools.isShortScreen property real _adjustableFontPointSize: _saveRealEstate ? ScreenTools.smallFontPointSize : ScreenTools.defaultFontPointSize @@ -100,15 +103,27 @@ QGCView { _map.visible = true _tileSetList.visible = false infoView.visible = false + _exporTiles.visible = false addNewSetView.visible = true } function showList() { + _exporTiles.visible = false isMapInteractive = false _map.visible = false _tileSetList.visible = true infoView.visible = false addNewSetView.visible = false + QGroundControl.mapEngineManager.resetAction(); + } + + function showExport() { + isMapInteractive = false + _map.visible = false + _tileSetList.visible = false + infoView.visible = false + addNewSetView.visible = false + _exporTiles.visible = true } function showInfo() { @@ -796,7 +811,7 @@ QGCView { clip: true anchors.margins: ScreenTools.defaultFontPixelWidth anchors.top: parent.top - anchors.bottom: _optionsButton.top + anchors.bottom: _listButtonRow.top anchors.left: parent.left anchors.right: parent.right contentHeight: _cacheList.height @@ -806,7 +821,6 @@ QGCView { width: Math.min(_tileSetList.width, (ScreenTools.defaultFontPixelWidth * 50).toFixed(0)) spacing: ScreenTools.defaultFontPixelHeight * 0.5 anchors.horizontalCenter: parent.horizontalCenter - OfflineMapButton { id: firstButton text: qsTr("Add new set") @@ -834,15 +848,365 @@ QGCView { } } } + Row { + id: _listButtonRow + visible: _tileSetList.visible + spacing: _margins + anchors.bottom: parent.bottom + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + QGCButton { + text: qsTr("Import") + width: _buttonSize + visible: !ScreenTools.isMobile + onClicked: rootLoader.sourceComponent = importDialog + } + QGCButton { + text: qsTr("Export") + width: _buttonSize + visible: !ScreenTools.isMobile + enabled: QGroundControl.mapEngineManager.tileSets.count > 1 + onClicked: showExport() + } + QGCButton { + text: qsTr("Options") + width: _buttonSize + onClicked: showDialog(optionsDialogComponent, qsTr("Offline Maps Options"), qgcView.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel) + } + } - QGCButton { - id: _optionsButton - text: qsTr("Options") - visible: _tileSetList.visible - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: ScreenTools.defaultFontPixelWidth - onClicked: showDialog(optionsDialogComponent, qsTr("Offline Maps Options"), qgcView.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel) + //-- Export Tile Sets + QGCFlickable { + id: _exporTiles + clip: true + visible: false + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.top: parent.top + anchors.bottom: _exportButtonRow.top + anchors.left: parent.left + anchors.right: parent.right + contentHeight: _exportList.height + Column { + id: _exportList + width: Math.min(_exporTiles.width, (ScreenTools.defaultFontPixelWidth * 50).toFixed(0)) + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.horizontalCenter: parent.horizontalCenter + QGCLabel { + text: qsTr("Select Tile Sets to Export") + font.pointSize: ScreenTools.mediumFontPointSize + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight; } + Repeater { + model: QGroundControl.mapEngineManager.tileSets + delegate: QGCCheckBox { + text: object.name + checked: object.selected + onClicked: { + object.selected = checked + } + } + } + } + } + Row { + id: _exportButtonRow + visible: _exporTiles.visible + spacing: _margins + anchors.bottom: parent.bottom + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + QGCButton { + text: qsTr("Select All") + width: _bigButtonSize + onClicked: QGroundControl.mapEngineManager.selectAll() + } + QGCButton { + text: qsTr("Select None") + width: _bigButtonSize + onClicked: QGroundControl.mapEngineManager.selectNone() + } + QGCButton { + text: qsTr("Export to Disk") + width: _bigButtonSize + enabled: QGroundControl.mapEngineManager.selectedCount > 0 + onClicked: { + showList(); + if(QGroundControl.mapEngineManager.exportSets()) { + rootLoader.sourceComponent = exportToDiskProgress + } + } + } + QGCButton { + text: qsTr("Export to Device") + width: _bigButtonSize + enabled: QGroundControl.mapEngineManager.selectedCount > 0 + onClicked: { + rootLoader.sourceComponent = exportToDevice + } + } + QGCButton { + text: qsTr("Cancel") + width: _bigButtonSize + onClicked: showList() + } } } // QGCViewPanel + + Component { + id: exportToDiskProgress + Rectangle { + width: mainWindow.width + height: mainWindow.height + color: "black" + anchors.centerIn: parent + Rectangle { + width: parent.width * 0.5 + height: exportCol.height * 1.25 + radius: ScreenTools.defaultFontPixelWidth + color: qgcPal.windowShadeDark + border.color: qgcPal.text + anchors.centerIn: parent + Column { + id: exportCol + spacing: ScreenTools.defaultFontPixelHeight + width: parent.width + anchors.centerIn: parent + QGCLabel { + text: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionExporting ? qsTr("Tile Set Export Progress") : qsTr("Tile Set Export Completed") + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + ProgressBar { + id: progressBar + width: parent.width * 0.45 + maximumValue: 100 + value: QGroundControl.mapEngineManager.actionProgress + anchors.horizontalCenter: parent.horizontalCenter + } + BusyIndicator { + visible: QGroundControl.mapEngineManager.exporting + running: QGroundControl.mapEngineManager.exporting + width: exportCloseButton.height + height: exportCloseButton.height + anchors.horizontalCenter: parent.horizontalCenter + } + QGCButton { + id: exportCloseButton + text: qsTr("Close") + width: _buttonSize + visible: !QGroundControl.mapEngineManager.exporting + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + rootLoader.sourceComponent = null + } + } + } + } + } + } + + Component { + id: importDialog + Rectangle { + width: mainWindow.width + height: mainWindow.height + color: "black" + anchors.centerIn: parent + Rectangle { + width: parent.width * 0.45 + height: importCol.height * 1.5 + radius: ScreenTools.defaultFontPixelWidth + color: qgcPal.windowShadeDark + border.color: qgcPal.text + anchors.centerIn: parent + Column { + id: importCol + spacing: ScreenTools.defaultFontPixelHeight + width: parent.width + anchors.centerIn: parent + QGCLabel { + text: { + if(QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionNone) { + return qsTr("Map Tile Set Import"); + } else if(QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionImporting) { + return qsTr("Map Tile Set Import Progress"); + } else { + return qsTr("Map Tile Set Import Completed"); + } + } + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + ProgressBar { + id: progressBar + width: parent.width * 0.45 + maximumValue: 100 + visible: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionImporting + value: QGroundControl.mapEngineManager.actionProgress + anchors.horizontalCenter: parent.horizontalCenter + } + BusyIndicator { + visible: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionImporting + running: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionImporting + width: ScreenTools.defaultFontPixelWidth * 2 + height: width + anchors.horizontalCenter: parent.horizontalCenter + } + ExclusiveGroup { id: radioGroup } + Column { + spacing: ScreenTools.defaultFontPixelHeight + width: ScreenTools.defaultFontPixelWidth * 24 + anchors.horizontalCenter: parent.horizontalCenter + QGCRadioButton { + exclusiveGroup: radioGroup + text: qsTr("Append to existing set") + checked: !QGroundControl.mapEngineManager.importReplace + onClicked: QGroundControl.mapEngineManager.importReplace = !checked + visible: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionNone + } + QGCRadioButton { + exclusiveGroup: radioGroup + text: qsTr("Replace existing set") + checked: QGroundControl.mapEngineManager.importReplace + onClicked: QGroundControl.mapEngineManager.importReplace = checked + visible: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionNone + } + } + QGCButton { + text: qsTr("Close") + width: _bigButtonSize * 1.25 + visible: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionDone + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + showList(); + rootLoader.sourceComponent = null + } + } + Row { + spacing: _margins + visible: QGroundControl.mapEngineManager.importAction === QGCMapEngineManager.ActionNone + anchors.horizontalCenter: parent.horizontalCenter + QGCButton { + text: qsTr("Import From Disk") + width: _bigButtonSize * 1.25 + onClicked: { + if(!QGroundControl.mapEngineManager.importSets()) { + showList(); + rootLoader.sourceComponent = null + } + } + } + QGCButton { + text: qsTr("Import From Device") + width: _bigButtonSize * 1.25 + onClicked: { + rootLoader.sourceComponent = importFromDevice + } + } + QGCButton { + text: qsTr("Cancel") + width: _bigButtonSize * 1.25 + onClicked: { + showList(); + rootLoader.sourceComponent = null + } + } + } + } + } + } + } + + Component { + id: importFromDevice + Rectangle { + width: mainWindow.width + height: mainWindow.height + color: "black" + anchors.centerIn: parent + Rectangle { + width: parent.width * 0.45 + height: importCol.height * 1.5 + radius: ScreenTools.defaultFontPixelWidth + color: qgcPal.windowShadeDark + border.color: qgcPal.text + anchors.centerIn: parent + Column { + id: importCol + spacing: ScreenTools.defaultFontPixelHeight + width: parent.width + anchors.centerIn: parent + QGCLabel { + text: qsTr("Map Tile Set Import From Device"); + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("NOT YET IMPLEMENTED"); + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + QGCButton { + text: qsTr("Close") + width: _bigButtonSize * 1.25 + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + showList(); + rootLoader.sourceComponent = null + } + } + } + } + } + } + + Component { + id: exportToDevice + Rectangle { + width: mainWindow.width + height: mainWindow.height + color: "black" + anchors.centerIn: parent + Rectangle { + width: parent.width * 0.45 + height: importCol.height * 1.5 + radius: ScreenTools.defaultFontPixelWidth + color: qgcPal.windowShadeDark + border.color: qgcPal.text + anchors.centerIn: parent + Column { + id: importCol + spacing: ScreenTools.defaultFontPixelHeight + width: parent.width + anchors.centerIn: parent + QGCLabel { + text: qsTr("Map Tile Set Export To Device"); + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("NOT YET IMPLEMENTED"); + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + QGCButton { + text: qsTr("Close") + width: _bigButtonSize * 1.25 + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + showList(); + rootLoader.sourceComponent = null + } + } + } + } + } + } + } // QGCView diff --git a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc index e05dfeb34437b1b0c86919e21ee1cbb64662ac0b..f4c009b1c7e375166ca8a2081451321c0019f853 100644 --- a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc +++ b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc @@ -11,6 +11,11 @@ /// @file /// @author Gus Grubba +#if !defined(__mobile__) +#include "QGCFileDialog.h" +#include "MainWindow.h" +#endif + #include "QGCMapEngineManager.h" #include "QGCApplication.h" #include "QGCMapTileSet.h" @@ -36,6 +41,9 @@ QGCMapEngineManager::QGCMapEngineManager(QGCApplication* app) , _setID(UINT64_MAX) , _freeDiskSpace(0) , _diskSpace(0) + , _actionProgress(0) + , _importAction(ActionNone) + , _importReplace(false) { } @@ -308,6 +316,9 @@ QGCMapEngineManager::taskError(QGCMapTask::TaskType type, QString error) case QGCMapTask::taskReset: task = "Reset Tile Sets"; break; + case QGCMapTask::taskExport: + task = "Export Tile Sets"; + break; default: task = "Database Error"; break; @@ -351,6 +362,142 @@ QGCMapEngineManager::findName(const QString& name) return false; } +//----------------------------------------------------------------------------- +void +QGCMapEngineManager::selectAll() { + for(int i = 0; i < _tileSets.count(); i++ ) { + QGCCachedTileSet* set = qobject_cast(_tileSets.get(i)); + Q_ASSERT(set); + set->setSelected(true); + } +} + +//----------------------------------------------------------------------------- +void +QGCMapEngineManager::selectNone() { + for(int i = 0; i < _tileSets.count(); i++ ) { + QGCCachedTileSet* set = qobject_cast(_tileSets.get(i)); + Q_ASSERT(set); + set->setSelected(false); + } +} + +//----------------------------------------------------------------------------- +int +QGCMapEngineManager::selectedCount() { + int count = 0; + for(int i = 0; i < _tileSets.count(); i++ ) { + QGCCachedTileSet* set = qobject_cast(_tileSets.get(i)); + Q_ASSERT(set); + if(set->selected()) { + count++; + } + } + return count; +} + +//----------------------------------------------------------------------------- +bool +QGCMapEngineManager::importSets(QString path) { + _importAction = ActionNone; + emit importActionChanged(); + QString dir = path; + if(dir.isEmpty()) { +#if defined(__mobile__) + //-- TODO: This has to be something fixed + dir = QDir(QDir::homePath()).filePath(QString("export_%1.db").arg(QDateTime::currentDateTime().toTime_t())); +#else + dir = QGCFileDialog::getOpenFileName( + MainWindow::instance(), + "Export Tile Set", + QDir::homePath(), + "Tile Sets (*.qgctiledb)"); +#endif + } + if(!dir.isEmpty()) { + _importAction = ActionImporting; + emit importActionChanged(); + QGCImportTileTask* task = new QGCImportTileTask(dir, _importReplace); + connect(task, &QGCImportTileTask::actionCompleted, this, &QGCMapEngineManager::_actionCompleted); + connect(task, &QGCImportTileTask::actionProgress, this, &QGCMapEngineManager::_actionProgressHandler); + connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError); + getQGCMapEngine()->addTask(task); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +bool +QGCMapEngineManager::exportSets(QString path) { + _importAction = ActionNone; + emit importActionChanged(); + QString dir = path; + if(dir.isEmpty()) { +#if defined(__mobile__) + dir = QDir(QDir::homePath()).filePath(QString("export_%1.db").arg(QDateTime::currentDateTime().toTime_t())); +#else + dir = QGCFileDialog::getSaveFileName( + MainWindow::instance(), + "Export Tile Set", + QDir::homePath(), + "Tile Sets (*.qgctiledb)", + "qgctiledb", + true); +#endif + } + if(!dir.isEmpty()) { + QVector sets; + for(int i = 0; i < _tileSets.count(); i++ ) { + QGCCachedTileSet* set = qobject_cast(_tileSets.get(i)); + Q_ASSERT(set); + if(set->selected()) { + sets.append(set); + } + } + if(sets.count()) { + _importAction = ActionExporting; + emit importActionChanged(); + QGCExportTileTask* task = new QGCExportTileTask(sets, dir); + connect(task, &QGCExportTileTask::actionCompleted, this, &QGCMapEngineManager::_actionCompleted); + connect(task, &QGCExportTileTask::actionProgress, this, &QGCMapEngineManager::_actionProgressHandler); + connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError); + getQGCMapEngine()->addTask(task); + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +void +QGCMapEngineManager::_actionProgressHandler(int percentage) +{ + _actionProgress = percentage; + emit actionProgressChanged(); +} + +//----------------------------------------------------------------------------- +void +QGCMapEngineManager::_actionCompleted() +{ + ImportAction oldState = _importAction; + _importAction = ActionDone; + emit importActionChanged(); + //-- If we just imported, reload it all + if(oldState == ActionImporting) { + loadTileSets(); + } +} + +//----------------------------------------------------------------------------- +void +QGCMapEngineManager::resetAction() +{ + _importAction = ActionNone; + emit importActionChanged(); +} + //----------------------------------------------------------------------------- QString QGCMapEngineManager::getUniqueName() diff --git a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h index e168c6b7f171a37b47cb45348afdc8038f8f918a..7efe5c6df6534c7e987d650bda348dc8417e5e6b 100644 --- a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h +++ b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h @@ -29,6 +29,14 @@ public: QGCMapEngineManager(QGCApplication* app); ~QGCMapEngineManager(); + enum ImportAction { + ActionNone, + ActionImporting, + ActionExporting, + ActionDone, + }; + Q_ENUMS(ImportAction) + Q_PROPERTY(int tileX0 READ tileX0 NOTIFY tileX0Changed) Q_PROPERTY(int tileX1 READ tileX1 NOTIFY tileX1Changed) Q_PROPERTY(int tileY0 READ tileY0 NOTIFY tileY0Changed) @@ -46,6 +54,12 @@ public: //-- Disk Space in MB Q_PROPERTY(quint32 freeDiskSpace READ freeDiskSpace NOTIFY freeDiskSpaceChanged) Q_PROPERTY(quint32 diskSpace READ diskSpace CONSTANT) + //-- Tile set export + Q_PROPERTY(int selectedCount READ selectedCount NOTIFY selectedCountChanged) + Q_PROPERTY(int actionProgress READ actionProgress NOTIFY actionProgressChanged) + Q_PROPERTY(ImportAction importAction READ importAction NOTIFY importActionChanged) + + Q_PROPERTY(bool importReplace READ importReplace WRITE setImportReplace NOTIFY importReplaceChanged) Q_INVOKABLE void loadTileSets (); Q_INVOKABLE void updateForCurrentView (double lon0, double lat0, double lon1, double lat1, int minZoom, int maxZoom, const QString& mapName); @@ -55,6 +69,11 @@ public: Q_INVOKABLE void deleteTileSet (QGCCachedTileSet* tileSet); Q_INVOKABLE QString getUniqueName (); Q_INVOKABLE bool findName (const QString& name); + Q_INVOKABLE void selectAll (); + Q_INVOKABLE void selectNone (); + Q_INVOKABLE bool exportSets (QString path = QString()); + Q_INVOKABLE bool importSets (QString path = QString()); + Q_INVOKABLE void resetAction (); int tileX0 () { return _totalSet.tileX0; } int tileX1 () { return _totalSet.tileX1; } @@ -72,10 +91,15 @@ public: QString errorMessage () { return _errorMessage; } quint64 freeDiskSpace () { return _freeDiskSpace; } quint64 diskSpace () { return _diskSpace; } + int selectedCount (); + int actionProgress () { return _actionProgress; } + ImportAction importAction () { return _importAction; } + bool importReplace () { return _importReplace; } void setMapboxToken (QString token); void setMaxMemCache (quint32 size); void setMaxDiskCache (quint32 size); + void setImportReplace (bool replace) { _importReplace = replace; emit importReplaceChanged(); } void setErrorMessage (const QString& error) { _errorMessage = error; emit errorMessageChanged(); } @@ -95,6 +119,10 @@ signals: void maxDiskCacheChanged (); void errorMessageChanged (); void freeDiskSpaceChanged (); + void selectedCountChanged (); + void actionProgressChanged (); + void importActionChanged (); + void importReplaceChanged (); public slots: void taskError (QGCMapTask::TaskType type, QString error); @@ -105,6 +133,8 @@ private slots: void _tileSetDeleted (quint64 setID); void _updateTotals (quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize); void _resetCompleted (); + void _actionCompleted (); + void _actionProgressHandler (int percentage); private: void _updateDiskFreeSpace (); @@ -122,6 +152,9 @@ private: quint32 _diskSpace; QmlObjectListModel _tileSets; QString _errorMessage; + int _actionProgress; + ImportAction _importAction; + bool _importReplace; }; #endif diff --git a/src/VehicleSetup/SetupView.qml b/src/VehicleSetup/SetupView.qml index 64e542b3c49969d4418f9cc4bac2f5c278f7d5b9..a4b7b7a74ca78911ab88316adaa2d2de8ef8958f 100644 --- a/src/VehicleSetup/SetupView.qml +++ b/src/VehicleSetup/SetupView.qml @@ -232,14 +232,14 @@ Rectangle { } Repeater { - model: _corePlugin.settingsPages - visible: _corePlugin.options.combineSettingsAndSetup + model: _corePlugin ? _corePlugin.settingsPages : [] + visible: _corePlugin && _corePlugin.options.combineSettingsAndSetup SubMenuButton { imageResource: modelData.icon setupIndicator: false exclusiveGroup: setupButtonGroup text: modelData.title - visible: _corePlugin.options.combineSettingsAndSetup + visible: _corePlugin && _corePlugin.options.combineSettingsAndSetup onClicked: panelLoader.setSource(modelData.url) Layout.fillWidth: true } @@ -312,7 +312,7 @@ Rectangle { SubMenuButton { setupIndicator: false exclusiveGroup: setupButtonGroup - visible: QGroundControl.multiVehicleManager.parameterReadyVehicleAvailable && _corePlugin.showAdvancedUI + visible: QGroundControl.multiVehicleManager && QGroundControl.multiVehicleManager.parameterReadyVehicleAvailable && _corePlugin.showAdvancedUI text: "Parameters" Layout.fillWidth: true