diff --git a/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp b/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp index e142969e98..8b8457bcb5 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp @@ -1,979 +1,1091 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-21 * Description : A container to hold GPS information about an item. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010-2014 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #include "gpsitemcontainer.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "gpsitemmodel.h" #include "dmetadata.h" #include "metaenginesettings.h" namespace Digikam { bool setExifXmpTagDataVariant(DMetadata* const meta, const char* const exifTagName, const char* const xmpTagName, const QVariant& value) { bool success = meta->setExifTagVariant(exifTagName, value); if (success) { - /** @todo Here we save all data types as XMP Strings. Is that okay or do we have to store them as some other type? + /** + * @todo Here we save all data types as XMP Strings. Is that okay or do we have to store them as some other type? */ switch (value.type()) { case QVariant::Int: case QVariant::UInt: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: success = meta->setXmpTagString(xmpTagName, QString::number(value.toInt())); break; case QVariant::Double: { long num, den; meta->convertToRationalSmallDenominator(value.toDouble(), &num, &den); success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); break; } + case QVariant::List: { long num = 0, den = 1; QList list = value.toList(); if (list.size() >= 1) + { num = list[0].toInt(); + } if (list.size() >= 2) + { den = list[1].toInt(); + } success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); break; } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = value.toDateTime(); if (!dateTime.isValid()) { success = false; break; } success = meta->setXmpTagString(xmpTagName, dateTime.toString(QLatin1String("yyyy:MM:dd hh:mm:ss"))); break; } case QVariant::String: case QVariant::Char: success = meta->setXmpTagString(xmpTagName, value.toString()); break; case QVariant::ByteArray: /// @todo I don't know a straightforward way to convert a byte array to XMP success = false; break; default: success = false; break; } } return success; } GPSItemContainer::GPSItemContainer(const QUrl& url) : m_model(nullptr), m_url(url), m_dateTime(), m_dirty(false), m_gpsData(), m_savedState(), m_tagListDirty(false), m_tagList(), m_savedTagList(), m_writeXmpTags(true) { } GPSItemContainer::~GPSItemContainer() { } DMetadata* GPSItemContainer::getMetadataForFile() const { QScopedPointer meta(new DMetadata); if (!meta->load(m_url.toLocalFile())) { // It is possible that no sidecar file has yet been created. // If writing to sidecar file is activated, we ignore the loading error of the metadata. + if (MetaEngineSettings::instance()->settings().metadataWritingMode == DMetadata::WRITE_TO_FILE_ONLY) { return nullptr; } } return meta.take(); } int getWarningLevelFromGPSDataContainer(const GPSDataContainer& data) { - if (data.hasDop()) + if (data.hasDop()) { const int dopValue = data.getDop(); if (dopValue < 2) + { return 1; + } if (dopValue < 4) + { return 2; + } if (dopValue < 10) + { return 3; + } return 4; } else if (data.hasFixType()) { if (data.getFixType() < 3) + { return 4; + } } else if (data.hasNSatellites()) { if (data.getNSatellites() < 4) + { return 4; + } } // no warning level + return -1; } bool GPSItemContainer::loadImageData() { QScopedPointer meta(getMetadataForFile()); if (meta && !m_dateTime.isValid()) { m_dateTime = meta->getItemDateTime(); } if (!m_dateTime.isValid()) { // Get date from filesystem. + QFileInfo info(m_url.toLocalFile()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + QDateTime ctime = info.birthTime(); + #else + QDateTime ctime = info.created(); + #endif QDateTime mtime = info.lastModified(); if (ctime.isNull() || mtime.isNull()) { m_dateTime = qMax(ctime, mtime); } else { m_dateTime = qMin(ctime, mtime); } } if (!meta) return false; // The way we read the coordinates here is problematic // if the coordinates were in the file initially, but // the user deleted them in the database. Then we still load // them from the file. On the other hand, we can not clear // the coordinates, because then we would loose them if // they are only stored in the database. -// m_gpsData.clear(); +/* + m_gpsData.clear(); +*/ if (!m_gpsData.hasCoordinates()) { // could not load the coordinates from the interface, // read them directly from the file double lat, lng; bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng); if (haveCoordinates) { GeoCoordinates coordinates(lat, lng); double alt; if (meta->getGPSAltitude(&alt)) { coordinates.setAlt(alt); } m_gpsData.setCoordinates(coordinates); } } - /** @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist, - * therefore no need to read XMP as well? - */ + /** + * @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist, + * therefore no need to read XMP as well? + */ + // read the remaining GPS information from the file: const QByteArray speedRef = meta->getExifTagData("Exif.GPSInfo.GPSSpeedRef"); bool success = !speedRef.isEmpty(); long num, den; success &= meta->getExifTagRational("Exif.GPSInfo.GPSSpeed", num, den); if (success) { // be relaxed about 0/0 + if ((num == 0.0) && (den == 0.0)) + { den = 1.0; + } const qreal speedInRef = qreal(num)/qreal(den); qreal FactorToMetersPerSecond; - if (speedRef.startsWith('K')) + if (speedRef.startsWith('K')) { // km/h = 1000 * 3600 + FactorToMetersPerSecond = 1.0/3.6; } else if (speedRef.startsWith('M')) { // TODO: someone please check that this is the 'right' mile // miles/hour = 1609.344 meters / hour = 1609.344 meters / 3600 seconds + FactorToMetersPerSecond = 1.0 / (1609.344 / 3600.0); } else if (speedRef.startsWith('N')) { // speed is in knots. // knot = one nautical mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds + FactorToMetersPerSecond = 1.0 / (1852.0 / 3600.0); } else { success = false; } if (success) { const qreal speedInMetersPerSecond = speedInRef * FactorToMetersPerSecond; m_gpsData.setSpeed(speedInMetersPerSecond); } } // number of satellites + const QString gpsSatellitesString = meta->getExifTagString("Exif.GPSInfo.GPSSatellites"); bool satellitesOkay = !gpsSatellitesString.isEmpty(); if (satellitesOkay) { /** * @todo Here we only accept a single integer denoting the number of satellites used * but not detailed information about all satellites. */ + const int nSatellites = gpsSatellitesString.toInt(&satellitesOkay); if (satellitesOkay) { m_gpsData.setNSatellites(nSatellites); } } // fix type / measure mode + const QByteArray gpsMeasureModeByteArray = meta->getExifTagData("Exif.GPSInfo.GPSMeasureMode"); bool measureModeOkay = !gpsMeasureModeByteArray.isEmpty(); if (measureModeOkay) { const int measureMode = gpsMeasureModeByteArray.toInt(&measureModeOkay); if (measureModeOkay) { if ((measureMode == 2) || (measureMode == 3)) { m_gpsData.setFixType(measureMode); } } } // read the DOP value: + success = meta->getExifTagRational("Exif.GPSInfo.GPSDOP", num, den); if (success) { // be relaxed about 0/0 + if ((num == 0.0) && (den == 0.0)) + { den = 1.0; + } const qreal dop = qreal(num)/qreal(den); m_gpsData.setDop(dop); } // mark us as not-dirty, because the data was just loaded: + m_dirty = false; m_savedState = m_gpsData; emitDataChanged(); return true; } QVariant GPSItemContainer::data(const int column, const int role) const { - if ((column == ColumnFilename) && (role == Qt::DisplayRole)) + if ((column == ColumnFilename) && (role == Qt::DisplayRole)) { return m_url.fileName(); } else if ((column == ColumnDateTime) && (role == Qt::DisplayRole)) { if (m_dateTime.isValid()) { return QLocale().toString(m_dateTime, QLocale::ShortFormat); } return i18n("Not available"); } else if (role == RoleCoordinates) { return QVariant::fromValue(m_gpsData.getCoordinates()); } else if ((column == ColumnLatitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasLatitude()) + { return QString(); + } return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lat(), 7); } else if ((column == ColumnLongitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasLongitude()) + { return QString(); + } return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lon(), 7); } else if ((column == ColumnAltitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasAltitude()) + { return QString(); + } return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().alt(), 7); } else if (column == ColumnAccuracy) { if (role == Qt::DisplayRole) { if (m_gpsData.hasDop()) { return i18n("DOP: %1", m_gpsData.getDop()); } if (m_gpsData.hasFixType()) { return i18n("Fix: %1d", m_gpsData.getFixType()); } if (m_gpsData.hasNSatellites()) { return i18n("#Sat: %1", m_gpsData.getNSatellites()); } } else if (role == Qt::BackgroundRole) { const int warningLevel = getWarningLevelFromGPSDataContainer(m_gpsData); switch (warningLevel) { case 1: return QBrush(Qt::green); + case 2: return QBrush(Qt::yellow); + case 3: // orange return QBrush(QColor(0xff, 0x80, 0x00)); + case 4: return QBrush(Qt::red); + default: break; } } } else if ((column == ColumnDOP) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasDop()) + { return QString(); + } return QString::number(m_gpsData.getDop()); } else if ((column == ColumnFixType) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasFixType()) + { return QString(); + } return i18n("%1d", m_gpsData.getFixType()); } else if ((column == ColumnNSatellites) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasNSatellites()) + { return QString(); + } return QString::number(m_gpsData.getNSatellites()); } else if ((column == ColumnSpeed) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasSpeed()) + { return QString(); + } return QString::number(m_gpsData.getSpeed()); } else if ((column == ColumnStatus) && (role == Qt::DisplayRole)) { if (m_dirty || m_tagListDirty) { return i18n("Modified"); } return QString(); } else if ((column == ColumnTags) && (role == Qt::DisplayRole)) { if (!m_tagList.isEmpty()) { QString myTagsList; for (int i = 0 ; i < m_tagList.count() ; ++i) { QString myTag; for (int j = 0 ; j < m_tagList[i].count() ; ++j) { myTag.append(QLatin1Char('/') + m_tagList[i].at(j).tagName); if (j == 0) + { myTag.remove(0, 1); + } } if (!myTagsList.isEmpty()) + { myTagsList.append(QLatin1String(", ")); + } myTagsList.append(myTag); } return myTagsList; } return QString(); } return QVariant(); } void GPSItemContainer::setCoordinates(const GeoCoordinates& newCoordinates) { m_gpsData.setCoordinates(newCoordinates); m_dirty = true; emitDataChanged(); } void GPSItemContainer::setModel(GPSItemModel* const model) { m_model = model; } void GPSItemContainer::emitDataChanged() { if (m_model) { m_model->itemChanged(this); } } void GPSItemContainer::setHeaderData(GPSItemModel* const model) { model->setColumnCount(ColumnGPSItemContainerCount); model->setHeaderData(ColumnThumbnail, Qt::Horizontal, i18n("Thumbnail"), Qt::DisplayRole); model->setHeaderData(ColumnFilename, Qt::Horizontal, i18n("Filename"), Qt::DisplayRole); model->setHeaderData(ColumnDateTime, Qt::Horizontal, i18n("Date and time"), Qt::DisplayRole); model->setHeaderData(ColumnLatitude, Qt::Horizontal, i18n("Latitude"), Qt::DisplayRole); model->setHeaderData(ColumnLongitude, Qt::Horizontal, i18n("Longitude"), Qt::DisplayRole); model->setHeaderData(ColumnAltitude, Qt::Horizontal, i18n("Altitude"), Qt::DisplayRole); model->setHeaderData(ColumnAccuracy, Qt::Horizontal, i18n("Accuracy"), Qt::DisplayRole); model->setHeaderData(ColumnDOP, Qt::Horizontal, i18n("DOP"), Qt::DisplayRole); model->setHeaderData(ColumnFixType, Qt::Horizontal, i18n("Fix type"), Qt::DisplayRole); model->setHeaderData(ColumnNSatellites, Qt::Horizontal, i18n("# satellites"), Qt::DisplayRole); model->setHeaderData(ColumnSpeed, Qt::Horizontal, i18n("Speed"), Qt::DisplayRole); model->setHeaderData(ColumnStatus, Qt::Horizontal, i18n("Status"), Qt::DisplayRole); model->setHeaderData(ColumnTags, Qt::Horizontal, i18n("Tags"), Qt::DisplayRole); } bool GPSItemContainer::lessThan(const GPSItemContainer* const otherItem, const int column) const { switch (column) { case ColumnThumbnail: return false; case ColumnFilename: - return m_url < otherItem->m_url; + return (m_url < otherItem->m_url); case ColumnDateTime: - return m_dateTime < otherItem->m_dateTime; + return (m_dateTime < otherItem->m_dateTime); case ColumnAltitude: { if (!m_gpsData.hasAltitude()) + { return false; + } if (!otherItem->m_gpsData.hasAltitude()) + { return true; + } - return m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt(); + return (m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt()); } case ColumnNSatellites: { if (!m_gpsData.hasNSatellites()) + { return false; + } if (!otherItem->m_gpsData.hasNSatellites()) + { return true; + } - return m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites(); + return (m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites()); } case ColumnAccuracy: { const int myWarning = getWarningLevelFromGPSDataContainer(m_gpsData); const int otherWarning = getWarningLevelFromGPSDataContainer(otherItem->m_gpsData); if (myWarning < 0) + { return false; + } if (otherWarning < 0) + { return true; + } if (myWarning != otherWarning) - return myWarning < otherWarning; + { + return (myWarning < otherWarning); + } // TODO: this may not be the best way to sort images with equal warning levels // but it works for now if (m_gpsData.hasDop() != otherItem->m_gpsData.hasDop()) + { return !m_gpsData.hasDop(); + } if (m_gpsData.hasDop() && otherItem->m_gpsData.hasDop()) { - return m_gpsData.getDop() < otherItem->m_gpsData.getDop(); + return (m_gpsData.getDop() < otherItem->m_gpsData.getDop()); } if (m_gpsData.hasFixType() != otherItem->m_gpsData.hasFixType()) + { return m_gpsData.hasFixType(); + } if (m_gpsData.hasFixType() && otherItem->m_gpsData.hasFixType()) { - return m_gpsData.getFixType() > otherItem->m_gpsData.getFixType(); + return (m_gpsData.getFixType() > otherItem->m_gpsData.getFixType()); } if (m_gpsData.hasNSatellites() != otherItem->m_gpsData.hasNSatellites()) + { return m_gpsData.hasNSatellites(); + } if (m_gpsData.hasNSatellites() && otherItem->m_gpsData.hasNSatellites()) { - return m_gpsData.getNSatellites() > otherItem->m_gpsData.getNSatellites(); + return (m_gpsData.getNSatellites() > otherItem->m_gpsData.getNSatellites()); } return false; } case ColumnDOP: { if (!m_gpsData.hasDop()) + { return false; + } if (!otherItem->m_gpsData.hasDop()) + { return true; + } - return m_gpsData.getDop() < otherItem->m_gpsData.getDop(); + return (m_gpsData.getDop() < otherItem->m_gpsData.getDop()); } case ColumnFixType: { if (!m_gpsData.hasFixType()) + { return false; + } if (!otherItem->m_gpsData.hasFixType()) + { return true; + } - return m_gpsData.getFixType() < otherItem->m_gpsData.getFixType(); + return (m_gpsData.getFixType() < otherItem->m_gpsData.getFixType()); } case ColumnSpeed: { if (!m_gpsData.hasSpeed()) + { return false; + } if (!otherItem->m_gpsData.hasSpeed()) + { return true; + } - return m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed(); + return (m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed()); } case ColumnLatitude: { if (!m_gpsData.hasCoordinates()) + { return false; + } if (!otherItem->m_gpsData.hasCoordinates()) + { return true; + } - return m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat(); + return (m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat()); } case ColumnLongitude: { if (!m_gpsData.hasCoordinates()) + { return false; + } if (!otherItem->m_gpsData.hasCoordinates()) + { return true; + } - return m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon(); + return (m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon()); } case ColumnStatus: { - return m_dirty && !otherItem->m_dirty; + return (m_dirty && !otherItem->m_dirty); } default: + { return false; + } } } SaveProperties GPSItemContainer::saveProperties() const { SaveProperties p; // do we have gps information? if (m_gpsData.hasCoordinates()) { p.shouldWriteCoordinates = true; p.latitude = m_gpsData.getCoordinates().lat(); p.longitude = m_gpsData.getCoordinates().lon(); if (m_gpsData.hasAltitude()) { p.shouldWriteAltitude = true; p.altitude = m_gpsData.getCoordinates().alt(); } else { p.shouldRemoveAltitude = true; } } else { p.shouldRemoveCoordinates = true; } return p; } QString GPSItemContainer::saveChanges() { SaveProperties p = saveProperties(); QString returnString; // first try to write the information to the image file + bool success = false; QScopedPointer meta(getMetadataForFile()); if (!meta) { // TODO: more verbosity! + returnString = i18n("Failed to open file."); } else { if (p.shouldWriteCoordinates) { if (p.shouldWriteAltitude) { success = meta->setGPSInfo(p.altitude, p.latitude, p.longitude); } else { success = meta->setGPSInfo(nullptr, p.latitude, p.longitude); } // write all other GPS information here too + if (success && m_gpsData.hasSpeed()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeedRef", "Xmp.exif.GPSSpeedRef", QVariant(QLatin1String("K"))); if (success) { const qreal speedInMetersPerSecond = m_gpsData.getSpeed(); // km/h = 0.001 * m / ( s * 1/(60*60) ) = 3.6 * m/s + const qreal speedInKilometersPerHour = 3.6 * speedInMetersPerSecond; success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeed", "Xmp.exif.GPSSpeed", QVariant(speedInKilometersPerHour)); } } if (success && m_gpsData.hasNSatellites()) { /** * @todo According to the EXIF 2.2 spec, GPSSatellites is a free form field which can either hold only the * number of satellites or more details about each satellite used. For now, we just write * the number of satellites. Are we using the correct format for the number of satellites here? */ success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites", QVariant(QString::number(m_gpsData.getNSatellites()))); } if (success && m_gpsData.hasFixType()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode", QVariant(QString::number(m_gpsData.getFixType()))); } // write DOP + if (success && m_gpsData.hasDop()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSDOP", "Xmp.exif.GPSDOP", QVariant(m_gpsData.getDop())); } if (!success) { returnString = i18n("Failed to add GPS info to image."); } } if (p.shouldRemoveCoordinates) { // TODO: remove only the altitude if requested + success = meta->removeGPSInfo(); if (!success) { returnString = i18n("Failed to remove GPS info from image"); } } if (!m_tagList.isEmpty() && m_writeXmpTags) { QStringList tagSeq = meta->getXmpTagStringSeq("Xmp.digiKam.TagsList", false); for (int i = 0 ; i < m_tagList.count() ; ++i) { QList currentTagList = m_tagList[i]; QString tag; for (int j = 0 ; j < currentTagList.count() ; ++j) { tag.append(QLatin1Char('/') + currentTagList[j].tagName); } tag.remove(0, 1); if (!tagSeq.contains(tag)) { tagSeq.append(tag); } } bool success = meta->setXmpTagStringSeq("Xmp.digiKam.TagsList", tagSeq); if (!success) { returnString = i18n("Failed to save tags to file."); } success = meta->setXmpTagStringSeq("Xmp.dc.subject", tagSeq); if (!success) { returnString = i18n("Failed to save tags to file."); } } } if (success) { success = meta->save(m_url.toLocalFile()); if (!success) { returnString = i18n("Unable to save changes to file"); } else { m_dirty = false; m_savedState = m_gpsData; m_tagListDirty = false; m_savedTagList = m_tagList; } } if (returnString.isEmpty()) { // mark all changes as not dirty and tell the model: + emitDataChanged(); } return returnString; } /** * @brief Restore the gps data to @p container. Sets m_dirty to false if container equals savedState. */ void GPSItemContainer::restoreGPSData(const GPSDataContainer& container) { m_dirty = !(container == m_savedState); m_gpsData = container; emitDataChanged(); } void GPSItemContainer::restoreRGTagList(const QList >& tagList) { - //TODO: override == operator + // TODO: override == operator if (tagList.count() != m_savedTagList.count()) { m_tagListDirty = true; } else { for (int i = 0 ; i < tagList.count() ; ++i) { bool foundNotEqual = false; if (tagList[i].count() != m_savedTagList[i].count()) { m_tagListDirty = true; break; } for (int j = 0 ; j < tagList[i].count() ; ++j) { if (tagList[i].at(j).tagName != m_savedTagList[i].at(j).tagName) { foundNotEqual = true; break; } } if (foundNotEqual) { m_tagListDirty = true; break; } } } m_tagList = tagList; emitDataChanged(); } bool GPSItemContainer::isDirty() const { return m_dirty; } QUrl GPSItemContainer::url() const { return m_url; } QDateTime GPSItemContainer::dateTime() const { return m_dateTime; } GeoCoordinates GPSItemContainer::coordinates() const { return m_gpsData.getCoordinates(); } GPSDataContainer GPSItemContainer::gpsData() const { return m_gpsData; } void GPSItemContainer::setGPSData(const GPSDataContainer& container) { m_gpsData = container; m_dirty = true; emitDataChanged(); } void GPSItemContainer::setTagList(const QList >& externalTagList) { m_tagList = externalTagList; m_tagListDirty = true; emitDataChanged(); } bool GPSItemContainer::isTagListDirty() const { return m_tagListDirty; } QList > GPSItemContainer::getTagList() const { return m_tagList; } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/items/gpsitemcontainer.h b/core/utilities/geolocation/geoiface/items/gpsitemcontainer.h index 304ea1d6dd..96a9eb9e20 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemcontainer.h +++ b/core/utilities/geolocation/geoiface/items/gpsitemcontainer.h @@ -1,197 +1,197 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-21 * Description : A container to hold GPS information about an item. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010-2014 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #ifndef DIGIKAM_GPS_ITEM_CONTAINER_H #define DIGIKAM_GPS_ITEM_CONTAINER_H // Qt includes #include #include #include // Local includes #include "geoifacetypes.h" #include "digikam_export.h" #include "gpsdatacontainer.h" #include "rginfo.h" namespace Digikam { enum Type { TypeChild = 1, TypeSpacer = 2, TypeNewChild = 4 }; typedef struct TagData { QString tagName; QString tipName; Type tagType; } TagData; class SaveProperties { public: explicit SaveProperties() + : shouldRemoveCoordinates(false), + shouldRemoveAltitude(false), + shouldWriteCoordinates(false), + shouldWriteAltitude(false), + altitude(0.0), + latitude(0.0), + longitude(0.0) { - shouldRemoveCoordinates = false; - shouldRemoveAltitude = false; - shouldWriteCoordinates = false; - shouldWriteAltitude = false; - altitude = 0.0; - latitude = 0.0; - longitude = 0.0; } bool shouldRemoveCoordinates; bool shouldRemoveAltitude; bool shouldWriteCoordinates; bool shouldWriteAltitude; qreal altitude; qreal latitude; qreal longitude; }; class GPSItemModel; class DMetadata; class DIGIKAM_EXPORT GPSItemContainer { public: static const int RoleCoordinates = Qt::UserRole + 1; static const int ColumnThumbnail = 0; static const int ColumnFilename = 1; static const int ColumnDateTime = 2; static const int ColumnLatitude = 3; static const int ColumnLongitude = 4; static const int ColumnAltitude = 5; static const int ColumnAccuracy = 6; static const int ColumnTags = 7; static const int ColumnStatus = 8; static const int ColumnDOP = 9; static const int ColumnFixType = 10; static const int ColumnNSatellites = 11; static const int ColumnSpeed = 12; static const int ColumnGPSItemContainerCount = 13; public: explicit GPSItemContainer(const QUrl& url); virtual ~GPSItemContainer(); /// @name Loading and saving //@{ virtual QString saveChanges(); virtual bool loadImageData(); //@} - bool isDirty() const; - QUrl url() const; - QDateTime dateTime() const; + bool isDirty() const; + QUrl url() const; + QDateTime dateTime() const; /// @name Functions used by the model //@{ static void setHeaderData(GPSItemModel* const model); - bool lessThan(const GPSItemContainer* const otherItem, const int column) const; + bool lessThan(const GPSItemContainer* const otherItem, const int column) const; //@} /// @name GPS related functions //@{ void setCoordinates(const GeoCoordinates& newCoordinates); - GeoCoordinates coordinates() const; - GPSDataContainer gpsData() const; + GeoCoordinates coordinates() const; + GPSDataContainer gpsData() const; void setGPSData(const GPSDataContainer& container); void restoreGPSData(const GPSDataContainer& container); //@} /// @name Tag related functions //@{ /** * The tags added in reverse geocoding process are stored in each image, before they end up in external tag model. This function adds them. * @param externalTagList A list containing tags. */ void setTagList(const QList >& externalTagList); /** * @return Returns true is the current image has been modified and not saved. */ - bool isTagListDirty() const; + bool isTagListDirty() const; /** * Returns the tag list of the current image. */ - QList > getTagList() const; + QList > getTagList() const; /** * Replaces the current tag list with the one contained in tagList. */ void restoreRGTagList(const QList >& tagList); /** * Writes the current tags to XMP metadata. */ void writeTagsToXmp(const bool writeXmpTags) { m_writeXmpTags = writeXmpTags; } //@} protected: - // these are only to be called by the GPSItemModel - QVariant data(const int column, const int role) const; + /// these are only to be called by the GPSItemModel + QVariant data(const int column, const int role) const; void setModel(GPSItemModel* const model); void emitDataChanged(); - DMetadata* getMetadataForFile() const; - SaveProperties saveProperties() const; + DMetadata* getMetadataForFile() const; + SaveProperties saveProperties() const; protected: GPSItemModel* m_model; QUrl m_url; QDateTime m_dateTime; bool m_dirty; GPSDataContainer m_gpsData; GPSDataContainer m_savedState; bool m_tagListDirty; QList > m_tagList; QList > m_savedTagList; bool m_writeXmpTags; friend class GPSItemModel; }; } // namespace Digikam #endif // DIGIKAM_GPS_ITEM_CONTAINER_H diff --git a/core/utilities/geolocation/geoiface/items/gpsitemdelegate.cpp b/core/utilities/geolocation/geoiface/items/gpsitemdelegate.cpp index 8173210518..028025208a 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemdelegate.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemdelegate.cpp @@ -1,133 +1,137 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-22 * Description : A model for the view to display a list of items. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #include "gpsitemdelegate.h" // Qt includes #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN GPSItemDelegate::Private { public: explicit Private() : imageList(nullptr), thumbnailSize(60) { } GPSItemList* imageList; - int thumbnailSize; + int thumbnailSize; }; GPSItemDelegate::GPSItemDelegate(GPSItemList* const imageList, QObject* const parent) : QItemDelegate(parent), d(new Private()) { d->imageList = imageList; } GPSItemDelegate::~GPSItemDelegate() { delete d; } void GPSItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& sortMappedindex) const { if (sortMappedindex.column() != GPSItemContainer::ColumnThumbnail) { QItemDelegate::paint(painter, option, sortMappedindex); + return; } const QModelIndex& sourceModelIndex = d->imageList->getSortProxyModel()->mapToSource(sortMappedindex); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } // TODO: clipping, selected state, disabled state, etc. + QPixmap itemPixmap = d->imageList->getModel()->getPixmapForIndex(sourceModelIndex, d->thumbnailSize); if (itemPixmap.isNull()) { // TODO: paint some default logo // TODO: cache this logo + itemPixmap = QIcon::fromTheme(QLatin1String("view-preview")) .pixmap(d->thumbnailSize, QIcon::Disabled); } const QSize availableSize = option.rect.size(); const QSize pixmapSize = itemPixmap.size().boundedTo(availableSize); QPoint startPoint((availableSize.width() - pixmapSize.width()) / 2, (availableSize.height() - pixmapSize.height()) / 2); startPoint += option.rect.topLeft(); painter->drawPixmap(QRect(startPoint, pixmapSize), itemPixmap, QRect(QPoint(0, 0), pixmapSize)); } QSize GPSItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& sortMappedindex) const { if (sortMappedindex.column() == GPSItemContainer::ColumnThumbnail) { return QSize(d->thumbnailSize, d->thumbnailSize); } const QSize realSizeHint = QItemDelegate::sizeHint(option, sortMappedindex); return QSize(realSizeHint.width(), d->thumbnailSize); } void GPSItemDelegate::setThumbnailSize(const int size) { d->thumbnailSize = size; GPSItemModel* const imageModel = d->imageList->getModel(); if (!imageModel) return; if (imageModel->rowCount() > 0) { // TODO: is it enough to emit this signal for only 1 item? // seems to work in Qt4.5 with QTreeView::setUniformRowHeights(true) + emit sizeHintChanged(imageModel->index(0, 0)); } } int GPSItemDelegate::getThumbnailSize() const { return d->thumbnailSize; } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/items/gpsitemdelegate.h b/core/utilities/geolocation/geoiface/items/gpsitemdelegate.h index 9422d445c0..321911ab9a 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemdelegate.h +++ b/core/utilities/geolocation/geoiface/items/gpsitemdelegate.h @@ -1,62 +1,62 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-22 * Description : A model for the view to display a list of items. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #ifndef DIGIKAM_GPS_ITEM_DELEGATE_H #define DIGIKAM_GPS_ITEM_DELEGATE_H // Qt includes #include // Local includes #include "gpsitemlist.h" namespace Digikam { class GPSItemDelegate : public QItemDelegate { Q_OBJECT public: explicit GPSItemDelegate(GPSItemList* const imageList, QObject* const parent = nullptr); virtual ~GPSItemDelegate(); void setThumbnailSize(const int size); - int getThumbnailSize() const; + int getThumbnailSize() const; virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& sortMappedindex) const override; - virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& sortMappedindex) const override; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& sortMappedindex) const override; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_GPS_ITEM_DELEGATE_H diff --git a/core/utilities/geolocation/geoiface/items/gpsitemlist.cpp b/core/utilities/geolocation/geoiface/items/gpsitemlist.cpp index 41432da871..802c2f0189 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemlist.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemlist.cpp @@ -1,301 +1,316 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-22 * Description : A view to display a list of items with GPS info. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #include "gpsitemlist.h" // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "gpsitemdelegate.h" #include "gpsitemlistdragdrophandler.h" namespace Digikam { class Q_DECL_HIDDEN GPSItemList::Private { public: explicit Private() : editEnabled(true), dragEnabled(false), model(nullptr), selectionModel(nullptr), itemDelegate(nullptr), imageSortProxyModel(nullptr), dragDropHandler(nullptr) { } - bool editEnabled; - bool dragEnabled; + bool editEnabled; + bool dragEnabled; GPSItemModel* model; - QItemSelectionModel* selectionModel; - GPSItemDelegate* itemDelegate; + QItemSelectionModel* selectionModel; + GPSItemDelegate* itemDelegate; GPSItemSortProxyModel* imageSortProxyModel; ItemListDragDropHandler* dragDropHandler; }; GPSItemList::GPSItemList(QWidget* const parent) : QTreeView(parent), d(new Private()) { header()->setSectionsMovable(true); setUniformRowHeights(true); setRootIsDecorated(false); setAlternatingRowColors(true); d->itemDelegate = new GPSItemDelegate(this, this); setItemDelegate(d->itemDelegate); setThumbnailSize(60); slotUpdateActionsEnabled(); header()->installEventFilter(this); } GPSItemList::~GPSItemList() { delete d; } void GPSItemList::startDrag(Qt::DropActions supportedActions) { if (!d->dragDropHandler) { QTreeView::startDrag(supportedActions); return; } // NOTE: read the selected indices from the source selection model, not our selection model, // which is for the sorted model! + const QList selectedIndicesFromModel = d->selectionModel->selectedIndexes(); QList selectedIndices; for (int i = 0 ; i < selectedIndicesFromModel.count() ; ++i) { selectedIndices << selectedIndicesFromModel.at(i); } QMimeData* const dragMimeData = d->dragDropHandler->createMimeData(selectedIndices); if (!dragMimeData) + { return; + } QDrag* const drag = new QDrag(this); drag->setMimeData(dragMimeData); drag->exec(Qt::CopyAction); } void GPSItemList::setModelAndSelectionModel(GPSItemModel* const model, QItemSelectionModel* const selectionModel) { d->model = model; d->selectionModel = selectionModel; d->imageSortProxyModel = new GPSItemSortProxyModel(d->model, d->selectionModel); setModel(d->imageSortProxyModel); connect(d->model, SIGNAL(signalThumbnailForIndexAvailable(QPersistentModelIndex,QPixmap)), this, SLOT(slotThumbnailFromModel(QPersistentModelIndex,QPixmap))); connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(slotInternalTreeViewImageActivated(QModelIndex))); if (d->imageSortProxyModel->mappedSelectionModel()) + { setSelectionModel(d->imageSortProxyModel->mappedSelectionModel()); + } } void GPSItemList::setDragDropHandler(ItemListDragDropHandler* const dragDropHandler) { d->dragDropHandler = dragDropHandler; } GPSItemModel* GPSItemList::getModel() const { return d->model; } void GPSItemList::setThumbnailSize(const int size) { d->itemDelegate->setThumbnailSize(size); setColumnWidth(GPSItemContainer::ColumnThumbnail, size); } void GPSItemList::slotIncreaseThumbnailSize() { // TODO: pick reasonable limits and make sure we stay on multiples of 5 + const int currentThumbnailSize = d->itemDelegate->getThumbnailSize(); if (currentThumbnailSize < 200) + { setThumbnailSize(currentThumbnailSize + 5); + } } void GPSItemList::slotDecreaseThumbnailSize() { const int currentThumbnailSize = d->itemDelegate->getThumbnailSize(); if (currentThumbnailSize > 30) + { setThumbnailSize(currentThumbnailSize - 5); + } } void GPSItemList::wheelEvent(QWheelEvent* we) { if ((we->modifiers() & Qt::ControlModifier) == 0) { QTreeView::wheelEvent(we); return; } we->accept(); if (we->delta() > 0) { slotIncreaseThumbnailSize(); } else { slotDecreaseThumbnailSize(); } } void GPSItemList::slotThumbnailFromModel(const QPersistentModelIndex& index, const QPixmap& /*pixmap*/) { // TODO: verify that the size corresponds to the size of our thumbnails! + update(d->imageSortProxyModel->mapFromSource(index)); } void GPSItemList::saveSettingsToGroup(KConfigGroup* const group) { group->writeEntry("Image List Thumbnail Size", d->itemDelegate->getThumbnailSize()); group->writeEntry("Header State", header()->saveState()); } void GPSItemList::readSettingsFromGroup(const KConfigGroup* const group) { setThumbnailSize(group->readEntry("Image List Thumbnail Size", 60)); const QByteArray headerState = group->readEntry("Header State", QByteArray()); if (!headerState.isEmpty()) { header()->restoreState(headerState); } else { // by default, hide the advanced columns: + header()->setSectionHidden(GPSItemContainer::ColumnDOP, true); header()->setSectionHidden(GPSItemContainer::ColumnFixType, true); header()->setSectionHidden(GPSItemContainer::ColumnNSatellites, true); } } QItemSelectionModel* GPSItemList::getSelectionModel() const { return d->selectionModel; } void GPSItemList::slotInternalTreeViewImageActivated(const QModelIndex& index) { qCDebug(DIGIKAM_GENERAL_LOG) << index << d->imageSortProxyModel->mapToSource(index); emit signalImageActivated(d->imageSortProxyModel->mapToSource(index)); } GPSItemSortProxyModel* GPSItemList::getSortProxyModel() const { return d->imageSortProxyModel; } void GPSItemList::setEditEnabled(const bool state) { d->editEnabled = state; slotUpdateActionsEnabled(); } void GPSItemList::setDragEnabled(const bool state) { d->dragEnabled = state; slotUpdateActionsEnabled(); } void GPSItemList::slotUpdateActionsEnabled() { QTreeView::setDragEnabled(d->dragEnabled && d->editEnabled); if (d->dragEnabled && d->editEnabled) { QTreeView::setDragDropMode(QAbstractItemView::DragOnly); } } bool GPSItemList::eventFilter(QObject* watched, QEvent* event) { QHeaderView* const headerView = header(); if (!d->model || (watched != headerView) || (event->type() != QEvent::ContextMenu)) + { return QWidget::eventFilter(watched, event); + } QMenu* const menu = new QMenu(this); // add action for all the columns + for (int i = 0 ; i < d->model->columnCount() ; ++i) { const QString columnName = d->model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); const bool isVisible = !headerView->isSectionHidden(i); QAction* const columnAction = new QAction(columnName, menu); columnAction->setCheckable(true); columnAction->setChecked(isVisible); columnAction->setData(i); menu->addAction(columnAction); } connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(slotColumnVisibilityActionTriggered(QAction*))); QContextMenuEvent* const e = static_cast(event); menu->exec(e->globalPos()); return true; } void GPSItemList::slotColumnVisibilityActionTriggered(QAction* action) { const int columnNumber = action->data().toInt(); const bool columnIsVisible = action->isChecked(); header()->setSectionHidden(columnNumber, !columnIsVisible); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/items/gpsitemlist.h b/core/utilities/geolocation/geoiface/items/gpsitemlist.h index 7ec96ccc71..2eee0ed627 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemlist.h +++ b/core/utilities/geolocation/geoiface/items/gpsitemlist.h @@ -1,97 +1,97 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-22 * Description : A view to display a list of items with GPS info. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #ifndef DIGIKAM_GPS_ITEM_LIST_H #define DIGIKAM_GPS_ITEM_LIST_H // Qt includes #include // Local includes #include "gpsitemmodel.h" #include "gpsitemsortproxymodel.h" #include "digikam_export.h" class QWheelEvent; class KConfigGroup; namespace Digikam { class ItemListDragDropHandler; class DIGIKAM_EXPORT GPSItemList : public QTreeView { Q_OBJECT public: explicit GPSItemList(QWidget* const parent = nullptr); ~GPSItemList(); void setModelAndSelectionModel(GPSItemModel* const model, QItemSelectionModel* const selectionModel); - GPSItemModel* getModel() const; - QItemSelectionModel* getSelectionModel() const; + GPSItemModel* getModel() const; + QItemSelectionModel* getSelectionModel() const; void setDragDropHandler(ItemListDragDropHandler* const dragDropHandler); void setThumbnailSize(const int size); - GPSItemSortProxyModel* getSortProxyModel() const; + GPSItemSortProxyModel* getSortProxyModel() const; void saveSettingsToGroup(KConfigGroup* const group); void readSettingsFromGroup(const KConfigGroup* const group); void setEditEnabled(const bool state); void setDragEnabled(const bool state); Q_SIGNALS: void signalImageActivated(const QModelIndex& index); public Q_SLOTS: void slotIncreaseThumbnailSize(); void slotDecreaseThumbnailSize(); void slotUpdateActionsEnabled(); private Q_SLOTS: void slotThumbnailFromModel(const QPersistentModelIndex& index, const QPixmap& pixmap); void slotInternalTreeViewImageActivated(const QModelIndex& index); void slotColumnVisibilityActionTriggered(QAction* action); protected: virtual bool eventFilter(QObject* watched, QEvent* event) override; - virtual void startDrag(Qt::DropActions supportedActions) override; - virtual void wheelEvent(QWheelEvent* we) override; + virtual void startDrag(Qt::DropActions supportedActions) override; + virtual void wheelEvent(QWheelEvent* we) override; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_GPS_ITEM_LIST_H diff --git a/core/utilities/geolocation/geoiface/items/gpsitemlistcontextmenu.cpp b/core/utilities/geolocation/geoiface/items/gpsitemlistcontextmenu.cpp index 0d14131975..5ab5e3c5f5 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemlistcontextmenu.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemlistcontextmenu.cpp @@ -1,803 +1,819 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-05-07 * Description : Context menu for GPS list view. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2014 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #include "gpsitemlistcontextmenu.h" // Qt includes: #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "gpsundocommand.h" #include "gpscommon.h" #include "gpsitemcontainer.h" #include "lookupfactory.h" #include "gpsbookmarkowner.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN GPSItemListContextMenu::Private { public: explicit Private() : enabled(true), actionBookmark(nullptr), bookmarkOwner(nullptr), actionCopy(nullptr), actionPaste(nullptr), actionPasteSwap(nullptr), actionRemoveCoordinates(nullptr), actionRemoveAltitude(nullptr), actionRemoveUncertainty(nullptr), actionRemoveSpeed(nullptr), actionLookupMissingAltitudes(nullptr), imagesList(nullptr), altitudeLookup(), altitudeUndoCommand(nullptr), altitudeRequestedCount(0), altitudeReceivedCount(0) { } bool enabled; QAction* actionBookmark; GPSBookmarkOwner* bookmarkOwner; QAction* actionCopy; QAction* actionPaste; QAction* actionPasteSwap; QAction* actionRemoveCoordinates; QAction* actionRemoveAltitude; QAction* actionRemoveUncertainty; QAction* actionRemoveSpeed; QAction* actionLookupMissingAltitudes; - GPSItemList* imagesList; + GPSItemList* imagesList; - // Altitude lookup + /// Altitude lookup QPointer altitudeLookup; GPSUndoCommand* altitudeUndoCommand; int altitudeRequestedCount; int altitudeReceivedCount; }; GPSItemListContextMenu::GPSItemListContextMenu(GPSItemList* const imagesList, - GPSBookmarkOwner* const bookmarkOwner) + GPSBookmarkOwner* const bookmarkOwner) : QObject(imagesList), d(new Private) { d->imagesList = imagesList; d->actionCopy = new QAction(i18n("Copy coordinates"), this); d->actionCopy->setIcon(QIcon::fromTheme(QLatin1String("edit-copy"))); d->actionPaste = new QAction(i18n("Paste coordinates"), this); d->actionPaste->setIcon(QIcon::fromTheme(QLatin1String("edit-paste"))); d->actionPasteSwap = new QAction(i18n("Paste coordinates swapped"), this); d->actionPasteSwap->setIcon(QIcon::fromTheme(QLatin1String("edit-paste"))); d->actionRemoveCoordinates = new QAction(i18n("Remove coordinates"), this); d->actionRemoveAltitude = new QAction(i18n("Remove altitude"), this); d->actionRemoveUncertainty = new QAction(i18n("Remove uncertainty"), this); d->actionRemoveSpeed = new QAction(i18n("Remove speed"), this); d->actionLookupMissingAltitudes = new QAction(i18n("Look up missing altitude values"), this); connect(d->actionCopy, SIGNAL(triggered()), this, SLOT(copyActionTriggered())); connect(d->actionPaste, SIGNAL(triggered()), this, SLOT(pasteActionTriggered())); connect(d->actionPasteSwap, SIGNAL(triggered()), this, SLOT(pasteSwapActionTriggered())); connect(d->actionRemoveCoordinates, SIGNAL(triggered()), this, SLOT(slotRemoveCoordinates())); connect(d->actionRemoveAltitude, SIGNAL(triggered()), this, SLOT(slotRemoveAltitude())); connect(d->actionRemoveUncertainty, SIGNAL(triggered()), this, SLOT(slotRemoveUncertainty())); connect(d->actionRemoveSpeed, SIGNAL(triggered()), this, SLOT(slotRemoveSpeed())); connect(d->actionLookupMissingAltitudes, SIGNAL(triggered()), this, SLOT(slotLookupMissingAltitudes())); if (bookmarkOwner) { d->bookmarkOwner = bookmarkOwner; d->actionBookmark = new QAction(i18n("Bookmarks"), this); d->actionBookmark->setMenu(d->bookmarkOwner->getMenu()); connect(d->bookmarkOwner, SIGNAL(positionSelected(GPSDataContainer)), this, SLOT(slotBookmarkSelected(GPSDataContainer))); } d->imagesList->installEventFilter(this); } GPSItemListContextMenu::~GPSItemListContextMenu() { delete d->altitudeUndoCommand; delete d; } bool GPSItemListContextMenu::eventFilter(QObject* watched, QEvent* event) { // We are only interested in context-menu events. if ((event->type() == QEvent::ContextMenu) && d->enabled) { - // enable or disable the actions: + // enable or disable the actions + GPSItemModel* const imageModel = d->imagesList->getModel(); QItemSelectionModel* const selectionModel = d->imagesList->getSelectionModel(); const QList selectedIndices = selectionModel->selectedRows(); const int nSelected = selectedIndices.size(); // "copy" are only available for one selected image with geo data: + bool copyAvailable = (nSelected == 1); bool removeAltitudeAvailable = false; bool removeCoordinatesAvailable = false; bool removeUncertaintyAvailable = false; bool removeSpeedAvailable = false; bool lookupMissingAltitudesAvailable = false; for (int i = 0 ; i < nSelected ; ++i) { GPSItemContainer* const gpsItem = imageModel->itemFromIndex(selectedIndices.at(i)); if (gpsItem) { const GPSDataContainer gpsData = gpsItem->gpsData(); const bool itemHasCoordinates = gpsData.getCoordinates().hasCoordinates(); copyAvailable &= itemHasCoordinates; removeCoordinatesAvailable |= itemHasCoordinates; removeAltitudeAvailable |= gpsData.getCoordinates().hasAltitude(); removeUncertaintyAvailable |= gpsData.hasNSatellites() | gpsData.hasDop() | gpsData.hasFixType(); removeSpeedAvailable |= gpsData.hasSpeed(); lookupMissingAltitudesAvailable |= itemHasCoordinates && !gpsData.getCoordinates().hasAltitude(); } } d->actionCopy->setEnabled(copyAvailable); d->actionRemoveAltitude->setEnabled(removeAltitudeAvailable); d->actionRemoveCoordinates->setEnabled(removeCoordinatesAvailable); d->actionRemoveUncertainty->setEnabled(removeUncertaintyAvailable); d->actionRemoveSpeed->setEnabled(removeSpeedAvailable); d->actionLookupMissingAltitudes->setEnabled(lookupMissingAltitudesAvailable); if (d->bookmarkOwner) { d->bookmarkOwner->changeAddBookmark(copyAvailable); GPSDataContainer position; QUrl itemUrl; getCurrentItemPositionAndUrl(&position, &itemUrl); const QString itemFileName = itemUrl.fileName(); d->bookmarkOwner->setPositionAndTitle(position.getCoordinates(), itemFileName); } // "paste" is only available if there is geo data in the clipboard // and at least one photo is selected: + bool pasteAvailable = (nSelected >= 1); bool pasteSwapAvailable = true; if (pasteAvailable) { QClipboard* const clipboard = QApplication::clipboard(); const QMimeData* mimedata = clipboard->mimeData(); bool hasXmlFormat = mimedata->hasFormat(QLatin1String("application/gpx+xml")); pasteAvailable = hasXmlFormat || mimedata->hasText(); pasteSwapAvailable = !hasXmlFormat && mimedata->hasText(); } d->actionPaste->setEnabled(pasteAvailable); d->actionPasteSwap->setEnabled(pasteSwapAvailable); // construct the context-menu: + QMenu* const menu = new QMenu(d->imagesList); menu->addAction(d->actionCopy); menu->addAction(d->actionPaste); menu->addAction(d->actionPasteSwap); menu->addSeparator(); menu->addAction(d->actionRemoveCoordinates); menu->addAction(d->actionRemoveAltitude); menu->addAction(d->actionRemoveUncertainty); menu->addAction(d->actionRemoveSpeed); menu->addAction(d->actionLookupMissingAltitudes); if (d->actionBookmark) { menu->addSeparator(); menu->addAction(d->actionBookmark); d->actionBookmark->setEnabled(nSelected >= 1); } QContextMenuEvent* const e = static_cast(event); menu->exec(e->globalPos()); delete menu; return true; } else { return QObject::eventFilter(watched, event); } } bool GPSItemListContextMenu::getCurrentItemPositionAndUrl(GPSDataContainer* const gpsInfo, QUrl* const itemUrl) { // NOTE: currentIndex does not seem to work any more since we use KLinkItemSelectionModel + GPSItemModel* const imageModel = d->imagesList->getModel(); QItemSelectionModel* const selectionModel = d->imagesList->getSelectionModel(); const QList selectedIndices = selectionModel->selectedRows(); if (selectedIndices.count() != 1) { return false; } const QModelIndex currentIndex = selectedIndices.first(); if (!currentIndex.isValid()) { return false; } GPSItemContainer* const gpsItem = imageModel->itemFromIndex(currentIndex); if (gpsItem) { if (gpsInfo) { *gpsInfo = gpsItem->gpsData(); } if (itemUrl) { *itemUrl = gpsItem->url(); } return true; } return false; } void GPSItemListContextMenu::copyActionTriggered() { GPSDataContainer gpsInfo; QUrl itemUrl; if (!getCurrentItemPositionAndUrl(&gpsInfo, &itemUrl)) { return; } coordinatesToClipboard(gpsInfo.getCoordinates(), itemUrl, QString()); } void GPSItemListContextMenu::pasteSwapActionTriggered() { pasteActionTriggered(true); } void GPSItemListContextMenu::pasteActionTriggered(bool swap) { // extract the coordinates from the clipboard: + QClipboard* const clipboard = QApplication::clipboard(); const QMimeData* mimedata = clipboard->mimeData(); GPSDataContainer gpsData; bool foundData = false; if (mimedata->hasFormat(QLatin1String("application/gpx+xml"))) { const QByteArray data = mimedata->data(QLatin1String("application/gpx+xml")); bool xmlOkay = true; bool foundDoubleData = false; // code adapted from gpsdataparser.cpp + QDomDocument gpxDoc(QLatin1String("gpx")); if (!gpxDoc.setContent(data)) { xmlOkay = false; } if (xmlOkay) { const QDomElement gpxDocElem = gpxDoc.documentElement(); if (gpxDocElem.tagName() != QLatin1String("gpx")) { xmlOkay = false; } if (xmlOkay) { - for (QDomNode nWpt = gpxDocElem.firstChild(); !nWpt.isNull(); nWpt = nWpt.nextSibling()) + for (QDomNode nWpt = gpxDocElem.firstChild() ; !nWpt.isNull() ; nWpt = nWpt.nextSibling()) { const QDomElement wptElem = nWpt.toElement(); if (wptElem.isNull()) { continue; } if (wptElem.tagName() != QLatin1String("wpt")) { continue; } double ptAltitude = 0.0; double ptLatitude = 0.0; double ptLongitude = 0.0; bool haveAltitude = false; // Get GPS position. If not available continue to next point. - const QString lat = wptElem.attribute(QLatin1String("lat")); - const QString lon = wptElem.attribute(QLatin1String("lon")); + + const QString lat = wptElem.attribute(QLatin1String("lat")); + const QString lon = wptElem.attribute(QLatin1String("lon")); if (lat.isEmpty() || lon.isEmpty()) { continue; } - ptLatitude = lat.toDouble(); - ptLongitude = lon.toDouble(); + ptLatitude = lat.toDouble(); + ptLongitude = lon.toDouble(); if (foundData) { foundDoubleData = true; break; } // Get metadata of way point (altitude and time stamp) - for (QDomNode nWptMeta = wptElem.firstChild(); !nWptMeta.isNull(); nWptMeta = nWptMeta.nextSibling()) + + for (QDomNode nWptMeta = wptElem.firstChild() ; !nWptMeta.isNull() ; nWptMeta = nWptMeta.nextSibling()) { const QDomElement wptMetaElem = nWptMeta.toElement(); if (wptMetaElem.isNull()) { continue; } if (wptMetaElem.tagName() == QLatin1String("ele")) { // Get GPS point altitude. If not available continue to next point. + QString ele = wptMetaElem.text(); if (!ele.isEmpty()) { - ptAltitude = ele.toDouble(&haveAltitude); + ptAltitude = ele.toDouble(&haveAltitude); break; } } } foundData = true; GeoCoordinates coordinates(ptLatitude, ptLongitude); if (haveAltitude) { coordinates.setAlt(ptAltitude); } gpsData.setCoordinates(coordinates); } } } if (foundDoubleData) { QMessageBox::information(d->imagesList, i18n("GPS Sync"), i18n("Found more than one point on the clipboard - can only assign one point at a time.")); } } if ((!foundData)&&(mimedata->hasText())) { const QString textdata = mimedata->text(); bool foundGeoUrl = false; GeoCoordinates testCoordinates = GeoCoordinates::fromGeoUrl(textdata, &foundGeoUrl); if (foundGeoUrl) { gpsData.setCoordinates(testCoordinates); foundData = true; } else { /// @todo this is legacy code from before we used geo-url + const QStringList parts = textdata.split(QLatin1Char(',')); if ((parts.size() == 3) || (parts.size() == 2)) { double ptLatitude = 0.0; double ptAltitude = 0.0; bool haveAltitude = false; bool okay = true; double ptLongitude = parts[0].toDouble(&okay); if (okay) { ptLatitude = parts[1].toDouble(&okay); } if (okay && (parts.size() == 3)) { ptAltitude = parts[2].toDouble(&okay); haveAltitude = okay; } foundData = okay; if (okay) { if (swap) { std::swap(ptLongitude, ptLatitude); } GeoCoordinates coordinates(ptLatitude, ptLongitude); if (haveAltitude) { coordinates.setAlt(ptAltitude); } gpsData.setCoordinates(coordinates); } } } } if (!foundData) { QMessageBox::information(d->imagesList, i18n("Geolocation Editor"), i18n("Could not find any coordinates on the clipboard.")); return; } setGPSDataForSelectedItems(gpsData, i18n("Coordinates pasted")); } void GPSItemListContextMenu::setGPSDataForSelectedItems(const GPSDataContainer& gpsData, const QString& undoDescription) { - GPSItemModel* const imageModel = d->imagesList->getModel(); + GPSItemModel* const imageModel = d->imagesList->getModel(); QItemSelectionModel* const selectionModel = d->imagesList->getSelectionModel(); const QList selectedIndices = selectionModel->selectedRows(); const int nSelected = selectedIndices.size(); GPSUndoCommand* const undoCommand = new GPSUndoCommand(); for (int i = 0 ; i < nSelected ; ++i) { const QModelIndex itemIndex = selectedIndices.at(i); GPSItemContainer* const gpsItem = imageModel->itemFromIndex(itemIndex); GPSUndoCommand::UndoInfo undoInfo(itemIndex); undoInfo.readOldDataFromItem(gpsItem); gpsItem->setGPSData(gpsData); undoInfo.readNewDataFromItem(gpsItem); undoCommand->addUndoInfo(undoInfo); } undoCommand->setText(undoDescription); emit signalUndoCommand(undoCommand); } void GPSItemListContextMenu::slotBookmarkSelected(const GPSDataContainer& position) { setGPSDataForSelectedItems(position, i18n("Bookmark selected")); } bool GPSItemListContextMenu::getCurrentPosition(GPSDataContainer* position, void* mydata) { if (!position || !mydata) { return false; } GPSItemListContextMenu* const me = reinterpret_cast(mydata); return me->getCurrentItemPositionAndUrl(position, nullptr); } void GPSItemListContextMenu::removeInformationFromSelectedImages(const GPSDataContainer::HasFlags flagsToClear, const QString& undoDescription) { - // enable or disable the actions: + // enable or disable the actions + GPSItemModel* const imageModel = d->imagesList->getModel(); QItemSelectionModel* const selectionModel = d->imagesList->getSelectionModel(); const QList selectedIndices = selectionModel->selectedRows(); const int nSelected = selectedIndices.size(); GPSUndoCommand* const undoCommand = new GPSUndoCommand(); for (int i = 0 ; i < nSelected ; ++i) { - const QModelIndex itemIndex = selectedIndices.at(i); + const QModelIndex itemIndex = selectedIndices.at(i); GPSItemContainer* const gpsItem = imageModel->itemFromIndex(itemIndex); GPSUndoCommand::UndoInfo undoInfo(itemIndex); undoInfo.readOldDataFromItem(gpsItem); - GPSDataContainer newGPSData = gpsItem->gpsData(); - bool didSomething = false; + GPSDataContainer newGPSData = gpsItem->gpsData(); + bool didSomething = false; if (flagsToClear.testFlag(GPSDataContainer::HasCoordinates)) { if (newGPSData.hasCoordinates()) { didSomething = true; newGPSData.clear(); } } if (flagsToClear.testFlag(GPSDataContainer::HasAltitude)) { if (newGPSData.hasAltitude()) { didSomething = true; newGPSData.clearAltitude(); } } if (flagsToClear.testFlag(GPSDataContainer::HasNSatellites)) { if (newGPSData.hasNSatellites()) { didSomething = true; newGPSData.clearNSatellites(); } } if (flagsToClear.testFlag(GPSDataContainer::HasDop)) { if (newGPSData.hasDop()) { didSomething = true; newGPSData.clearDop(); } } if (flagsToClear.testFlag(GPSDataContainer::HasFixType)) { if (newGPSData.hasFixType()) { didSomething = true; newGPSData.clearFixType(); } } if (flagsToClear.testFlag(GPSDataContainer::HasSpeed)) { if (newGPSData.hasSpeed()) { didSomething = true; newGPSData.clearSpeed(); } } if (didSomething) { gpsItem->setGPSData(newGPSData); undoInfo.readNewDataFromItem(gpsItem); undoCommand->addUndoInfo(undoInfo); } } if (undoCommand->affectedItemCount() > 0) { undoCommand->setText(undoDescription); emit signalUndoCommand(undoCommand); } else { delete undoCommand; } } void GPSItemListContextMenu::slotRemoveCoordinates() { removeInformationFromSelectedImages(GPSDataContainer::HasCoordinates, i18n("Remove coordinates information")); } void GPSItemListContextMenu::slotRemoveAltitude() { removeInformationFromSelectedImages(GPSDataContainer::HasAltitude, i18n("Remove altitude information")); } void GPSItemListContextMenu::slotRemoveUncertainty() { removeInformationFromSelectedImages(GPSDataContainer::HasNSatellites|GPSDataContainer::HasDop|GPSDataContainer::HasFixType, i18n("Remove uncertainty information")); } void GPSItemListContextMenu::setEnabled(const bool state) { d->enabled = state; } void GPSItemListContextMenu::slotRemoveSpeed() { removeInformationFromSelectedImages(GPSDataContainer::HasSpeed, i18n("Remove speed")); } void GPSItemListContextMenu::slotLookupMissingAltitudes() { GPSItemModel* const imageModel = d->imagesList->getModel(); QItemSelectionModel* const selectionModel = d->imagesList->getSelectionModel(); const QList selectedIndices = selectionModel->selectedRows(); -// const int nSelected = selectedIndices.size(); - +/* + const int nSelected = selectedIndices.size(); +*/ // find the indices which have coordinates but no altitude + LookupAltitude::Request::List altitudeQueries; foreach (const QModelIndex& currentIndex, selectedIndices) { GPSItemContainer* const gpsItem = imageModel->itemFromIndex(currentIndex); if (!gpsItem) { continue; } - const GPSDataContainer gpsData = gpsItem->gpsData(); + const GPSDataContainer gpsData = gpsItem->gpsData(); const GeoCoordinates coordinates = gpsData.getCoordinates(); if ((!coordinates.hasCoordinates()) || coordinates.hasAltitude()) { continue; } // the item has coordinates but no altitude, create a query + LookupAltitude::Request myLookup; myLookup.coordinates = coordinates; myLookup.data = QVariant::fromValue(QPersistentModelIndex(currentIndex)); altitudeQueries << myLookup; } if (altitudeQueries.isEmpty()) { return; } d->altitudeLookup = LookupFactory::getAltitudeLookup(QLatin1String("geonames"), this); connect(d->altitudeLookup, SIGNAL(signalRequestsReady(QList)), this, SLOT(slotAltitudeLookupReady(QList))); connect(d->altitudeLookup, SIGNAL(signalDone()), this, SLOT(slotAltitudeLookupDone())); emit signalSetUIEnabled(false, this, QLatin1String(SLOT(slotAltitudeLookupCancel()))); emit signalProgressSetup(altitudeQueries.count(), i18n("Looking up altitudes")); d->altitudeUndoCommand = new GPSUndoCommand(); d->altitudeRequestedCount = altitudeQueries.count(); d->altitudeReceivedCount = 0; d->altitudeLookup->addRequests(altitudeQueries); d->altitudeLookup->startLookup(); } void GPSItemListContextMenu::slotAltitudeLookupReady(const QList& readyRequests) { GPSItemModel* const imageModel = d->imagesList->getModel(); foreach (const int requestIndex, readyRequests) { - const LookupAltitude::Request myLookup = d->altitudeLookup->getRequest(requestIndex); - const QPersistentModelIndex markerIndex = myLookup.data.value(); + const LookupAltitude::Request myLookup = d->altitudeLookup->getRequest(requestIndex); + const QPersistentModelIndex markerIndex = myLookup.data.value(); if (!markerIndex.isValid()) { continue; } - GPSItemContainer* const gpsItem = imageModel->itemFromIndex(markerIndex); + GPSItemContainer* const gpsItem = imageModel->itemFromIndex(markerIndex); if (!gpsItem) { continue; } GPSUndoCommand::UndoInfo undoInfo(markerIndex); undoInfo.readOldDataFromItem(gpsItem); - GPSDataContainer gpsData = gpsItem->gpsData(); + GPSDataContainer gpsData = gpsItem->gpsData(); gpsData.setCoordinates(myLookup.coordinates); gpsItem->setGPSData(gpsData); undoInfo.readNewDataFromItem(gpsItem); d->altitudeUndoCommand->addUndoInfo(undoInfo); d->altitudeReceivedCount++; } signalProgressChanged(d->altitudeReceivedCount); } void GPSItemListContextMenu::slotAltitudeLookupDone() { LookupAltitude::StatusAltitude requestStatus = d->altitudeLookup->getStatus(); if (requestStatus == LookupAltitude::StatusError) { const QString errorMessage = i18n("Altitude lookup failed:\n%1", d->altitudeLookup->errorMessage()); QMessageBox::information(d->imagesList, i18n("GPS Sync"),errorMessage); } if (d->altitudeReceivedCount > 0) { // at least some queries returned a result, save the undo command + d->altitudeUndoCommand->setText(i18n("Altitude looked up")); emit signalUndoCommand(d->altitudeUndoCommand); } else { delete d->altitudeUndoCommand; } d->altitudeUndoCommand = nullptr; d->altitudeLookup->deleteLater(); emit signalSetUIEnabled(true); } void GPSItemListContextMenu::slotAltitudeLookupCancel() { if (d->altitudeLookup) { d->altitudeLookup->cancel(); } } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/items/gpsitemmodel.cpp b/core/utilities/geolocation/geoiface/items/gpsitemmodel.cpp index 775cae0296..df5385e598 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemmodel.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemmodel.cpp @@ -1,293 +1,317 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-21 * Description : A model to hold GPS information about items. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #include "gpsitemmodel.h" // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN GPSItemModel::Private { public: explicit Private() : items(), columnCount(0), thumbnailLoadThread(nullptr) { } - QList items; + QList items; int columnCount; QMap, QVariant> headerData; ThumbnailLoadThread* thumbnailLoadThread; }; GPSItemModel::GPSItemModel(QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->thumbnailLoadThread = new ThumbnailLoadThread(this); connect(d->thumbnailLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); } GPSItemModel::~GPSItemModel() { // TODO: send a signal before deleting the items? + qDeleteAll(d->items); delete d; } int GPSItemModel::columnCount(const QModelIndex& /*parent*/) const { return d->columnCount; } QVariant GPSItemModel::data(const QModelIndex& index, int role) const { if (index.isValid()) { Q_ASSERT(index.model() == this); } const int rowNumber = index.row(); if ((rowNumber < 0) || (rowNumber >= d->items.count())) { return QVariant(); } return d->items.at(rowNumber)->data(index.column(), role); } QModelIndex GPSItemModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) { Q_ASSERT(parent.model() == this); } // qCDebug(DIGIKAM_GENERAL_LOG)<= d->columnCount) || (row < 0) || (row >= d->items.count()) ) return QModelIndex(); return createIndex(row, column, (void*)nullptr); } QModelIndex GPSItemModel::parent(const QModelIndex& /*index*/) const { // we have only top level items + return QModelIndex(); } void GPSItemModel::addItem(GPSItemContainer* const newItem) { beginInsertRows(QModelIndex(), d->items.count(), d->items.count()); newItem->setModel(this); d->items << newItem; endInsertRows(); } void GPSItemModel::setColumnCount(const int nColumns) { emit layoutAboutToBeChanged(); d->columnCount = nColumns; emit layoutChanged(); } void GPSItemModel::itemChanged(GPSItemContainer* const changedItem) { const int itemIndex = d->items.indexOf(changedItem); if (itemIndex < 0) + { return; + } const QModelIndex itemModelIndexStart = createIndex(itemIndex, 0, (void*)nullptr); const QModelIndex itemModelIndexEnd = createIndex(itemIndex, d->columnCount - 1, (void*)nullptr); emit dataChanged(itemModelIndexStart, itemModelIndexEnd); } GPSItemContainer* GPSItemModel::itemFromIndex(const QModelIndex& index) const { if (index.isValid()) { Q_ASSERT(index.model() == this); } if (!index.isValid()) + { return nullptr; + } const int row = index.row(); if ((row < 0) || (row >= d->items.count())) + { return nullptr; + } return d->items.at(row); } int GPSItemModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { Q_ASSERT(parent.model() == this); } if (parent.isValid()) + { return 0; + } return d->items.count(); } bool GPSItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) { if ((section >= d->columnCount) || (orientation != Qt::Horizontal)) + { return false; + } const QPair headerIndex = QPair(section, role); d->headerData[headerIndex] = value; return true; } QVariant GPSItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((section >= d->columnCount) || (orientation != Qt::Horizontal)) + { return false; + } const QPair headerIndex = QPair(section, role); return d->headerData.value(headerIndex); } bool GPSItemModel::setData(const QModelIndex& index, const QVariant& value, int role) { Q_UNUSED(index); Q_UNUSED(value); Q_UNUSED(role); return false; } Qt::ItemFlags GPSItemModel::flags(const QModelIndex& index) const { if (index.isValid()) { Q_ASSERT(index.model() == this); } if (!index.isValid()) + { return nullptr; + } - return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled; + return (QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled); } GPSItemContainer* GPSItemModel::itemFromUrl(const QUrl& url) const { for (int i = 0 ; i < d->items.count() ; ++i) { if (d->items.at(i)->url() == url) + { return d->items.at(i); + } } return nullptr; } QModelIndex GPSItemModel::indexFromUrl(const QUrl& url) const { for (int i = 0 ; i < d->items.count() ; ++i) { if (d->items.at(i)->url() == url) + { return index(i, 0, QModelIndex()); + } } return QModelIndex(); } QPixmap GPSItemModel::getPixmapForIndex(const QPersistentModelIndex& itemIndex, const int size) { if (itemIndex.isValid()) { Q_ASSERT(itemIndex.model() == this); } // TODO: should we cache the pixmap on our own here or does the interface usually cache it for us? // TODO: do we need to make sure we do not request the same pixmap twice in a row? - // construct the key under which we stored the pixmap in the cache: + // construct the key under which we stored the pixmap in the cache + GPSItemContainer* const imageItem = itemFromIndex(itemIndex); if (!imageItem) + { return QPixmap(); + } QPixmap thumbnail; if (d->thumbnailLoadThread->find(ThumbnailIdentifier(imageItem->url().toLocalFile()), thumbnail, size)) { return thumbnail.copy(1, 1, thumbnail.size().width()-2, thumbnail.size().height()-2); } return QPixmap(); } void GPSItemModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) { if (thumb.isNull()) { return; } const QModelIndex currentIndex = indexFromUrl(QUrl::fromLocalFile(loadingDescription.filePath)); if (currentIndex.isValid()) { QPersistentModelIndex goodIndex(currentIndex); emit signalThumbnailForIndexAvailable(goodIndex, thumb.copy(1, 1, thumb.size().width()-2, thumb.size().height()-2)); } } Qt::DropActions GPSItemModel::supportedDragActions() const { return Qt::CopyAction; } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/items/gpsitemmodel.h b/core/utilities/geolocation/geoiface/items/gpsitemmodel.h index 3765952afc..22311d27ca 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemmodel.h +++ b/core/utilities/geolocation/geoiface/items/gpsitemmodel.h @@ -1,94 +1,96 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-21 * Description : A model to hold GPS information about items. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #ifndef DIGIKAM_GPS_ITEM_MODEL_H #define DIGIKAM_GPS_ITEM_MODEL_H // Qt includes #include #include // Local includes #include "gpsitemcontainer.h" #include "thumbnailloadthread.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT GPSItemModel : public QAbstractItemModel { Q_OBJECT public: explicit GPSItemModel(QObject* const parent = nullptr); ~GPSItemModel(); // own functions: + void addItem(GPSItemContainer* const newItem); void setColumnCount(const int nColumns); - GPSItemContainer* itemFromIndex(const QModelIndex& index) const; - GPSItemContainer* itemFromUrl(const QUrl& url) const; - QModelIndex indexFromUrl(const QUrl& url) const; + GPSItemContainer* itemFromIndex(const QModelIndex& index) const; + GPSItemContainer* itemFromUrl(const QUrl& url) const; + QModelIndex indexFromUrl(const QUrl& url) const; QPixmap getPixmapForIndex(const QPersistentModelIndex& itemIndex, const int size); // QAbstractItemModel: - virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; - virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - virtual QModelIndex parent(const QModelIndex& index) const override; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; - virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual Qt::ItemFlags flags(const QModelIndex& index) const override; - virtual Qt::DropActions supportedDragActions() const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual Qt::DropActions supportedDragActions() const override; protected: void itemChanged(GPSItemContainer* const changedItem); Q_SIGNALS: void signalThumbnailForIndexAvailable(const QPersistentModelIndex& index, const QPixmap& pixmap); protected Q_SLOTS: void slotThumbnailLoaded(const LoadingDescription&, const QPixmap&); private: class Private; Private* const d; friend class GPSItemContainer; }; } // namespace Digikam #endif // DIGIKAM_GPS_ITEM_MODEL_H diff --git a/core/utilities/geolocation/geoiface/items/gpsitemsortproxymodel.cpp b/core/utilities/geolocation/geoiface/items/gpsitemsortproxymodel.cpp index f52c3d8b5d..374cb9f911 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemsortproxymodel.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemsortproxymodel.cpp @@ -1,609 +1,616 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-21 * Description : A model to hold GPS information about items. * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Michael G. Hansen * * 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 2, 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. * * ============================================================ */ #include "gpsitemsortproxymodel.h" // Qt includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN GPSItemSortProxyModel::Private { public: explicit Private() + : imageModel(nullptr), + sourceSelectionModel(nullptr), + linkItemSelectionModel(nullptr) { - imageModel = nullptr; - sourceSelectionModel = nullptr; - linkItemSelectionModel = nullptr; } - GPSItemModel* imageModel; + GPSItemModel* imageModel; QItemSelectionModel* sourceSelectionModel; GPSLinkItemSelectionModel* linkItemSelectionModel; }; GPSItemSortProxyModel::GPSItemSortProxyModel(GPSItemModel* const imageModel, - QItemSelectionModel* const sourceSelectionModel) + QItemSelectionModel* const sourceSelectionModel) : QSortFilterProxyModel(imageModel), d(new Private()) { d->imageModel = imageModel; d->sourceSelectionModel = sourceSelectionModel; setSourceModel(imageModel); d->linkItemSelectionModel = new GPSLinkItemSelectionModel(this, d->sourceSelectionModel); } GPSItemSortProxyModel::~GPSItemSortProxyModel() { delete d; } bool GPSItemSortProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { if ((!left.isValid())||(!right.isValid())) { // qCDebug(DIGIKAM_GENERAL_LOG) << "INVALID INDICES" << left << right; return false; } - const int column = left.column(); + const int column = left.column(); const GPSItemContainer* const itemLeft = d->imageModel->itemFromIndex(left); const GPSItemContainer* const itemRight = d->imageModel->itemFromIndex(right); // qCDebug(DIGIKAM_GENERAL_LOG) << itemLeft << itemRight << column << rowCount() << d->imageModel->rowCount(); return itemLeft->lessThan(itemRight, column); } QItemSelectionModel* GPSItemSortProxyModel::mappedSelectionModel() const { return d->linkItemSelectionModel; } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN GPSLinkItemSelectionModelPrivate { public: explicit GPSLinkItemSelectionModelPrivate(GPSLinkItemSelectionModel* const proxySelectionModel) : q_ptr(proxySelectionModel), m_linkedItemSelectionModel(nullptr), m_ignoreCurrentChanged(false), m_indexMapper(nullptr) { QObject::connect(q_ptr, &QItemSelectionModel::currentChanged, q_ptr, [this](const QModelIndex& idx) { slotCurrentChanged(idx); } ); QObject::connect(q_ptr, &QItemSelectionModel::modelChanged, q_ptr, [this]{ reinitializeIndexMapper(); } ); } public: Q_DECLARE_PUBLIC(GPSLinkItemSelectionModel) GPSLinkItemSelectionModel* const q_ptr; public: bool assertSelectionValid(const QItemSelection& selection) const { foreach (const QItemSelectionRange& range, selection) { if (!range.isValid()) { qCDebug(DIGIKAM_GENERAL_LOG) << selection; } Q_ASSERT(range.isValid()); } return true; } void reinitializeIndexMapper() { delete m_indexMapper; m_indexMapper = nullptr; if (!q_ptr->model() || !m_linkedItemSelectionModel || !m_linkedItemSelectionModel->model()) { return; } m_indexMapper = new GPSModelIndexProxyMapper(q_ptr->model(), m_linkedItemSelectionModel->model(), q_ptr); const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(m_linkedItemSelectionModel->selection()); q_ptr->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::ClearAndSelect); } void sourceSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void sourceCurrentChanged(const QModelIndex& current); void slotCurrentChanged(const QModelIndex& current); public: QItemSelectionModel* m_linkedItemSelectionModel; bool m_ignoreCurrentChanged; GPSModelIndexProxyMapper* m_indexMapper; }; GPSLinkItemSelectionModel::GPSLinkItemSelectionModel(QAbstractItemModel* const model, QItemSelectionModel* const proxySelector, QObject* const parent) : QItemSelectionModel(model, parent), d_ptr(new GPSLinkItemSelectionModelPrivate(this)) { setLinkedItemSelectionModel(proxySelector); } GPSLinkItemSelectionModel::GPSLinkItemSelectionModel(QObject* const parent) : QItemSelectionModel(nullptr, parent), d_ptr(new GPSLinkItemSelectionModelPrivate(this)) { } GPSLinkItemSelectionModel::~GPSLinkItemSelectionModel() { delete d_ptr; } QItemSelectionModel* GPSLinkItemSelectionModel::linkedItemSelectionModel() const { Q_D(const GPSLinkItemSelectionModel); return d->m_linkedItemSelectionModel; } void GPSLinkItemSelectionModel::setLinkedItemSelectionModel(QItemSelectionModel* const selectionModel) { Q_D(GPSLinkItemSelectionModel); if (d->m_linkedItemSelectionModel != selectionModel) { if (d->m_linkedItemSelectionModel) { disconnect(d->m_linkedItemSelectionModel); } d->m_linkedItemSelectionModel = selectionModel; if (d->m_linkedItemSelectionModel) { connect(d->m_linkedItemSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(sourceSelectionChanged(QItemSelection,QItemSelection))); connect(d->m_linkedItemSelectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(sourceCurrentChanged(QModelIndex))); connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::modelChanged, this, [this] { d_ptr->reinitializeIndexMapper(); }); } d->reinitializeIndexMapper(); emit linkedItemSelectionModelChanged(); } } void GPSLinkItemSelectionModel::select(const QModelIndex& index, QItemSelectionModel::SelectionFlags command) { Q_D(GPSLinkItemSelectionModel); // When an item is removed, the current index is set to the top index in the model. // That causes a selectionChanged signal with a selection which we do not want. + if (d->m_ignoreCurrentChanged) { return; } // Do *not* replace next line with: QItemSelectionModel::select(index, command) // // Doing so would end up calling GPSLinkItemSelectionModel::select(QItemSelection, QItemSelectionModel::SelectionFlags) // // This is because the code for QItemSelectionModel::select(QModelIndex, QItemSelectionModel::SelectionFlags) looks like this: // { // QItemSelection selection(index, index); // select(selection, command); // } // So it calls GPSLinkItemSelectionModel overload of // select(QItemSelection, QItemSelectionModel::SelectionFlags) // // When this happens and the selection flags include Toggle, it causes the // selection to be toggled twice. QItemSelectionModel::select(QItemSelection(index, index), command); if (index.isValid()) { d->m_linkedItemSelectionModel->select(d->m_indexMapper->mapSelectionLeftToRight(QItemSelection(index, index)), command); } else { d->m_linkedItemSelectionModel->clearSelection(); } } void GPSLinkItemSelectionModel::select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command) { Q_D(GPSLinkItemSelectionModel); d->m_ignoreCurrentChanged = true; QItemSelection _selection = selection; QItemSelectionModel::select(_selection, command); Q_ASSERT(d->assertSelectionValid(_selection)); QItemSelection mappedSelection = d->m_indexMapper->mapSelectionLeftToRight(_selection); Q_ASSERT(d->assertSelectionValid(mappedSelection)); d->m_linkedItemSelectionModel->select(mappedSelection, command); d->m_ignoreCurrentChanged = false; } void GPSLinkItemSelectionModelPrivate::slotCurrentChanged(const QModelIndex& current) { const QModelIndex mappedCurrent = m_indexMapper->mapLeftToRight(current); if (!mappedCurrent.isValid()) { return; } m_linkedItemSelectionModel->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate); } void GPSLinkItemSelectionModelPrivate::sourceSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { Q_Q(GPSLinkItemSelectionModel); QItemSelection _selected = selected; QItemSelection _deselected = deselected; Q_ASSERT(assertSelectionValid(_selected)); Q_ASSERT(assertSelectionValid(_deselected)); const QItemSelection mappedDeselection = m_indexMapper->mapSelectionRightToLeft(_deselected); const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(_selected); q->QItemSelectionModel::select(mappedDeselection, QItemSelectionModel::Deselect); q->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::Select); } void GPSLinkItemSelectionModelPrivate::sourceCurrentChanged(const QModelIndex& current) { Q_Q(GPSLinkItemSelectionModel); const QModelIndex mappedCurrent = m_indexMapper->mapRightToLeft(current); if (!mappedCurrent.isValid()) { return; } q->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate); } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN GPSModelIndexProxyMapperPrivate { public: explicit GPSModelIndexProxyMapperPrivate(const QAbstractItemModel* const leftModel, const QAbstractItemModel* const rightModel, GPSModelIndexProxyMapper* const qq) : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel), mConnected(false) { createProxyChain(); } void createProxyChain(); void checkConnected(); void setConnected(bool connected); // cppcheck-suppress unusedPrivateFunction bool assertSelectionValid(const QItemSelection& selection) const { foreach (const QItemSelectionRange& range, selection) { if (!range.isValid()) { qCDebug(DIGIKAM_GENERAL_LOG) << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp; } Q_ASSERT(range.isValid()); } return true; } public: Q_DECLARE_PUBLIC(GPSModelIndexProxyMapper) GPSModelIndexProxyMapper* const q_ptr; QList > m_proxyChainUp; QList > m_proxyChainDown; QPointer m_leftModel; QPointer m_rightModel; bool mConnected; }; /** - The idea here is that this selection model and proxySelectionModel might be in different parts of the - proxy chain. We need to build up to two chains of proxy models to create mappings between them. - - We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is - already in the first chain. -*/ + * The idea here is that this selection model and proxySelectionModel might be in different parts of the + * proxy chain. We need to build up to two chains of proxy models to create mappings between them. + * + * We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is + * already in the first chain. + */ void GPSModelIndexProxyMapperPrivate::createProxyChain() { foreach (auto p, m_proxyChainUp) { p->disconnect(q_ptr); } foreach (auto p, m_proxyChainDown) { p->disconnect(q_ptr); } m_proxyChainUp.clear(); m_proxyChainDown.clear(); QPointer targetModel = m_rightModel; QList > proxyChainDown; QPointer selectionTargetProxyModel = qobject_cast(targetModel); while (selectionTargetProxyModel) { proxyChainDown.prepend(selectionTargetProxyModel); QObject::connect(selectionTargetProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] { createProxyChain(); } ); selectionTargetProxyModel = qobject_cast(selectionTargetProxyModel->sourceModel()); if (selectionTargetProxyModel == m_leftModel) { m_proxyChainDown = proxyChainDown; checkConnected(); + return; } } QPointer sourceModel = m_leftModel; QPointer sourceProxyModel = qobject_cast(sourceModel); while (sourceProxyModel) { m_proxyChainUp.append(sourceProxyModel); QObject::connect(sourceProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] { createProxyChain(); } ); sourceProxyModel = qobject_cast(sourceProxyModel->sourceModel()); const int targetIndex = proxyChainDown.indexOf(sourceProxyModel); if (targetIndex != -1) { m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size()); checkConnected(); + return; } } + m_proxyChainDown = proxyChainDown; checkConnected(); } void GPSModelIndexProxyMapperPrivate::checkConnected() { auto konamiRight = m_proxyChainUp.isEmpty() ? m_leftModel : m_proxyChainUp.last()->sourceModel(); auto konamiLeft = m_proxyChainDown.isEmpty() ? m_rightModel : m_proxyChainDown.first()->sourceModel(); setConnected(konamiLeft && (konamiLeft == konamiRight)); } void GPSModelIndexProxyMapperPrivate::setConnected(bool connected) { if (mConnected != connected) { Q_Q(GPSModelIndexProxyMapper); mConnected = connected; emit q->isConnectedChanged(); } } GPSModelIndexProxyMapper::GPSModelIndexProxyMapper(const QAbstractItemModel* const leftModel, const QAbstractItemModel* const rightModel, QObject* const parent) : QObject(parent), d_ptr(new GPSModelIndexProxyMapperPrivate(leftModel, rightModel, this)) { } GPSModelIndexProxyMapper::~GPSModelIndexProxyMapper() { delete d_ptr; } QModelIndex GPSModelIndexProxyMapper::mapLeftToRight(const QModelIndex& index) const { const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index)); if (selection.isEmpty()) { return QModelIndex(); } return selection.indexes().first(); } QModelIndex GPSModelIndexProxyMapper::mapRightToLeft(const QModelIndex& index) const { const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index)); if (selection.isEmpty()) { return QModelIndex(); } return selection.indexes().first(); } QItemSelection GPSModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection& selection) const { Q_D(const GPSModelIndexProxyMapper); if (selection.isEmpty() || !d->mConnected) { return QItemSelection(); } if (selection.first().model() != d->m_leftModel) { qCDebug(DIGIKAM_GENERAL_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel; } Q_ASSERT(selection.first().model() == d->m_leftModel); QItemSelection seekSelection = selection; Q_ASSERT(d->assertSelectionValid(seekSelection)); QListIterator > iUp(d->m_proxyChainUp); while (iUp.hasNext()) { const QPointer proxy = iUp.next(); if (!proxy) { return QItemSelection(); } Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy); seekSelection = proxy->mapSelectionToSource(seekSelection); Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel()); Q_ASSERT(d->assertSelectionValid(seekSelection)); } QListIterator > iDown(d->m_proxyChainDown); while (iDown.hasNext()) { const QPointer proxy = iDown.next(); if (!proxy) { return QItemSelection(); } Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel()); seekSelection = proxy->mapSelectionFromSource(seekSelection); Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy); Q_ASSERT(d->assertSelectionValid(seekSelection)); } Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel) || true); + return seekSelection; } QItemSelection GPSModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection& selection) const { Q_D(const GPSModelIndexProxyMapper); if (selection.isEmpty() || !d->mConnected) { return QItemSelection(); } if (selection.first().model() != d->m_rightModel) { qCDebug(DIGIKAM_GENERAL_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel; } Q_ASSERT(selection.first().model() == d->m_rightModel); QItemSelection seekSelection = selection; Q_ASSERT(d->assertSelectionValid(seekSelection)); QListIterator > iDown(d->m_proxyChainDown); iDown.toBack(); while (iDown.hasPrevious()) { const QPointer proxy = iDown.previous(); if (!proxy) { return QItemSelection(); } seekSelection = proxy->mapSelectionToSource(seekSelection); Q_ASSERT(d->assertSelectionValid(seekSelection)); } QListIterator > iUp(d->m_proxyChainUp); iUp.toBack(); while (iUp.hasPrevious()) { const QPointer proxy = iUp.previous(); if (!proxy) { return QItemSelection(); } seekSelection = proxy->mapSelectionFromSource(seekSelection); Q_ASSERT(d->assertSelectionValid(seekSelection)); } Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel) || true); + return seekSelection; } bool GPSModelIndexProxyMapper::isConnected() const { Q_D(const GPSModelIndexProxyMapper); + return d->mConnected; } } // namespace Digikam #include "moc_gpsitemsortproxymodel.cpp"