diff --git a/core/libs/album/manager/albummanager.cpp b/core/libs/album/manager/albummanager.cpp index c05540e1cd..818660707e 100644 --- a/core/libs/album/manager/albummanager.cpp +++ b/core/libs/album/manager/albummanager.cpp @@ -1,299 +1,296 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-06-15 * Description : Albums manager interface. * * Copyright (C) 2004 by Renchi Raju * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2011 by Marcel Wiesweg * 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 "albummanager_p.h" namespace Digikam { Q_GLOBAL_STATIC(AlbumManagerCreator, creator) // A friend-class shortcut to circumvent accessing this from within the destructor AlbumManager* AlbumManager::internalInstance = nullptr; AlbumManager* AlbumManager::instance() { return &creator->object; } // ----------------------------------------------------------------------------------- AlbumManager::AlbumManager() : d(new Private) { qRegisterMetaType>("QMap"); qRegisterMetaType>("QMap"); qRegisterMetaType >>("QMap >"); internalInstance = this; d->albumWatch = new AlbumWatch(this); // these operations are pretty fast, no need for long queuing d->scanPAlbumsTimer = new QTimer(this); d->scanPAlbumsTimer->setInterval(150); d->scanPAlbumsTimer->setSingleShot(true); connect(d->scanPAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanPAlbums())); d->scanTAlbumsTimer = new QTimer(this); d->scanTAlbumsTimer->setInterval(150); d->scanTAlbumsTimer->setSingleShot(true); connect(d->scanTAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanTAlbums())); d->scanSAlbumsTimer = new QTimer(this); d->scanSAlbumsTimer->setInterval(150); d->scanSAlbumsTimer->setSingleShot(true); connect(d->scanSAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanSAlbums())); d->updatePAlbumsTimer = new QTimer(this); d->updatePAlbumsTimer->setInterval(150); d->updatePAlbumsTimer->setSingleShot(true); connect(d->updatePAlbumsTimer, SIGNAL(timeout()), this, SLOT(updateChangedPAlbums())); // this operation is much more expensive than the other scan methods d->scanDAlbumsTimer = new QTimer(this); d->scanDAlbumsTimer->setInterval(30 * 1000); d->scanDAlbumsTimer->setSingleShot(true); connect(d->scanDAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanDAlbumsScheduled())); // moderately expensive d->albumItemCountTimer = new QTimer(this); d->albumItemCountTimer->setInterval(1000); d->albumItemCountTimer->setSingleShot(true); connect(d->albumItemCountTimer, SIGNAL(timeout()), this, SLOT(getAlbumItemsCount())); // more expensive d->tagItemCountTimer = new QTimer(this); d->tagItemCountTimer->setInterval(2500); d->tagItemCountTimer->setSingleShot(true); connect(d->tagItemCountTimer, SIGNAL(timeout()), this, SLOT(getTagItemsCount())); } AlbumManager::~AlbumManager() { delete d->rootPAlbum; delete d->rootTAlbum; delete d->rootDAlbum; delete d->rootSAlbum; internalInstance = nullptr; delete d; } void AlbumManager::cleanUp() { // This is what we prefer to do before Application destruction if (d->dateListJob) { d->dateListJob->cancel(); d->dateListJob = nullptr; } if (d->albumListJob) { d->albumListJob->cancel(); d->albumListJob = nullptr; } if (d->tagListJob) { d->tagListJob->cancel(); d->tagListJob = nullptr; } if (d->personListJob) { d->personListJob->cancel(); d->personListJob = nullptr; } } void AlbumManager::startScan() { if (!d->changed) { return; } d->changed = false; // create root albums d->rootPAlbum = new PAlbum(i18n("Albums")); insertPAlbum(d->rootPAlbum, nullptr); d->rootTAlbum = new TAlbum(i18n("Tags"), 0, true); insertTAlbum(d->rootTAlbum, nullptr); d->rootSAlbum = new SAlbum(i18n("Searches"), 0, true); emit signalAlbumAboutToBeAdded(d->rootSAlbum, nullptr, nullptr); d->allAlbumsIdHash[d->rootSAlbum->globalID()] = d->rootSAlbum; emit signalAlbumAdded(d->rootSAlbum); d->rootDAlbum = new DAlbum(QDate(), true); emit signalAlbumAboutToBeAdded(d->rootDAlbum, nullptr, nullptr); d->allAlbumsIdHash[d->rootDAlbum->globalID()] = d->rootDAlbum; emit signalAlbumAdded(d->rootDAlbum); // Create albums for album roots. Reuse logic implemented in the method foreach (const CollectionLocation& location, CollectionManager::instance()->allLocations()) { handleCollectionStatusChange(location, CollectionLocation::LocationNull); } // listen to location status changes connect(CollectionManager::instance(), SIGNAL(locationStatusChanged(CollectionLocation,int)), this, SLOT(slotCollectionLocationStatusChanged(CollectionLocation,int))); connect(CollectionManager::instance(), SIGNAL(locationPropertiesChanged(CollectionLocation)), this, SLOT(slotCollectionLocationPropertiesChanged(CollectionLocation))); // reload albums refresh(); // listen to album database changes connect(CoreDbAccess::databaseWatch(), SIGNAL(albumChange(AlbumChangeset)), this, SLOT(slotAlbumChange(AlbumChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(tagChange(TagChangeset)), this, SLOT(slotTagChange(TagChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(searchChange(SearchChangeset)), this, SLOT(slotSearchChange(SearchChangeset))); // listen to collection image changes connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), this, SLOT(slotCollectionImageChange(CollectionImageChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), this, SLOT(slotImageTagChange(ImageTagChangeset))); // listen to image attribute changes connect(ItemAttributesWatch::instance(), SIGNAL(signalImageDateChanged(qlonglong)), d->scanDAlbumsTimer, SLOT(start())); emit signalAllAlbumsLoaded(); } bool AlbumManager::isShowingOnlyAvailableAlbums() const { return d->showOnlyAvailableAlbums; } void AlbumManager::setShowOnlyAvailableAlbums(bool onlyAvailable) { if (d->showOnlyAvailableAlbums == onlyAvailable) { return; } d->showOnlyAvailableAlbums = onlyAvailable; emit signalShowOnlyAvailableAlbumsChanged(d->showOnlyAvailableAlbums); // We need to update the unavailable locations. // We assume the handleCollectionStatusChange does the right thing (even though old status == current status) foreach (const CollectionLocation& location, CollectionManager::instance()->allLocations()) { if (location.status() == CollectionLocation::LocationUnavailable) { handleCollectionStatusChange(location, CollectionLocation::LocationUnavailable); } } } void AlbumManager::refresh() { scanPAlbums(); scanTAlbums(); scanSAlbums(); scanDAlbums(); } void AlbumManager::prepareItemCounts() { // There is no way to find out if any data we had collected // previously is still valid - recompute scanDAlbums(); getAlbumItemsCount(); getTagItemsCount(); } void AlbumManager::slotImagesDeleted(const QList& imageIds) { qCDebug(DIGIKAM_GENERAL_LOG) << "Got image deletion notification from ItemViewUtilities for " << imageIds.size() << " images."; QSet sAlbumsToUpdate; QSet deletedImages = imageIds.toSet(); QList sAlbums = findSAlbumsBySearchType(DatabaseSearch::DuplicatesSearch); foreach (SAlbum* const sAlbum, sAlbums) { // Read the search query XML and save the image ids SearchXmlReader reader(sAlbum->query()); SearchXml::Element element; QSet images; while ((element = reader.readNext()) != SearchXml::End) { if ((element == SearchXml::Field) && (reader.fieldName().compare(QLatin1String("imageid")) == 0)) { images = reader.valueToLongLongList().toSet(); } } // If the deleted images are part of the SAlbum, // mark the album as ready for deletion and the images as ready for rescan. -#if QT_VERSION >= 0x050600 + if (images.intersects(deletedImages)) -#else - if (images.intersect(deletedImages).isEmpty()) -#endif { sAlbumsToUpdate.insert(sAlbum); } } if (!sAlbumsToUpdate.isEmpty()) { emit signalUpdateDuplicatesAlbums(sAlbumsToUpdate.values(), deletedImages.values()); } } } // namespace Digikam diff --git a/core/libs/dialogs/webbrowserdlg.cpp b/core/libs/dialogs/webbrowserdlg.cpp index e9dd588690..e96c64a97a 100644 --- a/core/libs/dialogs/webbrowserdlg.cpp +++ b/core/libs/dialogs/webbrowserdlg.cpp @@ -1,274 +1,272 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-06-21 * Description : a simple web browser dialog based on Qt WebEngine. * * Copyright (C) 2017-2020 by Gilles Caulier * * 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 "webbrowserdlg.h" #include "digikam_config.h" // Qt includes #include #include #include #include #include #include #include #ifdef HAVE_QWEBENGINE # include # include # include # include #else # include #endif // KDE includes #include #include #include // Local includes #include "statusprogressbar.h" #include "searchtextbar.h" #include "dxmlguiwindow.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN WebBrowserDlg::Private { public: explicit Private() : browser(nullptr), toolbar(nullptr), progressbar(nullptr), searchbar(nullptr) { } public: QUrl home; #ifdef HAVE_QWEBENGINE QWebEngineView* browser; #else QWebView* browser; #endif QToolBar* toolbar; StatusProgressBar* progressbar; SearchTextBar* searchbar; }; WebBrowserDlg::WebBrowserDlg(const QUrl& url, QWidget* const parent, bool hideDeskBrowser) : QDialog(parent), d(new Private) { setModal(false); d->home = url; #ifdef HAVE_QWEBENGINE d->browser = new QWebEngineView(this); d->browser->page()->profile()->cookieStore()->deleteAllCookies(); #else d->browser = new QWebView(this); d->browser->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); d->browser->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); #endif // -------------------------- d->toolbar = new QToolBar(this); d->toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); #ifdef HAVE_QWEBENGINE d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Back)); d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Forward)); d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Reload)); d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Stop)); #else d->toolbar->addAction(d->browser->pageAction(QWebPage::Back)); d->toolbar->addAction(d->browser->pageAction(QWebPage::Forward)); d->toolbar->addAction(d->browser->pageAction(QWebPage::Reload)); d->toolbar->addAction(d->browser->pageAction(QWebPage::Stop)); #endif QAction* const gohome = new QAction(QIcon::fromTheme(QLatin1String("go-home")), i18n("Home"), this); gohome->setToolTip(i18n("Go back to Home page")); d->toolbar->addAction(gohome); QAction* const deskweb = new QAction(QIcon::fromTheme(QLatin1String("internet-web-browser")), i18n("Desktop Browser"), this); deskweb->setToolTip(i18n("Open Home page with default desktop Web browser")); if (!hideDeskBrowser) { d->toolbar->addAction(deskweb); } // -------------------------- d->searchbar = new SearchTextBar(this, QLatin1String("WebBrowserDlgSearchBar")); d->searchbar->setHighlightOnResult(true); d->progressbar = new StatusProgressBar(this); d->progressbar->setProgressTotalSteps(100); d->progressbar->setAlignment(Qt::AlignLeft); d->progressbar->setNotify(false); // ---------------------- QGridLayout* const grid = new QGridLayout(this); grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->addWidget(d->toolbar, 0, 0, 1, 1); grid->addWidget(d->searchbar, 0, 2, 1, 1); grid->addWidget(d->browser, 1, 0, 1, 3); grid->addWidget(d->progressbar, 2, 0, 1, 3); grid->setColumnStretch(1, 10); grid->setRowStretch(1, 10); setLayout(grid); // ---------------------- /* -#if QT_VERSION >= 0x050700 connect(d->browser, SIGNAL(iconChanged(const QIcon&)), this, SLOT(slotIconChanged(const QIcon&))); -#endif */ connect(d->browser, SIGNAL(titleChanged(QString)), this, SLOT(slotTitleChanged(QString))); connect(d->browser, SIGNAL(urlChanged(QUrl)), this, SLOT(slotUrlChanged(QUrl))); connect(d->browser, SIGNAL(loadStarted()), this, SLOT(slotLoadingStarted())); connect(d->browser, SIGNAL(loadFinished(bool)), this, SLOT(slotLoadingFinished(bool))); connect(d->searchbar, SIGNAL(signalSearchTextSettings(SearchTextSettings)), this, SLOT(slotSearchTextChanged(SearchTextSettings))); connect(d->browser, SIGNAL(loadProgress(int)), d->progressbar, SLOT(setProgressValue(int))); connect(gohome, SIGNAL(triggered()), this, SLOT(slotGoHome())); connect(deskweb, SIGNAL(triggered()), this, SLOT(slotDesktopWebBrowser())); // ---------------------- KConfigGroup group = KSharedConfig::openConfig()->group("WebBrowserDlg"); winId(); windowHandle()->resize(800, 600); DXmlGuiWindow::restoreWindowSize(windowHandle(), group); resize(windowHandle()->size()); slotGoHome(); } WebBrowserDlg::~WebBrowserDlg() { delete d; } void WebBrowserDlg::closeEvent(QCloseEvent* e) { KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("WebBrowserDlg")); DXmlGuiWindow::saveWindowSize(windowHandle(), group); emit closeView(false); e->accept(); } void WebBrowserDlg::slotUrlChanged(const QUrl& url) { d->progressbar->setText(url.toString()); emit urlChanged(url); } void WebBrowserDlg::slotTitleChanged(const QString& title) { setWindowTitle(title); } void WebBrowserDlg::slotIconChanged(const QIcon& icon) { setWindowIcon(icon); } void WebBrowserDlg::slotLoadingStarted() { d->progressbar->setProgressBarMode(StatusProgressBar::ProgressBarMode); } void WebBrowserDlg::slotLoadingFinished(bool b) { QString curUrl = d->browser->url().toString(); d->progressbar->setProgressBarMode(StatusProgressBar::TextMode, curUrl); if (!b) { d->progressbar->setText(i18n("Cannot load page %1", curUrl)); } } void WebBrowserDlg::slotSearchTextChanged(const SearchTextSettings& settings) { #ifdef HAVE_QWEBENGINE d->browser->findText(settings.text, (settings.caseSensitive == Qt::CaseSensitive) ? QWebEnginePage::FindCaseSensitively : QWebEnginePage::FindFlags(), [this](bool found) { d->searchbar->slotSearchResult(found); }); #else bool found = d->browser->findText( settings.text, (settings.caseSensitive == Qt::CaseInsensitive) ? QWebPage::FindCaseSensitively : QWebPage::FindFlags()); d->searchbar->slotSearchResult(found); #endif } void WebBrowserDlg::slotGoHome() { d->browser->setUrl(d->home); } void WebBrowserDlg::slotDesktopWebBrowser() { QDesktopServices::openUrl(d->home); } } // namespace Digikam diff --git a/core/tests/geolocation/kmlexport/test_geoparsing.cpp b/core/tests/geolocation/kmlexport/test_geoparsing.cpp index 4173b2d1b7..d6913c399f 100644 --- a/core/tests/geolocation/kmlexport/test_geoparsing.cpp +++ b/core/tests/geolocation/kmlexport/test_geoparsing.cpp @@ -1,130 +1,118 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-01-17 * Description : test parsing gpx data * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2017-2020 by Gilles Caulier * * 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 "test_geoparsing.h" // Qt includes #include #include // Local includes #include "geodataparser_time.h" using namespace DigikamGenericGeolocationEditPlugin; QTEST_GUILESS_MAIN(TestGPXParsing) /** * @brief Test how well QDateTime deals with various string representations * * The behavior of QDateTime::fromString changed in some Qt version, so here * we can test what the current behavior is and quickly detect if Qt changes * again. */ void TestGPXParsing::testQDateTimeParsing() { { // strings ending with a 'Z' are taken to be in UTC, regardless of milliseconds QDateTime time1 = QDateTime::fromString(QLatin1String("2009-03-11T13:39:55.622Z"), Qt::ISODate); QCOMPARE(time1.timeSpec(), Qt::UTC); QDateTime time2 = QDateTime::fromString(QLatin1String("2009-03-11T13:39:55Z"), Qt::ISODate); QCOMPARE(time2.timeSpec(), Qt::UTC); } { // eCoach in N900 records GPX files with this kind of date format: // 2010-01-14T09:26:02.287+02:00 QDateTime time1 = QDateTime::fromString(QLatin1String("2010-01-14T09:26:02.287+02:00"), Qt::ISODate); -#if QT_VERSION>=0x040700 - - // Qt >= 4.7: both date and time are parsed fine /// @todo What about the timezone? QCOMPARE(time1.date(), QDate(2010, 01, 14)); QCOMPARE(time1.time(), QTime(9, 26, 2, 287)); -#else - - // Qt < 4.7: the date is parsed fine, but the time fails: - - QCOMPARE(time1.date(), QDate(2010, 01, 14)); - QCOMPARE(time1.time(), QTime(0, 0, 0)); - -#endif - // when we omit the time zone data, parsing succeeds // time is interpreted as local time QDateTime time2 = QDateTime::fromString(QLatin1String("2010-01-14T09:26:02.287")/*"+02:00"*/, Qt::ISODate); QCOMPARE(time2.date(), QDate(2010, 01, 14)); QCOMPARE(time2.time(), QTime(9, 26, 2, 287)); QCOMPARE(time2.timeSpec(), Qt::LocalTime); } } /** * @brief Test our custom parsing function */ void TestGPXParsing::testCustomParsing() { { // this should work as usual: const QDateTime time1 = GeoDataParserParseTime(QLatin1String("2009-03-11T13:39:55.622Z")); QCOMPARE(time1.timeSpec(), Qt::UTC); QCOMPARE(time1.date(), QDate(2009, 03, 11)); QCOMPARE(time1.time(), QTime(13, 39, 55, 622)); } { // eCoach in N900: 2010-01-14T09:26:02.287+02:00 const QDateTime time1 = GeoDataParserParseTime(QLatin1String("2010-01-14T09:26:02.287+02:00")); QCOMPARE(time1.timeSpec(), Qt::UTC); QCOMPARE(time1.date(), QDate(2010, 01, 14)); QCOMPARE(time1.time(), QTime(7, 26, 02, 287)); } { // test negative time zone offset: 2010-01-14T09:26:02.287+02:00 const QDateTime time1 = GeoDataParserParseTime(QLatin1String("2010-01-14T09:26:02.287-02:00")); QCOMPARE(time1.timeSpec(), Qt::UTC); QCOMPARE(time1.date(), QDate(2010, 01, 14)); QCOMPARE(time1.time(), QTime(11, 26, 02, 287)); } { // test negative time zone offset with minutes: 2010-01-14T09:26:02.287+03:15 const QDateTime time1 = GeoDataParserParseTime(QLatin1String("2010-01-14T09:26:02.287-03:15")); QCOMPARE(time1.timeSpec(), Qt::UTC); QCOMPARE(time1.date(), QDate(2010, 01, 14)); QCOMPARE(time1.time(), QTime(12, 41, 02, 287)); } } diff --git a/core/utilities/geolocation/geoiface/tiles/itemmarkertiler.cpp b/core/utilities/geolocation/geoiface/tiles/itemmarkertiler.cpp index 805353a9fc..e3fa331fe0 100644 --- a/core/utilities/geolocation/geoiface/tiles/itemmarkertiler.cpp +++ b/core/utilities/geolocation/geoiface/tiles/itemmarkertiler.cpp @@ -1,861 +1,848 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-07-17 * Description : A marker tiler operating on item models * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010-2011 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 "itemmarkertiler.h" // local includes #include "geomodelhelper.h" #include "digikam_debug.h" #include "geoifacecommon.h" namespace Digikam { class Q_DECL_HIDDEN ItemMarkerTiler::MyTile : public Tile { public: MyTile() : Tile(), markerIndices(), selectedCount(0) { } /** * Note: MyTile is only deleted by ItemMarkerTiler::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. */ virtual ~MyTile() { } void removeMarkerIndexOrInvalidIndex(const QModelIndex& indexToRemove); public: QList markerIndices; int selectedCount; }; void ItemMarkerTiler::MyTile::removeMarkerIndexOrInvalidIndex(const QModelIndex& indexToRemove) { int i = 0; while (i < markerIndices.count()) { const QPersistentModelIndex& currentIndex = markerIndices.at(i); // NOTE: this function is usually called after the model has sent // an aboutToRemove-signal. It is possible that the persistent // marker index became invalid before the caller received the signal. // we remove any invalid indices as we find them. if (!currentIndex.isValid()) { markerIndices.takeAt(i); continue; } if (currentIndex == indexToRemove) { markerIndices.takeAt(i); return; } ++i; } } // ------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN ItemMarkerTiler::Private { public: explicit Private() : modelHelper(nullptr), selectionModel(nullptr), markerModel(nullptr), activeState(false) { } GeoModelHelper* modelHelper; QItemSelectionModel* selectionModel; QAbstractItemModel* markerModel; bool activeState; }; ItemMarkerTiler::ItemMarkerTiler(GeoModelHelper* const modelHelper, QObject* const parent) : AbstractMarkerTiler(parent), d(new Private()) { resetRootTile(); setMarkerGeoModelHelper(modelHelper); } ItemMarkerTiler::~ItemMarkerTiler() { // 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 ItemMarkerTiler::setMarkerGeoModelHelper(GeoModelHelper* const modelHelper) { d->modelHelper = modelHelper; d->markerModel = modelHelper->model(); d->selectionModel = modelHelper->selectionModel(); if (d->markerModel != nullptr) { // TODO: disconnect the old model if there was one connect(d->markerModel, &QAbstractItemModel::rowsInserted, this, &ItemMarkerTiler::slotSourceModelRowsInserted); connect(d->markerModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ItemMarkerTiler::slotSourceModelRowsAboutToBeRemoved); // TODO: this signal now has to be monitored in the model helper /* connect(d->markerModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotSourceModelDataChanged(QModelIndex,QModelIndex))); */ connect(d->modelHelper, &GeoModelHelper::signalModelChangedDrastically, this, &ItemMarkerTiler::slotSourceModelReset); connect(d->markerModel, &QAbstractItemModel::modelReset, this, &ItemMarkerTiler::slotSourceModelReset); connect(d->markerModel, &QAbstractItemModel::layoutChanged, this, &ItemMarkerTiler::slotSourceModelLayoutChanged); connect(d->modelHelper, &GeoModelHelper::signalThumbnailAvailableForIndex, this, &ItemMarkerTiler::slotThumbnailAvailableForIndex); if (d->selectionModel) { connect(d->selectionModel, &QItemSelectionModel::selectionChanged, this, &ItemMarkerTiler::slotSelectionChanged); } } setDirty(); } QVariant ItemMarkerTiler::getTileRepresentativeMarker(const TileIndex& tileIndex, const int sortKey) { const QList modelIndices = getTileMarkerIndices(tileIndex); if (modelIndices.isEmpty()) { return QVariant(); } return QVariant::fromValue(d->modelHelper->bestRepresentativeIndexFromList(modelIndices, sortKey)); } QPixmap ItemMarkerTiler::pixmapFromRepresentativeIndex(const QVariant& index, const QSize& size) { return d->modelHelper->pixmapFromRepresentativeIndex(index.value(), size); } QVariant ItemMarkerTiler::bestRepresentativeIndexFromList(const QList& indices, const int sortKey) { QList indexList; for (int i = 0 ; i < indices.count() ; ++i) { indexList << indices.at(i).value(); } return QVariant::fromValue(d->modelHelper->bestRepresentativeIndexFromList(indexList, sortKey)); } void ItemMarkerTiler::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { /* qCDebug(DIGIKAM_GEOIFACE_LOG)<isDirty=true; emit signalTilesOrSelectionChanged(); return; */ for (int i = 0 ; i < selected.count() ; ++i) { const QItemSelectionRange selectionRange = selected.at(i); for (int row = selectionRange.top() ; row <= selectionRange.bottom() ; ++row) { // get the coordinates of the item GeoCoordinates coordinates; if (!d->modelHelper->itemCoordinates(d->markerModel->index(row, 0, selectionRange.parent()), &coordinates)) { continue; } for (int l = 0; l <= TileIndex::MaxLevel; ++l) { const TileIndex tileIndex = TileIndex::fromCoordinates(coordinates, l); MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { break; } myTile->selectedCount++; // qCDebug(DIGIKAM_GEOIFACE_LOG) << l << tileIndex << myTile->selectedCount; GEOIFACE_ASSERT(myTile->selectedCount <= myTile->markerIndices.count()); if (myTile->childrenEmpty()) { break; } } } } for (int i = 0; i < deselected.count(); ++i) { const QItemSelectionRange selectionRange = deselected.at(i); for (int row = selectionRange.top() ; row <= selectionRange.bottom() ; ++row) { // get the coordinates of the item GeoCoordinates coordinates; if (!d->modelHelper->itemCoordinates(d->markerModel->index(row, 0, selectionRange.parent()), &coordinates)) { continue; } for (int l = 0 ; l <= TileIndex::MaxLevel ; ++l) { const TileIndex tileIndex = TileIndex::fromCoordinates(coordinates, l); MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { break; } myTile->selectedCount--; GEOIFACE_ASSERT(myTile->selectedCount >= 0); if (myTile->childrenEmpty()) { break; } } } } emit signalTilesOrSelectionChanged(); } void ItemMarkerTiler::slotSourceModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { qCDebug(DIGIKAM_GEOIFACE_LOG) << topLeft << bottomRight; setDirty(); if (d->activeState) { emit signalTilesOrSelectionChanged(); } // TODO: if only a few items were changed, try to see whether they are still in the right tiles } void ItemMarkerTiler::slotSourceModelRowsInserted(const QModelIndex& parentIndex, int start, int end) { if (isDirty()) { // rows will be added once the tiles are regenerated return; } // sort the new items into our tiles: for (int i = start ; i <= end ; ++i) { addMarkerIndexToGrid(QPersistentModelIndex(d->markerModel->index(i, 0, parentIndex))); } emit signalTilesOrSelectionChanged(); } void ItemMarkerTiler::slotSourceModelRowsAboutToBeRemoved(const QModelIndex& parentIndex, int start, int end) { // TODO: emit signalTilesOrSelectionChanged(); in rowsWereRemoved -#if QT_VERSION < 0x040600 - - // removeMarkerIndexFromGrid does not work in Qt 4.5 because the model has already deleted all - // the data of the item, but we need the items coordinates to work efficiently - - setDirty(); - - return; - -#else - if (isDirty()) { return; } // remove the items from their tiles: for (int i = start ; i <= end ; ++i) { const QModelIndex itemIndex = d->markerModel->index(start, 0, parentIndex); // remove the marker from the grid, but leave the selection count alone because the // selection model will send a signal about the deselection of the marker removeMarkerIndexFromGrid(itemIndex, true); } - -#endif } void ItemMarkerTiler::slotThumbnailAvailableForIndex(const QPersistentModelIndex& index, const QPixmap& pixmap) { emit signalThumbnailAvailableForIndex(QVariant::fromValue(index), pixmap); } void ItemMarkerTiler::slotSourceModelReset() { qCDebug(DIGIKAM_GEOIFACE_LOG) << "----"; setDirty(); } /** * @brief Remove a marker from the grid * @param ignoreSelection Do not remove the marker from the count of selected items. * This is only used by slotSourceModelRowsAboutToBeRemoved internally, * because the selection model sends us an extra signal about the deselection. */ void ItemMarkerTiler::removeMarkerIndexFromGrid(const QModelIndex& markerIndex, const bool ignoreSelection) { if (isDirty()) { // if the model is dirty, there is no need to remove the marker // because the tiles will be regenerated on the next call // that requests data return; } GEOIFACE_ASSERT(markerIndex.isValid()); bool markerIsSelected = false; if (d->selectionModel) { markerIsSelected = d->selectionModel->isSelected(markerIndex); } // remove the marker from the grid: GeoCoordinates markerCoordinates; if (!d->modelHelper->itemCoordinates(markerIndex, &markerCoordinates)) { return; } const TileIndex tileIndex = TileIndex::fromCoordinates(markerCoordinates, TileIndex::MaxLevel); QList tiles; // here l functions as the number of indices that we actually use, therefore we have to go one more up // in this case, l==0 returns the root tile for (int l = 0 ; l <= TileIndex::MaxLevel+1 ; ++l) { MyTile* const currentTile = static_cast(getTile(tileIndex.mid(0, l), true)); if (!currentTile) { break; } tiles << currentTile; currentTile->removeMarkerIndexOrInvalidIndex(markerIndex); if (markerIsSelected&&!ignoreSelection) { currentTile->selectedCount--; GEOIFACE_ASSERT(currentTile->selectedCount >= 0); } } // delete the tiles which are now empty! for (int l = tiles.count()-1 ; l > 0 ; --l) { MyTile* const currentTile = tiles.at(l); if (!currentTile->markerIndices.isEmpty()) { break; } MyTile* const parentTile = tiles.at(l-1); tileDeleteChild(parentTile, currentTile); } } int ItemMarkerTiler::getTileMarkerCount(const TileIndex& tileIndex) { if (isDirty()) { regenerateTiles(); } GEOIFACE_ASSERT(tileIndex.level() <= TileIndex::MaxLevel); MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { return 0; } return myTile->markerIndices.count(); } int ItemMarkerTiler::getTileSelectedCount(const TileIndex& tileIndex) { if (isDirty()) { regenerateTiles(); } GEOIFACE_ASSERT(tileIndex.level() <= TileIndex::MaxLevel); MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { return 0; } return myTile->selectedCount; } GeoGroupState ItemMarkerTiler::getTileGroupState(const TileIndex& tileIndex) { if (isDirty()) { regenerateTiles(); } GEOIFACE_ASSERT(tileIndex.level() <= TileIndex::MaxLevel); MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { return SelectedNone; } const int selectedCount = myTile->selectedCount; if (selectedCount == 0) { return SelectedNone; } else if (selectedCount == myTile->markerIndices.count()) { return SelectedAll; } return SelectedSome; } AbstractMarkerTiler::Tile* ItemMarkerTiler::getTile(const TileIndex& tileIndex, const bool stopIfEmpty) { if (isDirty()) { regenerateTiles(); } GEOIFACE_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 there are any markers in the tile, // we have to sort them into the child tiles: if (!tile->markerIndices.isEmpty()) { for (int i = 0 ; i < tile->markerIndices.count() ; ++i) { const QPersistentModelIndex currentMarkerIndex = tile->markerIndices.at(i); GEOIFACE_ASSERT(currentMarkerIndex.isValid()); // get the tile index for this marker: GeoCoordinates currentMarkerCoordinates; if (!d->modelHelper->itemCoordinates(currentMarkerIndex, ¤tMarkerCoordinates)) { continue; } const TileIndex markerTileIndex = TileIndex::fromCoordinates(currentMarkerCoordinates, level); const int newTileIndex = markerTileIndex.toIntList().last(); MyTile* newTile = static_cast(tile->getChild(newTileIndex)); if (newTile == nullptr) { newTile = static_cast(tileNew()); tile->addChild(newTileIndex, newTile); } newTile->markerIndices<selectionModel) { if (d->selectionModel->isSelected(currentMarkerIndex)) { newTile->selectedCount++; } } } } } 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; } QList ItemMarkerTiler::getTileMarkerIndices(const TileIndex& tileIndex) { if (isDirty()) { regenerateTiles(); } GEOIFACE_ASSERT(tileIndex.level() <= TileIndex::MaxLevel); MyTile* const myTile = static_cast(getTile(tileIndex, true)); if (!myTile) { return QList(); } return myTile->markerIndices; } void ItemMarkerTiler::addMarkerIndexToGrid(const QPersistentModelIndex& markerIndex) { if (isDirty()) { // the model is dirty, so let regenerateTiles do the rest regenerateTiles(); return; } GeoCoordinates markerCoordinates; if (!d->modelHelper->itemCoordinates(markerIndex, &markerCoordinates)) { return; } TileIndex tileIndex = TileIndex::fromCoordinates(markerCoordinates, TileIndex::MaxLevel); GEOIFACE_ASSERT(tileIndex.level() == TileIndex::MaxLevel); bool markerIsSelected = false; if (d->selectionModel) { markerIsSelected = d->selectionModel->isSelected(markerIndex); } // add the marker to all existing tiles: MyTile* currentTile = static_cast(rootTile()); for (int l = 0 ; l <= TileIndex::MaxLevel ; ++l) { currentTile->markerIndices<selectedCount++; } // does the tile have any children? if (currentTile->childrenEmpty()) { break; } // the tile has children. make sure the tile for our marker exists: const int nextIndex = tileIndex.linearIndex(l); MyTile* nextTile = static_cast(currentTile->getChild(nextIndex)); if (nextTile == nullptr) { // we have to create the tile: nextTile = static_cast(tileNew()); currentTile->addChild(nextIndex, nextTile); } // if this is the last loop iteration, populate the next tile now: if (l == TileIndex::MaxLevel) { nextTile->markerIndices<selectedCount++; } } currentTile = nextTile; } } void ItemMarkerTiler::prepareTiles(const GeoCoordinates& /*upperLeft*/, const GeoCoordinates&, int /*level*/) { } void ItemMarkerTiler::regenerateTiles() { resetRootTile(); setDirty(false); if (!d->markerModel) { return; } // read out all existing markers into tiles: for (int row = 0 ; row < d->markerModel->rowCount() ; ++row) { const QModelIndex modelIndex = d->markerModel->index(row, 0); addMarkerIndexToGrid(QPersistentModelIndex(modelIndex)); } } bool ItemMarkerTiler::indicesEqual(const QVariant& a, const QVariant& b) const { return a.value()==b.value(); } void ItemMarkerTiler::onIndicesClicked(const ClickInfo& clickInfo) { QList clickedMarkers; for (int i = 0 ; i < clickInfo.tileIndicesList.count() ; ++i) { const TileIndex tileIndex = clickInfo.tileIndicesList.at(i); clickedMarkers << getTileMarkerIndices(tileIndex); } const QPersistentModelIndex representativeModelIndex = clickInfo.representativeIndex.value(); if ((clickInfo.currentMouseMode == MouseModeSelectThumbnail) && d->selectionModel) { const bool doSelect = (clickInfo.groupSelectionState & SelectedMask) != SelectedAll; const QItemSelectionModel::SelectionFlags selectionFlags = (doSelect ? QItemSelectionModel::Select : QItemSelectionModel::Deselect) | QItemSelectionModel::Rows; for (int i = 0 ; i < clickedMarkers.count() ; ++i) { if (d->selectionModel->isSelected(clickedMarkers.at(i)) != doSelect) { d->selectionModel->select(clickedMarkers.at(i), selectionFlags); } } if (representativeModelIndex.isValid()) { d->selectionModel->setCurrentIndex(representativeModelIndex, selectionFlags); } /** * @todo When do we report the clicks to the modelHelper? * Or do we only report selection changes to the selection model? */ /* d->modelHelper->onIndicesClicked(clickedMarkers); */ } else if (clickInfo.currentMouseMode == MouseModeFilter) { /// @todo Also forward the representative index and the mouse mode in this call d->modelHelper->onIndicesClicked(clickedMarkers); } } void ItemMarkerTiler::onIndicesMoved(const TileIndex::List& tileIndicesList, const GeoCoordinates& targetCoordinates, const QPersistentModelIndex& targetSnapIndex) { QList movedMarkers; if (tileIndicesList.isEmpty()) { // complicated case: all selected markers were moved QModelIndexList selectedIndices = d->selectionModel->selectedIndexes(); for (int i = 0 ; i < selectedIndices.count() ; ++i) { // TODO: correctly handle items with multiple columns QModelIndex movedMarker = selectedIndices.at(i); if (movedMarker.column() == 0) { movedMarkers << movedMarker; } } } else { // only the tiles in tileIndicesList were moved for (int i = 0 ; i < tileIndicesList.count() ; ++i) { const TileIndex tileIndex = tileIndicesList.at(i); movedMarkers << getTileMarkerIndices(tileIndex); } } d->modelHelper->onIndicesMoved(movedMarkers, targetCoordinates, targetSnapIndex); } void ItemMarkerTiler::slotSourceModelLayoutChanged() { setDirty(); } void ItemMarkerTiler::setActive(const bool state) { d->activeState = state; } AbstractMarkerTiler::Tile* ItemMarkerTiler::tileNew() { return new MyTile(); } void ItemMarkerTiler::tileDeleteInternal(AbstractMarkerTiler::Tile* const tile) { delete static_cast(tile); } AbstractMarkerTiler::TilerFlags ItemMarkerTiler::tilerFlags() const { TilerFlags resultFlags = FlagNull; if (d->modelHelper->modelFlags().testFlag(GeoModelHelper::FlagMovable)) { resultFlags |= FlagMovable; } return resultFlags; } GeoGroupState ItemMarkerTiler::getGlobalGroupState() { if (d->selectionModel) { if (d->selectionModel->hasSelection()) { return SelectedMask; } } return SelectedNone; } } // namespace Digikam