diff --git a/core/utilities/geolocation/mapsearches/gpsmarkertiler.cpp b/core/utilities/geolocation/mapsearches/gpsmarkertiler.cpp index 1436eb6ec3..5a0b0928f1 100644 --- a/core/utilities/geolocation/mapsearches/gpsmarkertiler.cpp +++ b/core/utilities/geolocation/mapsearches/gpsmarkertiler.cpp @@ -1,1044 +1,1090 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-07-20 * Description : GPS search marker tiler * * Copyright (C) 2010 by Marcel Wiesweg * Copyright (C) 2010 by Gabriel Voicu * Copyright (C) 2010-2011 by Michael G. Hansen * Copyright (C) 2015 by Mohamed_Anwer * * 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 "gpsmarkertiler.h" // Qt includes #include #include #include // Local includes #include "groupstatecomputer.h" // Local includes #include "gpsiteminfosorter.h" #include "dnotificationwrapper.h" #include "digikamapp.h" #include "digikam_debug.h" #include "dbjobsmanager.h" /// @todo Actually use this definition! typedef QPair MapPair; Q_DECLARE_METATYPE(MapPair) namespace Digikam { /** * @class GPSMarkerTiler * * @brief Marker model for storing data needed to display markers on the map. The data is retrieved from Digikam's database. */ class Q_DECL_HIDDEN GPSMarkerTiler::MyTile : public Tile { public: MyTile() : Tile() { } /** * Note: MyTile is only deleted by GPSMarkerTiler::tileDelete. * All subclasses of AbstractMarkerTiler have to reimplement tileDelete * to delete their Tile subclasses. * This was done in order not to have any virtual functions * in Tile and its subclasses in order to save memory, since there * can be a lot of tiles in a MarkerTiler. */ ~MyTile() { } QList imagesId; }; class Q_DECL_HIDDEN GPSMarkerTiler::Private { public: class Q_DECL_HIDDEN InternalJobs { public: InternalJobs() : level(0), jobThread(nullptr), dataFromDatabase() { } - int level; - GPSDBJobsThread* jobThread; + int level; + GPSDBJobsThread* jobThread; QList dataFromDatabase; }; explicit Private() : jobs(), thumbnailLoadThread(nullptr), thumbnailMap(), rectList(), rectLevel(), activeState(true), imagesHash(), imageFilterModel(), imageAlbumModel(), selectionModel(), currentRegionSelection(), mapGlobalGroupState() { } - QList jobs; - ThumbnailLoadThread* thumbnailLoadThread; - QHash thumbnailMap; - QList rectList; - QList rectLevel; - bool activeState; - QHash imagesHash; - ItemFilterModel* imageFilterModel; - ItemAlbumModel* imageAlbumModel; - QItemSelectionModel* selectionModel; + QList jobs; + ThumbnailLoadThread* thumbnailLoadThread; + QHash thumbnailMap; + QList rectList; + QList rectLevel; + bool activeState; + QHash imagesHash; + ItemFilterModel* imageFilterModel; + ItemAlbumModel* imageAlbumModel; + QItemSelectionModel* selectionModel; GeoCoordinates::Pair currentRegionSelection; - GeoGroupState mapGlobalGroupState; + GeoGroupState mapGlobalGroupState; }; /** * @brief Constructor * @param parent Parent object */ -GPSMarkerTiler::GPSMarkerTiler(QObject* const parent, ItemFilterModel* const imageFilterModel, QItemSelectionModel* const selectionModel) +GPSMarkerTiler::GPSMarkerTiler(QObject* const parent, ItemFilterModel* const imageFilterModel, + QItemSelectionModel* const selectionModel) : AbstractMarkerTiler(parent), d(new Private()) { resetRootTile(); d->thumbnailLoadThread = new ThumbnailLoadThread(this); d->imageFilterModel = imageFilterModel; d->imageAlbumModel = qobject_cast(imageFilterModel->sourceModel()); d->selectionModel = selectionModel; connect(d->thumbnailLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), this, SLOT(slotImageChange(ImageChangeset)), Qt::QueuedConnection); connect(d->imageAlbumModel, SIGNAL(imageInfosAdded(QList)), this, SLOT(slotNewModelData(QList))); connect(d->selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); } /** * @brief Destructor */ GPSMarkerTiler::~GPSMarkerTiler() { // WARNING: we have to call clear! By the time AbstractMarkerTiler calls clear, // this object does not exist any more, and thus the tiles are not correctly destroyed! + clear(); delete d; } void GPSMarkerTiler::regenerateTiles() { } /** * @brief Requests all images inside a given rectangle from the database. * * This function calls the database for the images found inside a rectangle * defined by upperLeft and lowerRight points. The images are returned from * the database in batches. * * @param upperLeft The North-West point. * @param lowerRight The South-East point. * @param level The requested tiling level. */ void GPSMarkerTiler::prepareTiles(const GeoCoordinates& upperLeft, const GeoCoordinates& lowerRight, int level) { qreal lat1 = upperLeft.lat(); qreal lng1 = upperLeft.lon(); qreal lat2 = lowerRight.lat(); qreal lng2 = lowerRight.lon(); const QRectF requestedRect(lat1, lng1, lat2 - lat1, lng2 - lng1); for (int i = 0 ; i < d->rectList.count() ; ++i) { if (level != d->rectLevel.at(i)) { continue; } qreal rectLat1, rectLng1, rectLat2, rectLng2; const QRectF currentRect = d->rectList.at(i); currentRect.getCoords(&rectLat1, &rectLng1, &rectLat2, &rectLng2); - //do nothing if this rectangle was already requested + // do nothing if this rectangle was already requested + if (currentRect.contains(requestedRect)) { return; } - if (currentRect.contains(lat1, lng1)) + if (currentRect.contains(lat1, lng1)) { if (currentRect.contains(lat2, lng1)) { lng1 = rectLng2; break; } } else if (currentRect.contains(lat2, lng1)) { if (currentRect.contains(lat2, lng2)) { lat2 = rectLat1; break; } } else if (currentRect.contains(lat2, lng2)) { if (currentRect.contains(lat1, lng2)) { lng2 = rectLng1; break; } } else if (currentRect.contains(lat1, lng2)) { if (currentRect.contains(lat1, lng1)) { lat1 = rectLat2; break; } } } const QRectF newRect(lat1, lng1, lat2 - lat1, lng2 - lng1); d->rectList.append(newRect); d->rectLevel.append(level); qCDebug(DIGIKAM_GENERAL_LOG) << "Listing" << lat1 << lat2 << lng1 << lng2; GPSDBJobInfo jobInfo; jobInfo.setLat1(lat1); jobInfo.setLat2(lat2); jobInfo.setLng1(lng1); jobInfo.setLng2(lng2); GPSDBJobsThread *const currentJob = DBJobsManager::instance()->startGPSJobThread(jobInfo); Private::InternalJobs currentJobInfo; currentJobInfo.jobThread = currentJob; currentJobInfo.level = level; d->jobs.append(currentJobInfo); connect(currentJob, SIGNAL(finished()), this, SLOT(slotMapImagesJobResult())); connect(currentJob, SIGNAL(data(QList)), this, SLOT(slotMapImagesJobData(QList))); } /** * @brief Returns a pointer to a tile. * @param tileIndex The index of a tile. * @param stopIfEmpty Determines whether child tiles are also created for empty tiles. */ AbstractMarkerTiler::Tile* GPSMarkerTiler::getTile(const TileIndex& tileIndex, const bool stopIfEmpty) { Q_ASSERT(tileIndex.level() <= TileIndex::MaxLevel); MyTile* tile = static_cast(rootTile()); for (int level = 0 ; level < tileIndex.indexCount() ; ++level) { const int currentIndex = tileIndex.linearIndex(level); MyTile* childTile = nullptr; if (tile->childrenEmpty()) { if (stopIfEmpty) { return nullptr; } for (int i = 0 ; i < tile->imagesId.count() ; ++i) { - const int currentImageId = tile->imagesId.at(i); - const GPSItemInfo currentItemInfo = d->imagesHash[currentImageId]; - const TileIndex markerTileIndex = TileIndex::fromCoordinates(currentItemInfo.coordinates, level); - const int newTileIndex = markerTileIndex.lastIndex(); - - MyTile* const newTile = static_cast(tile->getChild(newTileIndex)); + const int currentImageId = tile->imagesId.at(i); + const GPSItemInfo currentItemInfo = d->imagesHash[currentImageId]; + const TileIndex markerTileIndex = TileIndex::fromCoordinates(currentItemInfo.coordinates, level); + const int newTileIndex = markerTileIndex.lastIndex(); + MyTile* const newTile = static_cast(tile->getChild(newTileIndex)); if (newTile == nullptr) { MyTile* const newTile = static_cast(tileNew()); newTile->imagesId.append(currentImageId); tile->addChild(newTileIndex, newTile); } else { if (!newTile->imagesId.contains(currentImageId)) { newTile->imagesId.append(currentImageId); } } } } childTile = static_cast(tile->getChild(currentIndex)); if (childTile == nullptr) { if (stopIfEmpty) { // there will be no markers in this tile, therefore stop + return nullptr; } childTile = static_cast(tileNew()); tile->addChild(currentIndex, childTile); } tile = childTile; } return tile; } int GPSMarkerTiler::getTileMarkerCount(const TileIndex& tileIndex) { MyTile* const tile = static_cast(getTile(tileIndex)); if (tile) { return tile->imagesId.count(); } return 0; } int GPSMarkerTiler::getTileSelectedCount(const TileIndex& tileIndex) { Q_UNUSED(tileIndex) return 0; } /** - @brief This function finds the best representative marker from a tile of markers. + * @brief This function finds the best representative marker from a tile of markers. * @param tileIndex Index of the tile from which the best marker should be found. * @param sortKey Sets the criteria for selecting the representative thumbnail, a combination of the SortOptions bits. * @return Returns the internally used index of the marker. */ QVariant GPSMarkerTiler::getTileRepresentativeMarker(const TileIndex& tileIndex, const int sortKey) { MyTile* const tile = static_cast(getTile(tileIndex, true)); if (!tile) { return QVariant(); } if (tile->imagesId.isEmpty()) { return QVariant(); } - GPSItemInfo bestMarkerInfo = d->imagesHash.value(tile->imagesId.first()); + GPSItemInfo bestMarkerInfo = d->imagesHash.value(tile->imagesId.first()); GeoGroupState bestMarkerGroupState = getImageState(bestMarkerInfo.id); for (int i = 1 ; i < tile->imagesId.count() ; ++i) { - const GPSItemInfo currentMarkerInfo = d->imagesHash.value(tile->imagesId.at(i)); + const GPSItemInfo currentMarkerInfo = d->imagesHash.value(tile->imagesId.at(i)); const GeoGroupState currentMarkerGroupState = getImageState(currentMarkerInfo.id); - if (GPSItemInfoSorter::fitsBetter(bestMarkerInfo, bestMarkerGroupState, currentMarkerInfo, currentMarkerGroupState, getGlobalGroupState(), GPSItemInfoSorter::SortOptions(sortKey))) + if (GPSItemInfoSorter::fitsBetter(bestMarkerInfo, + bestMarkerGroupState, + currentMarkerInfo, + currentMarkerGroupState, + getGlobalGroupState(), + GPSItemInfoSorter::SortOptions(sortKey))) { bestMarkerInfo = currentMarkerInfo; bestMarkerGroupState = currentMarkerGroupState; } } const QPair returnedMarker(tileIndex, bestMarkerInfo.id); return QVariant::fromValue(returnedMarker); } /** - @brief This function finds the best representative marker from a group of markers. This is needed to display a thumbnail for a marker group. + * @brief This function finds the best representative marker from a group of markers. This is needed to display a thumbnail for a marker group. * @param indices A list containing markers, obtained by getTileRepresentativeMarker. * @param sortKey Sets the criteria for selecting the representative thumbnail, a combination of the SortOptions bits. * @return Returns the internally used index of the marker. */ QVariant GPSMarkerTiler::bestRepresentativeIndexFromList(const QList& indices, const int sortKey) { if (indices.isEmpty()) { return QVariant(); } const QPair firstIndex = indices.first().value >(); - GPSItemInfo bestMarkerInfo = d->imagesHash.value(firstIndex.second); - GeoGroupState bestMarkerGroupState = getImageState(firstIndex.second); + GPSItemInfo bestMarkerInfo = d->imagesHash.value(firstIndex.second); + GeoGroupState bestMarkerGroupState = getImageState(firstIndex.second); TileIndex bestMarkerTileIndex = firstIndex.first; for (int i = 1 ; i < indices.count() ; ++i) { const QPair currentIndex = indices.at(i).value >(); - GPSItemInfo currentMarkerInfo = d->imagesHash.value(currentIndex.second); - GeoGroupState currentMarkerGroupState = getImageState(currentIndex.second); + GPSItemInfo currentMarkerInfo = d->imagesHash.value(currentIndex.second); + GeoGroupState currentMarkerGroupState = getImageState(currentIndex.second); - if (GPSItemInfoSorter::fitsBetter(bestMarkerInfo, bestMarkerGroupState, currentMarkerInfo, currentMarkerGroupState, getGlobalGroupState(), GPSItemInfoSorter::SortOptions(sortKey))) + if (GPSItemInfoSorter::fitsBetter(bestMarkerInfo, + bestMarkerGroupState, + currentMarkerInfo, + currentMarkerGroupState, + getGlobalGroupState(), + GPSItemInfoSorter::SortOptions(sortKey))) { bestMarkerInfo = currentMarkerInfo; bestMarkerGroupState = currentMarkerGroupState; bestMarkerTileIndex = currentIndex.first; } } const QPair returnedMarker(bestMarkerTileIndex, bestMarkerInfo.id); return QVariant::fromValue(returnedMarker); } /** * @brief This function retrieves the thumbnail for an index. * @param index The marker's index. * @param size The size of the thumbnail. - * @return If the thumbnail has been loaded in the ThumbnailLoadThread instance, it is returned. If not, a QPixmap is returned and ThumbnailLoadThread's signal named signalThumbnailLoaded is emitted when the thumbnail becomes available. + * @return If the thumbnail has been loaded in the ThumbnailLoadThread instance, it is returned. + * If not, a QPixmap is returned and ThumbnailLoadThread's signal named signalThumbnailLoaded is emitted when the thumbnail becomes available. */ QPixmap GPSMarkerTiler::pixmapFromRepresentativeIndex(const QVariant& index, const QSize& size) { QPair indexForPixmap = index.value >(); QPixmap thumbnail; ItemInfo info(indexForPixmap.second); d->thumbnailMap.insert(info.id(), index); if (d->thumbnailLoadThread->find(info.thumbnailIdentifier(), thumbnail, qMax(size.width() + 2, size.height() + 2))) { // digikam returns thumbnails with a border around them, // but geolocation interface expects them without a border + return thumbnail.copy(1, 1, thumbnail.size().width() - 2, thumbnail.size().height() - 2); } else { return QPixmap(); } } /** * @brief This function compares two marker indices. */ bool GPSMarkerTiler::indicesEqual(const QVariant& a, const QVariant& b) const { QPair firstIndex = a.value >(); QPair secondIndex = b.value >(); - QList aIndicesList = firstIndex.first.toIntList(); - QList bIndicesList = secondIndex.first.toIntList(); + QList aIndicesList = firstIndex.first.toIntList(); + QList bIndicesList = secondIndex.first.toIntList(); - if (firstIndex.second == secondIndex.second && aIndicesList == bIndicesList) + if ((firstIndex.second == secondIndex.second) && (aIndicesList == bIndicesList)) { return true; } return false; } GeoGroupState GPSMarkerTiler::getTileGroupState(const TileIndex& tileIndex) { const bool haveGlobalSelection = (d->mapGlobalGroupState & (FilteredPositiveMask | RegionSelectedMask)); if (!haveGlobalSelection) { return SelectedNone; } /// @todo Store this state in the tiles! + MyTile* const tile = static_cast(getTile(tileIndex, true)); GroupStateComputer tileStateComputer; for (int i = 0 ; i < tile->imagesId.count() ; ++i) { const GeoGroupState imageState = getImageState(tile->imagesId.at(i)); tileStateComputer.addState(imageState); } return tileStateComputer.getState(); } /** * @brief The marker data is returned from the database in batches. This function takes and unites the batches. */ void GPSMarkerTiler::slotMapImagesJobData(const QList& records) { if (records.isEmpty()) { return; } Private::InternalJobs* internalJob = nullptr; for (int i = 0 ; i < d->jobs.count() ; ++i) { if (sender() == d->jobs.at(i).jobThread) { /// @todo Is this really safe? + internalJob = &d->jobs[i]; break; } } if (!internalJob) { return; } foreach (const ItemListerRecord &record, records) { if (record.extraValues.count() < 2) { // skip info without coordinates + continue; } GPSItemInfo entry; entry.id = record.imageID; entry.rating = record.rating; entry.dateTime = record.creationDate; entry.coordinates.setLatLon(record.extraValues.first().toDouble(), record.extraValues.last().toDouble()); internalJob->dataFromDatabase << entry; } } /** * @brief Now, all the marker data has been retrieved from the database. Here, the markers are sorted into tiles. */ void GPSMarkerTiler::slotMapImagesJobResult() { int foundIndex = -1; for (int i = 0 ; i < d->jobs.count() ; ++i) { if (sender() == d->jobs.at(i).jobThread) { foundIndex = i; break; } } if (foundIndex < 0) { // this should not happen, but ok... + return; } if (d->jobs.at(foundIndex).jobThread->hasErrors()) { const QString &err = d->jobs.at(foundIndex).jobThread->errorsList().first(); qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list images in selected area: " << err; // Pop-up a message about the error. + DNotificationWrapper(QString(), err, DigikamApp::instance(), DigikamApp::instance()->windowTitle()); } // get the results from the job: + const QList returnedItemInfo = d->jobs.at(foundIndex).dataFromDatabase; - /// @todo Currently, we ignore the wanted level and just add the images - // const int wantedLevel = d->jobs.at(foundIndex).level; + /// @todo Currently, we ignore the wanted level and just add the images +/* + const int wantedLevel = d->jobs.at(foundIndex).level; +*/ // remove the finished job + d->jobs[foundIndex].jobThread->cancel(); d->jobs[foundIndex].jobThread = nullptr; d->jobs.removeAt(foundIndex); if (returnedItemInfo.isEmpty()) { return; } for (int i = 0 ; i < returnedItemInfo.count() ; ++i) { const GPSItemInfo currentItemInfo = returnedItemInfo.at(i); if (!currentItemInfo.coordinates.hasCoordinates()) { continue; } d->imagesHash.insert(currentItemInfo.id, currentItemInfo); const TileIndex markerTileIndex = TileIndex::fromCoordinates(currentItemInfo.coordinates, TileIndex::MaxLevel); addMarkerToTileAndChildren(currentItemInfo.id, markerTileIndex, static_cast(rootTile()), 0); } emit signalTilesOrSelectionChanged(); } /** - * @brief Because of a call to pixmapFromRepresentativeIndex, some thumbnails are not yet loaded at the time of requesting. When each thumbnail loads, this slot is called and emits a signal that announces the map that the thumbnail is available. + * @brief Because of a call to pixmapFromRepresentativeIndex, some thumbnails are not yet loaded at the time of requesting. + * When each thumbnail loads, this slot is called and emits a signal that announces the map that the thumbnail is available. */ void GPSMarkerTiler::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumbnail) { QVariant index = d->thumbnailMap.value(loadingDescription.thumbnailIdentifier().id); - // QPair indexForPixmap = - // index.value >(); +/* + QPair indexForPixmap = + index.value >(); +*/ emit signalThumbnailAvailableForIndex(index, thumbnail.copy(1, 1, thumbnail.size().width() - 2, thumbnail.size().height() - 2)); } /** * @brief Sets the map active/inactive * @param state New state of the map, true means active. */ void GPSMarkerTiler::setActive(const bool state) { d->activeState = state; } AbstractMarkerTiler::Tile* GPSMarkerTiler::tileNew() { return new MyTile(); } void GPSMarkerTiler::tileDelete(AbstractMarkerTiler::Tile* const tile) { delete static_cast(tile); } /** * @brief Receives notifications from the database when images were changed and updates the tiler */ void GPSMarkerTiler::slotImageChange(const ImageChangeset& changeset) { const DatabaseFields::Set changes = changeset.changes(); - // const DatabaseFields::ItemPositions imagePositionChanges = changes; - +/* + const DatabaseFields::ItemPositions imagePositionChanges = changes; +*/ if (!((changes & DatabaseFields::LatitudeNumber) || (changes & DatabaseFields::LongitudeNumber) || (changes & DatabaseFields::Altitude))) { return; } foreach (const qlonglong& id, changeset.ids()) { const ItemInfo newItemInfo(id); if (!newItemInfo.hasCoordinates()) { // the image has no coordinates any more // remove it from the tiles and the image list - const GPSItemInfo oldInfo = d->imagesHash.value(id); + + const GPSItemInfo oldInfo = d->imagesHash.value(id); const GeoCoordinates oldCoordinates = oldInfo.coordinates; const TileIndex oldTileIndex = TileIndex::fromCoordinates(oldCoordinates, TileIndex::MaxLevel); removeMarkerFromTileAndChildren(id, oldTileIndex, static_cast(rootTile()), 0, nullptr); d->imagesHash.remove(id); continue; } GeoCoordinates newCoordinates(newItemInfo.latitudeNumber(), newItemInfo.longitudeNumber()); if (newItemInfo.hasAltitude()) { newCoordinates.setAlt(newItemInfo.altitudeNumber()); } if (d->imagesHash.contains(id)) { // the image id is known, therefore the image has already been sorted into tiles. // We assume that the coordinates of the image have changed. - const GPSItemInfo oldInfo = d->imagesHash.value(id); + const GPSItemInfo oldInfo = d->imagesHash.value(id); const GeoCoordinates oldCoordinates = oldInfo.coordinates; - const GPSItemInfo currentItemInfo = GPSItemInfo::fromIdCoordinatesRatingDateTime(id, newCoordinates, newItemInfo.rating(), newItemInfo.dateTime()); + const GPSItemInfo currentItemInfo = GPSItemInfo::fromIdCoordinatesRatingDateTime(id, newCoordinates, newItemInfo.rating(), newItemInfo.dateTime()); d->imagesHash.insert(id, currentItemInfo); const TileIndex oldTileIndex = TileIndex::fromCoordinates(oldCoordinates, TileIndex::MaxLevel); const TileIndex newTileIndex = TileIndex::fromCoordinates(newCoordinates, TileIndex::MaxLevel); // find out up to which level the tile indices are equal - int separatorLevel = -1; + + int separatorLevel = -1; for (int i = 0 ; i < TileIndex::MaxLevel ; ++i) { if (oldTileIndex.at(i) != newTileIndex.at(i)) { separatorLevel = i; break; } } if (separatorLevel == -1) { // the tile index has not changed + continue; } MyTile* currentTileOld = static_cast(rootTile()); MyTile* currentTileNew = currentTileOld; int level = 0; for (level = 0 ; level <= oldTileIndex.level() ; ++level) { if (currentTileOld->childrenEmpty()) { break; } const int tileIndex = oldTileIndex.at(level); MyTile* const childTileOld = static_cast(currentTileOld->getChild(tileIndex)); if (childTileOld == nullptr) { break; } if (level < separatorLevel) { currentTileOld = childTileOld; currentTileNew = currentTileOld; } else { removeMarkerFromTileAndChildren(id, oldTileIndex, childTileOld, level, currentTileOld); break; } } addMarkerToTileAndChildren(id, newTileIndex, currentTileNew, level); } else { // the image is new, add it to the existing tiles + const GPSItemInfo currentItemInfo = GPSItemInfo::fromIdCoordinatesRatingDateTime(id, newCoordinates, newItemInfo.rating(), newItemInfo.dateTime()); d->imagesHash.insert(id, currentItemInfo); const TileIndex newMarkerTileIndex = TileIndex::fromCoordinates(currentItemInfo.coordinates, TileIndex::MaxLevel); addMarkerToTileAndChildren(id, newMarkerTileIndex, static_cast(rootTile()), 0); } } emit signalTilesOrSelectionChanged(); } /** * @brief Receives notifications from the album model about new items */ void GPSMarkerTiler::slotNewModelData(const QList& infoList) { // We do not actually store the data from the model, we just want // to know that something was changed. /// @todo Also monitor removed, reset, etc. signals + Q_UNUSED(infoList); emit signalTilesOrSelectionChanged(); } void GPSMarkerTiler::setRegionSelection(const GeoCoordinates::Pair& sel) { d->currentRegionSelection = sel; if (sel.first.hasCoordinates()) { d->mapGlobalGroupState |= RegionSelectedMask; } else { d->mapGlobalGroupState &= ~RegionSelectedMask; } emit signalTilesOrSelectionChanged(); } void GPSMarkerTiler::removeCurrentRegionSelection() { d->currentRegionSelection.first.clear(); d->mapGlobalGroupState &= ~RegionSelectedMask; emit signalTilesOrSelectionChanged(); } void GPSMarkerTiler::onIndicesClicked(const ClickInfo& clickInfo) { /// @todo Also handle the representative index QList clickedImagesId; foreach (const TileIndex& tileIndex, clickInfo.tileIndicesList) { clickedImagesId << getTileMarkerIds(tileIndex); } int repImageId = -1; if (clickInfo.representativeIndex.canConvert >()) { repImageId = clickInfo.representativeIndex.value >().second; } if (clickInfo.currentMouseMode == MouseModeSelectThumbnail && d->selectionModel) { /** * @todo This does not work properly, because not all images in a tile * may be selectable because some of them are outside of the region selection */ const bool doSelect = (clickInfo.groupSelectionState & SelectedMask) != SelectedAll; const QItemSelectionModel::SelectionFlags selectionFlags = - (doSelect ? QItemSelectionModel::Select : QItemSelectionModel::Deselect) - | QItemSelectionModel::Rows; + (doSelect ? QItemSelectionModel::Select : QItemSelectionModel::Deselect) + | QItemSelectionModel::Rows; for (int i = 0 ; i < clickedImagesId.count() ; ++i) { const QModelIndex currentIndex = d->imageFilterModel->indexForImageId(clickedImagesId.at(i)); if (d->selectionModel->isSelected(currentIndex) != doSelect) { d->selectionModel->select(currentIndex, selectionFlags); } } if (repImageId >= 0) { const QModelIndex repImageIndex = d->imageFilterModel->indexForImageId(repImageId); if (repImageIndex.isValid()) { d->selectionModel->setCurrentIndex(repImageIndex, selectionFlags); } } } else if (clickInfo.currentMouseMode == MouseModeFilter) { setPositiveFilterIsActive(true); emit signalModelFilteredImages(clickedImagesId); } } QList GPSMarkerTiler::getTileMarkerIds(const TileIndex& tileIndex) { Q_ASSERT(tileIndex.level() <= TileIndex::MaxLevel); const MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { return QList(); } return myTile->imagesId; } GeoGroupState GPSMarkerTiler::getGlobalGroupState() { return d->mapGlobalGroupState; } GeoGroupState GPSMarkerTiler::getImageState(const qlonglong imageId) { GeoGroupState imageState; // is the image inside the region selection? + if (d->mapGlobalGroupState & RegionSelectedMask) { const QModelIndex imageAlbumModelIndex = d->imageAlbumModel->indexForImageId(imageId); if (imageAlbumModelIndex.isValid()) { imageState |= RegionSelectedAll; } else { // not inside region selection, therefore // no other flags can apply + return RegionSelectedNone; } } // is the image positively filtered? + if (d->mapGlobalGroupState & FilteredPositiveMask) { const QModelIndex imageIndexInFilterModel = d->imageFilterModel->indexForImageId(imageId); if (imageIndexInFilterModel.isValid()) { imageState |= FilteredPositiveAll; // is the image selected? + if (d->selectionModel->hasSelection()) { if (d->selectionModel->isSelected(imageIndexInFilterModel)) { imageState |= SelectedAll; } } } else { // the image is not positively filtered, therefore it can // not be selected + return imageState; } } else { // is the image selected? + if (d->selectionModel->hasSelection()) { const QModelIndex imageIndexInFilterModel = d->imageFilterModel->indexForImageId(imageId); if (d->selectionModel->isSelected(imageIndexInFilterModel)) { imageState |= SelectedAll; } } } return imageState; } void GPSMarkerTiler::setPositiveFilterIsActive(const bool state) { if (state) { d->mapGlobalGroupState |= FilteredPositiveMask; } else { d->mapGlobalGroupState &= ~FilteredPositiveMask; } - /// @todo Somehow, a delay is necessary before emitting this signal - probably the order in which the filtering is propagated to other parts of digikam is wrong or just takes too long + /// @todo Somehow, a delay is necessary before emitting this signal - probably the order in which the filtering + /// is propagated to other parts of digikam is wrong or just takes too long + QTimer::singleShot(100, this, SIGNAL(signalTilesOrSelectionChanged())); - // emit signalTilesOrSelectionChanged(); +/* + emit signalTilesOrSelectionChanged(); +*/ } void GPSMarkerTiler::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { /// @todo Buffer this information, update the tiles, etc. + Q_UNUSED(selected); Q_UNUSED(deselected); emit signalTilesOrSelectionChanged(); } void GPSMarkerTiler::removeMarkerFromTileAndChildren(const qlonglong imageId, const TileIndex& markerTileIndex, MyTile* const startTile, const int startTileLevel, MyTile* const parentTile) { MyTile* currentParentTile = parentTile; MyTile* currentTile = startTile; for (int level = startTileLevel ; level <= markerTileIndex.level() ; ++level) { if (!currentTile->imagesId.contains(imageId)) { break; } currentTile->imagesId.removeOne(imageId); if (currentTile->imagesId.isEmpty()) { if (currentTile == rootTile()) { break; } // this tile can be deleted + tileDeleteChild(currentParentTile, currentTile); break; } currentParentTile = currentTile; currentTile = static_cast(currentParentTile->getChild(markerTileIndex.at(level))); if (!currentTile) { break; } } } void GPSMarkerTiler::addMarkerToTileAndChildren(const qlonglong imageId, const TileIndex& markerTileIndex, MyTile* const startTile, const int startTileLevel) { MyTile* currentTile = startTile; for (int level = startTileLevel ; level <= markerTileIndex.level() ; ++level) { /// @todo This could be possible until all code paths are checked + if (!currentTile->imagesId.contains(imageId)) { currentTile->imagesId.append(imageId); } if (currentTile->childrenEmpty()) { break; } MyTile* nextTile = static_cast(currentTile->getChild(markerTileIndex.at(level))); if (!nextTile) { nextTile = static_cast(tileNew()); currentTile->addChild(markerTileIndex.at(level), nextTile); } currentTile = nextTile; } } } // namespace Digikam diff --git a/core/utilities/geolocation/mapsearches/gpsmarkertiler.h b/core/utilities/geolocation/mapsearches/gpsmarkertiler.h index 3b5d8c5116..27ea6a9b29 100644 --- a/core/utilities/geolocation/mapsearches/gpsmarkertiler.h +++ b/core/utilities/geolocation/mapsearches/gpsmarkertiler.h @@ -1,131 +1,138 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-07-20 * Description : GPS search marker tiler * * Copyright (C) 2010 by Marcel Wiesweg * Copyright (C) 2010 by Gabriel Voicu * Copyright (C) 2010-2011 by Michael G. Hansen * Copyright (C) 2015 by Mohamed_Anwer * * 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_MARKER_TILER_H #define DIGIKAM_GPS_MARKER_TILER_H // Qt includes #include #include #include // Local includes #include "abstractmarkertiler.h" #include "mapwidget.h" // Local includes #include "digikam_export.h" #include "itemposition.h" #include "coredbchangesets.h" #include "itemlister.h" #include "coredbaccess.h" #include "coredb.h" #include "iteminfo.h" #include "thumbnailloadthread.h" #include "thumbsdbaccess.h" #include "thumbsdb.h" #include "coredbwatch.h" #include "coredbfields.h" #include "itemalbummodel.h" #include "itemfiltermodel.h" namespace Digikam { class GPSItemInfo; class GPSMarkerTiler : public AbstractMarkerTiler { Q_OBJECT public: class MyTile; explicit GPSMarkerTiler(QObject* const parent, ItemFilterModel* const imageFilterModel, QItemSelectionModel* const selectionModel); virtual ~GPSMarkerTiler(); virtual Tile* tileNew(); virtual void tileDelete(Tile* const tile); virtual void prepareTiles(const GeoCoordinates& upperLeft, const GeoCoordinates& lowerRight, int level); virtual void regenerateTiles(); virtual AbstractMarkerTiler::Tile* getTile(const TileIndex& tileIndex, const bool stopIfEmpty = false); virtual int getTileMarkerCount(const TileIndex& tileIndex); virtual int getTileSelectedCount(const TileIndex& tileIndex); virtual QVariant getTileRepresentativeMarker(const TileIndex& tileIndex, const int sortKey); virtual QVariant bestRepresentativeIndexFromList(const QList& indices, const int sortKey); virtual QPixmap pixmapFromRepresentativeIndex(const QVariant& index, const QSize& size); virtual bool indicesEqual(const QVariant& a, const QVariant& b) const; virtual GeoGroupState getTileGroupState(const TileIndex& tileIndex); virtual GeoGroupState getGlobalGroupState(); virtual void onIndicesClicked(const ClickInfo& clickInfo); virtual void setActive(const bool state); void setRegionSelection(const GeoCoordinates::Pair& sel); void removeCurrentRegionSelection(); void setPositiveFilterIsActive(const bool state); Q_SIGNALS: void signalModelFilteredImages(const QList& imagesId); public Q_SLOTS: void slotNewModelData(const QList& infoList); private Q_SLOTS: /// @todo Do we monitor all signals of the source models? void slotMapImagesJobResult(); void slotMapImagesJobData(const QList& records); void slotThumbnailLoaded(const LoadingDescription&, const QPixmap&); void slotImageChange(const ImageChangeset& changeset); void slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); private: QList getTileMarkerIds(const TileIndex& tileIndex); GeoGroupState getImageState(const qlonglong imageId); - void removeMarkerFromTileAndChildren(const qlonglong imageId, const TileIndex& markerTileIndex, MyTile* const startTile, const int startTileLevel, MyTile* const parentTile); - void addMarkerToTileAndChildren(const qlonglong imageId, const TileIndex& markerTileIndex, MyTile* const startTile, const int startTileLevel); + void removeMarkerFromTileAndChildren(const qlonglong imageId, + const TileIndex& markerTileIndex, + MyTile* const startTile, + const int startTileLevel, + MyTile* const parentTile); + void addMarkerToTileAndChildren(const qlonglong imageId, + const TileIndex& markerTileIndex, + MyTile* const startTile, + const int startTileLevel); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_GPS_MARKER_TILER_H diff --git a/core/utilities/geolocation/mapsearches/gpssearchview.cpp b/core/utilities/geolocation/mapsearches/gpssearchview.cpp index f58a769c2b..d1415eb4e8 100644 --- a/core/utilities/geolocation/mapsearches/gpssearchview.cpp +++ b/core/utilities/geolocation/mapsearches/gpssearchview.cpp @@ -1,674 +1,685 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-05-30 * Description : GPS search sidebar tab contents. * * Copyright (C) 2008-2020 by Gilles Caulier * Copyright (C) 2009 by Johannes Wienke * Copyright (C) 2010-2011 by Michael G. Hansen * Copyright (C) 2014 by Mohamed_Anwer * * 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 "gpssearchview.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "searchtreeview.h" #include "editablesearchtreeview.h" #include "iteminfojob.h" #include "coredbsearchxml.h" #include "gpsmarkertiler.h" #include "gpsiteminfosorter.h" namespace Digikam { class Q_DECL_HIDDEN GPSSearchView::Private { public: explicit Private() : saveBtn(nullptr), nameEdit(nullptr), imageInfoJob(), searchGPSBar(nullptr), searchTreeView(nullptr), splitter(nullptr), mapSearchWidget(nullptr), gpsMarkerTiler(nullptr), imageAlbumModel(nullptr), imageFilterModel(nullptr), selectionModel(nullptr), searchModel(nullptr), sortOrderOptionsHelper(nullptr) { } - static const QString configSplitterStateEntry; - QToolButton* saveBtn; - QLineEdit* nameEdit; - ItemInfoJob imageInfoJob; - SearchTextBar* searchGPSBar; - EditableSearchTreeView* searchTreeView; - QSplitter* splitter; - MapWidget* mapSearchWidget; - GPSMarkerTiler* gpsMarkerTiler; - ItemAlbumModel* imageAlbumModel; - ItemFilterModel* imageFilterModel; - QItemSelectionModel* selectionModel; - SearchModel* searchModel; - GPSItemInfoSorter* sortOrderOptionsHelper; - QString nonGeonlocatedItemsXml; + static const QString configSplitterStateEntry; + QToolButton* saveBtn; + QLineEdit* nameEdit; + ItemInfoJob imageInfoJob; + SearchTextBar* searchGPSBar; + EditableSearchTreeView* searchTreeView; + QSplitter* splitter; + MapWidget* mapSearchWidget; + GPSMarkerTiler* gpsMarkerTiler; + ItemAlbumModel* imageAlbumModel; + ItemFilterModel* imageFilterModel; + QItemSelectionModel* selectionModel; + SearchModel* searchModel; + GPSItemInfoSorter* sortOrderOptionsHelper; + QString nonGeonlocatedItemsXml; }; const QString GPSSearchView::Private::configSplitterStateEntry(QLatin1String("SplitterState")); /** * @brief Constructor * @param parent Parent object. * @param searchModel The model that stores the searches. * @param imageFilterModel The image model used by displaying the selected images on map. * @param itemSelectionModel The selection model corresponding to the imageFilterModel. */ GPSSearchView::GPSSearchView(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper, ItemFilterModel* const imageFilterModel, QItemSelectionModel* const itemSelectionModel) : QWidget(parent), StateSavingObject(this), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); /// @todo Really? + setAcceptDrops(true); d->imageAlbumModel = qobject_cast(imageFilterModel->sourceModel()); d->selectionModel = itemSelectionModel; d->imageFilterModel = imageFilterModel; d->searchModel = searchModel; // --------------------------------------------------------------- QVBoxLayout* const vlay = new QVBoxLayout(this); QFrame* const mapPanel = new QFrame(this); mapPanel->setMinimumWidth(256); mapPanel->setMinimumHeight(256); QVBoxLayout* const vlay2 = new QVBoxLayout(mapPanel); d->mapSearchWidget = new MapWidget(mapPanel); d->mapSearchWidget->setBackend(QLatin1String("marble")); d->mapSearchWidget->setShowThumbnails(true); d->gpsMarkerTiler = new GPSMarkerTiler(this, d->imageFilterModel, d->selectionModel); d->mapSearchWidget->setGroupedModel(d->gpsMarkerTiler); mapPanel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); mapPanel->setLineWidth(style()->pixelMetric(QStyle::PM_DefaultFrameWidth)); d->sortOrderOptionsHelper = new GPSItemInfoSorter(this); d->sortOrderOptionsHelper->addToMapWidget(d->mapSearchWidget); vlay2->addWidget(d->mapSearchWidget); vlay2->setContentsMargins(QMargins()); vlay2->setSpacing(0); // --------------------------------------------------------------- DHBox* const hbox = new DHBox(this); hbox->setContentsMargins(QMargins()); hbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->nameEdit = new QLineEdit(hbox); d->nameEdit->setClearButtonEnabled(true); d->nameEdit->setWhatsThis(i18n("Enter the name of the current map search to save in the " "\"Map Searches\" view.")); d->saveBtn = new QToolButton(hbox); d->saveBtn->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); d->saveBtn->setEnabled(false); d->saveBtn->setToolTip(i18n("Save current map search to a new virtual album.")); d->saveBtn->setWhatsThis(i18n("If this button is pressed, the current map search " "will be saved to a new search " "virtual album using the name " "set on the left side.")); // --------------------------------------------------------------- d->searchTreeView = new EditableSearchTreeView(this, d->searchModel, searchModificationHelper); d->searchTreeView->filteredModel()->setFilterSearchType(DatabaseSearch::MapSearch); d->searchTreeView->filteredModel()->setListTemporarySearches(true); d->searchTreeView->setAlbumManagerCurrentAlbum(true); d->searchGPSBar = new SearchTextBar(this, QLatin1String("GPSSearchViewSearchGPSBar")); d->searchGPSBar->setModel(d->searchTreeView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchGPSBar->setFilterModel(d->searchTreeView->albumFilterModel()); // --------------------------------------------------------------- d->splitter = new QSplitter(Qt::Vertical, this); QFrame* const frameTop = new QFrame(d->splitter); QVBoxLayout* const vlayTop = new QVBoxLayout(frameTop); vlayTop->addWidget(mapPanel); vlayTop->addWidget(d->mapSearchWidget->getControlWidget()); d->mapSearchWidget->setAvailableMouseModes(MouseModePan | MouseModeRegionSelection | MouseModeZoomIntoGroup | MouseModeRegionSelectionFromIcon | MouseModeFilter | MouseModeSelectThumbnail); d->mapSearchWidget->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeFilter | MouseModeSelectThumbnail); // construct a second row of control actions below the control widget /// @todo Should we still replace the icons of the actions with text as discussed during the sprint? + QWidget* const secondActionRow = new QWidget(); QHBoxLayout* const secondActionRowHBox = new QHBoxLayout(); secondActionRowHBox->setContentsMargins(QMargins()); secondActionRow->setLayout(secondActionRowHBox); QLabel* const secondActionRowLabel = new QLabel(i18n("Search by area:")); secondActionRowHBox->addWidget(secondActionRowLabel); QToolButton* const tbRegionSelection = new QToolButton(secondActionRow); tbRegionSelection->setDefaultAction(d->mapSearchWidget->getControlAction(QLatin1String("mousemode-regionselectionmode"))); secondActionRowHBox->addWidget(tbRegionSelection); QToolButton* const tbRegionFromIcon = new QToolButton(secondActionRow); tbRegionFromIcon->setDefaultAction(d->mapSearchWidget->getControlAction(QLatin1String("mousemode-regionselectionfromiconmode"))); secondActionRowHBox->addWidget(tbRegionFromIcon); QToolButton* const tbClearRegionSelection = new QToolButton(secondActionRow); tbClearRegionSelection->setDefaultAction(d->mapSearchWidget->getControlAction(QLatin1String("mousemode-removecurrentregionselection"))); secondActionRowHBox->addWidget(tbClearRegionSelection); secondActionRowHBox->addStretch(10); vlayTop->addWidget(secondActionRow); // end of the second action row // Show Non Geolocated Items row QWidget* const nonGeolocatedActionRow = new QWidget(); QVBoxLayout* const thirdActionRowVBox = new QVBoxLayout(); thirdActionRowVBox->setContentsMargins(QMargins()); nonGeolocatedActionRow->setLayout(thirdActionRowVBox); QPushButton* const nonGeolocatedBtn = new QPushButton(nonGeolocatedActionRow); nonGeolocatedBtn->setText(i18n("Show Non-Geolocated Items")); nonGeolocatedBtn->setIcon(QIcon::fromTheme(QLatin1String("emblem-unmounted"))); thirdActionRowVBox->addWidget(nonGeolocatedBtn); thirdActionRowVBox->addStretch(10); vlayTop->addWidget(nonGeolocatedActionRow); // end of the third action row vlayTop->addWidget(hbox); vlayTop->setStretchFactor(mapPanel, 10); vlayTop->setContentsMargins(QMargins()); vlayTop->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QFrame* const frameBottom = new QFrame(d->splitter); QVBoxLayout* const vlayBottom = new QVBoxLayout(frameBottom); vlayBottom->addWidget(d->searchTreeView); vlayBottom->addWidget(d->searchGPSBar); vlayBottom->setContentsMargins(QMargins()); vlayBottom->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->splitter->addWidget(frameTop); d->splitter->addWidget(frameBottom); // --------------------------------------------------------------- vlay->addWidget(d->splitter); // --------------------------------------------------------------- connect(d->searchTreeView, SIGNAL(currentAlbumChanged(Album*)), this, SLOT(slotAlbumSelected(Album*))); connect(d->saveBtn, SIGNAL(clicked()), this, SLOT(slotSaveGPSSAlbum())); connect(d->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(slotCheckNameEditGPSConditions())); connect(d->nameEdit, SIGNAL(returnPressed()), d->saveBtn, SLOT(animateClick())); connect(d->mapSearchWidget, SIGNAL(signalRegionSelectionChanged()), this, SLOT(slotRegionSelectionChanged())); connect(d->gpsMarkerTiler, SIGNAL(signalModelFilteredImages(QList)), this, SLOT(slotMapSoloItems(QList))); connect(d->mapSearchWidget, SIGNAL(signalRemoveCurrentFilter()), this, SLOT(slotRemoveCurrentFilter())); connect(nonGeolocatedBtn, SIGNAL(clicked()), d->mapSearchWidget->getControlAction(QLatin1String("mousemode-removecurrentregionselection")), SIGNAL(triggered())); connect(nonGeolocatedBtn, SIGNAL(clicked()), this, SLOT(showNonGeolocatedItems())); // --------------------------------------------------------------- slotCheckNameEditGPSConditions(); } GPSSearchView::~GPSSearchView() { delete d; } void GPSSearchView::setConfigGroup(const KConfigGroup& group) { StateSavingObject::setConfigGroup(group); d->searchTreeView->setConfigGroup(group); } void GPSSearchView::doLoadState() { KConfigGroup group = getConfigGroup(); if (group.hasKey(entryName(d->configSplitterStateEntry))) { const QByteArray splitterState = QByteArray::fromBase64(group.readEntry(entryName(d->configSplitterStateEntry), QByteArray())); if (!splitterState.isEmpty()) { d->splitter->restoreState(splitterState); } } d->sortOrderOptionsHelper->setSortOptions(GPSItemInfoSorter::SortOptions(group.readEntry(entryName(QLatin1String("Sort Order")), int(d->sortOrderOptionsHelper->getSortOptions())))); const KConfigGroup groupMapWidget = KConfigGroup(&group, entryName(QLatin1String("GPSSearch Map Widget"))); d->mapSearchWidget->readSettingsFromGroup(&groupMapWidget); d->searchTreeView->loadState(); AlbumManager::instance()->clearCurrentAlbums(); d->searchTreeView->clearSelection(); } void GPSSearchView::doSaveState() { - KConfigGroup group = getConfigGroup(); + KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->configSplitterStateEntry), d->splitter->saveState().toBase64()); group.writeEntry(entryName(QLatin1String("Sort Order")), int(d->sortOrderOptionsHelper->getSortOptions())); KConfigGroup groupMapWidget = KConfigGroup(&group, entryName(QLatin1String("GPSSearch Map Widget"))); d->mapSearchWidget->saveSettingsToGroup(&groupMapWidget); d->searchTreeView->saveState(); group.sync(); } /** * @brief Sets the widget active or inactive. * * Called when the GPSSearch tab becomes the current/not current tab. * * @param state When true, the widget is enabled. */ void GPSSearchView::setActive(bool state) { if (!state) { // make sure we reset the custom filters set by the map: + emit signalMapSoloItems(QList(), QLatin1String("gpssearch")); d->mapSearchWidget->setActive(false); } else { d->mapSearchWidget->setActive(true); if (d->searchTreeView->currentAlbum()) { AlbumManager::instance()->setCurrentAlbums(QList() << d->searchTreeView->currentAlbum()); } slotClearImages(); } } void GPSSearchView::changeAlbumFromHistory(SAlbum* const album) { d->searchTreeView->setCurrentAlbums(QList() << album); } /** * This slot saves the current album. */ void GPSSearchView::slotSaveGPSSAlbum() { QString name = d->nameEdit->text(); if (!checkName(name)) { return; } createNewGPSSearchAlbum(name); } /** * This slot is called when a new selection is made. It creates a new Search Album. */ void GPSSearchView::slotRegionSelectionChanged() { const GeoCoordinates::Pair newRegionSelection = d->mapSearchWidget->getRegionSelection(); - const bool haveRegionSelection = newRegionSelection.first.hasCoordinates(); + const bool haveRegionSelection = newRegionSelection.first.hasCoordinates(); if (haveRegionSelection) { slotCheckNameEditGPSConditions(); createNewGPSSearchAlbum(SAlbum::getTemporaryTitle(DatabaseSearch::MapSearch)); } else { // reset the search rectangle of the temporary album: + createNewGPSSearchAlbum(SAlbum::getTemporaryTitle(DatabaseSearch::MapSearch)); d->gpsMarkerTiler->removeCurrentRegionSelection(); d->searchTreeView->clearSelection(); slotClearImages(); } // also remove any filters which may have been there + slotRemoveCurrentFilter(); slotRefreshMap(); } /** * @brief This function creates a new Search Album. * @param name The name of the new album. */ void GPSSearchView::createNewGPSSearchAlbum(const QString& name) { - //AlbumManager::instance()->clearCurrentAlbums(); - +/* + AlbumManager::instance()->clearCurrentAlbums(); +*/ // We query the database here const GeoCoordinates::Pair coordinates = d->mapSearchWidget->getRegionSelection(); const bool haveCoordinates = coordinates.first.hasCoordinates(); if (haveCoordinates) { d->gpsMarkerTiler->setRegionSelection(coordinates); } // NOTE: coordinates as lon1, lat1, lon2, lat2 (or West, North, East, South) // as left/top, right/bottom rectangle. + QList coordinatesList = QList() << coordinates.first.lon() << coordinates.first.lat() << coordinates.second.lon() << coordinates.second.lat(); if (!haveCoordinates) { /// @todo We need to create a search album with invalid coordinates + coordinatesList.clear(); coordinatesList << -200 << -200 << -200 << -200; } qCDebug(DIGIKAM_GENERAL_LOG) << "West, North, East, South: " << coordinatesList; SearchXmlWriter writer; writer.writeGroup(); writer.writeField(QLatin1String("position"), SearchXml::Inside); writer.writeAttribute(QLatin1String("type"), QLatin1String("rectangle")); writer.writeValue(coordinatesList); writer.finishField(); writer.finishGroup(); SAlbum* const salbum = AlbumManager::instance()->createSAlbum(name, DatabaseSearch::MapSearch, writer.xml()); AlbumManager::instance()->setCurrentAlbums(QList() << salbum); d->imageInfoJob.allItemsFromAlbum(salbum); d->searchTreeView->setCurrentAlbums(QList() << salbum); d->imageAlbumModel->openAlbum(QList() << salbum); } /** * @brief An album is selected in the saved searches list. * @param a This album will be selected. */ void GPSSearchView::slotAlbumSelected(Album* a) { /// @todo This re-sets the region selection unwantedly... SAlbum* const salbum = dynamic_cast(a); if (!salbum) { return; } SearchXmlReader reader(salbum->query()); reader.readToFirstField(); - QStringRef type = reader.attributes().value(QLatin1String("type")); + QStringRef type = reader.attributes().value(QLatin1String("type")); if (type == QLatin1String("rectangle")) { const QList list = reader.valueToDoubleList(); const GeoCoordinates::Pair coordinates( GeoCoordinates(list.at(1), list.at(0)), GeoCoordinates(list.at(3), list.at(2)) ); /// @todo Currently, invalid coordinates are stored as -200: + if (list.at(1) != -200) { d->mapSearchWidget->setRegionSelection(coordinates); d->gpsMarkerTiler->setRegionSelection(coordinates); } else { d->mapSearchWidget->clearRegionSelection(); d->gpsMarkerTiler->removeCurrentRegionSelection(); } slotCheckNameEditGPSConditions(); } d->imageInfoJob.allItemsFromAlbum(salbum); } /** * @brief Checks whether the newly added search name already exists. * @param name The name of the current search. */ bool GPSSearchView::checkName(QString& name) { bool checked = checkAlbum(name); while (!checked) { QString label = i18n("Search name already exists.\n" "Please enter a new name:"); bool ok; QString newTitle = QInputDialog::getText(this, i18n("Name exists"), label, QLineEdit::Normal, name, &ok); if (!ok) { return false; } name = newTitle; checked = checkAlbum(name); } return true; } /** * @brief Checks whether the newly added album name already exists. * @param name The name of the album. */ bool GPSSearchView::checkAlbum(const QString& name) const { const AlbumList list = AlbumManager::instance()->allSAlbums(); for (AlbumList::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it) { const SAlbum* const album = (SAlbum*)(*it); if (album->title() == name) { return false; } } return true; } /** * @brief Remove the current filter. */ void GPSSearchView::slotRemoveCurrentFilter() { d->gpsMarkerTiler->setPositiveFilterIsActive(false); const QList emptyIdList; emit signalMapSoloItems(emptyIdList, QLatin1String("gpssearch")); slotRefreshMap(); d->mapSearchWidget->slotUpdateActionsEnabled(); } /** * @brief Enable or disable the album saving controls. */ void GPSSearchView::slotCheckNameEditGPSConditions() { if (d->mapSearchWidget->getRegionSelection().first.hasCoordinates()) { d->nameEdit->setEnabled(true); if (!d->nameEdit->text().isEmpty()) { d->saveBtn->setEnabled(true); } } else { d->nameEdit->setEnabled(false); d->saveBtn->setEnabled(false); } } /** * @brief Slot which gets called when the user makes items 'solo' on the map * @param gpsList List of GPSInfos which are 'solo' */ void GPSSearchView::slotMapSoloItems(const QList& idList) { emit signalMapSoloItems(idList, QLatin1String("gpssearch")); d->mapSearchWidget->slotUpdateActionsEnabled(); } void GPSSearchView::showNonGeolocatedItems() { if (d->nonGeonlocatedItemsXml.isEmpty()) { SearchXmlWriter writer; writer.setFieldOperator((SearchXml::standardFieldOperator())); writer.writeGroup(); writer.writeField(QLatin1String("nogps"), SearchXml::Equal); writer.finishField(); writer.finishGroup(); writer.finish(); d->nonGeonlocatedItemsXml = writer.xml(); } QString title = SAlbum::getTemporaryTitle(DatabaseSearch::MapSearch); SAlbum* album = AlbumManager::instance()->findSAlbum(title); int id; if (album) { id = album->id(); CoreDbAccess().db()->updateSearch(id, DatabaseSearch::AdvancedSearch, - SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->nonGeonlocatedItemsXml); + SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), + d->nonGeonlocatedItemsXml); } else { id = CoreDbAccess().db()->addSearch(DatabaseSearch::AdvancedSearch, - SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->nonGeonlocatedItemsXml); + SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), + d->nonGeonlocatedItemsXml); } album = new SAlbum(i18n("Non Geo-located Items"), id); if (album) { AlbumManager::instance()->setCurrentAlbums(QList() << album); } } void GPSSearchView::slotRefreshMap() { d->mapSearchWidget->refreshMap(); } void GPSSearchView::slotClearImages() { if (d->mapSearchWidget->getActiveState()) { d->imageAlbumModel->clearItemInfos(); } } } // namespace Digikam