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: