diff --git a/changelog.txt b/changelog.txt index 97bb17ce2..20cb22989 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ V1.XX.X [QMS-992] Fix: Changing items does not update the map view [QMS-994] Fix: QMapTool not callable from QMapShack (macOS) [QMS-996] Fix: POIs not displayed (macOS) +[QMS-998] Add progress bar to GPS devices while loading records V1.20.1 [QMS-945] Redesign icons for focus, autosave, sync, and DB buttons diff --git a/src/qmapshack/CMakeLists.txt b/src/qmapshack/CMakeLists.txt index 7bb011174..ba0cda376 100644 --- a/src/qmapshack/CMakeLists.txt +++ b/src/qmapshack/CMakeLists.txt @@ -191,6 +191,7 @@ set( SRCS helpers/CProgressDialog.cpp helpers/CSelectCopyAction.cpp helpers/CSelectProjectDialog.cpp + helpers/CThread.cpp helpers/CTimeDialog.cpp helpers/CToolBarConfig.cpp helpers/CToolBarSetupDialog.cpp @@ -563,6 +564,7 @@ set( HDRS helpers/CSelectCopyAction.h helpers/CSelectProjectDialog.h helpers/CSettings.h + helpers/CThread.h helpers/CTryMutexLocker.h helpers/CTimeDialog.h helpers/CToolBarConfig.h diff --git a/src/qmapshack/device/CDeviceAccessKMtp.cpp b/src/qmapshack/device/CDeviceAccessKMtp.cpp index 902bc948d..fe1f991fa 100644 --- a/src/qmapshack/device/CDeviceAccessKMtp.cpp +++ b/src/qmapshack/device/CDeviceAccessKMtp.cpp @@ -132,7 +132,7 @@ QStringList CDeviceAccessKMtp::listDirsOnStorage(const QString& path) { int CDeviceAccessKMtp::waitForCopyOperation(const org::kde::kmtp::Storage* storage, fn_operation operation) { QEventLoop loop; connect(storage, &org::kde::kmtp::Storage::copyProgress, &loop, - [](qulonglong sent, qulonglong total) { qDebug() << "processed size:" << sent << "of" << total; }); + [](qulonglong sent, qulonglong total) { /*qDebug() << "processed size:" << sent << "of" << total;*/ }); connect(storage, &org::kde::kmtp::Storage::copyFinished, &loop, &QEventLoop::exit); if (operation() == 0) { diff --git a/src/qmapshack/device/CDeviceGarmin.cpp b/src/qmapshack/device/CDeviceGarmin.cpp index d1f63825c..0f8d04b91 100644 --- a/src/qmapshack/device/CDeviceGarmin.cpp +++ b/src/qmapshack/device/CDeviceGarmin.cpp @@ -28,10 +28,11 @@ #include "gis/gpx/CGpxProject.h" #include "gis/tcx/CTcxProject.h" #include "gis/wpt/CGisItemWpt.h" +#include "helpers/CThread.h" CDeviceGarmin::CDeviceGarmin(const QString& path, const QString& key, const QString& model, const QString& garminDeviceXml, QTreeWidget* parent) - : IDevice(path, eTypeGarmin, key, parent), model(model), cntImages(0) { + : QObject(parent), IDevice(path, eTypeGarmin, key, parent), model(model), cntImages(0) { name = "Garmin"; QFile file(QDir(path).absoluteFilePath(garminDeviceXml)); @@ -118,51 +119,95 @@ CDeviceGarmin::CDeviceGarmin(const QString& path, const QString& key, const QStr dir.mkpath(pathTcx); } - createProjectsFromFiles(pathGpx, "gpx"); - createProjectsFromFiles(pathGpx + "/Current", "gpx"); + threadLoadData = new CThread([this]() { + CDeviceMountLock mountLock(*this); + quint32 total = 0; + total += QDir(dir.absoluteFilePath(pathGpx)).entryList(QStringList("*.gpx")).count(); + total += QDir(dir.absoluteFilePath(pathGpx + "/Current")).entryList(QStringList("*.gpx")).count(); + total += QDir(dir.absoluteFilePath(pathActivities)).entryList(QStringList("*.fit")).count(); + total += QDir(dir.absoluteFilePath(pathCourses)).entryList(QStringList("*.fit")).count(); + total += QDir(dir.absoluteFilePath(pathLocations)).entryList(QStringList("*.fit")).count(); + if (!pathTcx.isEmpty()) { + total += QDir(dir.absoluteFilePath(pathTcx)).entryList(QStringList("*.tcx")).count(); + } + quint32 count = 0; + + createProjectsFromFiles(pathGpx, "gpx", count, total); + createProjectsFromFiles(pathGpx + "/Current", "gpx", count, total); + createProjectsFromFiles(pathActivities, "fit", count, total); + createProjectsFromFiles(pathCourses, "fit", count, total); + createProjectsFromFiles(pathLocations, "fit", count, total); + if (!pathTcx.isEmpty()) { + createProjectsFromFiles(pathTcx, "tcx", count, total); + } - QDir dirArchive(dir.absoluteFilePath(pathGpx + "/Archive")); - if (dirArchive.exists() && (dirArchive.entryList(QStringList("*.gpx")).count() != 0)) { - new CDeviceGarminArchive(dir.absoluteFilePath(pathGpx + "/Archive"), this); - } + QMetaObject::invokeMethod( + this, + [this]() { + QMutexLocker lock(&IGisItem::mutexItems); + QDir dirArchive(dir.absoluteFilePath(pathGpx + "/Archive")); + if (dirArchive.exists() && (dirArchive.entryList(QStringList("*.gpx")).count() != 0)) { + new CDeviceGarminArchive(dir.absoluteFilePath(pathGpx + "/Archive"), this); + } + }, + Qt::BlockingQueuedConnection); + + setProgress(total, total); + }); + + threadLoadData->start(); +} - createProjectsFromFiles(pathActivities, "fit"); - createProjectsFromFiles(pathCourses, "fit"); - createProjectsFromFiles(pathLocations, "fit"); - if (!pathTcx.isEmpty()) { - createProjectsFromFiles(pathTcx, "tcx"); +CDeviceGarmin::~CDeviceGarmin() { + if (threadLoadData != nullptr) { + threadLoadData->cancel(); } } QString CDeviceGarmin::getInfo(quint32) const { return QString("%1 (%2, %3)").arg(description, partno, model); } -void CDeviceGarmin::createProjectsFromFiles(QString subdirecoty, QString fileEnding) { +void CDeviceGarmin::createProjectsFromFiles(QString subdirecoty, QString fileEnding, quint32& count, + const quint32 total) { QDir dirLoop(dir.absoluteFilePath(subdirecoty)); qDebug() << "reading files from device: " << dirLoop.path(); const QStringList& entries = dirLoop.entryList(QStringList("*." + fileEnding)); for (const QString& entry : entries) { - const QString filename = dirLoop.absoluteFilePath(entry); - IGisProject* project = nullptr; - if (fileEnding == "fit") { - project = new CFit2Project(filename, this); - } else if (fileEnding == "gpx") { - project = new CGpxProject(filename, this); - } else if (fileEnding == "tcx") { - project = new CTcxProject(filename, this); + if (threadLoadData->isInterruptionRequested()) { + break; } - if (project) { - if (!project->isValid()) { - delete project; - } else { - project->setVisibility(isVisible()); - } - } + setProgress(++count, total); + const QString filename = dirLoop.absoluteFilePath(entry); + + QMetaObject::invokeMethod( + this, + [this, filename, fileEnding]() { + QMutexLocker lock(&IGisItem::mutexItems); + IGisProject* project = nullptr; + try { + if (fileEnding == "fit") { + project = new CFit2Project(filename, this); + } else if (fileEnding == "gpx") { + project = new CGpxProject(filename, this); + } else if (fileEnding == "tcx") { + project = new CTcxProject(filename, this); + } + + if (project) { + if (!project->isValid()) { + delete project; + } else { + project->setVisibility(isVisible()); + } + } + } catch (const QString& msg) { + qWarning() << msg; + } + }, + Qt::BlockingQueuedConnection); } } -CDeviceGarmin::~CDeviceGarmin() {} - void CDeviceGarmin::insertCopyOfProject(IGisProject* project) { if (description.startsWith("EDGE 5", Qt::CaseInsensitive)) { insertCopyOfProjectAsTcx(project); diff --git a/src/qmapshack/device/CDeviceGarmin.h b/src/qmapshack/device/CDeviceGarmin.h index 8df23dae5..a01388dcd 100644 --- a/src/qmapshack/device/CDeviceGarmin.h +++ b/src/qmapshack/device/CDeviceGarmin.h @@ -19,12 +19,15 @@ #ifndef CDEVICEGARMIN_H #define CDEVICEGARMIN_H +#include + #include "device/IDevice.h" class CDeviceGarminArchive; +class CThread; -class CDeviceGarmin : public IDevice { - Q_DECLARE_TR_FUNCTIONS(CDeviceGarmin) +class CDeviceGarmin : public QObject, public IDevice { + Q_OBJECT public: CDeviceGarmin(const QString& path, const QString& key, const QString& model, const QString& garminDeviceXml, QTreeWidget* parent); @@ -38,7 +41,7 @@ class CDeviceGarmin : public IDevice { QString getInfo(quint32) const override; private: - void createProjectsFromFiles(QString subdirecoty, QString fileEnding); + void createProjectsFromFiles(QString subdirecoty, QString fileEnding, quint32& count, const quint32 total); void createAdventureFromProject(IGisProject* project, const QString& gpxFilename); void insertCopyOfProjectAsGpx(IGisProject* project); void insertCopyOfProjectAsTcx(IGisProject* project); @@ -61,6 +64,8 @@ class CDeviceGarmin : public IDevice { QString pathTcx; // no default int cntImages = 0; + + QPointer threadLoadData; }; #endif // CDEVICEGARMIN_H diff --git a/src/qmapshack/device/CDeviceGarminArchive.cpp b/src/qmapshack/device/CDeviceGarminArchive.cpp index a050562b9..f8b0f97d4 100644 --- a/src/qmapshack/device/CDeviceGarminArchive.cpp +++ b/src/qmapshack/device/CDeviceGarminArchive.cpp @@ -20,43 +20,72 @@ #include -#include "canvas/CCanvas.h" #include "device/CDeviceGarmin.h" #include "gis/CGisListWks.h" #include "gis/CGisWorkspace.h" #include "gis/gpx/CGpxProject.h" +#include "helpers/CThread.h" CDeviceGarminArchive::CDeviceGarminArchive(const QString& path, CDeviceGarmin* parent) - : IDevice(path, parent->getKey(), parent) { + : QObject(parent), IDevice(path, parent->getKey(), parent) { name = tr("Archive - expand to load"); setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); connect(treeWidget(), &QTreeWidget::itemExpanded, this, &CDeviceGarminArchive::slotExpanded); connect(treeWidget(), &QTreeWidget::itemCollapsed, this, &CDeviceGarminArchive::slotCollapsed); } +CDeviceGarminArchive::~CDeviceGarminArchive() { + if (threadLoadData != nullptr) { + threadLoadData->cancel(); + } +}; + void CDeviceGarminArchive::slotExpanded(QTreeWidgetItem* item) { if ((item != this) || (childCount() != 0)) { return; } - name = tr("Archive - loaded"); - - QMutexLocker lock(&IGisItem::mutexItems); - CDeviceMountLock mountLock(*this); - CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__); + name = tr("Archive - loading"); qDebug() << "reading files from device: " << dir.path(); - const QStringList& entries = dir.entryList(QStringList("*.gpx")); - for (const QString& entry : entries) { - const QString& filename = dir.absoluteFilePath(entry); - IGisProject* project = new CGpxProject(filename, this); - if (project) { - if (!project->isValid()) { - delete project; - } else { - project->setVisibility(isVisible()); + + threadLoadData = new CThread([this]() { + CDeviceMountLock mountLock(*this); + const QStringList& entries = dir.entryList(QStringList("*.gpx")); + + quint32 total = entries.count(); + quint32 count = 0; + + for (const QString& entry : entries) { + if (threadLoadData->isInterruptionRequested()) { + break; } + setProgress(++count, total); + const QString& filename = dir.absoluteFilePath(entry); + QMetaObject::invokeMethod( + this, + [this, filename]() { + QMutexLocker lock(&IGisItem::mutexItems); + try { + IGisProject* project = new CGpxProject(filename, this); + if (project) { + if (!project->isValid()) { + delete project; + } else { + project->setVisibility(isVisible()); + } + } + } catch (const QString& msg) { + qWarning() << msg; + } + }, + Qt::BlockingQueuedConnection); } - } + + name = tr("Archive - loaded"); + setProgress(total, total); + }); + + threadLoadData->start(); } void CDeviceGarminArchive::slotCollapsed(QTreeWidgetItem* item) { @@ -64,10 +93,11 @@ void CDeviceGarminArchive::slotCollapsed(QTreeWidgetItem* item) { return; } - QMutexLocker lock(&IGisItem::mutexItems); - CDeviceMountLock mountLock(*this); - CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__); + if (threadLoadData != nullptr) { + threadLoadData->cancel(); + } + QMutexLocker lock(&IGisItem::mutexItems); qDeleteAll(takeChildren()); name = tr("Archive - expand to load"); diff --git a/src/qmapshack/device/CDeviceGarminArchive.h b/src/qmapshack/device/CDeviceGarminArchive.h index 191d48a1c..9502bd94b 100644 --- a/src/qmapshack/device/CDeviceGarminArchive.h +++ b/src/qmapshack/device/CDeviceGarminArchive.h @@ -22,16 +22,18 @@ #include #include #include +#include #include "device/IDevice.h" class CDeviceGarmin; +class CThread; class CDeviceGarminArchive : public QObject, public IDevice { Q_OBJECT public: CDeviceGarminArchive(const QString& path, CDeviceGarmin* parent); - virtual ~CDeviceGarminArchive() = default; + virtual ~CDeviceGarminArchive(); QString getInfo(quint32) const override { return ""; } @@ -41,6 +43,9 @@ class CDeviceGarminArchive : public QObject, public IDevice { private slots: void slotExpanded(QTreeWidgetItem* item); void slotCollapsed(QTreeWidgetItem* item); + + private: + QPointer threadLoadData; }; #endif // CDEVICEGARMINARCHIVE_H diff --git a/src/qmapshack/device/CDeviceGarminArchiveMtp.cpp b/src/qmapshack/device/CDeviceGarminArchiveMtp.cpp index d6827b734..e83163d42 100644 --- a/src/qmapshack/device/CDeviceGarminArchiveMtp.cpp +++ b/src/qmapshack/device/CDeviceGarminArchiveMtp.cpp @@ -18,52 +18,83 @@ #include "device/CDeviceGarminArchiveMtp.h" -#include "canvas/CCanvas.h" #include "device/CDeviceGarminMtp.h" #include "device/IDeviceAccess.h" #include "gis/CGisListWks.h" #include "gis/CGisWorkspace.h" #include "gis/gpx/CGpxProject.h" +#include "helpers/CThread.h" #include "misc.h" CDeviceGarminArchiveMtp::CDeviceGarminArchiveMtp(const QString& path, IDeviceAccess* device, CDeviceGarminMtp* parent) - : IDevice(path, parent->getKey(), parent), device(device) { + : QObject(parent), IDevice(path, parent->getKey(), parent), device(device) { name = tr("Archive - expand to load"); setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); connect(treeWidget(), &QTreeWidget::itemExpanded, this, &CDeviceGarminArchiveMtp::slotExpanded); connect(treeWidget(), &QTreeWidget::itemCollapsed, this, &CDeviceGarminArchiveMtp::slotCollapsed); } +CDeviceGarminArchiveMtp::~CDeviceGarminArchiveMtp() { + if (threadLoadData != nullptr) { + threadLoadData->cancel(); + } +}; + void CDeviceGarminArchiveMtp::slotExpanded(QTreeWidgetItem* item) { if ((item != this) || (childCount() != 0)) { return; } - name = tr("Archive - loaded"); - QMutexLocker lock(&IGisItem::mutexItems); - CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__); - qDebug() << "reading files from device: " << dir.path(); - const QStringList& entries = device->listFilesOnStorage(dir.path()); - for (const QString& entry : entries) { - if (!entry.endsWith(".gpx")) { - continue; - } + name = tr("Archive - loading"); - const QString& filename = dir.filePath(entry); - QTemporaryFile tempFile; - if (!device->readFileFromStorage(filename, tempFile)) { - continue; - } - openFileCheckSuccess(QIODevice::ReadWrite, tempFile); - IGisProject* project = new CGpxProject(tempFile, filename, this); - if (project) { - if (!project->isValid()) { - delete project; - } else { - project->setVisibility(isVisible()); + threadLoadData = new CThread([this]() { + qDebug() << "reading files from device: " << dir.path(); + const QStringList& entries = device->listFilesOnStorage(dir.path()); + const quint32 total = entries.count(); + quint32 count = 0; + for (const QString& entry : entries) { + if (threadLoadData->isInterruptionRequested()) { + break; + } + + setProgress(++count, total); + if (!entry.endsWith(".gpx")) { + continue; } + + QMetaObject::invokeMethod( + this, + [this, entry]() { + const QString& filename = dir.filePath(entry); + QTemporaryFile tempFile; + if (!device->readFileFromStorage(filename, tempFile)) { + return; + } + openFileCheckSuccess(QIODevice::ReadWrite, tempFile); + + QMutexLocker lock(&IGisItem::mutexItems); + try { + IGisProject* project = new CGpxProject(tempFile, filename, this); + if (project) { + if (!project->isValid()) { + delete project; + } else { + project->setVisibility(isVisible()); + } + } + } catch (const QString& msg) { + qWarning() << msg; + } + }, + Qt::BlockingQueuedConnection); } - } + + QMutexLocker lock(&IGisItem::mutexItems); + name = tr("Archive - loaded"); + setProgress(total, total); + }); + + threadLoadData->start(); } void CDeviceGarminArchiveMtp::slotCollapsed(QTreeWidgetItem* item) { @@ -71,11 +102,12 @@ void CDeviceGarminArchiveMtp::slotCollapsed(QTreeWidgetItem* item) { return; } - QMutexLocker lock(&IGisItem::mutexItems); - CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__); + if (threadLoadData != nullptr) { + threadLoadData->cancel(); + } + QMutexLocker lock(&IGisItem::mutexItems); qDeleteAll(takeChildren()); - name = tr("Archive - expand to load"); emit CGisWorkspace::self().sigChanged(); } diff --git a/src/qmapshack/device/CDeviceGarminArchiveMtp.h b/src/qmapshack/device/CDeviceGarminArchiveMtp.h index ed38eb2ae..6e01fb15b 100644 --- a/src/qmapshack/device/CDeviceGarminArchiveMtp.h +++ b/src/qmapshack/device/CDeviceGarminArchiveMtp.h @@ -19,16 +19,19 @@ #ifndef CDEVICEGARMINARCHIVEMTP_H #define CDEVICEGARMINARCHIVEMTP_H +#include + #include "device/IDevice.h" class CDeviceGarminMtp; class IDeviceAccess; +class CThread; class CDeviceGarminArchiveMtp : public QObject, public IDevice { Q_OBJECT public: CDeviceGarminArchiveMtp(const QString& path, IDeviceAccess* device, CDeviceGarminMtp* parent); - virtual ~CDeviceGarminArchiveMtp() = default; + virtual ~CDeviceGarminArchiveMtp(); QString getInfo(quint32) const override { return ""; } @@ -41,6 +44,8 @@ class CDeviceGarminArchiveMtp : public QObject, public IDevice { private: IDeviceAccess* device; + + QPointer threadLoadData; }; #endif // CDEVICEGARMINARCHIVEMTP_H diff --git a/src/qmapshack/device/CDeviceGarminMtp.cpp b/src/qmapshack/device/CDeviceGarminMtp.cpp index 9ecdfea2b..a83b0bd72 100644 --- a/src/qmapshack/device/CDeviceGarminMtp.cpp +++ b/src/qmapshack/device/CDeviceGarminMtp.cpp @@ -24,21 +24,28 @@ #include "gis/CGisListWks.h" #include "gis/fit2/CFit2Project.h" #include "gis/gpx/CGpxProject.h" +#include "helpers/CThread.h" #include "misc.h" CDeviceGarminMtp::CDeviceGarminMtp(const GVFSMount& mount, const QString& storagePath, const QString& key, QTreeWidget* parent) - : IDevice("", eTypeGarminMtp, key, parent), QObject(parent) { + : QObject(parent), IDevice("", eTypeGarminMtp, key, parent) { device = new CDeviceAccessGvfsMtp(mount, storagePath, this); setup(); } CDeviceGarminMtp::CDeviceGarminMtp(const QDBusObjectPath& objectPathStorage, const QString& key, QTreeWidget* parent) - : IDevice("", eTypeGarminMtp, key, parent), QObject(parent) { + : QObject(parent), IDevice("", eTypeGarminMtp, key, parent) { device = new CDeviceAccessKMtp(objectPathStorage, this); setup(); } +CDeviceGarminMtp::~CDeviceGarminMtp() { + if (threadLoadData != nullptr) { + threadLoadData->cancel(); + } +} + QString CDeviceGarminMtp::getInfo(quint32) const { // update the tool tip with information from the device xml return QString("%1 (%2, V%3)").arg(description, partno, softwareVersion); @@ -134,17 +141,38 @@ void CDeviceGarminMtp::setup() { qDebug() << pathAdventures; qDebug() << pathTcx; - createProjectsFromFiles(pathGpx, "gpx"); - createProjectsFromFiles(pathActivities, "fit"); - createProjectsFromFiles(pathCourses, "fit"); - if (!pathLocations.isEmpty()) { - createProjectsFromFiles(pathLocations, "fit"); - } + threadLoadData = new CThread([this]() { + quint32 total = 0; + total += device->listFilesOnStorage(pathGpx).count(); + total += device->listFilesOnStorage(pathActivities).count(); + total += device->listFilesOnStorage(pathCourses).count(); + if (!pathLocations.isEmpty()) { + total += device->listFilesOnStorage(pathCourses).count(); + } + quint32 count = 0; + + createProjectsFromFiles(pathGpx, "gpx", count, total); + createProjectsFromFiles(pathActivities, "fit", count, total); + createProjectsFromFiles(pathCourses, "fit", count, total); + if (!pathLocations.isEmpty()) { + createProjectsFromFiles(pathLocations, "fit", count, total); + } - if (device->listDirsOnStorage(pathGpx).contains("Archive") && - !device->listFilesOnStorage(QDir(pathGpx).filePath("Archive")).isEmpty()) { - new CDeviceGarminArchiveMtp(QDir(pathGpx).filePath("Archive"), device, this); - }; + QMetaObject::invokeMethod( + this, + [this]() { + QMutexLocker lock(&IGisItem::mutexItems); + if (device->listDirsOnStorage(pathGpx).contains("Archive") && + !device->listFilesOnStorage(QDir(pathGpx).filePath("Archive")).isEmpty()) { + new CDeviceGarminArchiveMtp(QDir(pathGpx).filePath("Archive"), device, this); + }; + }, + Qt::BlockingQueuedConnection); + + setProgress(total, total); + }); + + threadLoadData->start(); } bool CDeviceGarminMtp::removeFromDevice(const QString& filename) { @@ -178,30 +206,50 @@ void CDeviceGarminMtp::insertCopyOfProject(IGisProject* project) { reorderProjects(gpx); } -void CDeviceGarminMtp::createProjectsFromFiles(QString subdirectory, QString extension) { +void CDeviceGarminMtp::createProjectsFromFiles(QString subdirectory, QString extension, quint32& count, + const quint32 total) { QDir d(subdirectory); const QStringList& files = device->listFilesOnStorage(subdirectory); for (const QString& file : files) { - if (file.endsWith(extension)) { - QTemporaryFile tempFile; - if (!device->readFileFromStorage(d.filePath(file), tempFile)) { - return; - } - openFileCheckSuccess(QIODevice::ReadWrite, tempFile); - IGisProject* project = nullptr; - if (extension == "gpx") { - project = new CGpxProject(tempFile, d.filePath(file), this); - } else if (extension == "fit") { - project = new CFit2Project(tempFile, d.filePath(file), this); - } - if (project) { - if (!project->isValid()) { - delete project; - } else { - project->setVisibility(isVisible()); - } - } + if (QThread::currentThread()->isInterruptionRequested()) { + return; } + + setProgress(++count, total); + if (!file.endsWith(extension)) { + continue; + } + + QMetaObject::invokeMethod( + this, + [this, d, file, extension]() { + QTemporaryFile tempFile; + if (!device->readFileFromStorage(d.filePath(file), tempFile)) { + return; + } + openFileCheckSuccess(QIODevice::ReadWrite, tempFile); + + QMutexLocker lock(&IGisItem::mutexItems); + + IGisProject* project = nullptr; + try { + if (extension == "gpx") { + project = new CGpxProject(tempFile, d.filePath(file), this); + } else if (extension == "fit") { + project = new CFit2Project(tempFile, d.filePath(file), this); + } + if (project) { + if (!project->isValid()) { + delete project; + } else { + project->setVisibility(isVisible()); + } + } + } catch (const QString& msg) { + qWarning() << msg; + } + }, + Qt::BlockingQueuedConnection); } } diff --git a/src/qmapshack/device/CDeviceGarminMtp.h b/src/qmapshack/device/CDeviceGarminMtp.h index 9711d1bad..07f1ffeb3 100644 --- a/src/qmapshack/device/CDeviceGarminMtp.h +++ b/src/qmapshack/device/CDeviceGarminMtp.h @@ -19,22 +19,23 @@ #ifndef CDEVICEGARMINMTP_H #define CDEVICEGARMINMTP_H -#include +#include #include "device/IDevice.h" class QDBusObjectPath; class IDeviceAccess; class GVFSMount; +class CThread; -class CDeviceGarminMtp : public IDevice, private QObject { - Q_DECLARE_TR_FUNCTIONS(CDeviceGarminMtp) +class CDeviceGarminMtp : public QObject, public IDevice { + Q_OBJECT public: CDeviceGarminMtp(const QDBusObjectPath& objectPathStorage, const QString& key, QTreeWidget* parent); CDeviceGarminMtp(const GVFSMount& mount, const QString& storagePath, const QString& key, QTreeWidget* parent); - virtual ~CDeviceGarminMtp() = default; + virtual ~CDeviceGarminMtp(); bool removeFromDevice(const QString& filename); @@ -43,11 +44,9 @@ class CDeviceGarminMtp : public IDevice, private QObject { protected: void insertCopyOfProject(IGisProject* project) override; - // org::kde::kmtp::Storage* storage; - private: void setup(); - void createProjectsFromFiles(QString subdirectory, QString extension); + void createProjectsFromFiles(QString subdirectory, QString extension, quint32& count, const quint32 total); QString createFileName(IGisProject* project, const QString& path, const QString& suffix) const; QString simplifiedName(IGisProject* project) const; void reorderProjects(IGisProject* project); @@ -66,6 +65,8 @@ class CDeviceGarminMtp : public IDevice, private QObject { QString pathLocations; QString pathAdventures; // no default QString pathTcx; // no default + + QPointer threadLoadData; }; #endif // CDEVICEGARMINMTP_H diff --git a/src/qmapshack/device/CDeviceGenericMtp.cpp b/src/qmapshack/device/CDeviceGenericMtp.cpp index 47bb209d2..fd7c0f4fb 100644 --- a/src/qmapshack/device/CDeviceGenericMtp.cpp +++ b/src/qmapshack/device/CDeviceGenericMtp.cpp @@ -23,23 +23,30 @@ #include "gis/CGisListWks.h" #include "gis/fit2/CFit2Project.h" #include "gis/gpx/CGpxProject.h" +#include "helpers/CThread.h" #include "misc.h" // Used by gvfs CDeviceGenericMtp::CDeviceGenericMtp(const GVFSMount& mount, const QString& storagePath, const QString& key, QTreeWidget* parent) - : IDevice("", eTypeGenericMtp, key, parent), QObject(parent) { + : QObject(parent), IDevice("", eTypeGenericMtp, key, parent) { device = new CDeviceAccessGvfsMtp(mount, storagePath, this); setup(); } // Used by kiod6 CDeviceGenericMtp::CDeviceGenericMtp(const QDBusObjectPath& objectPathStorage, const QString& key, QTreeWidget* parent) - : IDevice("", eTypeGenericMtp, key, parent), QObject(parent) { + : QObject(parent), IDevice("", eTypeGenericMtp, key, parent) { device = new CDeviceAccessKMtp(objectPathStorage, this); setup(); } +CDeviceGenericMtp::~CDeviceGenericMtp() { + if (threadLoadData != nullptr) { + threadLoadData->cancel(); + } +} + QString CDeviceGenericMtp::getInfo(quint32) const { return ""; } void CDeviceGenericMtp::setup() { @@ -73,17 +80,35 @@ void CDeviceGenericMtp::setup() { IWksItem::icon = pixmap; } - for (const QString& path : exportPathsRelativ) { - const QString& _path = QDir::cleanPath(rootPath.filePath(path)); - createProjectsFromFiles(_path); - exportPaths << _path; - } + name = QString("%1 (%2)").arg(description, device->decription()); - for (const QString& path : importPathsRelativ) { - createProjectsFromFiles(QDir::cleanPath(rootPath.filePath(path))); - } + threadLoadData = new CThread([this, rootPath, exportPathsRelativ, importPathsRelativ]() { + quint32 total = 0; + for (const QString& path : exportPathsRelativ) { + const QString& subdirectory = QDir::cleanPath(rootPath.filePath(path)); + total += device->listFilesOnStorage(subdirectory).count(); + } + for (const QString& path : importPathsRelativ) { + const QString& subdirectory = QDir::cleanPath(rootPath.filePath(path)); + total += device->listFilesOnStorage(subdirectory).count(); + } + quint32 count = 0; + + for (const QString& path : exportPathsRelativ) { + const QString& _path = QDir::cleanPath(rootPath.filePath(path)); + createProjectsFromFiles(_path, count, total); + exportPaths << _path; + } + + for (const QString& path : importPathsRelativ) { + createProjectsFromFiles(QDir::cleanPath(rootPath.filePath(path)), count, total); + } + + setProgress(total, total); + }); } - name = QString("%1 (%2)").arg(description, device->decription()); + + threadLoadData->start(); } bool CDeviceGenericMtp::removeFromDevice(const QString& filename) { @@ -119,33 +144,51 @@ void CDeviceGenericMtp::insertCopyOfProject(IGisProject* project) { } } -void CDeviceGenericMtp::createProjectsFromFiles(const QString& subdirectory) { +void CDeviceGenericMtp::createProjectsFromFiles(const QString& subdirectory, quint32& count, const quint32 total) { static const QStringList knownSuffix = {"gpx", "fit"}; QDir d(subdirectory); const QStringList& files = device->listFilesOnStorage(subdirectory); for (const QString& file : files) { + if (QThread::currentThread()->isInterruptionRequested()) { + break; + } + const QString suffix = QFileInfo(file).suffix().toLower(); - if (knownSuffix.contains(suffix)) { - QTemporaryFile tempFile; - if (!device->readFileFromStorage(d.filePath(file), tempFile)) { - return; - } - openFileCheckSuccess(QIODevice::ReadWrite, tempFile); - IGisProject* project = nullptr; - if (suffix == "gpx") { - project = new CGpxProject(tempFile, d.filePath(file), this); - } else if (suffix == "fit") { - project = new CFit2Project(tempFile, d.filePath(file), this); - } - if (project) { - if (!project->isValid()) { - delete project; - } else { - project->setVisibility(isVisible()); - } - } + if (!knownSuffix.contains(suffix)) { + continue; } + + setProgress(++count, total); + + QMetaObject::invokeMethod( + this, + [this, suffix, d, file]() { + QTemporaryFile tempFile; + if (!device->readFileFromStorage(d.filePath(file), tempFile)) { + return; + } + openFileCheckSuccess(QIODevice::ReadWrite, tempFile); + IGisProject* project = nullptr; + + try { + if (suffix == "gpx") { + project = new CGpxProject(tempFile, d.filePath(file), this); + } else if (suffix == "fit") { + project = new CFit2Project(tempFile, d.filePath(file), this); + } + if (project) { + if (!project->isValid()) { + delete project; + } else { + project->setVisibility(isVisible()); + } + } + } catch (const QString& msg) { + qWarning() << msg; + } + }, + Qt::BlockingQueuedConnection); } } diff --git a/src/qmapshack/device/CDeviceGenericMtp.h b/src/qmapshack/device/CDeviceGenericMtp.h index 3e832f008..b2644389a 100644 --- a/src/qmapshack/device/CDeviceGenericMtp.h +++ b/src/qmapshack/device/CDeviceGenericMtp.h @@ -19,22 +19,23 @@ #ifndef CDEVICEGENERICMTP_H #define CDEVICEGENERICMTP_H -#include +#include #include "device/IDevice.h" class QDBusObjectPath; class IDeviceAccess; class GVFSMount; +class CThread; -class CDeviceGenericMtp : public IDevice, private QObject { - Q_DECLARE_TR_FUNCTIONS(CDeviceGenericMtp) +class CDeviceGenericMtp : public QObject, public IDevice { + Q_OBJECT public: CDeviceGenericMtp(const QDBusObjectPath& objectPathStorage, const QString& key, QTreeWidget* parent); CDeviceGenericMtp(const GVFSMount& mount, const QString& storagePath, const QString& key, QTreeWidget* parent); - virtual ~CDeviceGenericMtp() = default; + virtual ~CDeviceGenericMtp(); bool removeFromDevice(const QString& filename); @@ -52,13 +53,15 @@ class CDeviceGenericMtp : public IDevice, private QObject { }; void setup(); - void createProjectsFromFiles(const QString& subdirectory); + void createProjectsFromFiles(const QString& subdirectory, quint32& count, const quint32 total); QString createFileName(IGisProject* project, const QString& path, const QString& suffix) const; QString simplifiedName(IGisProject* project) const; void reorderProjects(IGisProject* project); IDeviceAccess* device; QStringList exportPaths; + + QPointer threadLoadData; }; #endif // CDEVICEGENERICMTP_H diff --git a/src/qmapshack/device/IDevice.cpp b/src/qmapshack/device/IDevice.cpp index 9f6683321..9ff93e03d 100644 --- a/src/qmapshack/device/IDevice.cpp +++ b/src/qmapshack/device/IDevice.cpp @@ -28,12 +28,13 @@ #include #endif -int IDevice::cnt = 0; +quint32 IDevice::countDevice = 0; +quint32 IDevice::countMount = 0; IDevice::IDevice(const QString& path, type_e type, const QString& key, QTreeWidget* parent) : IWksItem(parent, type), dir(path), key(key) { icon = QPixmap("://icons/32x32/Device.png"); - cnt++; + countDevice++; setVisibility(false); } @@ -45,7 +46,7 @@ IDevice::IDevice(const QString& path, const QString& key, IDevice* parent) IDevice::~IDevice() { if (type() != eTypeVirtual) { - cnt--; + countDevice--; } } @@ -62,27 +63,39 @@ void IDevice::setVisibility(bool visible) { void IDevice::mount(const QString& path) { #ifdef HAVE_DBUS + QMutexLocker lock(&IGisItem::mutexItems); + if (++countMount != 1) { + return; + } + qDebug() << "mount" << path; QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.UDisks2", path, "org.freedesktop.UDisks2.Filesystem", "Mount"); QVariantMap args; args.insert("options", "sync"); message << args; -#if defined(Q_OS_FREEBSD) - // XXX Hunc sint race conditions - call bsdisks (UDisks2) too fast, - // get a malformed reply, crash. - QThread::sleep(1); -#endif - QDBusConnection::systemBus().call(message); + + const QDBusMessage& res = QDBusConnection::systemBus().call(message); + if (res.type() == QDBusMessage::ErrorMessage) { + qWarning() << res; + } #endif } void IDevice::umount(const QString& path) { #ifdef HAVE_DBUS + QMutexLocker lock(&IGisItem::mutexItems); + if (--countMount != 0) { + return; + } + qDebug() << "umount" << path; QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.UDisks2", path, "org.freedesktop.UDisks2.Filesystem", "Unmount"); QVariantMap args; message << args; - QDBusConnection::systemBus().call(message); + const QDBusMessage& res = QDBusConnection::systemBus().call(message); + if (res.type() == QDBusMessage::ErrorMessage) { + qWarning() << res; + } #endif } diff --git a/src/qmapshack/device/IDevice.h b/src/qmapshack/device/IDevice.h index f90d017e2..31e4c732a 100644 --- a/src/qmapshack/device/IDevice.h +++ b/src/qmapshack/device/IDevice.h @@ -37,7 +37,7 @@ class IDevice : public IWksItem { static void mount(const QString& path); static void umount(const QString& path); - static int count() { return cnt; } + static int count() { return countDevice; } void mount() { mount(key); } void umount() { umount(key); } @@ -84,10 +84,10 @@ class IDevice : public IWksItem { */ bool testForExternalProject(const QString& filename); - static int cnt; - QDir dir; QString key; + static quint32 countMount; + static quint32 countDevice; }; class CDeviceMountLock { diff --git a/src/qmapshack/gis/CWksItemDelegate.cpp b/src/qmapshack/gis/CWksItemDelegate.cpp index 3f31cc63d..bc589c740 100644 --- a/src/qmapshack/gis/CWksItemDelegate.cpp +++ b/src/qmapshack/gis/CWksItemDelegate.cpp @@ -37,6 +37,8 @@ constexpr int kMargin = 1; constexpr int kFontSizeDiffProject = 2; constexpr int kFontSizeDiffItem = 3; constexpr int kFontSizeInvalid = -1; +constexpr int kProgressBarHeight = 5; +constexpr int kProgressBarHeightHalf = 3; CWksItemDelegate::CWksItemDelegate(CGisListWks* parent) : QStyledItemDelegate(parent), treeWidget(parent) { SETTINGS; @@ -253,7 +255,7 @@ std::tuple CWksItemDelegate::getRectan return {fontName, fontStatus, rectIcon, rectName, rectStatus, rectChanged}; } -std::tuple CWksItemDelegate::getRectanglesDevice( +std::tuple CWksItemDelegate::getRectanglesDevice( const QStyleOptionViewItem& opt, const IWksItem& item) const { QFont fontName = opt.font; QFontMetrics fmName(fontName); @@ -263,14 +265,16 @@ std::tuple CWksItemDelegate::getRectan QFontMetrics fmStatus(fontStatus); const QRect& r = opt.rect.adjusted(2 * kMargin, 2 * kMargin, -2 * kMargin, -2 * kMargin); - const QRect& rectIcon = r.adjusted(-kMargin, -kMargin, -(r.width() - r.height()), kMargin); + const QRect rectIcon(r.left(), r.top(), r.height(), r.height()); const QRect rectVisible(r.right() - fmName.height(), r.top(), fmName.height(), fmName.height()); - const QRect rectName(rectIcon.right() + kMargin, r.top(), + const QRect rectName(rectIcon.right() + 2 * kMargin, r.top(), r.width() - rectIcon.width() - rectVisible.width() - 2 * kMargin, fmName.height()); - const QRect rectStatus(rectIcon.right() + kMargin, r.bottom() - fmStatus.height(), - r.width() - rectIcon.width() - 2 * kMargin, fmStatus.height()); + const QRect rectStatus(rectIcon.right() + 2 * kMargin, r.bottom() - fmStatus.height(), + r.width() - rectIcon.width() - 2 * kMargin, fmStatus.height()); + const QRect rectProgress(rectIcon.right() + 4 * kMargin, r.bottom() - kProgressBarHeight, + r.width() - rectIcon.width() - 8 * kMargin, kProgressBarHeight); - return {fontName, fontStatus, rectIcon, rectName, rectStatus, rectVisible}; + return {fontName, fontStatus, rectIcon, rectName, rectStatus, rectProgress, rectVisible}; } std::tuple CWksItemDelegate::getRectanglesGeoSearch( @@ -327,6 +331,16 @@ void CWksItemDelegate::drawToolButton(QPainter* p, const QStyleOptionViewItem& o opt.widget->style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, p, opt.widget); } +void CWksItemDelegate::drawProgressBar(QPainter* p, const QRect& rect, qreal progress) { + quint32 width = qRound(rect.width() * progress / 100.0); + const QLine line(rect.left(), rect.bottom() - kProgressBarHeightHalf, rect.left() + width, + rect.bottom() - kProgressBarHeightHalf); + p->setPen(QPen(Qt::white, 5, Qt::SolidLine, Qt::RoundCap)); + p->drawLine(line); + p->setPen(QPen(Qt::darkGreen, 3, Qt::SolidLine, Qt::RoundCap)); + p->drawLine(line); +} + void CWksItemDelegate::drawRatingStars(qreal rating, QPainter* p, QIcon::Mode iconMode, QRect& rectStatus) const { const qint32 N = qRound(rating); if (rating != 0) { @@ -340,7 +354,6 @@ void CWksItemDelegate::drawRatingStars(qreal rating, QPainter* p, QIcon::Mode ic } } - void CWksItemDelegate::paint(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index) const { IWksItem* item = indexToItem(index); if (item == nullptr) { @@ -520,7 +533,8 @@ void CWksItemDelegate::paintProject(QPainter* p, const QStyleOptionViewItem& opt void CWksItemDelegate::paintDevice(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index, const IWksItem& item) const { - auto [fontName, fontStatus, rectIcon, rectName, rectStatus, rectVisible] = getRectanglesDevice(opt, item); + auto [fontName, fontStatus, rectIcon, rectName, rectStatus, rectProgress, rectVisible] = + getRectanglesDevice(opt, item); const bool isVisible = item.isVisible(); const QColor& colorName = @@ -533,17 +547,24 @@ void CWksItemDelegate::paintDevice(QPainter* p, const QStyleOptionViewItem& opt, p->setFont(fontName); p->drawText(rectName.adjusted(0, -1, 0, 1), Qt::AlignLeft | Qt::AlignTop, item.getName()); - // draw status - p->setPen(colorName); - p->setFont(fontStatus); - p->drawText(rectStatus.adjusted(0, -1, 0, 1), Qt::AlignLeft | Qt::AlignTop, item.getInfo(IWksItem::eFeatureShowName)); - // draw icon QIcon(item.getIcon()).paint(p, rectIcon, Qt::AlignCenter, isVisible ? QIcon::Normal : QIcon::Disabled); // draw tool button to activate drawToolButton(p, opt, rectVisible, isVisible ? QIcon(":/icons/32x32/ShowAll.png") : QIcon(":/icons/32x32/ShowNone.png"), true, isVisible); + + // draw progress bar + auto [hasProgress, progress] = item.getProgress(); + if (hasProgress) { + drawProgressBar(p, rectStatus, progress); + } else { + // draw status + p->setPen(colorName); + p->setFont(fontStatus); + p->drawText(rectStatus.adjusted(0, -1, 0, 1), Qt::AlignLeft | Qt::AlignTop, + item.getInfo(IWksItem::eFeatureShowName)); + } } void CWksItemDelegate::paintItem(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index, @@ -792,7 +813,8 @@ bool CWksItemDelegate::mousePressProject(QMouseEvent* me, const QStyleOptionView bool CWksItemDelegate::mousePressDevice(QMouseEvent* me, const QStyleOptionViewItem& opt, const QModelIndex& index, IWksItem& item) { - auto [fontName, fontStatus, rectIcon, rectName, rectStatus, rectVisible] = getRectanglesDevice(opt, item); + auto [fontName, fontStatus, rectIcon, rectName, rectStatus, rectProgress, rectVisible] = + getRectanglesDevice(opt, item); if (rectVisible.contains(me->pos())) { item.setVisibility(!item.isVisible()); diff --git a/src/qmapshack/gis/CWksItemDelegate.h b/src/qmapshack/gis/CWksItemDelegate.h index 328b4abb5..242e19e44 100644 --- a/src/qmapshack/gis/CWksItemDelegate.h +++ b/src/qmapshack/gis/CWksItemDelegate.h @@ -149,8 +149,8 @@ class CWksItemDelegate : public QStyledItemDelegate { std::tuple getRectanglesProject( const QStyleOptionViewItem& opt, IWksItem& item) const; - std::tuple getRectanglesDevice(const QStyleOptionViewItem& opt, - const IWksItem& item) const; + std::tuple getRectanglesDevice(const QStyleOptionViewItem& opt, + const IWksItem& item) const; std::tuple getRectanglesGeoSearch( const QStyleOptionViewItem& opt) const; std::tuple getRectanglesGeoSearchError(const QStyleOptionViewItem& opt) const; @@ -160,6 +160,8 @@ class CWksItemDelegate : public QStyledItemDelegate { static void drawToolButton(QPainter* p, const QStyleOptionViewItem& opt, const QRect& rect, const QIcon& icon, bool enabled, bool pressed); + static void drawProgressBar(QPainter* p, const QRect& rect, qreal progress); + void drawRatingStars(qreal rating, QPainter* p, QIcon::Mode iconMode, QRect& rectStatus) const; void paintProject(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index, IWksItem& item) const; diff --git a/src/qmapshack/gis/IWksItem.cpp b/src/qmapshack/gis/IWksItem.cpp index 1e8fe403a..a499b077e 100644 --- a/src/qmapshack/gis/IWksItem.cpp +++ b/src/qmapshack/gis/IWksItem.cpp @@ -18,21 +18,30 @@ #include "gis/IWksItem.h" +#include #include +#include "gis/IGisItem.h" + +void checkForThread() { + if (!QThread::isMainThread()) { + qFatal() << "Called from a thread other than the maiUI thread! This will not work!"; + } +} + IWksItem::IWksItem(QTreeWidgetItem* parent, int type) : QTreeWidgetItem(parent, type) { setupAnimations(); } IWksItem::IWksItem(QTreeWidget* parent, int type) : QTreeWidgetItem(parent, type) { setupAnimations(); } void IWksItem::setupAnimations() { - animationOpacityOfFocusBasedItems = std::make_shared(); + animationOpacityOfFocusBasedItems = new QVariantAnimation(treeWidget()); animationOpacityOfFocusBasedItems->setDuration(250); animationOpacityOfFocusBasedItems->setEasingCurve(QEasingCurve::InOutQuad); - animationOpacityOfFocusBasedItems->connect(animationOpacityOfFocusBasedItems.get(), &QVariantAnimation::valueChanged, - [this](QVariant v) { - opacityOfFocusBasedItems = v.toFloat(); - treeWidget()->viewport()->update(treeWidget()->visualItemRect(this)); - }); + QObject::connect(animationOpacityOfFocusBasedItems, &QVariantAnimation::valueChanged, + animationOpacityOfFocusBasedItems, [this](QVariant v) { + opacityOfFocusBasedItems = v.toFloat(); + treeWidget()->viewport()->update(treeWidget()->visualItemRect(this)); + }); } IWksItem::eBaseType IWksItem::getBaseType() const { @@ -51,13 +60,19 @@ IWksItem::eBaseType IWksItem::getBaseType() const { } void IWksItem::updateDecoration(quint32 enable, quint32 disable) { + checkForThread(); flagsDecoration |= enable; flagsDecoration &= ~disable; updateItem(); } void IWksItem::updateItem() { - QTreeWidget* tree = treeWidget(); + checkForThread(); + QPointer tree = treeWidget(); + if (tree == nullptr) { + return; + } + if (tree == nullptr) { return; } @@ -70,6 +85,7 @@ void IWksItem::updateItem() { } bool IWksItem::holdUiFocus(const QStyleOptionViewItem& opt) { + checkForThread(); bool hasFocus = (opt.state & QStyle::State_HasFocus) != 0; if (hasFocus != lastFocusState) { float opacity = hasFocus ? 1.0 : 0.0; @@ -83,3 +99,36 @@ bool IWksItem::holdUiFocus(const QStyleOptionViewItem& opt) { return hasFocus || (animationOpacityOfFocusBasedItems->state() == QAbstractAnimation::Running); } + +void IWksItem::setVisibility(bool visible) { + checkForThread(); + this->visible = visible; + updateItem(); +} + +void IWksItem::setAutoSave(bool on) { + checkForThread(); + autoSave = on; + updateItem(); +} + +void IWksItem::setAutoSyncToDev(bool on) { + checkForThread(); + autoSyncToDev = on; + updateItem(); +} + +void IWksItem::setProgress(quint32 count, quint32 total) { + // ok to call from any thread + QMutexLocker lock(&IGisItem::mutexItems); + countProgress = count; + totalProgress = total; + QMetaObject::invokeMethod(treeWidget(), [this]() { updateItem(); }); +} + +std::tuple IWksItem::getProgress() const { + checkForThread(); + QMutexLocker lock(&IGisItem::mutexItems); + const qreal progress = (100.0 * countProgress) / totalProgress; + return {countProgress != totalProgress, progress}; +} diff --git a/src/qmapshack/gis/IWksItem.h b/src/qmapshack/gis/IWksItem.h index 3ee778e3b..4823b33cb 100644 --- a/src/qmapshack/gis/IWksItem.h +++ b/src/qmapshack/gis/IWksItem.h @@ -19,8 +19,8 @@ #ifndef IWKSITEM_H #define IWKSITEM_H +#include #include -#include class QVariantAnimation; @@ -88,12 +88,12 @@ class IWksItem : public QTreeWidgetItem { virtual const qint32 isOnDevice() const { return false; } virtual const bool canSave() const { return false; } virtual bool hasUserFocus() const = 0; - /** - @brief Get a short string with the items properties to be displayed in tool tips or similar + virtual std::tuple getProgress() const; + virtual quint32 getFlagsDecoration() const { return flagsDecoration; } - @param showName set true if the first line should be the item's name + /** + @brief Get a short string with the items properties to be displayed in tool tips or similar @param features a combination of features_e types - @return A string object. */ virtual QString getInfo(uint32_t features) const = 0; @@ -103,32 +103,20 @@ class IWksItem : public QTreeWidgetItem { @return True if the are changes to be saved */ virtual const bool isChanged() const { return (flagsDecoration & eMarkChanged) != 0; } - quint32 getFlagsDecoration() const { return flagsDecoration; } - - virtual void setVisibility(bool visible) { - this->visible = visible; - updateItem(); - } - virtual void setAutoSave(bool on) { - autoSave = on; - updateItem(); - } - virtual void setAutoSyncToDev(bool on) { - autoSyncToDev = on; - updateItem(); - } - + virtual void setVisibility(bool visible); + virtual void setAutoSave(bool on); + virtual void setAutoSyncToDev(bool on); + virtual void setProgress(quint32 count, quint32 total); virtual void gainUserFocus(bool yes) = 0; - void updateItem(); - bool holdUiFocus(const QStyleOptionViewItem& opt); float getOpacityOfFocusBasedItems() { return opacityOfFocusBasedItems; } + void updateItem(); + protected: virtual void updateDecoration(quint32 enable, quint32 disable); QString name; - // QString toolTipName; QPixmap icon; /// labeling the GisItems @@ -142,9 +130,12 @@ class IWksItem : public QTreeWidgetItem { bool autoSave = false; ///< flag to show if auto save is on or off bool autoSyncToDev = false; ///< if set true sync the project with every device connected + quint32 countProgress = 0; + quint32 totalProgress = 0; + float opacityOfFocusBasedItems = 0.0; bool lastFocusState = false; - std::shared_ptr animationOpacityOfFocusBasedItems; + QPointer animationOpacityOfFocusBasedItems; }; #endif // IWKSITEM_H diff --git a/src/qmapshack/gis/fit2/CFit2Project.cpp b/src/qmapshack/gis/fit2/CFit2Project.cpp index 139598013..60d69f553 100644 --- a/src/qmapshack/gis/fit2/CFit2Project.cpp +++ b/src/qmapshack/gis/fit2/CFit2Project.cpp @@ -206,8 +206,8 @@ void CFit2Project::OnMesg(fit::RecordMesg& mesg) { if (trkpt.isValid(CTrackData::trkpt_t::eValidPos)) { segment.pts.append(trkpt); } else { - qWarning() << "invalid track point in FIT record" << trkpt.time << trkpt << trkpt.ele << trkpt.extensions - << "- skip"; + // qWarning() << "invalid track point in FIT record" << trkpt.time << trkpt << trkpt.ele << trkpt.extensions + // << "- skip"; } } diff --git a/src/qmapshack/gis/prj/IGisProject.cpp b/src/qmapshack/gis/prj/IGisProject.cpp index 9f74c26cd..7ca8b0ebb 100644 --- a/src/qmapshack/gis/prj/IGisProject.cpp +++ b/src/qmapshack/gis/prj/IGisProject.cpp @@ -43,7 +43,6 @@ #include "gis/tcx/CTcxProject.h" #include "gis/trk/CGisItemTrk.h" #include "gis/wpt/CGisItemWpt.h" -#include "helpers/CProgressDialog.h" #include "helpers/CSelectCopyAction.h" #include "helpers/CSettings.h" #include "misc.h" diff --git a/src/qmapshack/gis/search/CGeoSearchWeb.cpp b/src/qmapshack/gis/search/CGeoSearchWeb.cpp index d7587f6e2..cb44b1945 100644 --- a/src/qmapshack/gis/search/CGeoSearchWeb.cpp +++ b/src/qmapshack/gis/search/CGeoSearchWeb.cpp @@ -19,7 +19,6 @@ #include "gis/search/CGeoSearchWeb.h" #include -#include #include "CMainWindow.h" #include "gis/search/CGeoSearchWebConfigDialog.h" diff --git a/src/qmapshack/gis/summary/CGisSummarySetup.cpp b/src/qmapshack/gis/summary/CGisSummarySetup.cpp index e960d9de6..1ca9b443a 100644 --- a/src/qmapshack/gis/summary/CGisSummarySetup.cpp +++ b/src/qmapshack/gis/summary/CGisSummarySetup.cpp @@ -19,7 +19,6 @@ #include "gis/summary/CGisSummarySetup.h" #include -#include #include "gis/db/CSelectDBFolder.h" #include "gis/db/IDBFolder.h" diff --git a/src/qmapshack/gis/trk/CActivityTrk.cpp b/src/qmapshack/gis/trk/CActivityTrk.cpp index 32d1b165a..8540cc7c7 100644 --- a/src/qmapshack/gis/trk/CActivityTrk.cpp +++ b/src/qmapshack/gis/trk/CActivityTrk.cpp @@ -18,8 +18,6 @@ #include "gis/trk/CActivityTrk.h" -#include - #include "gis/CGisWorkspace.h" #include "gis/trk/CGisItemTrk.h" #include "helpers/CSettings.h" diff --git a/src/qmapshack/helpers/CThread.cpp b/src/qmapshack/helpers/CThread.cpp new file mode 100644 index 000000000..a6400d5b1 --- /dev/null +++ b/src/qmapshack/helpers/CThread.cpp @@ -0,0 +1,33 @@ +/********************************************************************************************** + Copyright (C) 2026 Oliver Eichler + + This program 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. + + This program 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 this program. If not, see . + +**********************************************************************************************/ + +#include "helpers/CThread.h" + +CThread::CThread(CThread::fCallback worker) : worker(worker) { + connect(this, &CThread::finished, this, &CThread::deleteLater); +} + +void CThread::run() { worker(); } + +void CThread::cancel() { + if (isFinished()) { + return; + } + requestInterruption(); + wait(5000); +} diff --git a/src/qmapshack/helpers/CThread.h b/src/qmapshack/helpers/CThread.h new file mode 100644 index 000000000..b18ce3d8e --- /dev/null +++ b/src/qmapshack/helpers/CThread.h @@ -0,0 +1,39 @@ +/********************************************************************************************** + Copyright (C) 2026 Oliver Eichler + + This program 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. + + This program 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 this program. If not, see . + +**********************************************************************************************/ + +#ifndef CTHREAD_H +#define CTHREAD_H + +#include + +class CThread : public QThread { + public: + using fCallback = std::function; + + CThread(fCallback worker); + virtual ~CThread() = default; + + void run() override; + + void cancel(); + + private: + fCallback worker; +}; + +#endif // CTHREAD_H diff --git a/src/qmapshack/mouse/CMouseRuler.cpp b/src/qmapshack/mouse/CMouseRuler.cpp index 5d06078be..b413b6315 100644 --- a/src/qmapshack/mouse/CMouseRuler.cpp +++ b/src/qmapshack/mouse/CMouseRuler.cpp @@ -19,7 +19,6 @@ #include "mouse/CMouseRuler.h" #include -#include #include "canvas/CCanvas.h" #include "gis/CGisDraw.h" diff --git a/src/qmapshack/mouse/CScrOptRuler.cpp b/src/qmapshack/mouse/CScrOptRuler.cpp index 6b22a0103..c6f55645d 100644 --- a/src/qmapshack/mouse/CScrOptRuler.cpp +++ b/src/qmapshack/mouse/CScrOptRuler.cpp @@ -19,7 +19,6 @@ #include "mouse/CScrOptRuler.h" #include -#include #include "canvas/CCanvas.h" #include "helpers/CSettings.h" diff --git a/src/qmapshack/plot/CPlot.h b/src/qmapshack/plot/CPlot.h index f1c6a43d4..2944aaf01 100644 --- a/src/qmapshack/plot/CPlot.h +++ b/src/qmapshack/plot/CPlot.h @@ -19,8 +19,6 @@ #ifndef CPLOT_H #define CPLOT_H -#include - #include "gis/trk/CGisItemTrk.h" #include "gis/trk/CPropertyTrk.h" #include "plot/IPlot.h" diff --git a/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.cpp b/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.cpp index c8d8570f3..e64fef9ae 100644 --- a/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.cpp +++ b/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.cpp @@ -25,10 +25,9 @@ #include "CMainWindow.h" #include "canvas/CCanvas.h" #include "realtime/gpstether/CRtGpsTether.h" +#include "realtime/gpstether/CRtGpsTetherRecord.h" #include "units/IUnit.h" -class CRtGpsTether; - CRtGpsTetherInfo::CRtGpsTetherInfo(CRtGpsTether& source, QWidget* parent) : IRtInfo(&source, parent) { setupUi(this); connect(toolHelp, &QToolButton::clicked, this, &CRtGpsTetherInfo::slotHelp); diff --git a/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.h b/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.h index ae480f24d..84bc853df 100644 --- a/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.h +++ b/src/qmapshack/realtime/gpstether/CRtGpsTetherInfo.h @@ -24,7 +24,6 @@ #include #include "realtime/IRtInfo.h" -#include "realtime/gpstether/CRtGpsTetherRecord.h" #include "ui_IRtGpsTetherInfo.h" class CRtGpsTether;