diff --git a/CMakeLists.txt b/CMakeLists.txt index a6786f2a..5d786f18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,553 +1,554 @@ cmake_minimum_required(VERSION 3.2.0) project(kphotoalbum VERSION 5.5) if(POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif() # provide drop-down menu for build-type in cmake-gui: set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ";Debug;Release;RelWithDebInfo;MinSizeRel") list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) find_package(ECM REQUIRED NO_MODULE) list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ) include(KDEInstallDirs) include(KDECompilerSettings) include(KDECMakeSettings) include(FeatureSummary) # enable exceptions: kde_enable_exceptions() add_definitions( -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY -DQT_DEPRECATED_WARNINGS -DQT_STRICT_ITERATORS ) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_REQUIRED TRUE) ########### dependencies ############### find_package(Qt5 5.9 REQUIRED COMPONENTS Sql Xml Widgets Network) find_package(Phonon4Qt5 REQUIRED) find_package(KF5 5.44 REQUIRED COMPONENTS Archive Completion Config CoreAddons DocTools I18n IconThemes JobWidgets KIO TextWidgets XmlGui WidgetsAddons) find_package(JPEG REQUIRED) if(JPEG_FOUND) include_directories(${JPEG_INCLUDE_DIR}) endif() ### 2018-12-30 jzarl # When Exiv2 0.26 can be deprecated, FindExiv2.cmake should be removed # and only find_package(exiv2) should be used find_package(exiv2 CONFIG QUIET) if(exiv2_FOUND) # search againg with REQUIRED, so that the feature summary correctly shows exiv as required dependency find_package(exiv2 CONFIG REQUIRED) set(EXIV2_LIBRARIES exiv2lib) else() find_package(Exiv2 REQUIRED) endif() find_package(KF5Kipi 5.1.0) set_package_properties(KF5Kipi PROPERTIES TYPE RECOMMENDED PURPOSE "Enables integration of KDE image plugin interface (shared functionality between KPhotoAlbum and other apps like gwenview or digikam)" ) set(HASKIPI ${KF5Kipi_FOUND}) find_package(KF5Purpose) set_package_properties(KF5Purpose PROPERTIES TYPE RECOMMENDED PURPOSE "Enables integration with KDE Purpose plugins, which provide image sharing and similar functionality." ) find_package(KF5KDcraw) set_package_properties(KF5KDcraw PROPERTIES TYPE OPTIONAL PURPOSE "Enables RAW image support" ) set(HAVE_KDCRAW ${KF5KDcraw_FOUND} ) find_package(Marble) set_package_properties(Marble PROPERTIES TYPE OPTIONAL PURPOSE "Enables support for geographic map location using embedded GPS information." ) set(HAVE_MARBLE ${Marble_FOUND}) if(Marble_FOUND) include(MarbleChecks) endif() add_custom_target( UpdateVersion ALL COMMAND ${CMAKE_COMMAND} -DBASE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DPROJECT_NAME=KPA -DPROJECT_VERSION="${PROJECT_VERSION}" -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/UpdateVersion.cmake" COMMENT "Updating version header." BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/version.h" ) # For config-kpa-*.h include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(libdatebar_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/DateBarWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/ViewHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/MouseHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/MouseHandler.cpp ) set(libSettings_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Settings/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Settings/SettingsData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/SettingsDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ViewerSizeConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/CategoryItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/CategoryPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/TagGroupsPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/GeneralPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/FileVersionDetectionPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ThumbnailsPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ViewerPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/DatabaseBackendPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/UntaggedGroupBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/CategoriesGroupsWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/BirthdayPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/DateTableWidgetItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/Logging.cpp ) set(libxmldb_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/Database.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XMLCategoryCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XMLCategory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XMLImageDateCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/NumberedBackup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/FileReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/FileWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/ElementWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XmlReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/CompressFileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/Logging.cpp ) set(libThumbnailView_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/FilterWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailToolTip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/GridResizeInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/GridResizeSlider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/SelectionInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/MouseTrackingInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/CellGeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailFacade.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/KeyboardEventHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailDND.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/Delegate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/SelectionMaintainer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/VideoThumbnailCycler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/MouseInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailFactory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/enums.cpp ) set(libPlugins_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Logging.cpp ) if(KF5Kipi_FOUND) set(libPlugins_SRCS ${libPlugins_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Interface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/CategoryImageCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageCollectionSelector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/PluginsPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/UploadWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/UploadImageCollection.cpp ) endif() if(KF5Purpose_FOUND) set(libPlugins_SRCS ${libPlugins_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/PurposeMenu.cpp ) endif() set(libViewer_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/ViewerWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/ImageDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/ViewHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/SpeedDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/InfoBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/CategoryImageConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/AbstractDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/VideoDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/TextDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/InfoBoxResizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/VisibleOptionsMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/VideoShooter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/TaggedArea.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/Logging.cpp ) set(libCategoryListView_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/DragableTreeWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/CheckDropItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/DragItemInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/Logging.cpp ) set(libHTMLGenerator_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/HTMLDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/Generator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/Setup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/ImageSizeCheckBox.h ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/ImageSizeCheckBox.cpp ) set(libUtilities_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/AlgorithmHelper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ShowBusyCursor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/List.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/UniqFilenameMapper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/FileUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/BooleanGuard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/Process.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/DeleteFiles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ToolTip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/JpeglibWithFix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/StringSet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/FastJpeg.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/DemoUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/DescriptionUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/FileNameUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/VideoUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ImageUtil.cpp ) set(libMainWindow_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DeleteDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/RunDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/FeatureDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/InvalidDateFinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/AutoStackImages.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/TokenEditor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/WelcomeDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/Window.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/SplashScreen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/ExternalPopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/CategoryImagePopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/SearchBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/ImageCounter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DirtyIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/StatisticsDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/BreadcrumbViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/StatusBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/UpdateVideoThumbnail.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DuplicateMerger/DuplicateMerger.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DuplicateMerger/DuplicateMatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DuplicateMerger/MergeToolTip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/CopyPopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/Options.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/Logging.cpp ) set(libImageManager_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageLoaderThread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/AsyncLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageClientInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/RawImageDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/RequestQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ThumbnailCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageEvent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ThumbnailBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/PreloadRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/CancelEvent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/VideoImageRescaleRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/VideoThumbnails.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/VideoLengthExtractor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ExtractOneVideoFrame.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/CacheFileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/enums.cpp ) set(libDB_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/DB/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/Category.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ExactCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageDate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MD5Map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MemberMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageInfoList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageDB.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/NegationCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/NewImageFinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageScout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/NoTagCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/GroupCounter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageSearchInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ContainerCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ValueCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/OrCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/AndCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FastDir.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/OptimizedFileList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FileName.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FileNameList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryPtr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ExifMode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageDateCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageInfoPtr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MD5.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MediaCount.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/RawId.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/SimpleCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/UIDelegate.cpp ) set(libImportExport_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/Import.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/XMLHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/MiniViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImageRow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportSettings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/KimFileReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/MD5CheckPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/Logging.cpp ) set(libAnnotationDialog_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/Dialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ListSelect.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ImagePreview.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ImagePreviewWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/DateEdit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/CompletableLineEdit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ListViewItemHider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ShowSelectionOnlyManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ShortCutManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ResizableFrame.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/DescriptionEdit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/AreaTagSelectDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/enums.cpp ) set(libBrowser_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Browser/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Browser/BrowserWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/BrowserPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/OverviewPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/CategoryPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/ImageViewPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/TreeFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/Breadcrumb.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/BreadcrumbList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/AbstractCategoryModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/FlatCategoryModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/TreeCategoryModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/CenteringIconView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/enums.cpp ) set(libExif_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Exif/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Database.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/InfoDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/SearchDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/SearchInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/TreeView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/RangeWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/DatabaseElement.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/ReReadDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Grid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/SearchDialogSettings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ExifPage.cpp ) set(libBackgroundTaskManager_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/StatusIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/CompletedJobInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/Priority.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/PriorityQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/Logging.cpp ) set(libBackgroundJobs_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/SearchForVideosWithoutLengthInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/ReadVideoLengthJob.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/SearchForVideosWithoutVideoThumbnailsJob.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/HandleVideoThumbnailRequestJob.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/ExtractOneThumbnailJob.cpp ) set(libRemoteControl_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteCommand.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteConnection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Server.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/SearchInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteImageRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/ImageNameStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/ConnectionIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Logging.cpp ) set(libMap_SRCS) if(Marble_FOUND) set(libMap_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Browser/GeoPositionPage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Map/GeoCluster.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/MapView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/GeoCoordinates.cpp ) endif() add_subdirectory(images) add_subdirectory(icons) add_subdirectory(demo) add_subdirectory(themes) add_subdirectory(scripts) add_subdirectory(doc) ########### next target ############### set(kphotoalbum_SRCS main.cpp ${libdatebar_SRCS} ${libSettings_SRCS} ${libsurvey_SRCS} ${libxmldb_SRCS} ${libThumbnailView_SRCS} ${libPlugins_SRCS} ${libViewer_SRCS} ${libCategoryListView_SRCS} ${libHTMLGenerator_SRCS} ${libMainWindow_SRCS} ${libImageManager_SRCS} ${libDB_SRCS} ${libImportExport_SRCS} ${libAnnotationDialog_SRCS} ${libExif_SRCS} ${libBrowser_SRCS} ${libBackgroundTaskManager_SRCS} ${libBackgroundJobs_SRCS} ${libRemoteControl_SRCS} ${libMap_SRCS} ${libUtilities_SRCS} # add doxygen headers so that they get visibiltiy in IDEs: ${CMAKE_CURRENT_SOURCE_DIR}/documentation/coding-standards.h ${CMAKE_CURRENT_SOURCE_DIR}/documentation/mainpage.h ${CMAKE_CURRENT_SOURCE_DIR}/documentation/phrase-book.h ${CMAKE_CURRENT_SOURCE_DIR}/documentation/videothumbnails.h ) add_executable(kphotoalbum ${kphotoalbum_SRCS}) add_dependencies(kphotoalbum UpdateVersion) # External components target_link_libraries(kphotoalbum ${JPEG_LIBRARY} ${EXIV2_LIBRARIES} KF5::Archive KF5::Completion KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::I18n KF5::IconThemes KF5::JobWidgets KF5::KIOCore KF5::KIOWidgets KF5::TextWidgets KF5::XmlGui KF5::WidgetsAddons Phonon::phonon4qt5 Qt5::Network Qt5::Sql ) if(KF5Kipi_FOUND) target_link_libraries(kphotoalbum KF5::Kipi) endif() if(KF5Purpose_FOUND) target_link_libraries(kphotoalbum KF5::Purpose KF5::PurposeWidgets) endif() if(KF5KDcraw_FOUND) target_link_libraries(kphotoalbum KF5::KDcraw) endif() if(Marble_FOUND) target_link_libraries(kphotoalbum Marble) endif() install(TARGETS kphotoalbum ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.kphotoalbum.desktop org.kde.kphotoalbum-import.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES kphotoalbumrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES tips default-setup DESTINATION ${KDE_INSTALL_DATADIR}/kphotoalbum) install(FILES kphotoalbumui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kphotoalbum) install(FILES org.kde.kphotoalbum.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) configure_file(config-kpa-kdcraw.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kdcraw.h) configure_file(config-kpa-kipi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kipi.h) configure_file(config-kpa-marble.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-marble.h) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) # vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Map/GeoCluster.cpp b/Map/GeoCluster.cpp new file mode 100644 index 00000000..e990e31c --- /dev/null +++ b/Map/GeoCluster.cpp @@ -0,0 +1,198 @@ +/* Copyright (C) 2019 The KPhotoAlbum Development Team + + 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 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "GeoCluster.h" +#include "GeoCoordinates.h" +#include "Logging.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ +// when the angular resolution is smaller than fineResolution, all details should be shown +constexpr qreal FINE_RESOLUTION = 0.000001; +// size of the markers in screen coordinates (pixel) +constexpr int MARKER_SIZE_PX = 40; + +/** + * @brief screenSize computes the screen size of a geographical region in pixels. + * If one of the bounding box edges is not visible, a null QizeF is retunred. + * @param viewPortParams the parameters of the current view port + * @param box the geographical region + * @return the size in pixels, or a null size + */ +QSizeF screenSize(const Marble::ViewportParams &viewPortParams, const Marble::GeoDataLatLonBox &box, bool debug = false) +{ + qreal NE_x; + qreal NE_y; + qreal SW_x; + qreal SW_y; + bool valid; + valid = viewPortParams.screenCoordinates(box.east(Marble::GeoDataCoordinates::Radian), + box.north(Marble::GeoDataCoordinates::Radian), + NE_x, NE_y); + valid &= viewPortParams.screenCoordinates(box.west(Marble::GeoDataCoordinates::Radian), + box.south(Marble::GeoDataCoordinates::Radian), + SW_x, SW_y); + if (debug) { + qCDebug(MapLog) << "coordinates" << NE_x << "-" << SW_x << "," << NE_y << "-" << SW_y << "are" << (valid ? "valid" : "invalid"); + } + if (!valid) + return QSizeF(); + return QSizeF { qAbs(NE_x - SW_x), qAbs(NE_y - SW_y) }; +} +} //namespace + +Marble::GeoDataLatLonAltBox Map::GeoCluster::boundingRegion() const +{ + if (m_boundingRegion.isEmpty()) { + for (const auto &subCluster : m_subClusters) { + m_boundingRegion |= subCluster->boundingRegion(); + } + } + return m_boundingRegion; +} + +Marble::GeoDataCoordinates Map::GeoCluster::center() const +{ + // TODO(jzarl): check how this compares to e.g. the center of all coordinates instead: + return boundingRegion().center(); +} + +void Map::GeoCluster::render(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, Map::MapStyle style) const +{ + if (viewPortParams.resolves(boundingRegion(), 2 * MARKER_SIZE_PX) || size() == 1 + || (viewPortParams.angularResolution() < FINE_RESOLUTION)) { + // if the region takes up enough screen space, we should display the subclusters individually. + // if all images have the same coordinates (null bounding region), this will never happen + // -> in this case, show the images when we're zoomed in enough + renderSubItems(painter, viewPortParams, alternatePixmap, style); + } else { + qCDebug(MapLog) << "GeoCluster has" << size() << "images."; + painter->setOpacity(0.5); + const QSizeF areaSizePx = screenSize(viewPortParams, boundingRegion()); + // doubling the area size gets nicer results on average: + const qreal heightPx = qMax(2 * areaSizePx.height(), (qreal)MARKER_SIZE_PX); + const qreal widthPx = qMax(2 * areaSizePx.width(), (qreal)MARKER_SIZE_PX); + painter->drawRect(center(), heightPx, widthPx); + painter->setOpacity(1); + QPen pen = painter->pen(); + painter->setPen(QPen(Qt::black)); + painter->drawText(center(), i18nc("The number of images in an area of the map", "%1", size()), -0.5 * MARKER_SIZE_PX, 0.5 * MARKER_SIZE_PX, MARKER_SIZE_PX, MARKER_SIZE_PX, QTextOption(Qt::AlignCenter)); + painter->setPen(pen); + } +} + +int Map::GeoCluster::size() const +{ + if (m_size == 0) { + for (const auto &subCluster : m_subClusters) { + m_size += subCluster->size(); + } + } + return m_size; +} + +void Map::GeoCluster::renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, Map::MapStyle style) const +{ + for (const auto &subCluster : m_subClusters) { + subCluster->render(painter, viewPortParams, alternatePixmap, style); + } +} + +Map::GeoCluster::GeoCluster(int lvl) + : m_level(lvl) +{ +} + +void Map::GeoCluster::addSubCluster(const Map::GeoCluster *subCluster) +{ + m_subClusters.append(subCluster); +} + +Map::GeoBin::GeoBin() + : GeoCluster(0) +{ +} + +void Map::GeoBin::addImage(DB::ImageInfoPtr image) +{ + m_images.append(image); + extendGeoDataLatLonBox(m_boundingRegion, image->coordinates()); +} + +Marble::GeoDataLatLonAltBox Map::GeoBin::boundingRegion() const +{ + return m_boundingRegion; +} + +int Map::GeoBin::size() const +{ + return m_images.size(); +} + +void Map::GeoBin::renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, Map::MapStyle style) const +{ + const auto viewPort = viewPortParams.viewLatLonAltBox(); + qCDebug(MapLog) << "GeoBin: drawing individual images"; + for (const DB::ImageInfoPtr &image : m_images) { + const Marble::GeoDataCoordinates pos(image->coordinates().lon(), image->coordinates().lat(), + image->coordinates().alt(), + Marble::GeoDataCoordinates::Degree); + if (viewPort.contains(pos)) { + if (style == MapStyle::ShowPins) { + painter->drawPixmap(pos, alternatePixmap); + } else { + // FIXME(l3u) Maybe we should cache the scaled thumbnails? + painter->drawPixmap(pos, ImageManager::ThumbnailCache::instance()->lookup(image->fileName()).scaled(QSize(MARKER_SIZE_PX, MARKER_SIZE_PX), Qt::KeepAspectRatio)); + } + } + } +} + +void Map::extendGeoDataLatLonBox(Marble::GeoDataLatLonBox &box, const Map::GeoCoordinates &coords) +{ + if (box.isEmpty()) { + box.setEast(coords.lon(), Marble::GeoDataCoordinates::Degree); + box.setWest(coords.lon(), Marble::GeoDataCoordinates::Degree); + box.setNorth(coords.lat(), Marble::GeoDataCoordinates::Degree); + box.setSouth(coords.lat(), Marble::GeoDataCoordinates::Degree); + } else { + if (box.east(Marble::GeoDataCoordinates::Degree) < coords.lon()) { + box.setEast(coords.lon(), Marble::GeoDataCoordinates::Degree); + } + if (box.west(Marble::GeoDataCoordinates::Degree) > coords.lon()) { + box.setWest(coords.lon(), Marble::GeoDataCoordinates::Degree); + } + if (box.north(Marble::GeoDataCoordinates::Degree) < coords.lat()) { + box.setNorth(coords.lat(), Marble::GeoDataCoordinates::Degree); + } + if (box.south(Marble::GeoDataCoordinates::Degree) > coords.lat()) { + box.setSouth(coords.lat(), Marble::GeoDataCoordinates::Degree); + } + } +} diff --git a/Map/GeoCluster.h b/Map/GeoCluster.h new file mode 100644 index 00000000..26c2f2b0 --- /dev/null +++ b/Map/GeoCluster.h @@ -0,0 +1,116 @@ +/* Copyright (C) 2019 The KPhotoAlbum Development Team + + 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 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MAP_GEOCLUSTER_H +#define MAP_GEOCLUSTER_H + +#include + +#include +#include + +namespace Marble +{ +class GeoDataLatLonBox; +class GeoPainter; +class ViewportParams; +} + +namespace Map +{ + +class GeoCoordinates; + +enum class MapStyle { + ShowPins, + ShowThumbnails +}; + +class GeoCluster +{ +public: + explicit GeoCluster(int lvl); + virtual ~GeoCluster() = default; + + void addSubCluster(const GeoCluster *subCluster); + /** + * @brief boundingRegion computes the bounding region for the GeoCluster + * All images in the GeoCluster are within the boundingRegion. + * The result is only computed once at the first call to the method. + * @return a GeoDataLatLonBox containing all images in all sub-clusters. + */ + virtual Marble::GeoDataLatLonAltBox boundingRegion() const; + /** + * @brief center + * @return the center of the boundingRegion + */ + virtual Marble::GeoDataCoordinates center() const; + void render(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, MapStyle style) const; + /** + * @brief size + * The result is only computed once at the first call to the method. + * @return the number of images in all sub-clusters + */ + virtual int size() const; + +private: + mutable int m_size = 0; + QList m_subClusters; + +protected: + mutable Marble::GeoDataLatLonAltBox m_boundingRegion; + const int m_level; + /** + * @brief renderSubItems renders the sub-items of this GeoCluster. + * @param painter + * @param viewPortParams + * @param alternatePixmap + * @param style + */ + virtual void renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, MapStyle style) const; +}; + +/** + * @brief The GeoBin class holds a number of images that are grouped into the same bin. + * I.e. they are in the direct vicinity of each other. + */ +class GeoBin : public GeoCluster +{ +public: + GeoBin(); + void addImage(DB::ImageInfoPtr image); + Marble::GeoDataLatLonAltBox boundingRegion() const override; + int size() const override; + +private: + QList m_images; + +protected: + void renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, MapStyle style) const override; +}; + +/** + * @brief extendGeoDataLatLonBox extend the given GeoDataLatLonBox to encompass the given coordinates. + * @param box + * @param coords + */ +void extendGeoDataLatLonBox(Marble::GeoDataLatLonBox &box, const Map::GeoCoordinates &coords); +} //namespace + +#endif diff --git a/Map/MapView.cpp b/Map/MapView.cpp index f9343652..d4a03214 100644 --- a/Map/MapView.cpp +++ b/Map/MapView.cpp @@ -1,606 +1,441 @@ /* Copyright (C) 2014-2019 The KPhotoAlbum Development Team 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 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Local includes #include "MapView.h" +#include "GeoCluster.h" #include "Logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { const QString MAPVIEW_FLOATER_VISIBLE_CONFIG_PREFIX = QStringLiteral("MarbleFloaterVisible "); const QStringList MAPVIEW_RENDER_POSITION({ QStringLiteral("HOVERS_ABOVE_SURFACE") }); const QVector WANTED_FLOATERS { QStringLiteral("Compass"), QStringLiteral("Scale Bar"), QStringLiteral("Navigation"), QStringLiteral("Overview Map") }; -// when the angular resolution is smaller than fineResolution, all details should be shown -constexpr qreal FINE_RESOLUTION = 0.000001; -// size of the markers in screen coordinates (pixel) -constexpr int MARKER_SIZE_PX = 40; // levels of clustering for geo coordinates constexpr int MAP_CLUSTER_LEVELS = 10; static_assert(MAP_CLUSTER_LEVELS > 0, "At least one level of clustering is needed for the map."); static_assert(MAP_CLUSTER_LEVELS < 32, "See coarsenBinAddress to know why this is a bad idea."); /** * @brief computeBinAddress calculates a "bin" for grouping coordinates that are near each other. * Using a signed 32-bit integer value allows for 2 decimal places of either coordinate part, * which is roughly equivalent to a spatial resolution of 1 km. * @param coords (more or less) precise coordinates * @return imprecise coordinates */ Map::GeoBinAddress computeBinAddress(const Map::GeoCoordinates &coords) { qint64 lat = qRound(coords.lat() * 100); qint64 lon = qRound(coords.lon() * 100); return static_cast((lat << 32) + lon); } /** * @brief coarsenBinAddress takes A GeoBinAddress and reduces its precision. * @param addr the address to be reduced in accuracy * @param level how many binary digits should be nulled out * @return */ Map::GeoBinAddress coarsenBinAddress(Map::GeoBinAddress addr, int level) { constexpr quint64 LO = 0x00000000ffffffff; constexpr quint64 HI = 0xffffffff00000000; // zero out the rightmost bits quint64 mask = 0xffffffffffffffff << level; // duplicate the mask onto the higher 32 bits mask = (HI & (mask << 32)) | (LO & mask); // apply mask return addr & mask; } /** * @brief buildClusterMap fills the lodMap by putting the GeoBin in GeoClusters * of decreasing levels of detail. * @param lodMap a vector containing a map for each level of detail * @param binAddress the GeoBinAddress for the newly added GeoBin * @param bin the GeoBin to add to the lodMap */ void buildClusterMap(QVector> &lodMap, Map::GeoBinAddress binAddress, const Map::GeoBin *bin) { const Map::GeoCluster *cluster = bin; for (int lvl = 1; lvl <= MAP_CLUSTER_LEVELS; lvl++) { QHash &map = lodMap[lvl - 1]; binAddress = coarsenBinAddress(binAddress, lvl); qCDebug(MapLog) << "adding GeoCluster with address" << binAddress << "at level" << lvl; if (map.contains(binAddress)) { map[binAddress]->addSubCluster(cluster); break; } else { map.insert(binAddress, new Map::GeoCluster(lvl)); map[binAddress]->addSubCluster(cluster); cluster = map[binAddress]; } } } -/** - * @brief extendGeoDataLatLonBox extend the given GeoDataLatLonBox to encompass the given coordinates. - * @param box - * @param coords - */ -void extendGeoDataLatLonBox(Marble::GeoDataLatLonBox &box, const Map::GeoCoordinates &coords) -{ - if (box.isEmpty()) { - box.setEast(coords.lon(), Marble::GeoDataCoordinates::Degree); - box.setWest(coords.lon(), Marble::GeoDataCoordinates::Degree); - box.setNorth(coords.lat(), Marble::GeoDataCoordinates::Degree); - box.setSouth(coords.lat(), Marble::GeoDataCoordinates::Degree); - } else { - if (box.east(Marble::GeoDataCoordinates::Degree) < coords.lon()) { - box.setEast(coords.lon(), Marble::GeoDataCoordinates::Degree); - } - if (box.west(Marble::GeoDataCoordinates::Degree) > coords.lon()) { - box.setWest(coords.lon(), Marble::GeoDataCoordinates::Degree); - } - if (box.north(Marble::GeoDataCoordinates::Degree) < coords.lat()) { - box.setNorth(coords.lat(), Marble::GeoDataCoordinates::Degree); - } - if (box.south(Marble::GeoDataCoordinates::Degree) > coords.lat()) { - box.setSouth(coords.lat(), Marble::GeoDataCoordinates::Degree); - } - } -} - -/** - * @brief screenSize computes the screen size of a geographical region in pixels. - * If one of the bounding box edges is not visible, a null QizeF is retunred. - * @param viewPortParams the parameters of the current view port - * @param box the geographical region - * @return the size in pixels, or a null size - */ -QSizeF screenSize(const Marble::ViewportParams &viewPortParams, const Marble::GeoDataLatLonBox &box, bool debug = false) -{ - qreal NE_x; - qreal NE_y; - qreal SW_x; - qreal SW_y; - bool valid; - valid = viewPortParams.screenCoordinates(box.east(Marble::GeoDataCoordinates::Radian), - box.north(Marble::GeoDataCoordinates::Radian), - NE_x, NE_y); - valid &= viewPortParams.screenCoordinates(box.west(Marble::GeoDataCoordinates::Radian), - box.south(Marble::GeoDataCoordinates::Radian), - SW_x, SW_y); - if (debug) { - qCDebug(MapLog) << "coordinates" << NE_x << "-" << SW_x << "," << NE_y << "-" << SW_y << "are" << (valid ? "valid" : "invalid"); - } - if (!valid) - return QSizeF(); - return QSizeF { qAbs(NE_x - SW_x), qAbs(NE_y - SW_y) }; -} -} - -Marble::GeoDataLatLonAltBox Map::GeoCluster::boundingRegion() const -{ - if (m_boundingRegion.isEmpty()) { - for (const auto &subCluster : m_subClusters) { - m_boundingRegion |= subCluster->boundingRegion(); - } - } - return m_boundingRegion; -} - -Marble::GeoDataCoordinates Map::GeoCluster::center() const -{ - // TODO(jzarl): check how this compares to e.g. the center of all coordinates instead: - return boundingRegion().center(); -} - -void Map::GeoCluster::render(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, Map::MapStyle style) const -{ - if (viewPortParams.resolves(boundingRegion(), 2 * MARKER_SIZE_PX) || size() == 1 - || (viewPortParams.angularResolution() < FINE_RESOLUTION)) { - // if the region takes up enough screen space, we should display the subclusters individually. - // if all images have the same coordinates (null bounding region), this will never happen - // -> in this case, show the images when we're zoomed in enough - renderSubItems(painter, viewPortParams, alternatePixmap, style); - } else { - qCDebug(MapLog) << "GeoCluster has" << size() << "images."; - painter->setOpacity(0.5); - const QSizeF areaSizePx = screenSize(viewPortParams, boundingRegion()); - // doubling the area size gets nicer results on average: - const qreal heightPx = qMax(2 * areaSizePx.height(), (qreal)MARKER_SIZE_PX); - const qreal widthPx = qMax(2 * areaSizePx.width(), (qreal)MARKER_SIZE_PX); - painter->drawRect(center(), heightPx, widthPx); - painter->setOpacity(1); - QPen pen = painter->pen(); - painter->setPen(QPen(Qt::black)); - painter->drawText(center(), i18nc("The number of images in an area of the map", "%1", size()), -0.5 * MARKER_SIZE_PX, 0.5 * MARKER_SIZE_PX, MARKER_SIZE_PX, MARKER_SIZE_PX, QTextOption(Qt::AlignCenter)); - painter->setPen(pen); - } -} - -int Map::GeoCluster::size() const -{ - if (m_size == 0) { - for (const auto &subCluster : m_subClusters) { - m_size += subCluster->size(); - } - } - return m_size; -} - -void Map::GeoCluster::renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, Map::MapStyle style) const -{ - for (const auto &subCluster : m_subClusters) { - subCluster->render(painter, viewPortParams, alternatePixmap, style); - } -} - -Map::GeoCluster::GeoCluster(int lvl) - : m_level(lvl) -{ -} - -void Map::GeoCluster::addSubCluster(const Map::GeoCluster *subCluster) -{ - m_subClusters.append(subCluster); -} - -Map::GeoBin::GeoBin() - : GeoCluster(0) -{ -} - -void Map::GeoBin::addImage(DB::ImageInfoPtr image) -{ - m_images.append(image); - extendGeoDataLatLonBox(m_boundingRegion, image->coordinates()); -} - -Marble::GeoDataLatLonAltBox Map::GeoBin::boundingRegion() const -{ - return m_boundingRegion; -} - -int Map::GeoBin::size() const -{ - return m_images.size(); -} - -void Map::GeoBin::renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, Map::MapStyle style) const -{ - const auto viewPort = viewPortParams.viewLatLonAltBox(); - qCDebug(MapLog) << "GeoBin: drawing individual images"; - for (const DB::ImageInfoPtr &image : m_images) { - const Marble::GeoDataCoordinates pos(image->coordinates().lon(), image->coordinates().lat(), - image->coordinates().alt(), - Marble::GeoDataCoordinates::Degree); - if (viewPort.contains(pos)) { - if (style == MapStyle::ShowPins) { - painter->drawPixmap(pos, alternatePixmap); - } else { - // FIXME(l3u) Maybe we should cache the scaled thumbnails? - painter->drawPixmap(pos, ImageManager::ThumbnailCache::instance()->lookup(image->fileName()).scaled(QSize(MARKER_SIZE_PX, MARKER_SIZE_PX), Qt::KeepAspectRatio)); - } - } - } } Map::MapView::MapView(QWidget *parent, UsageType type) : QWidget(parent) { if (type == UsageType::MapViewWindow) { setWindowFlags(Qt::Window); setAttribute(Qt::WA_DeleteOnClose); } QVBoxLayout *layout = new QVBoxLayout(this); m_statusLabel = new QLabel; m_statusLabel->setAlignment(Qt::AlignCenter); m_statusLabel->hide(); layout->addWidget(m_statusLabel); m_mapWidget = new Marble::MarbleWidget; m_mapWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_mapWidget->setProjection(Marble::Mercator); m_mapWidget->setMapThemeId(QStringLiteral("earth/openstreetmap/openstreetmap.dgml")); #ifdef MARBLE_HAS_regionSelected_NEW connect(m_mapWidget, &Marble::MarbleWidget::regionSelected, this, &Map::MapView::updateRegionSelection); #else connect(m_mapWidget, &Marble::MarbleWidget::regionSelected, this, &Map::MapView::updateRegionSelectionOld); #endif m_mapWidget->addLayer(this); layout->addWidget(m_mapWidget); m_mapWidget->show(); QHBoxLayout *controlLayout = new QHBoxLayout; layout->addLayout(controlLayout); // KPA's control buttons m_kpaButtons = new QWidget; QHBoxLayout *kpaButtonsLayout = new QHBoxLayout(m_kpaButtons); controlLayout->addWidget(m_kpaButtons); QPushButton *saveButton = new QPushButton; saveButton->setFlat(true); saveButton->setIcon(QPixmap(SmallIcon(QStringLiteral("media-floppy")))); saveButton->setToolTip(i18n("Save the current map settings")); kpaButtonsLayout->addWidget(saveButton); connect(saveButton, &QPushButton::clicked, this, &MapView::saveSettings); m_setLastCenterButton = new QPushButton; m_setLastCenterButton->setFlat(true); m_setLastCenterButton->setIcon(QPixmap(SmallIcon(QStringLiteral("go-first")))); m_setLastCenterButton->setToolTip(i18n("Go to last map position")); kpaButtonsLayout->addWidget(m_setLastCenterButton); connect(m_setLastCenterButton, &QPushButton::clicked, this, &MapView::setLastCenter); QPushButton *showThumbnails = new QPushButton; showThumbnails->setFlat(true); showThumbnails->setIcon(QPixmap(SmallIcon(QStringLiteral("view-preview")))); showThumbnails->setToolTip(i18n("Show thumbnails")); kpaButtonsLayout->addWidget(showThumbnails); showThumbnails->setCheckable(true); showThumbnails->setChecked(m_showThumbnails); connect(showThumbnails, &QPushButton::clicked, this, &MapView::setShowThumbnails); // Marble floater control buttons m_floaters = new QWidget; QHBoxLayout *floatersLayout = new QHBoxLayout(m_floaters); controlLayout->addStretch(); controlLayout->addWidget(m_floaters); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QStringLiteral("MapView")); for (const Marble::RenderPlugin *plugin : m_mapWidget->renderPlugins()) { if (plugin->renderType() != Marble::RenderPlugin::PanelRenderType) { continue; } const QString name = plugin->name(); if (!WANTED_FLOATERS.contains(name)) { continue; } QPushButton *button = new QPushButton; button->setCheckable(true); button->setFlat(true); button->setChecked(plugin->action()->isChecked()); button->setToolTip(plugin->description()); button->setProperty("floater", name); QPixmap icon = plugin->action()->icon().pixmap(QSize(20, 20)); if (icon.isNull()) { icon = QPixmap(20, 20); icon.fill(Qt::white); } button->setIcon(icon); connect(plugin->action(), &QAction::toggled, button, &QPushButton::setChecked); connect(button, &QPushButton::toggled, plugin->action(), &QAction::setChecked); floatersLayout->addWidget(button); const QVariant checked = group.readEntry(MAPVIEW_FLOATER_VISIBLE_CONFIG_PREFIX + name, true); button->setChecked(checked.toBool()); } m_pin = QPixmap(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("pics/pin.png"))); } void Map::MapView::clear() { m_markersBox.clear(); m_baseBins.clear(); m_geoClusters.clear(); m_regionSelected = false; } bool Map::MapView::addImage(DB::ImageInfoPtr image) { if (image->coordinates().hasCoordinates()) { const GeoBinAddress binAddress = computeBinAddress(image->coordinates()); if (!m_baseBins.contains(binAddress)) { m_baseBins.insert(binAddress, new GeoBin()); } m_baseBins[binAddress]->addImage(image); // Update the viewport for zoomToMarkers() extendGeoDataLatLonBox(m_markersBox, image->coordinates()); return true; } return false; } void Map::MapView::addImages(const DB::ImageSearchInfo &searchInfo) { QElapsedTimer timer; timer.start(); displayStatus(MapStatus::Loading); DB::FileNameList images = DB::ImageDB::instance()->search(searchInfo); int count = 0; int total = 0; // put images in bins for (const auto &imageInfo : images) { total++; if (addImage(imageInfo.info())) count++; } buildImageClusters(); displayStatus(MapStatus::SearchCoordinates); qCInfo(TimingLog) << "MapView::addImages(): added" << count << "of" << total << "images in" << timer.elapsed() << "ms."; } void Map::MapView::buildImageClusters() { QElapsedTimer timer; timer.start(); QVector> clusters { MAP_CLUSTER_LEVELS }; int count = 0; // aggregate bins to clusters for (auto it = m_baseBins.constBegin(); it != m_baseBins.constEnd(); ++it) { buildClusterMap(clusters, it.key(), it.value()); count++; } Q_ASSERT(clusters[MAP_CLUSTER_LEVELS - 1].size() > 0); for (int lvl = 0; lvl < MAP_CLUSTER_LEVELS; lvl++) { qCInfo(MapLog) << "MapView:" << clusters[lvl].size() << "clusters on level" << lvl; } m_geoClusters = clusters[MAP_CLUSTER_LEVELS - 1]; qCDebug(TimingLog) << "MapView::addImages(): aggregated" << count << "GeoClusters in" << timer.elapsed() << "ms."; } void Map::MapView::zoomToMarkers() { m_mapWidget->centerOn(m_markersBox); } void Map::MapView::setCenter(const DB::ImageInfoPtr image) { m_lastCenter = image->coordinates(); setLastCenter(); } void Map::MapView::saveSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QStringLiteral("MapView")); for (const QPushButton *button : m_floaters->findChildren()) { group.writeEntry(MAPVIEW_FLOATER_VISIBLE_CONFIG_PREFIX + button->property("floater").toString(), button->isChecked()); } config->sync(); QMessageBox::information(this, i18n("Map view"), i18n("Settings saved!")); } void Map::MapView::setShowThumbnails(bool state) { m_showThumbnails = state; m_mapWidget->reloadMap(); } void Map::MapView::displayStatus(MapStatus status) { switch (status) { case MapStatus::Loading: m_statusLabel->setText(i18n("Loading coordinates from the images ...")); m_statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_statusLabel->show(); m_mapWidget->hide(); m_regionSelected = false; m_setLastCenterButton->setEnabled(false); break; case MapStatus::ImageHasCoordinates: m_statusLabel->hide(); m_regionSelected = false; m_statusLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_mapWidget->show(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(true); break; case MapStatus::ImageHasNoCoordinates: m_statusLabel->setText(i18n("This image does not contain geographic coordinates.")); m_statusLabel->show(); m_statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_mapWidget->hide(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(false); break; case MapStatus::SomeImagesHaveNoCoordinates: m_statusLabel->setText(i18n("Some of the selected images do not contain geographic " "coordinates.")); m_statusLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_statusLabel->show(); m_regionSelected = false; m_mapWidget->show(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(true); break; case MapStatus::SearchCoordinates: m_statusLabel->setText(i18n("Search for geographic coordinates.")); m_statusLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_statusLabel->show(); m_mapWidget->show(); m_mapWidget->centerOn(0.0, 0.0); m_setLastCenterButton->hide(); break; case MapStatus::NoImagesHaveNoCoordinates: m_statusLabel->setText(i18n("None of the selected images contain geographic " "coordinates.")); m_statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_statusLabel->show(); m_mapWidget->hide(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(false); break; } emit displayStatusChanged(status); } void Map::MapView::setLastCenter() { m_mapWidget->centerOn(m_lastCenter.lon(), m_lastCenter.lat()); } void Map::MapView::updateRegionSelection(const Marble::GeoDataLatLonBox &selection) { m_regionSelected = true; m_regionSelection = selection; emit newRegionSelected(getRegionSelection()); } #ifndef MARBLE_HAS_regionSelected_NEW void Map::MapView::updateRegionSelectionOld(const QList &selection) { Q_ASSERT(selection.length() == 4); // see also: https://cgit.kde.org/marble.git/commit/?id=ec1f7f554e9f6ca248b4a3b01dbf08507870687e Marble::GeoDataLatLonBox sel { selection.at(1), selection.at(3), selection.at(2), selection.at(0), Marble::GeoDataCoordinates::Degree }; updateRegionSelection(sel); } #endif Map::GeoCoordinates::LatLonBox Map::MapView::getRegionSelection() const { return GeoCoordinates::LatLonBox( m_regionSelection.north(Marble::GeoDataCoordinates::Degree), m_regionSelection.south(Marble::GeoDataCoordinates::Degree), m_regionSelection.east(Marble::GeoDataCoordinates::Degree), m_regionSelection.west(Marble::GeoDataCoordinates::Degree)); } bool Map::MapView::regionSelected() const { return m_regionSelected; } QStringList Map::MapView::renderPosition() const { // we only ever paint on the same layer: return MAPVIEW_RENDER_POSITION; } bool Map::MapView::render(Marble::GeoPainter *painter, Marble::ViewportParams *viewPortParams, const QString &renderPos, Marble::GeoSceneLayer *) { Q_ASSERT(renderPos == renderPosition().first()); Q_ASSERT(viewPortParams != nullptr); QElapsedTimer timer; timer.start(); painter->setBrush(QBrush(QColor(Qt::red).lighter())); painter->setPen(QColor(Qt::red)); for (const auto *bin : m_geoClusters) { bin->render(painter, *viewPortParams, m_pin, m_showThumbnails ? MapStyle::ShowThumbnails : MapStyle::ShowPins); } qCDebug(TimingLog) << "Map rendered in" << timer.elapsed() << "ms."; return true; } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Map/MapView.h b/Map/MapView.h index 2b1ad1f8..15a36783 100644 --- a/Map/MapView.h +++ b/Map/MapView.h @@ -1,265 +1,197 @@ /* Copyright (C) 2014-2019 The KPhotoAlbum Development Team 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 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MAPVIEW_H #define MAPVIEW_H #include "config-kpa-marble.h" #include "GeoCoordinates.h" #include #include #include #include #include -#include #include #include namespace DB { class ImageSearchInfo; } namespace Marble { class MarbleWidget; } class QLabel; class QPushButton; namespace Map { +class GeoBin; +class GeoCluster; /** * UsageType: determines whether the widget is used as a standalone widget * or within another widget (e.g. the AnnotationDialog). * @see Viewer::ViewerWidget::UsageType */ enum class UsageType { InlineMapView, MapViewWindow }; /** * MapStatus: determines the visibility and text of the status label and the visibility of the * map, depending on the availability of coordinates of the image(s) that are displayed. */ enum class MapStatus { Loading, ImageHasCoordinates, ImageHasNoCoordinates, NoImagesHaveNoCoordinates, SomeImagesHaveNoCoordinates, SearchCoordinates }; -enum class MapStyle { - ShowPins, - ShowThumbnails -}; - -class GeoCluster -{ -public: - explicit GeoCluster(int lvl); - virtual ~GeoCluster() = default; - - void addSubCluster(const GeoCluster *subCluster); - /** - * @brief boundingRegion computes the bounding region for the GeoCluster - * All images in the GeoCluster are within the boundingRegion. - * The result is only computed once at the first call to the method. - * @return a GeoDataLatLonBox containing all images in all sub-clusters. - */ - virtual Marble::GeoDataLatLonAltBox boundingRegion() const; - /** - * @brief center - * @return the center of the boundingRegion - */ - virtual Marble::GeoDataCoordinates center() const; - void render(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, MapStyle style) const; - /** - * @brief size - * The result is only computed once at the first call to the method. - * @return the number of images in all sub-clusters - */ - virtual int size() const; - -private: - mutable int m_size = 0; - QList m_subClusters; - -protected: - mutable Marble::GeoDataLatLonAltBox m_boundingRegion; - const qreal m_resolution; - const int m_level; - /** - * @brief renderSubItems renders the sub-items of this GeoCluster. - * @param painter - * @param viewPortParams - * @param alternatePixmap - * @param style - */ - virtual void renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, MapStyle style) const; -}; - -/** - * @brief The GeoBin class holds a number of images that are grouped into the same bin. - * I.e. they are in the direct vicinity of each other. - */ -class GeoBin : public GeoCluster -{ -public: - GeoBin(); - void addImage(DB::ImageInfoPtr image); - Marble::GeoDataLatLonAltBox boundingRegion() const override; - int size() const override; - -private: - QList m_images; - -protected: - void renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const QPixmap &alternatePixmap, MapStyle style) const override; -}; - /** * \brief A conveniently 64bit word-sized type that holds an imprecise representation of a GeoCoordinate. * 64 bit allow for enough precision to represent the lat/lon part of the GeoCoordinate of a GeoBin, * while also having nice properties for being used as a key to a hashmap lookup. */ using GeoBinAddress = quint64; class MapView : public QWidget, public Marble::LayerInterface { Q_OBJECT public: explicit MapView(QWidget *parent = nullptr, UsageType type = UsageType::InlineMapView); ~MapView() override = default; /** * Removes all images from the map. */ void clear(); /** * Add an image to the map. * If you fill the Map using this method, don't forget to call buildImageClusters() afterwards. * @return \c true, if the image has coordinates and was added, \c false otherwise. */ bool addImage(DB::ImageInfoPtr image); /** * @brief addImages adds images matching a search info to the map. * @param searchInfo */ void addImages(const DB::ImageSearchInfo &searchInfo); /** * @brief buildImageClusters creates the GeoClusters that are used to group images that are close to each other. * This function is automatically called by addImages(), * but you need to call it yourself when adding individual images using addImage(). */ void buildImageClusters(); /** * Sets the map's zoom so that all images on the map are visible. * If no images have been added, the zoom is not altered. */ void zoomToMarkers(); /** * Sets the state of the "Show Thumbnails" button on the map's control widget. */ void setShowThumbnails(bool state); /** * This sets the status label text and it's visibility, as well as the visibilty of the map * itself to the state indicated by the given MapStatus. */ void displayStatus(MapStatus status); GeoCoordinates::LatLonBox getRegionSelection() const; bool regionSelected() const; // LayerInterface: /** * @brief renderPosition tells the LayerManager what layers we (currently) want to paint on. * Part of the LayerInterface; called by the LayerManager. * @return */ QStringList renderPosition() const override; /** * @brief Render all markers onto the marbleWidget. * Part of the LayerInterface; called by the LayerManager. * @param painter the painter used by the LayerManager * @param viewPortParams information about the region being in view * @param renderPos the layer name * @param layer always \c nullptr * @return \c true (return value is discarded by LayerManager::renderLayers()) */ bool render(Marble::GeoPainter *painter, Marble::ViewportParams *viewPortParams, const QString &renderPos, Marble::GeoSceneLayer *) override; Q_SIGNALS: void newRegionSelected(Map::GeoCoordinates::LatLonBox coordinates); void displayStatusChanged(MapStatus); public slots: /** * Centers the map on the coordinates of the given image. */ void setCenter(const DB::ImageInfoPtr image); private slots: void saveSettings(); void setLastCenter(); void updateRegionSelection(const Marble::GeoDataLatLonBox &selection); #ifndef MARBLE_HAS_regionSelected_NEW // remove once we don't care about Marble v17.12.3 and older anymore void updateRegionSelectionOld(const QList &selection); #endif private: // Variables Marble::MarbleWidget *m_mapWidget; QLabel *m_statusLabel; QPushButton *m_setLastCenterButton; GeoCoordinates m_lastCenter; QWidget *m_kpaButtons; QWidget *m_floaters; // filled by addImage() QHash m_baseBins; QHash m_geoClusters; Marble::GeoDataLatLonBox m_markersBox; bool m_showThumbnails = true; QPixmap m_pin; Marble::GeoDataLatLonBox m_regionSelection; bool m_regionSelected = false; }; } #endif // MAPVIEW_H // vi:expandtab:tabstop=4 shiftwidth=4: