diff --git a/CMakeLists.txt b/CMakeLists.txt
index 697511d3..63d09cf7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,534 +1,535 @@
cmake_minimum_required(VERSION 3.2.0)
project(kphotoalbum VERSION 5.4)
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
)
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(KF5KDcraw)
set_package_properties(KF5KDcraw
PROPERTIES
TYPE OPTIONAL
PURPOSE "Enables RAW image support"
)
set(HAVE_KDCRAW ${KF5KDcraw_FOUND} )
find_package(KF5KGeoMap)
set_package_properties(KF5KGeoMap
PROPERTIES
TYPE OPTIONAL
PURPOSE "Enables support for geographic map location using embedded GPS information."
)
set(HAVE_KGEOMAP ${KF5KGeoMap_FOUND})
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/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)
if(KF5Kipi_FOUND)
set(libPlugins_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/documentation.h
${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
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Logging.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/QStr.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(KF5KGeoMap_FOUND)
set(libMap_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/Browser/GeoPositionPage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Browser/PositionBrowserWidget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Map/MapView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Map/MapMarkerModelHelper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Map/SearchMarkerTiler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Map/Logging.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(KF5KDcraw_FOUND)
target_link_libraries(kphotoalbum KF5::KDcraw)
endif()
if(KF5KGeoMap_FOUND)
target_link_libraries(kphotoalbum KF5::KGeoMap )
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-kgeomap.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kgeomap.h)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
# vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageDB.cpp b/DB/ImageDB.cpp
index 6262d8b4..bd1fb187 100644
--- a/DB/ImageDB.cpp
+++ b/DB/ImageDB.cpp
@@ -1,194 +1,201 @@
-/* Copyright (C) 2003-2018 Jesper K. Pedersen
+/* Copyright (C) 2003-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) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ImageDB.h"
#include "XMLDB/Database.h"
#include
#include
#include "Browser/BrowserWidget.h"
#include "DB/CategoryCollection.h"
#include
#include "NewImageFinder.h"
+#include "UIDelegate.h"
#include
#include
#include
using namespace DB;
ImageDB* ImageDB::s_instance = nullptr;
ImageDB* DB::ImageDB::instance()
{
if ( s_instance == nullptr )
exit(0); // Either we are closing down or ImageDB::instance was called before ImageDB::setup
return s_instance;
}
-void ImageDB::setupXMLDB( const QString& configFile )
+void ImageDB::setupXMLDB(const QString &configFile, UIDelegate &delegate )
{
if (s_instance)
qFatal("ImageDB::setupXMLDB: Setup must be called only once.");
- s_instance = new XMLDB::Database( configFile );
+ s_instance = new XMLDB::Database( configFile, delegate );
connectSlots();
}
void ImageDB::deleteInstance()
{
delete s_instance;
s_instance = nullptr;
}
void ImageDB::connectSlots()
{
connect( Settings::SettingsData::instance(), SIGNAL(locked(bool,bool)), s_instance, SLOT(lockDB(bool,bool)) );
connect( &s_instance->memberMap(), SIGNAL(dirty()), s_instance, SLOT(markDirty()));
}
QString ImageDB::NONE()
{
static QString none = QString::fromLatin1("**NONE**");
return none;
}
DB::FileNameList ImageDB::currentScope(bool requireOnDisk) const
{
return search( Browser::BrowserWidget::instance()->currentContext(), requireOnDisk );
}
void ImageDB::markDirty()
{
emit dirty();
}
void ImageDB::setDateRange( const ImageDate& range, bool includeFuzzyCounts )
{
m_selectionRange = range;
m_includeFuzzyCounts = includeFuzzyCounts;
}
void ImageDB::clearDateRange()
{
m_selectionRange = ImageDate();
}
void ImageDB::slotRescan()
{
bool newImages = NewImageFinder().findImages();
if ( newImages )
markDirty();
emit totalChanged( totalCount() );
}
void ImageDB::slotRecalcCheckSums(const DB::FileNameList& inputList)
{
DB::FileNameList list = inputList;
if (list.isEmpty()) {
list = images();
md5Map()->clear();
}
bool d = NewImageFinder().calculateMD5sums( list, md5Map() );
if ( d )
markDirty();
emit totalChanged( totalCount() );
}
DB::FileNameSet DB::ImageDB::imagesWithMD5Changed()
{
MD5Map map;
bool wasCanceled;
NewImageFinder().calculateMD5sums(images(), &map, &wasCanceled);
if ( wasCanceled )
return DB::FileNameSet();
return md5Map()->diff( map );
}
+UIDelegate &DB::ImageDB::uiDelegate() const
+{
+ return m_UI;
+}
+
-ImageDB::ImageDB()
+ImageDB::ImageDB(UIDelegate &delegate)
+ : m_UI(delegate)
{
}
DB::MediaCount ImageDB::count( const ImageSearchInfo& searchInfo )
{
uint images = 0;
uint videos = 0;
for (const DB::FileName& fileName : search(searchInfo)) {
if ( info(fileName)->mediaType() == Image )
++images;
else
++videos;
}
return MediaCount( images, videos );
}
void ImageDB::slotReread( const DB::FileNameList& list, DB::ExifMode mode)
{
// Do here a reread of the exif info and change the info correctly in the database without loss of previous added data
QProgressDialog dialog( i18n("Loading information from images"),
i18n("Cancel"), 0, list.count() );
uint count=0;
for( DB::FileNameList::ConstIterator it = list.begin(); it != list.end(); ++it, ++count ) {
if ( count % 10 == 0 ) {
dialog.setValue( count ); // ensure to call setProgress(0)
qApp->processEvents( QEventLoop::AllEvents );
if ( dialog.wasCanceled() )
return;
}
QFileInfo fi( (*it).absolute() );
if (fi.exists())
info(*it)->readExif(*it, mode);
markDirty();
}
}
DB::FileName ImageDB::findFirstItemInRange(const DB::FileNameList& images,
const ImageDate& range,
bool includeRanges) const
{
DB::FileName candidate;
QDateTime candidateDateStart;
for (const DB::FileName& fileName : images) {
ImageInfoPtr iInfo = info(fileName);
ImageDate::MatchType match = iInfo->date().isIncludedIn(range);
if (match == DB::ImageDate::ExactMatch ||
(includeRanges && match == DB::ImageDate::RangeMatch)) {
if (candidate.isNull() ||
iInfo->date().start() < candidateDateStart) {
candidate = fileName;
// Looking at this, can't this just be iInfo->date().start()?
// Just in the middle of refactoring other stuff, so leaving
// this alone now. TODO(hzeller): revisit.
candidateDateStart = info(candidate)->date().start();
}
}
}
return candidate;
}
/** \fn void ImageDB::renameCategory( const QString& oldName, const QString newName )
* \brief Rename category in media items stored in database.
*/
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageDB.h b/DB/ImageDB.h
index fbf4d832..b210cc6e 100644
--- a/DB/ImageDB.h
+++ b/DB/ImageDB.h
@@ -1,171 +1,174 @@
-/* Copyright (C) 2003-2010 Jesper K. Pedersen
+/* Copyright (C) 2003-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) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef IMAGEDB_H
#define IMAGEDB_H
#include
#include
#include
#include
#include
#include
class QProgressBar;
namespace DB
{
class CategoryCollection;
class Category;
class MD5Map;
class MemberMap;
class ImageSearchInfo;
class FileName;
+class UIDelegate;
class ImageDB :public QObject {
Q_OBJECT
public:
static ImageDB* instance();
- static void setupXMLDB( const QString& configFile );
+ static void setupXMLDB( const QString &configFile, UIDelegate &delegate );
static void deleteInstance();
DB::FileNameSet imagesWithMD5Changed();
public slots:
void setDateRange( const ImageDate&, bool includeFuzzyCounts );
void clearDateRange();
virtual void slotRescan();
void slotRecalcCheckSums(const DB::FileNameList& selection);
virtual MediaCount count( const ImageSearchInfo& info );
virtual void slotReread( const DB::FileNameList& list, DB::ExifMode mode);
protected:
ImageDate m_selectionRange;
bool m_includeFuzzyCounts;
ImageInfoList m_clipboard;
+ UIDelegate &m_UI;
private:
static void connectSlots();
static ImageDB* s_instance;
protected:
- ImageDB();
+ ImageDB( UIDelegate &delegate);
+ UIDelegate& uiDelegate() const;
public:
static QString NONE();
DB::FileNameList currentScope(bool requireOnDisk) const;
virtual DB::FileName findFirstItemInRange(
const FileNameList& images,
const ImageDate& range,
bool includeRanges) const;
public: // Methods that must be overridden
virtual uint totalCount() const = 0;
virtual DB::FileNameList search(const ImageSearchInfo&, bool requireOnDisk=false) const = 0;
virtual void renameCategory( const QString& oldName, const QString newName ) = 0;
virtual QMap classify( const ImageSearchInfo& info, const QString & category, MediaType typemask ) = 0;
virtual FileNameList images() = 0;
/**
* @brief addImages to the database.
* The parameter \p doUpdate decides whether all bookkeeping should be done right away
* (\c true; the "normal" use-case), or if it should be deferred until later(\c false).
* If doUpdate is deferred, either commitDelayedImages() or clearDelayedImages() needs to be called afterwards.
* @param images
* @param doUpdate
*/
virtual void addImages( const ImageInfoList& images, bool doUpdate=true ) = 0;
virtual void commitDelayedImages() = 0;
virtual void clearDelayedImages() = 0;
/** @short Update file name stored in the DB */
virtual void renameImage( const ImageInfoPtr info, const DB::FileName& newName ) = 0;
virtual void addToBlockList(const DB::FileNameList& list) = 0;
virtual bool isBlocking( const DB::FileName& fileName ) = 0;
virtual void deleteList(const DB::FileNameList& list) = 0;
virtual ImageInfoPtr info( const DB::FileName& fileName ) const = 0;
virtual MemberMap& memberMap() = 0;
virtual void save( const QString& fileName, bool isAutoSave ) = 0;
virtual MD5Map* md5Map() = 0;
virtual void sortAndMergeBackIn(const DB::FileNameList& list) = 0;
virtual CategoryCollection* categoryCollection() = 0;
virtual QExplicitlySharedDataPointer rangeCollection() = 0;
/**
* Reorder the items in the database by placing all the items given in
* cutList directly before or after the given item.
* If the parameter "after" determines where to place it.
*/
virtual void reorder(const DB::FileName& item, const DB::FileNameList& cutList, bool after) = 0;
/** @short Create a stack of images/videos/whatever
*
* If the specified images already belong to different stacks, then no
* change happens and the function returns false.
*
* If some of them are in one stack and others aren't stacked at all, then
* the unstacked will be added to the existing stack and we return true.
*
* If none of them are stacked, then a new stack is created and we return
* true.
*
* All images which previously weren't in the stack are added in order they
* are present in the provided list and after all items that are already in
* the stack. The order of images which were already in the stack is not
* changed.
* */
virtual bool stack(const DB::FileNameList& items) = 0;
/** @short Remove all images from whichever stacks they might be in
*
* We might destroy some stacks in the process if they become empty or just
* containing one image.
*
* This function doesn't touch the order of images at all.
* */
virtual void unstack(const DB::FileNameList& images) = 0;
/** @short Return a list of images which are in the same stack as the one specified.
*
* Returns an empty list when the image is not stacked.
*
* They are returned sorted according to their stackOrder.
* */
virtual DB::FileNameList getStackFor(const DB::FileName& referenceId) const = 0;
virtual void copyData( const DB::FileName& from, const DB::FileName& to) = 0;
protected slots:
virtual void lockDB( bool lock, bool exclude ) = 0;
void markDirty();
signals:
void totalChanged( uint );
void dirty();
void imagesDeleted( const DB::FileNameList& );
};
}
#endif /* IMAGEDB_H */
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/UIDelegate.cpp b/DB/UIDelegate.cpp
new file mode 100644
index 00000000..8bcaf031
--- /dev/null
+++ b/DB/UIDelegate.cpp
@@ -0,0 +1,46 @@
+/* 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 "UIDelegate.h"
+#include "Logging.h"
+
+#include
+
+DB::UIFeedback DB::UIDelegate::warningContinueCancel(const QString &msg, const QString &title, const QString &logMessage)
+{
+ qCWarning(DBLog) << logMessage;
+ return warningContinueCancel(msg, title);
+}
+
+void DB::UIDelegate::information(const QString &msg, const QString &title, const QString &logMessage)
+{
+ qCInfo(DBLog) << logMessage;
+ information(msg, title);
+}
+
+void DB::UIDelegate::warning(const QString &msg, const QString &title, const QString &logMessage)
+{
+ qCWarning(DBLog) << logMessage;
+ warning(msg, title);
+}
+
+void DB::UIDelegate::error(const QString &msg, const QString &title, const QString &logMessage)
+{
+ qCCritical(DBLog) << logMessage;
+ error(msg, title);
+}
diff --git a/DB/UIDelegate.h b/DB/UIDelegate.h
new file mode 100644
index 00000000..e44eda33
--- /dev/null
+++ b/DB/UIDelegate.h
@@ -0,0 +1,109 @@
+/* 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 UIDELEGATE_H
+#define UIDELEGATE_H
+
+#include
+
+
+namespace DB {
+
+/**
+ * @brief The UIFeedback enum enumerates all possible results of a user interaction.
+ */
+enum class UIFeedback {
+ Continue ///< The user accepts the dialog, wanting to continue with the action.
+ , Cancel ///< The user cancels the dialog, not wanting to continue with the action.
+ , DefaultAction ///< The user did not take a choice (maybe there was no user to interact with in the current context). Usually this should mean the same as Cancel, not doing any permanent changes.
+};
+
+/**
+ * @brief The UIDelegate class encapsulates possible user interaction and feedback.
+ * This class is required because in the DB core there should not be any dependency on UI classes.
+ * Therefore, the usual routine of calling KMessageBox with some parent widget does not work.
+ *
+ * The user of the DB core is expected to subclass the pure virtual functions.
+ * @see FileReader::setUIDelegate()
+ * @see FileWriter::setUIDelegate()
+ */
+class UIDelegate
+{
+public:
+ /**
+ * @brief Similar to KMessageBox::warningContinueCancel, this method displays a message and prompts the user to continue or cancel.
+ *
+ * Additionally, a non-localized logMessage is logged within the DB log category.
+ * @param msg a localized message
+ * @param title a localized title for a possible message window
+ * @param logMessage a non-localized log message
+ * @return the user choice in form of a UIFeedback
+ */
+ UIFeedback warningContinueCancel(const QString &msg, const QString &title, const QString &logMessage);
+
+ /**
+ * @brief Displays an informational message to the user.
+ *
+ * Additionally, a non-localized logMessage is logged within the DB log category.
+ * @param msg a localized message
+ * @param title a localized title for a possible message window
+ * @param logMessage a non-localized log message
+ */
+ void information(const QString &msg, const QString &title, const QString &logMessage);
+ /**
+ * @brief Displays a warning message to the user.
+ *
+ * Additionally, a non-localized logMessage is logged within the DB log category.
+ * @param msg a localized message
+ * @param title a localized title for a possible message window
+ * @param logMessage a non-localized log message
+ */
+ void warning(const QString &msg, const QString &title, const QString &logMessage);
+ /**
+ * @brief Displays an error message to the user.
+ *
+ * Additionally, a non-localized logMessage is logged within the DB log category.
+ * @param msg a localized message
+ * @param title a localized title for a possible message window
+ * @param logMessage a non-localized log message
+ */
+ void error(const QString &msg, const QString &title, const QString &logMessage);
+protected:
+ virtual ~UIDelegate() = default;
+
+ virtual UIFeedback warningContinueCancel(const QString &msg, const QString &title) = 0;
+ virtual void information(const QString &msg, const QString &title) = 0;
+ virtual void warning(const QString &msg, const QString &title) = 0;
+ virtual void error(const QString &msg, const QString &title) = 0;
+};
+
+/**
+ * @brief The DummyUIDelegate class does nothing except returning UIFeedback::DefaultAction on questions.
+ */
+class DummyUIDelegate : public UIDelegate
+{
+protected:
+ virtual UIFeedback warningContinueCancel(const QString &, const QString &) override {return UIFeedback::DefaultAction; }
+ virtual void information(const QString &, const QString &) override {}
+ virtual void warning(const QString &, const QString &) override {}
+ virtual void error(const QString &, const QString &) override {}
+};
+
+} // namespace DB
+
+#endif // UIDELEGATE_H
diff --git a/MainWindow/Window.cpp b/MainWindow/Window.cpp
index 22a327d0..c7a1dbfe 100644
--- a/MainWindow/Window.cpp
+++ b/MainWindow/Window.cpp
@@ -1,1985 +1,1987 @@
/* Copyright (C) 2003-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) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include
#include "Window.h"
#include
#ifdef HAVE_STDLIB_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for #if KIO_VERSION...
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HASKIPI
# include
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HASKIPI
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "AutoStackImages.h"
#include "BreadcrumbViewer.h"
#include "CopyPopup.h"
#include "DeleteDialog.h"
#include "DirtyIndicator.h"
#include "DuplicateMerger/DuplicateMerger.h"
#include "ExternalPopup.h"
#include "FeatureDialog.h"
#include "ImageCounter.h"
#include "InvalidDateFinder.h"
#include "Logging.h"
#include "Options.h"
#include "SearchBar.h"
#include "SplashScreen.h"
#include "StatisticsDialog.h"
#include "StatusBar.h"
#include "TokenEditor.h"
#include "UpdateVideoThumbnail.h"
#include "WelcomeDialog.h"
using namespace DB;
MainWindow::Window* MainWindow::Window::s_instance = nullptr;
MainWindow::Window::Window( QWidget* parent )
:KXmlGuiWindow( parent ),
m_annotationDialog(nullptr),
m_deleteDialog( nullptr ), m_htmlDialog(nullptr), m_tokenEditor( nullptr )
{
#ifdef HAVE_KGEOMAP
m_positionBrowser = 0;
#endif
qCDebug(MainWindowLog) << "Using icon theme: " << QIcon::themeName();
qCDebug(MainWindowLog) << "Icon search paths: " << QIcon::themeSearchPaths();
QElapsedTimer timer;
timer.start();
SplashScreen::instance()->message( i18n("Loading Database") );
s_instance = this;
bool gotConfigFile = load();
if ( !gotConfigFile )
throw 0;
qCInfo(TimingLog) << "MainWindow: Loading Database: " << timer.restart() << "ms.";
SplashScreen::instance()->message( i18n("Loading Main Window") );
QWidget* top = new QWidget( this );
QVBoxLayout* lay = new QVBoxLayout( top );
lay->setSpacing(2);
lay->setContentsMargins(2,2,2,2);
setCentralWidget( top );
m_stack = new QStackedWidget( top );
lay->addWidget( m_stack, 1 );
m_dateBar = new DateBar::DateBarWidget( top );
lay->addWidget( m_dateBar );
m_dateBarLine = new QFrame( top );
m_dateBarLine->setFrameStyle( QFrame::HLine | QFrame::Plain );
m_dateBarLine->setLineWidth(0); m_dateBarLine->setMidLineWidth(0);
QPalette pal = m_dateBarLine->palette();
pal.setColor( QPalette::WindowText, QColor("#c4c1bd") );
m_dateBarLine->setPalette( pal );
lay->addWidget( m_dateBarLine );
setHistogramVisibilty(Settings::SettingsData::instance()->showHistogram());
m_browser = new Browser::BrowserWidget( m_stack );
m_thumbnailView = new ThumbnailView::ThumbnailFacade();
m_stack->addWidget( m_browser );
m_stack->addWidget( m_thumbnailView->gui() );
m_stack->setCurrentWidget( m_browser );
m_settingsDialog = nullptr;
qCInfo(TimingLog) << "MainWindow: Loading MainWindow: " << timer.restart() << "ms.";
setupMenuBar();
qCInfo(TimingLog) << "MainWindow: setupMenuBar: " << timer.restart() << "ms.";
createSearchBar();
qCInfo(TimingLog) << "MainWindow: createSearchBar: " << timer.restart() << "ms.";
setupStatusBar();
qCInfo(TimingLog) << "MainWindow: setupStatusBar: " << timer.restart() << "ms.";
// Misc
m_autoSaveTimer = new QTimer( this );
connect(m_autoSaveTimer, &QTimer::timeout, this, &Window::slotAutoSave);
startAutoSaveTimer();
connect(m_browser, &Browser::BrowserWidget::showingOverview, this, &Window::showBrowser);
connect( m_browser, SIGNAL(pathChanged(Browser::BreadcrumbList)), m_statusBar->mp_pathIndicator, SLOT(setBreadcrumbs(Browser::BreadcrumbList)) );
connect( m_statusBar->mp_pathIndicator, SIGNAL(widenToBreadcrumb(Browser::Breadcrumb)), m_browser, SLOT(widenToBreadcrumb(Browser::Breadcrumb)) );
connect( m_browser, SIGNAL(pathChanged(Browser::BreadcrumbList)), this, SLOT(updateDateBar(Browser::BreadcrumbList)) );
connect(m_dateBar, &DateBar::DateBarWidget::dateSelected, m_thumbnailView, &ThumbnailView::ThumbnailFacade::gotoDate);
connect(m_dateBar, &DateBar::DateBarWidget::toolTipInfo, this, &Window::showDateBarTip);
connect( Settings::SettingsData::instance(), SIGNAL(histogramSizeChanged(QSize)), m_dateBar, SLOT(setHistogramBarSize(QSize)) );
connect( Settings::SettingsData::instance(), SIGNAL(actualThumbnailSizeChanged(int)), this, SLOT(slotThumbnailSizeChanged()) );
connect(m_dateBar, &DateBar::DateBarWidget::dateRangeChange, this, &Window::setDateRange);
connect(m_dateBar, &DateBar::DateBarWidget::dateRangeCleared, this, &Window::clearDateRange);
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::currentDateChanged, m_dateBar, &DateBar::DateBarWidget::setDate);
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::showImage, this, &Window::showImage);
connect( m_thumbnailView, SIGNAL(showSelection()), this, SLOT(slotView()) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::fileIdUnderCursorChanged, this, &Window::slotSetFileName);
connect( DB::ImageDB::instance(), SIGNAL(totalChanged(uint)), this, SLOT(updateDateBar()) );
connect( DB::ImageDB::instance()->categoryCollection(), SIGNAL(categoryCollectionChanged()), this, SLOT(slotOptionGroupChanged()) );
connect( m_browser, SIGNAL(imageCount(uint)), m_statusBar->mp_partial, SLOT(showBrowserMatches(uint)) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::selectionChanged, this, &Window::updateContextMenuFromSelectionSize);
checkIfMplayerIsInstalled();
executeStartupActions();
qCInfo(TimingLog) << "MainWindow: executeStartupActions " << timer.restart() << "ms.";
QTimer::singleShot( 0, this, SLOT(delayedInit()) );
updateContextMenuFromSelectionSize(0);
// Automatically save toolbar settings
setAutoSaveSettings();
qCInfo(TimingLog) << "MainWindow: misc setup time: " << timer.restart() << "ms.";
}
MainWindow::Window::~Window()
{
DB::ImageDB::deleteInstance();
ImageManager::ThumbnailCache::deleteInstance();
Exif::Database::deleteInstance();
}
void MainWindow::Window::delayedInit()
{
QElapsedTimer timer;
timer.start();
SplashScreen* splash = SplashScreen::instance();
setupPluginMenu();
qCInfo(TimingLog) << "MainWindow: setupPluginMenu: " << timer.restart() << "ms.";
if ( Settings::SettingsData::instance()->searchForImagesOnStart() ||
Options::the()->searchForImagesOnStart() ) {
splash->message( i18n("Searching for New Files") );
qApp->processEvents();
DB::ImageDB::instance()->slotRescan();
qCInfo(TimingLog) << "MainWindow: Search for New Files: " << timer.restart() << "ms.";
}
if ( !Settings::SettingsData::instance()->delayLoadingPlugins() ) {
splash->message( i18n( "Loading Plug-ins" ) );
loadPlugins();
qCInfo(TimingLog) << "MainWindow: Loading Plug-ins: " << timer.restart() << "ms.";
}
splash->done();
show();
updateDateBar();
qCInfo(TimingLog) << "MainWindow: MainWindow.show():" << timer.restart() << "ms.";
QUrl importUrl = Options::the()->importFile();
if ( importUrl.isValid() )
{
// I need to do this in delayed init to get the import window on top of the normal window
ImportExport::Import::imageImport( importUrl );
qCInfo(TimingLog) << "MainWindow: imageImport:" << timer.restart() << "ms.";
} else {
// I need to postpone this otherwise the tip dialog will not get focus on start up
KTipDialog::showTip( this );
}
Exif::Database::instance(); // Load the database
qCInfo(TimingLog) << "MainWindow: Loading Exif DB:" << timer.restart() << "ms.";
if (!Options::the()->listen().isNull())
RemoteControl::RemoteInterface::instance().listen(Options::the()->listen());
else if ( Settings::SettingsData::instance()->listenForAndroidDevicesOnStartup())
RemoteControl::RemoteInterface::instance().listen();
}
bool MainWindow::Window::slotExit()
{
if ( Options::the()->demoMode() ) {
QString txt = i18n("Delete Your Temporary Demo Database
"
"I hope you enjoyed the KPhotoAlbum demo. The demo database was copied to "
"/tmp, should it be deleted now? If you do not delete it, it will waste disk space; "
"on the other hand, if you want to come back and try the demo again, you "
"might want to keep it around with the changes you made through this session.
" );
int ret = KMessageBox::questionYesNoCancel( this, txt, i18n("Delete Demo Database"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel(),
QString::fromLatin1("deleteDemoDatabase") );
if ( ret == KMessageBox::Cancel )
return false;
else if ( ret == KMessageBox::Yes ) {
Utilities::deleteDemo();
goto doQuit;
}
else {
// pass through to the check for dirtyness.
}
}
if ( m_statusBar->mp_dirtyIndicator->isSaveDirty() ) {
int ret = KMessageBox::warningYesNoCancel( this, i18n("Do you want to save the changes?"),
i18n("Save Changes?") );
if (ret == KMessageBox::Cancel) {
return false;
}
if ( ret == KMessageBox::Yes ) {
slotSave();
}
if ( ret == KMessageBox::No ) {
QDir().remove( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml") );
}
}
// Flush any remaining thumbnails
ImageManager::ThumbnailCache::instance()->save();
doQuit:
ImageManager::AsyncLoader::instance()->requestExit();
qApp->quit();
return true;
}
void MainWindow::Window::slotOptions()
{
if ( ! m_settingsDialog ) {
m_settingsDialog = new Settings::SettingsDialog( this );
connect( m_settingsDialog, SIGNAL(changed()), this, SLOT(reloadThumbnails()) );
connect(m_settingsDialog, &Settings::SettingsDialog::changed, this, &Window::startAutoSaveTimer);
connect(m_settingsDialog, &Settings::SettingsDialog::changed, m_browser, &Browser::BrowserWidget::reload);
}
m_settingsDialog->show();
}
void MainWindow::Window::slotCreateImageStack()
{
const DB::FileNameList list = selected();
if (list.size() < 2) {
// it doesn't make sense to make a stack from one image, does it?
return;
}
bool ok = DB::ImageDB::instance()->stack( list );
if ( !ok ) {
if ( KMessageBox::questionYesNo( this,
i18n("Some of the selected images already belong to a stack. "
"Do you want to remove them from their stacks and create a "
"completely new one?"), i18n("Stacking Error")) == KMessageBox::Yes ) {
DB::ImageDB::instance()->unstack(list);
if ( ! DB::ImageDB::instance()->stack(list)) {
KMessageBox::sorry( this,
i18n("Unknown error, stack creation failed."),
i18n("Stacking Error"));
return;
}
} else {
return;
}
}
DirtyIndicator::markDirty();
// The current item might have just became invisible
m_thumbnailView->setCurrentItem(list.at(0));
m_thumbnailView->updateDisplayModel();
}
/** @short Make the selected image the head of a stack
*
* The whole point of image stacking is to group images together and then select
* one of them as the "most important". This function is (maybe just a
* temporary) way of promoting a selected image to the "head" of a stack it
* belongs to. In future, it might get replaced by a Ligtroom-like interface.
* */
void MainWindow::Window::slotSetStackHead()
{
const DB::FileNameList list = selected();
if ( list.size() != 1 ) {
// this should be checked by enabling/disabling of QActions
return;
}
setStackHead( *list.begin() );
}
void MainWindow::Window::setStackHead( const DB::FileName& image )
{
if ( ! image.info()->isStacked() )
return;
unsigned int oldOrder = image.info()->stackOrder();
DB::FileNameList others = DB::ImageDB::instance()->getStackFor(image);
Q_FOREACH( const DB::FileName& current, others ) {
if (current == image) {
current.info()->setStackOrder( 1 );
} else if ( current.info()->stackOrder() < oldOrder ) {
current.info()->setStackOrder( current.info()->stackOrder() + 1 );
}
}
DirtyIndicator::markDirty();
m_thumbnailView->updateDisplayModel();
}
void MainWindow::Window::slotUnStackImages()
{
const DB::FileNameList& list = selected();
if (list.isEmpty())
return;
DB::ImageDB::instance()->unstack(list);
DirtyIndicator::markDirty();
m_thumbnailView->updateDisplayModel();
}
void MainWindow::Window::slotConfigureAllImages()
{
configureImages( false );
}
void MainWindow::Window::slotConfigureImagesOneAtATime()
{
configureImages( true );
}
void MainWindow::Window::configureImages( bool oneAtATime )
{
const DB::FileNameList& list = selected();
if (list.isEmpty()) {
KMessageBox::sorry( this, i18n("No item is selected."), i18n("No Selection") );
}
else {
DB::ImageInfoList images;
Q_FOREACH( const DB::FileName& fileName, list) {
images.append(fileName.info());
}
configureImages( images, oneAtATime );
}
}
void MainWindow::Window::configureImages( const DB::ImageInfoList& list, bool oneAtATime )
{
s_instance->configImages( list, oneAtATime );
}
void MainWindow::Window::configImages( const DB::ImageInfoList& list, bool oneAtATime )
{
createAnnotationDialog();
if ( m_annotationDialog->configure( list, oneAtATime ) == QDialog::Rejected )
return;
reloadThumbnails( ThumbnailView::MaintainSelection );
}
void MainWindow::Window::slotSearch()
{
createAnnotationDialog();
DB::ImageSearchInfo searchInfo = m_annotationDialog->search();
if ( !searchInfo.isNull() )
m_browser->addSearch( searchInfo );
}
void MainWindow::Window::createAnnotationDialog()
{
Utilities::ShowBusyCursor dummy;
if ( !m_annotationDialog.isNull() )
return;
m_annotationDialog = new AnnotationDialog::Dialog( nullptr );
connect(m_annotationDialog.data(), &AnnotationDialog::Dialog::imageRotated, this, &Window::slotImageRotated);
}
void MainWindow::Window::slotSave()
{
Utilities::ShowBusyCursor dummy;
m_statusBar->showMessage(i18n("Saving..."), 5000 );
DB::ImageDB::instance()->save( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1("index.xml"), false );
ImageManager::ThumbnailCache::instance()->save();
m_statusBar->mp_dirtyIndicator->saved();
QDir().remove( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml") );
m_statusBar->showMessage(i18n("Saving... Done"), 5000 );
}
void MainWindow::Window::slotDeleteSelected()
{
if ( ! m_deleteDialog )
m_deleteDialog = new DeleteDialog( this );
if ( m_deleteDialog->exec( selected() ) != QDialog::Accepted )
return;
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotCopySelectedURLs()
{
QList urls; int urlcount = 0;
Q_FOREACH(const DB::FileName &fileName, selected()) {
urls.append( QUrl::fromLocalFile(fileName.absolute()) );
urlcount++;
}
if (urlcount == 1) m_paste->setEnabled (true); else m_paste->setEnabled(false);
QMimeData* mimeData = new QMimeData;
mimeData->setUrls(urls);
QApplication::clipboard()->setMimeData( mimeData );
}
void MainWindow::Window::slotPasteInformation()
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
// Idealy this would look like
// QList urls;
// urls.fromMimeData(mimeData);
// if ( urls.count() != 1 ) return;
// const QString string = urls.first().path();
QString string = mimeData->text();
// fail silent if more than one image is in clipboard.
if (string.count(QString::fromLatin1("\n")) != 0) return;
const QString urlHead = QLatin1String("file://");
if (string.startsWith(urlHead)) {
string = string.right(string.size()-urlHead.size());
}
const DB::FileName fileName = DB::FileName::fromAbsolutePath(string);
// fail silent if there is no file.
if (fileName.isNull()) return;
MD5 originalSum = MD5Sum( fileName );
ImageInfoPtr originalInfo;
if ( DB::ImageDB::instance()->md5Map()->contains( originalSum ) ) {
originalInfo = DB::ImageDB::instance()->info( fileName );
} else {
originalInfo = fileName.info();
}
// fail silent if there is no info for the file.
if (!originalInfo) return;
Q_FOREACH(const DB::FileName& newFile, selected()) {
newFile.info()->copyExtraData(*originalInfo, false);
}
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotReReadExifInfo()
{
DB::FileNameList files = selectedOnDisk();
static Exif::ReReadDialog* dialog = nullptr;
if ( ! dialog )
dialog = new Exif::ReReadDialog( this );
if ( dialog->exec( files ) == QDialog::Accepted )
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotAutoStackImages()
{
const DB::FileNameList list = selected();
if (list.isEmpty()) {
KMessageBox::sorry( this, i18n("No item is selected."), i18n("No Selection") );
return;
}
QPointer stacker = new AutoStackImages( this, list );
if ( stacker->exec() == QDialog::Accepted )
showThumbNails();
delete stacker;
}
/**
* In thumbnail mode, return a list of files that are selected.
* Otherwise, return all images in the current scope/context.
*/
DB::FileNameList MainWindow::Window::selected( ThumbnailView::SelectionMode mode) const
{
if ( m_thumbnailView->gui() == m_stack->currentWidget() )
return m_thumbnailView->selection(mode);
else
// return all images in the current scope (parameter false: include images not on disk)
return DB::ImageDB::instance()->currentScope(false);
}
void MainWindow::Window::slotViewNewWindow()
{
slotView( false, false );
}
/*
* Returns a list of files that are both selected and on disk. If there are no
* selected files, returns all files form current context that are on disk.
* Note: On some setups (NFS), this can be a very time-consuming method!
* */
DB::FileNameList MainWindow::Window::selectedOnDisk()
{
const DB::FileNameList list = selected(ThumbnailView::NoExpandCollapsedStacks);
DB::FileNameList listOnDisk;
Q_FOREACH(const DB::FileName& fileName, list) {
if (DB::ImageInfo::imageOnDisk(fileName))
listOnDisk.append(fileName);
}
return listOnDisk;
}
void MainWindow::Window::slotView( bool reuse, bool slideShow, bool random )
{
launchViewer(selected(ThumbnailView::NoExpandCollapsedStacks), reuse, slideShow, random );
}
void MainWindow::Window::launchViewer(const DB::FileNameList& inputMediaList, bool reuse, bool slideShow, bool random)
{
DB::FileNameList mediaList = inputMediaList;
int seek = -1;
if (mediaList.isEmpty()) {
mediaList = m_thumbnailView->imageList( ThumbnailView::ViewOrder );
} else if (mediaList.size() == 1) {
// we fake it so it appears the user has selected all images
// and magically scrolls to the originally selected one
const DB::FileName first = mediaList.at(0);
mediaList = m_thumbnailView->imageList( ThumbnailView::ViewOrder );
seek = mediaList.indexOf(first);
}
if (mediaList.isEmpty())
mediaList = DB::ImageDB::instance()->currentScope( false );
if (mediaList.isEmpty()) {
KMessageBox::sorry( this, i18n("There are no images to be shown.") );
return;
}
if (random) {
mediaList = DB::FileNameList(Utilities::shuffleList(mediaList));
}
Viewer::ViewerWidget* viewer;
if ( reuse && Viewer::ViewerWidget::latest() ) {
viewer = Viewer::ViewerWidget::latest();
viewer->raise();
viewer->activateWindow();
}
else
viewer = new Viewer::ViewerWidget(Viewer::ViewerWidget::ViewerWindow,
&m_viewerInputMacros);
connect(viewer, &Viewer::ViewerWidget::soughtTo, m_thumbnailView, &ThumbnailView::ThumbnailFacade::changeSingleSelection);
connect(viewer, &Viewer::ViewerWidget::imageRotated, this, &Window::slotImageRotated);
viewer->show( slideShow );
viewer->load( mediaList, seek < 0 ? 0 : seek );
viewer->raise();
}
void MainWindow::Window::slotSortByDateAndTime()
{
DB::ImageDB::instance()->sortAndMergeBackIn(selected());
showThumbNails( DB::ImageDB::instance()->search( Browser::BrowserWidget::instance()->currentContext()));
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotSortAllByDateAndTime()
{
DB::ImageDB::instance()->sortAndMergeBackIn(DB::ImageDB::instance()->images());
if ( m_thumbnailView->gui() == m_stack->currentWidget() )
showThumbNails( DB::ImageDB::instance()->search( Browser::BrowserWidget::instance()->currentContext()));
DirtyIndicator::markDirty();
}
QString MainWindow::Window::welcome()
{
QString configFileName;
QPointer dialog = new WelcomeDialog( this );
// exit if the user dismissed the welcome dialog
if (!dialog->exec())
{
qApp->quit();
}
configFileName = dialog->configFileName();
delete dialog;
return configFileName;
}
void MainWindow::Window::closeEvent( QCloseEvent* e )
{
bool quit = true;
quit = slotExit();
// If I made it here, then the user canceled
if ( !quit )
e->ignore();
else
e->setAccepted(true);
}
void MainWindow::Window::slotLimitToSelected()
{
Utilities::ShowBusyCursor dummy;
showThumbNails( selected() );
}
void MainWindow::Window::setupMenuBar()
{
// File menu
KStandardAction::save( this, SLOT(slotSave()), actionCollection() );
KStandardAction::quit( this, SLOT(slotExit()), actionCollection() );
m_generateHtml = actionCollection()->addAction( QString::fromLatin1("exportHTML") );
m_generateHtml->setText( i18n("Generate HTML...") );
connect(m_generateHtml, &QAction::triggered, this, &Window::slotExportToHTML);
QAction* a = actionCollection()->addAction( QString::fromLatin1("import"), this, SLOT(slotImport()) );
a->setText( i18n( "Import...") );
a = actionCollection()->addAction( QString::fromLatin1("export"), this, SLOT(slotExport()) );
a->setText( i18n( "Export/Copy Images...") );
// Go menu
a = KStandardAction::back( m_browser, SLOT(back()), actionCollection() );
connect(m_browser, &Browser::BrowserWidget::canGoBack, a, &QAction::setEnabled);
a->setEnabled( false );
a = KStandardAction::forward( m_browser, SLOT(forward()), actionCollection() );
connect(m_browser, &Browser::BrowserWidget::canGoForward, a, &QAction::setEnabled);
a->setEnabled( false );
a = KStandardAction::home( m_browser, SLOT(home()), actionCollection() );
actionCollection()->setDefaultShortcut(a, Qt::CTRL + Qt::Key_Home);
connect(a, &QAction::triggered, m_dateBar, &DateBar::DateBarWidget::clearSelection);
KStandardAction::redisplay( m_browser, SLOT(go()), actionCollection() );
// The Edit menu
m_copy = KStandardAction::copy( this, SLOT(slotCopySelectedURLs()), actionCollection() );
m_paste = KStandardAction::paste( this, SLOT(slotPasteInformation()), actionCollection() );
m_paste->setEnabled(false);
m_selectAll = KStandardAction::selectAll( m_thumbnailView, SLOT(selectAll()), actionCollection() );
m_clearSelection = KStandardAction::deselect( m_thumbnailView, SLOT(clearSelection()), actionCollection() );
m_clearSelection->setEnabled(false);
KStandardAction::find( this, SLOT(slotSearch()), actionCollection() );
m_deleteSelected = actionCollection()->addAction(QString::fromLatin1("deleteSelected"));
m_deleteSelected->setText( i18nc("Delete selected images", "Delete Selected" ) );
m_deleteSelected->setIcon( QIcon::fromTheme( QString::fromLatin1("edit-delete") ) );
actionCollection()->setDefaultShortcut(m_deleteSelected, Qt::Key_Delete);
connect(m_deleteSelected, &QAction::triggered, this, &Window::slotDeleteSelected);
a = actionCollection()->addAction(QString::fromLatin1("removeTokens"), this, SLOT(slotRemoveTokens()));
a->setText( i18n("Remove Tokens...") );
a = actionCollection()->addAction(QString::fromLatin1("showListOfFiles"), this, SLOT(slotShowListOfFiles()));
a->setText( i18n("Open List of Files...")) ;
m_configOneAtATime = actionCollection()->addAction( QString::fromLatin1("oneProp"), this, SLOT(slotConfigureImagesOneAtATime()) );
m_configOneAtATime->setText( i18n( "Annotate Individual Items" ) );
actionCollection()->setDefaultShortcut(m_configOneAtATime, Qt::CTRL + Qt::Key_1);
m_configAllSimultaniously = actionCollection()->addAction( QString::fromLatin1("allProp"), this, SLOT(slotConfigureAllImages()) );
m_configAllSimultaniously->setText( i18n( "Annotate Multiple Items at a Time" ) );
actionCollection()->setDefaultShortcut(m_configAllSimultaniously, Qt::CTRL + Qt::Key_2);
m_createImageStack = actionCollection()->addAction( QString::fromLatin1("createImageStack"), this, SLOT(slotCreateImageStack()) );
m_createImageStack->setText( i18n("Merge Images into a Stack") );
actionCollection()->setDefaultShortcut(m_createImageStack, Qt::CTRL + Qt::Key_3);
m_unStackImages = actionCollection()->addAction( QString::fromLatin1("unStackImages"), this, SLOT(slotUnStackImages()) );
m_unStackImages->setText( i18n("Remove Images from Stack") );
m_setStackHead = actionCollection()->addAction( QString::fromLatin1("setStackHead"), this, SLOT(slotSetStackHead()) );
m_setStackHead->setText( i18n("Set as First Image in Stack") );
actionCollection()->setDefaultShortcut(m_setStackHead, Qt::CTRL + Qt::Key_4);
m_rotLeft = actionCollection()->addAction( QString::fromLatin1("rotateLeft"), this, SLOT(slotRotateSelectedLeft()) );
m_rotLeft->setText( i18n( "Rotate counterclockwise" ) );
actionCollection()->setDefaultShortcut(m_rotLeft, Qt::Key_7);
m_rotRight = actionCollection()->addAction( QString::fromLatin1("rotateRight"), this, SLOT(slotRotateSelectedRight()) );
m_rotRight->setText( i18n( "Rotate clockwise" ) );
actionCollection()->setDefaultShortcut(m_rotRight, Qt::Key_9);
// The Images menu
m_view = actionCollection()->addAction( QString::fromLatin1("viewImages"), this, SLOT(slotView()) );
m_view->setText( i18n("View") );
actionCollection()->setDefaultShortcut(m_view, Qt::CTRL + Qt::Key_I);
m_viewInNewWindow = actionCollection()->addAction( QString::fromLatin1("viewImagesNewWindow"), this, SLOT(slotViewNewWindow()) );
m_viewInNewWindow->setText( i18n("View (In New Window)") );
m_runSlideShow = actionCollection()->addAction( QString::fromLatin1("runSlideShow"), this, SLOT(slotRunSlideShow()) );
m_runSlideShow->setText( i18n("Run Slide Show") );
m_runSlideShow->setIcon( QIcon::fromTheme( QString::fromLatin1("view-presentation") ) );
actionCollection()->setDefaultShortcut(m_runSlideShow, Qt::CTRL + Qt::Key_R);
m_runRandomSlideShow = actionCollection()->addAction( QString::fromLatin1("runRandomizedSlideShow"), this, SLOT(slotRunRandomizedSlideShow()) );
m_runRandomSlideShow->setText( i18n( "Run Randomized Slide Show" ) );
a = actionCollection()->addAction( QString::fromLatin1("collapseAllStacks"),
m_thumbnailView, SLOT(collapseAllStacks()) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::collapseAllStacksEnabled, a, &QAction::setEnabled);
a->setEnabled(false);
a->setText( i18n("Collapse all stacks" ));
a = actionCollection()->addAction( QString::fromLatin1("expandAllStacks"),
m_thumbnailView, SLOT(expandAllStacks()) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::expandAllStacksEnabled, a, &QAction::setEnabled);
a->setEnabled(false);
a->setText( i18n("Expand all stacks" ));
QActionGroup* grp = new QActionGroup( this );
a = actionCollection()->add( QString::fromLatin1("orderIncr"), this, SLOT(slotOrderIncr()) );
a->setText( i18n("Show &Oldest First") ) ;
a->setActionGroup(grp);
a->setChecked( !Settings::SettingsData::instance()->showNewestThumbnailFirst() );
a = actionCollection()->add( QString::fromLatin1("orderDecr"), this, SLOT(slotOrderDecr()) );
a->setText( i18n("Show &Newest First") );
a->setActionGroup(grp);
a->setChecked( Settings::SettingsData::instance()->showNewestThumbnailFirst() );
m_sortByDateAndTime = actionCollection()->addAction( QString::fromLatin1("sortImages"), this, SLOT(slotSortByDateAndTime()) );
m_sortByDateAndTime->setText( i18n("Sort Selected by Date && Time") );
m_limitToMarked = actionCollection()->addAction( QString::fromLatin1("limitToMarked"), this, SLOT(slotLimitToSelected()) );
m_limitToMarked->setText( i18n("Limit View to Selection") );
m_jumpToContext = actionCollection()->addAction( QString::fromLatin1("jumpToContext"), this, SLOT(slotJumpToContext()) );
m_jumpToContext->setText( i18n("Jump to Context") );
actionCollection()->setDefaultShortcut(m_jumpToContext, Qt::CTRL + Qt::Key_J);
m_jumpToContext->setIcon( QIcon::fromTheme( QString::fromLatin1( "kphotoalbum" ) ) ); // icon suggestion: go-jump (don't know the exact meaning though, so I didn't replace it right away
m_lock = actionCollection()->addAction( QString::fromLatin1("lockToDefaultScope"), this, SLOT(lockToDefaultScope()) );
m_lock->setText( i18n("Lock Images") );
m_unlock = actionCollection()->addAction( QString::fromLatin1("unlockFromDefaultScope"), this, SLOT(unlockFromDefaultScope()) );
m_unlock->setText( i18n("Unlock") );
a = actionCollection()->addAction( QString::fromLatin1("changeScopePasswd"), this, SLOT(changePassword()) );
a->setText( i18n("Change Password...") );
actionCollection()->setDefaultShortcut(a, 0);
m_setDefaultPos = actionCollection()->addAction( QString::fromLatin1("setDefaultScopePositive"), this, SLOT(setDefaultScopePositive()) );
m_setDefaultPos->setText( i18n("Lock Away All Other Items") );
m_setDefaultNeg = actionCollection()->addAction( QString::fromLatin1("setDefaultScopeNegative"), this, SLOT(setDefaultScopeNegative()) );
m_setDefaultNeg->setText( i18n("Lock Away Current Set of Items") );
// Maintenance
a = actionCollection()->addAction( QString::fromLatin1("findUnavailableImages"), this, SLOT(slotShowNotOnDisk()) );
a->setText( i18n("Display Images and Videos Not on Disk") );
a = actionCollection()->addAction( QString::fromLatin1("findImagesWithInvalidDate"), this, SLOT(slotShowImagesWithInvalidDate()) );
a->setText( i18n("Display Images and Videos with Incomplete Dates...") );
#ifdef DOES_STILL_NOT_WORK_IN_KPA4
a = actionCollection()->addAction( QString::fromLatin1("findImagesWithChangedMD5Sum"), this, SLOT(slotShowImagesWithChangedMD5Sum()) );
a->setText( i18n("Display Images and Videos with Changed MD5 Sum") );
#endif //DOES_STILL_NOT_WORK_IN_KPA4
a = actionCollection()->addAction( QLatin1String("mergeDuplicates"), this, SLOT(mergeDuplicates()));
a->setText(i18n("Merge duplicates"));
a = actionCollection()->addAction( QString::fromLatin1("rebuildMD5s"), this, SLOT(slotRecalcCheckSums()) );
a->setText( i18n("Recalculate Checksum") );
a = actionCollection()->addAction( QString::fromLatin1("rescan"), DB::ImageDB::instance(), SLOT(slotRescan()) );
a->setIcon(QIcon::fromTheme( QString::fromLatin1( "document-import" ) ));
a->setText( i18n("Rescan for Images and Videos") );
QAction* recreateExif = actionCollection()->addAction( QString::fromLatin1( "recreateExifDB" ), this, SLOT(slotRecreateExifDB()) );
recreateExif->setText( i18n("Recreate Exif Search Database") );
QAction* rereadExif = actionCollection()->addAction( QString::fromLatin1("reReadExifInfo"), this, SLOT(slotReReadExifInfo()) );
rereadExif->setText( i18n("Read Exif Info from Files...") );
m_sortAllByDateAndTime = actionCollection()->addAction( QString::fromLatin1("sortAllImages"), this, SLOT(slotSortAllByDateAndTime()) );
m_sortAllByDateAndTime->setText( i18n("Sort All by Date && Time") );
m_sortAllByDateAndTime->setEnabled(true);
m_AutoStackImages = actionCollection()->addAction( QString::fromLatin1( "autoStack" ), this, SLOT (slotAutoStackImages()) );
m_AutoStackImages->setText( i18n("Automatically Stack Selected Images...") );
a = actionCollection()->addAction( QString::fromLatin1("buildThumbs"), this, SLOT(slotBuildThumbnails()) );
a->setText( i18n("Build Thumbnails") );
a = actionCollection()->addAction( QString::fromLatin1("statistics"), this, SLOT(slotStatistics()) );
a->setText( i18n("Statistics...") );
m_markUntagged = actionCollection()->addAction(QString::fromUtf8("markUntagged"),
this, SLOT(slotMarkUntagged()));
m_markUntagged->setText(i18n("Mark As Untagged"));
// Settings
KStandardAction::preferences( this, SLOT(slotOptions()), actionCollection() );
KStandardAction::keyBindings( this, SLOT(slotConfigureKeyBindings()), actionCollection() );
KStandardAction::configureToolbars( this, SLOT(slotConfigureToolbars()), actionCollection() );
a = actionCollection()->addAction( QString::fromLatin1("readdAllMessages"), this, SLOT(slotReenableMessages()) );
a->setText( i18n("Enable All Messages") );
m_viewMenu = actionCollection()->add( QString::fromLatin1("configureView") );
m_viewMenu->setText( i18n("Configure Current View") );
m_viewMenu->setIcon( QIcon::fromTheme( QString::fromLatin1( "view-list-details" ) ) );
m_viewMenu->setDelayed( false );
QActionGroup* viewGrp = new QActionGroup( this );
viewGrp->setExclusive( true );
m_smallListView = actionCollection()->add( QString::fromLatin1("smallListView"), m_browser, SLOT(slotSmallListView()) );
m_smallListView->setText( i18n("Tree") );
m_viewMenu->addAction( m_smallListView );
m_smallListView->setActionGroup( viewGrp );
m_largeListView = actionCollection()->add( QString::fromLatin1("largelistview"), m_browser, SLOT(slotLargeListView()) );
m_largeListView->setText( i18n("Tree with User Icons") );
m_viewMenu->addAction( m_largeListView );
m_largeListView->setActionGroup( viewGrp );
m_largeIconView = actionCollection()->add( QString::fromLatin1("largeiconview"), m_browser, SLOT(slotLargeIconView()) );
m_largeIconView->setText( i18n("Icons") );
m_viewMenu->addAction( m_largeIconView );
m_largeIconView->setActionGroup( viewGrp );
connect(m_browser, &Browser::BrowserWidget::isViewChangeable, viewGrp, &QActionGroup::setEnabled);
connect(m_browser, &Browser::BrowserWidget::currentViewTypeChanged, this, &Window::slotUpdateViewMenu);
// The help menu
KStandardAction::tipOfDay( this, SLOT(showTipOfDay()), actionCollection() );
a = actionCollection()->add( QString::fromLatin1("showToolTipOnImages") );
a->setText( i18n("Show Tooltips in Thumbnails Window") );
actionCollection()->setDefaultShortcut(a, Qt::CTRL + Qt::Key_T);
connect(a, &QAction::toggled, m_thumbnailView, &ThumbnailView::ThumbnailFacade::showToolTipsOnImages);
a = actionCollection()->addAction( QString::fromLatin1("runDemo"), this, SLOT(runDemo()) );
a->setText( i18n("Run KPhotoAlbum Demo") );
a = actionCollection()->addAction( QString::fromLatin1("features"), this, SLOT(showFeatures()) );
a->setText( i18n("KPhotoAlbum Feature Status") );
a = actionCollection()->addAction( QString::fromLatin1("showVideo"), this, SLOT(showVideos()) );
a->setText( i18n( "Show Demo Videos") );
// Context menu actions
m_showExifDialog = actionCollection()->addAction( QString::fromLatin1("showExifInfo"), this, SLOT(slotShowExifInfo()) );
m_showExifDialog->setText( i18n("Show Exif Info") );
m_recreateThumbnails = actionCollection()->addAction( QString::fromLatin1("recreateThumbnails"), m_thumbnailView, SLOT(slotRecreateThumbnail()) );
m_recreateThumbnails->setText( i18n("Recreate Selected Thumbnails") );
m_useNextVideoThumbnail = actionCollection()->addAction( QString::fromLatin1("useNextVideoThumbnail"), this, SLOT(useNextVideoThumbnail()));
m_useNextVideoThumbnail->setText(i18n("Use next video thumbnail"));
actionCollection()->setDefaultShortcut(m_useNextVideoThumbnail, Qt::CTRL + Qt::Key_Plus);
m_usePreviousVideoThumbnail = actionCollection()->addAction( QString::fromLatin1("usePreviousVideoThumbnail"), this, SLOT(usePreviousVideoThumbnail()));
m_usePreviousVideoThumbnail->setText(i18n("Use previous video thumbnail"));
actionCollection()->setDefaultShortcut(m_usePreviousVideoThumbnail, Qt::CTRL + Qt::Key_Minus);
createGUI( QString::fromLatin1( "kphotoalbumui.rc" ) );
}
void MainWindow::Window::slotExportToHTML()
{
if ( ! m_htmlDialog )
m_htmlDialog = new HTMLGenerator::HTMLDialog( this );
m_htmlDialog->exec(selectedOnDisk());
}
void MainWindow::Window::startAutoSaveTimer()
{
int i = Settings::SettingsData::instance()->autoSave();
m_autoSaveTimer->stop();
if ( i != 0 ) {
m_autoSaveTimer->start( i * 1000 * 60 );
}
}
void MainWindow::Window::slotAutoSave()
{
if ( m_statusBar->mp_dirtyIndicator->isAutoSaveDirty() ) {
Utilities::ShowBusyCursor dummy;
m_statusBar->showMessage(i18n("Auto saving...."));
DB::ImageDB::instance()->save( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml"), true );
ImageManager::ThumbnailCache::instance()->save();
m_statusBar->showMessage(i18n("Auto saving.... Done"), 5000);
m_statusBar->mp_dirtyIndicator->autoSaved();
}
}
void MainWindow::Window::showThumbNails()
{
m_statusBar->showThumbnailSlider();
reloadThumbnails( ThumbnailView::ClearSelection );
m_stack->setCurrentWidget( m_thumbnailView->gui() );
m_thumbnailView->gui()->setFocus();
updateStates( true );
}
void MainWindow::Window::showBrowser()
{
m_statusBar->clearMessage();
m_statusBar->hideThumbnailSlider();
m_stack->setCurrentWidget( m_browser );
m_browser->setFocus();
updateContextMenuFromSelectionSize( 0 );
updateStates( false );
}
void MainWindow::Window::slotOptionGroupChanged()
{
// FIXME: What if annotation dialog is open? (if that's possible)
delete m_annotationDialog;
m_annotationDialog = nullptr;
DirtyIndicator::markDirty();
}
void MainWindow::Window::showTipOfDay()
{
KTipDialog::showTip( this, QString(), true );
}
void MainWindow::Window::runDemo()
{
KProcess* process = new KProcess;
*process << QLatin1String("kphotoalbum") << QLatin1String("--demo");
process->startDetached();
}
bool MainWindow::Window::load()
{
// Let first try to find a config file.
QString configFile;
QUrl dbFileUrl = Options::the()->dbFile();
if ( !dbFileUrl.isEmpty() && dbFileUrl.isLocalFile() )
{
configFile = dbFileUrl.toLocalFile();
}
else if ( Options::the()->demoMode() )
{
configFile = Utilities::setupDemo();
}
else {
bool showWelcome = false;
KConfigGroup config = KSharedConfig::openConfig()->group(QString::fromUtf8("General"));
if ( config.hasKey( QString::fromLatin1("imageDBFile") ) ) {
configFile = config.readEntry( QString::fromLatin1("imageDBFile"), QString() );
if ( !QFileInfo( configFile ).exists() )
showWelcome = true;
}
else
showWelcome = true;
if ( showWelcome ) {
SplashScreen::instance()->hide();
configFile = welcome();
}
}
if ( configFile.isNull() )
return false;
if (configFile.startsWith( QString::fromLatin1( "~" ) ) )
configFile = QDir::home().path() + QString::fromLatin1( "/" ) + configFile.mid(1);
// To avoid a race conditions where both the image loader thread creates an instance of
// Settings, and where the main thread crates an instance, we better get it created now.
Settings::SettingsData::setup( QFileInfo( configFile ).absolutePath() );
if ( Settings::SettingsData::instance()->showSplashScreen() ) {
SplashScreen::instance()->show();
qApp->processEvents();
}
// Doing some validation on user provided index file
if ( Options::the()->dbFile().isValid() ) {
QFileInfo fi( configFile );
if ( !fi.dir().exists() ) {
KMessageBox::error( this, i18n("Could not open given index.xml as provided directory does not exist.
%1
",
fi.absolutePath()) );
return false;
}
// We use index.xml as the XML backend, thus we want to test for exactly it
fi.setFile( QString::fromLatin1( "%1/index.xml" ).arg( fi.dir().absolutePath() ) );
if ( !fi.exists() ) {
int answer = KMessageBox::questionYesNo(this,i18n("Given index file does not exist, do you want to create following?"
"
%1/index.xml
", fi.absolutePath() ) );
if (answer != KMessageBox::Yes)
return false;
}
configFile = fi.absoluteFilePath();
}
- DB::ImageDB::setupXMLDB( configFile );
+ auto delegate = new DB::DummyUIDelegate; // FIXME: leaks memory for now
+ DB::ImageDB::setupXMLDB( configFile, *delegate );
// some sanity checks:
if ( ! Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()
&& ! (Settings::SettingsData::instance()->untaggedCategory().isEmpty()
&& Settings::SettingsData::instance()->untaggedTag().isEmpty() )
&& ! Options::the()->demoMode() )
{
KMessageBox::error( this, i18n(
"You have configured a tag for untagged images, but either the tag itself "
"or its category does not exist in the database.
"
"Please review your untagged tag setting under "
"Settings|Configure KPhotoAlbum...|Categories
"));
}
return true;
}
void MainWindow::Window::contextMenuEvent( QContextMenuEvent* e )
{
if ( m_stack->currentWidget() == m_thumbnailView->gui() ) {
QMenu menu( this );
menu.addAction( m_configOneAtATime );
menu.addAction( m_configAllSimultaniously );
menu.addSeparator();
menu.addAction( m_createImageStack );
menu.addAction( m_unStackImages );
menu.addAction( m_setStackHead );
menu.addSeparator();
menu.addAction( m_runSlideShow );
menu.addAction(m_runRandomSlideShow );
menu.addAction( m_showExifDialog);
menu.addSeparator();
menu.addAction(m_rotLeft);
menu.addAction(m_rotRight);
menu.addAction(m_recreateThumbnails);
menu.addAction(m_useNextVideoThumbnail);
menu.addAction(m_usePreviousVideoThumbnail);
m_useNextVideoThumbnail->setEnabled(anyVideosSelected());
m_usePreviousVideoThumbnail->setEnabled(anyVideosSelected());
menu.addSeparator();
menu.addAction(m_view);
menu.addAction(m_viewInNewWindow);
// "Invoke external program"
ExternalPopup externalCommands { &menu };
DB::ImageInfoPtr info = m_thumbnailView->mediaIdUnderCursor().info();
externalCommands.populate( info, selected());
QAction* action = menu.addMenu( &externalCommands );
if (!info && selected().isEmpty())
action->setEnabled( false );
QUrl selectedFile;
if (info)
selectedFile = QUrl::fromLocalFile(info->fileName().absolute());
QList allSelectedFiles;
for (const QString &selectedPath : selected().toStringList(DB::AbsolutePath)) {
allSelectedFiles << QUrl::fromLocalFile(selectedPath);
}
// "Copy image(s) to ..."
CopyPopup copyMenu (&menu, selectedFile, allSelectedFiles, m_lastTarget, CopyPopup::Copy);
QAction *copyAction = menu.addMenu(©Menu);
if (!info && selected().isEmpty()) {
copyAction->setEnabled(false);
}
// "Link image(s) to ..."
CopyPopup linkMenu (&menu, selectedFile, allSelectedFiles, m_lastTarget, CopyPopup::Link);
QAction *linkAction = menu.addMenu(&linkMenu);
if (!info && selected().isEmpty()) {
linkAction->setEnabled(false);
}
menu.exec( QCursor::pos() );
}
e->setAccepted(true);
}
void MainWindow::Window::setDefaultScopePositive()
{
Settings::SettingsData::instance()->setCurrentLock( m_browser->currentContext(), false );
}
void MainWindow::Window::setDefaultScopeNegative()
{
Settings::SettingsData::instance()->setCurrentLock( m_browser->currentContext(), true );
}
void MainWindow::Window::lockToDefaultScope()
{
int i = KMessageBox::warningContinueCancel( this,
i18n( "The password protection is only a means of allowing your little sister "
"to look in your images, without getting to those embarrassing images from "
"your last party.
"
"In other words, anyone with access to the index.xml file can easily "
"circumvent this password.
"),
i18n("Password Protection"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QString::fromLatin1( "lockPassWordIsNotEncruption" ) );
if ( i == KMessageBox::Cancel )
return;
setLocked( true, false );
}
void MainWindow::Window::unlockFromDefaultScope()
{
bool OK = ( Settings::SettingsData::instance()->password().isEmpty() );
QPointer dialog = new KPasswordDialog( this );
while ( !OK ) {
dialog->setPrompt( i18n("Type in Password to Unlock") );
const int code = dialog->exec();
if ( code == QDialog::Rejected )
return;
const QString passwd = dialog->password();
OK = (Settings::SettingsData::instance()->password() == passwd);
if ( !OK )
KMessageBox::sorry( this, i18n("Invalid password.") );
}
setLocked( false, false );
delete dialog;
}
void MainWindow::Window::setLocked( bool locked, bool force, bool recount )
{
m_statusBar->setLocked( locked );
Settings::SettingsData::instance()->setLocked( locked, force );
m_lock->setEnabled( !locked );
m_unlock->setEnabled( locked );
m_setDefaultPos->setEnabled( !locked );
m_setDefaultNeg->setEnabled( !locked );
if (recount)
m_browser->reload();
}
void MainWindow::Window::changePassword()
{
bool OK = ( Settings::SettingsData::instance()->password().isEmpty() );
QPointer dialog = new KPasswordDialog;
while ( !OK ) {
dialog->setPrompt( i18n("Type in Old Password") );
const int code = dialog->exec();
if ( code == QDialog::Rejected )
return;
const QString passwd = dialog->password();
OK = (Settings::SettingsData::instance()->password() == QString(passwd));
if ( !OK )
KMessageBox::sorry( this, i18n("Invalid password.") );
}
dialog->setPrompt( i18n("Type in New Password") );
const int code = dialog->exec();
if ( code == QDialog::Accepted )
Settings::SettingsData::instance()->setPassword( dialog->password() );
delete dialog;
}
void MainWindow::Window::slotConfigureKeyBindings()
{
Viewer::ViewerWidget* viewer = new Viewer::ViewerWidget; // Do not show, this is only used to get a key configuration
KShortcutsDialog* dialog = new KShortcutsDialog();
dialog->addCollection( actionCollection(), i18n( "General" ) );
dialog->addCollection( viewer->actions(), i18n("Viewer") );
#ifdef HASKIPI
loadPlugins();
Q_FOREACH( const KIPI::PluginLoader::Info *pluginInfo, m_pluginLoader->pluginList() ) {
KIPI::Plugin* plugin = pluginInfo->plugin();
if ( plugin )
dialog->addCollection( plugin->actionCollection(),
i18nc("Add 'Plugin' prefix so that KIPI plugins are obvious in KShortcutsDialog…","Plugin: %1", pluginInfo->name()) );
}
#endif
createAnnotationDialog();
dialog->addCollection( m_annotationDialog->actions(), i18n("Annotation Dialog" ) );
dialog->configure();
delete dialog;
delete viewer;
}
void MainWindow::Window::slotSetFileName( const DB::FileName& fileName )
{
ImageInfoPtr info;
if ( fileName.isNull() )
m_statusBar->clearMessage();
else {
info = fileName.info();
if (info != ImageInfoPtr(nullptr) )
m_statusBar->showMessage( fileName.absolute(), 4000 );
}
}
void MainWindow::Window::updateContextMenuFromSelectionSize(int selectionSize)
{
m_configAllSimultaniously->setEnabled(selectionSize > 1);
m_configOneAtATime->setEnabled(selectionSize >= 1);
m_createImageStack->setEnabled(selectionSize > 1);
m_unStackImages->setEnabled(selectionSize >= 1);
m_setStackHead->setEnabled(selectionSize == 1); // FIXME: do we want to check if it's stacked here?
m_sortByDateAndTime->setEnabled(selectionSize > 1);
m_recreateThumbnails->setEnabled(selectionSize >= 1);
m_rotLeft->setEnabled(selectionSize >= 1);
m_rotRight->setEnabled(selectionSize >= 1);
m_AutoStackImages->setEnabled(selectionSize > 1);
m_markUntagged->setEnabled(selectionSize >= 1);
m_statusBar->mp_selected->setSelectionCount( selectionSize );
m_clearSelection->setEnabled(selectionSize > 0);
}
void MainWindow::Window::rotateSelected( int angle )
{
const DB::FileNameList list = selected();
if (list.isEmpty()) {
KMessageBox::sorry( this, i18n("No item is selected."),
i18n("No Selection") );
} else {
Q_FOREACH(const DB::FileName& fileName, list) {
fileName.info()->rotate(angle);
ImageManager::ThumbnailCache::instance()->removeThumbnail(fileName);
}
m_statusBar->mp_dirtyIndicator->markDirty();
}
}
void MainWindow::Window::slotRotateSelectedLeft()
{
rotateSelected( -90 );
reloadThumbnails();
}
void MainWindow::Window::slotRotateSelectedRight()
{
rotateSelected( 90 );
reloadThumbnails();
}
void MainWindow::Window::reloadThumbnails( ThumbnailView::SelectionUpdateMethod method )
{
m_thumbnailView->reload( method );
updateContextMenuFromSelectionSize( m_thumbnailView->selection().size() );
}
void MainWindow::Window::slotUpdateViewMenu( DB::Category::ViewType type )
{
if ( type == DB::Category::TreeView )
m_smallListView->setChecked( true );
else if ( type == DB::Category::ThumbedTreeView )
m_largeListView->setChecked( true );
else if ( type == DB::Category::ThumbedIconView )
m_largeIconView->setChecked( true );
}
void MainWindow::Window::slotShowNotOnDisk()
{
DB::FileNameList notOnDisk;
Q_FOREACH(const DB::FileName& fileName, DB::ImageDB::instance()->images()) {
if ( !fileName.exists() )
notOnDisk.append(fileName);
}
showThumbNails(notOnDisk);
}
void MainWindow::Window::slotShowImagesWithChangedMD5Sum()
{
#ifdef DOES_STILL_NOT_WORK_IN_KPA4
Utilities::ShowBusyCursor dummy;
StringSet changed = DB::ImageDB::instance()->imagesWithMD5Changed();
showThumbNails( changed.toList() );
#else // DOES_STILL_NOT_WORK_IN_KPA4
qFatal("Code commented out in MainWindow::Window::slotShowImagesWithChangedMD5Sum");
#endif // DOES_STILL_NOT_WORK_IN_KPA4
}
void MainWindow::Window::updateStates( bool thumbNailView )
{
m_selectAll->setEnabled( thumbNailView );
m_deleteSelected->setEnabled( thumbNailView );
m_limitToMarked->setEnabled( thumbNailView );
m_jumpToContext->setEnabled( thumbNailView );
}
void MainWindow::Window::slotRunSlideShow()
{
slotView( true, true );
}
void MainWindow::Window::slotRunRandomizedSlideShow()
{
slotView( true, true, true );
}
MainWindow::Window* MainWindow::Window::theMainWindow()
{
Q_ASSERT( s_instance );
return s_instance;
}
void MainWindow::Window::slotConfigureToolbars()
{
QPointer dlg = new KEditToolBar(guiFactory());
connect(dlg, SIGNAL(newToolbarConfig()),
SLOT(slotNewToolbarConfig()));
dlg->exec();
delete dlg;
}
void MainWindow::Window::slotNewToolbarConfig()
{
createGUI();
createSearchBar();
}
void MainWindow::Window::slotImport()
{
ImportExport::Import::imageImport();
}
void MainWindow::Window::slotExport()
{
ImportExport::Export::imageExport(selectedOnDisk());
}
void MainWindow::Window::slotReenableMessages()
{
int ret = KMessageBox::questionYesNo( this, i18n("Really enable all message boxes where you previously "
"checked the do-not-show-again check box?
" ) );
if ( ret == KMessageBox::Yes )
KMessageBox::enableAllMessages();
}
void MainWindow::Window::setupPluginMenu()
{
QMenu* menu = findChild( QString::fromLatin1("plugins") );
if ( !menu ) {
KMessageBox::error( this, i18n("KPhotoAlbum hit an internal error (missing plug-in menu in MainWindow::Window::setupPluginMenu). This indicate that you forgot to do a make install. If you did compile KPhotoAlbum yourself, then please run make install. If not, please report this as a bug.
KPhotoAlbum will continue execution, but it is not entirely unlikely that it will crash later on due to the missing make install.
" ), i18n("Internal Error") );
m_hasLoadedPlugins = true;
return; // This is no good, but lets try and continue.
}
#ifdef HASKIPI
connect(menu, &QMenu::aboutToShow, this, &Window::loadPlugins);
m_hasLoadedPlugins = false;
#else
menu->setEnabled(false);
m_hasLoadedPlugins = true;
#endif
}
void MainWindow::Window::loadPlugins()
{
#ifdef HASKIPI
Utilities::ShowBusyCursor dummy;
if ( m_hasLoadedPlugins )
return;
m_pluginInterface = new Plugins::Interface( this, QString::fromLatin1("KPhotoAlbum kipi interface") );
connect(m_pluginInterface, &Plugins::Interface::imagesChanged, this, &Window::slotImagesChanged);
QStringList ignores;
ignores << QString::fromLatin1( "CommentsEditor" )
<< QString::fromLatin1( "HelloWorld" );
m_pluginLoader = new KIPI::PluginLoader();
m_pluginLoader->setIgnoredPluginsList( ignores );
m_pluginLoader->setInterface( m_pluginInterface );
m_pluginLoader->init();
connect(m_pluginLoader, &KIPI::PluginLoader::replug, this, &Window::plug);
m_pluginLoader->loadPlugins();
// Setup signals
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::selectionChanged, this, &Window::slotSelectionChanged);
m_hasLoadedPlugins = true;
// Make sure selection is updated also when plugin loading is
// delayed. This is needed, because selection might already be
// non-empty when loading the plugins.
slotSelectionChanged(selected().size());
#endif // HASKIPI
}
void MainWindow::Window::plug()
{
#ifdef HASKIPI
unplugActionList( QString::fromLatin1("import_actions") );
unplugActionList( QString::fromLatin1("export_actions") );
unplugActionList( QString::fromLatin1("image_actions") );
unplugActionList( QString::fromLatin1("tool_actions") );
unplugActionList( QString::fromLatin1("batch_actions") );
QList importActions;
QList exportActions;
QList imageActions;
QList toolsActions;
QList batchActions;
KIPI::PluginLoader::PluginList list = m_pluginLoader->pluginList();
Q_FOREACH( const KIPI::PluginLoader::Info *pluginInfo, list ) {
KIPI::Plugin* plugin = pluginInfo->plugin();
if ( !plugin || !pluginInfo->shouldLoad() )
continue;
plugin->setup( this );
QList actions = plugin->actions();
Q_FOREACH( QAction *action, actions ) {
KIPI::Category category = plugin->category( action );
if ( category == KIPI::ImagesPlugin || category == KIPI::CollectionsPlugin )
imageActions.append( action );
else if ( category == KIPI::ImportPlugin )
importActions.append( action );
else if ( category == KIPI::ExportPlugin )
exportActions.append( action );
else if ( category == KIPI::ToolsPlugin )
toolsActions.append( action );
else if ( category == KIPI::BatchPlugin )
batchActions.append( action );
else {
qCWarning(MainWindowLog) << "Unknown category\n";
}
}
KConfigGroup group = KSharedConfig::openConfig()->group( QString::fromLatin1("Shortcuts") );
plugin->actionCollection()->importGlobalShortcuts( &group );
}
setPluginMenuState( "importplugin", importActions );
setPluginMenuState( "exportplugin", exportActions );
setPluginMenuState( "imagesplugins", imageActions );
setPluginMenuState( "batch_plugins", batchActions );
setPluginMenuState( "tool_plugins", toolsActions );
// For this to work I need to pass false as second arg for createGUI
plugActionList( QString::fromLatin1("import_actions"), importActions );
plugActionList( QString::fromLatin1("export_actions"), exportActions );
plugActionList( QString::fromLatin1("image_actions"), imageActions );
plugActionList( QString::fromLatin1("tool_actions"), toolsActions );
plugActionList( QString::fromLatin1("batch_actions"), batchActions );
#endif
}
void MainWindow::Window::setPluginMenuState( const char* name, const QList& actions )
{
QMenu* menu = findChild( QString::fromLatin1(name) );
if ( menu )
menu->setEnabled(actions.count() != 0);
}
void MainWindow::Window::slotImagesChanged( const QList& urls )
{
for( QList::ConstIterator it = urls.begin(); it != urls.end(); ++it ) {
DB::FileName fileName = DB::FileName::fromAbsolutePath((*it).path());
if ( !fileName.isNull()) {
// Plugins may report images outsite of the photodatabase
// This seems to be the case with the border image plugin, which reports the destination image
ImageManager::ThumbnailCache::instance()->removeThumbnail( fileName );
// update MD5sum:
MD5 md5sum = MD5Sum( fileName );
fileName.info()->setMD5Sum( md5sum );
}
}
m_statusBar->mp_dirtyIndicator->markDirty();
reloadThumbnails( ThumbnailView::MaintainSelection );
}
DB::ImageSearchInfo MainWindow::Window::currentContext()
{
return m_browser->currentContext();
}
QString MainWindow::Window::currentBrowseCategory() const
{
return m_browser->currentCategory();
}
void MainWindow::Window::slotSelectionChanged( int count )
{
#ifdef HASKIPI
m_pluginInterface->slotSelectionChanged( count != 0 );
#else
Q_UNUSED( count );
#endif
}
void MainWindow::Window::resizeEvent( QResizeEvent* )
{
if ( Settings::SettingsData::ready() && isVisible() )
Settings::SettingsData::instance()->setWindowGeometry( Settings::MainWindow, geometry() );
}
void MainWindow::Window::moveEvent( QMoveEvent * )
{
if ( Settings::SettingsData::ready() && isVisible() )
Settings::SettingsData::instance()->setWindowGeometry( Settings::MainWindow, geometry() );
}
void MainWindow::Window::slotRemoveTokens()
{
if ( !m_tokenEditor )
m_tokenEditor = new TokenEditor( this );
m_tokenEditor->show();
connect(m_tokenEditor, &TokenEditor::finished, m_browser, &Browser::BrowserWidget::go);
}
void MainWindow::Window::slotShowListOfFiles()
{
QStringList list = QInputDialog::getMultiLineText( this,
i18n("Open List of Files"),
i18n("You can open a set of files from KPhotoAlbum's image root by listing the files here.")
)
.split( QChar::fromLatin1('\n'), QString::SkipEmptyParts );
if ( list.isEmpty() )
return;
DB::FileNameList out;
for ( QStringList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) {
QString fileNameStr = Utilities::imageFileNameToAbsolute( *it );
if ( fileNameStr.isNull() )
continue;
const DB::FileName fileName = DB::FileName::fromAbsolutePath(fileNameStr);
if ( !fileName.isNull() )
out.append(fileName);
}
if (out.isEmpty())
KMessageBox::sorry( this, i18n("No images matching your input were found."), i18n("No Matches") );
else
showThumbNails(out);
}
void MainWindow::Window::updateDateBar( const Browser::BreadcrumbList& path )
{
static QString lastPath = QString::fromLatin1("ThisStringShouldNeverBeSeenSoWeUseItAsInitialContent");
if ( path.toString() != lastPath )
updateDateBar();
lastPath = path.toString();
}
void MainWindow::Window::updateDateBar()
{
m_dateBar->setImageDateCollection( DB::ImageDB::instance()->rangeCollection() );
}
void MainWindow::Window::slotShowImagesWithInvalidDate()
{
QPointer finder = new InvalidDateFinder( this );
if ( finder->exec() == QDialog::Accepted )
showThumbNails();
delete finder;
}
void MainWindow::Window::showDateBarTip( const QString& msg )
{
m_statusBar->showMessage( msg, 3000 );
}
void MainWindow::Window::slotJumpToContext()
{
const DB::FileName fileName =m_thumbnailView->currentItem();
if ( !fileName.isNull() ) {
m_browser->addImageView(fileName);
}
}
void MainWindow::Window::setDateRange( const DB::ImageDate& range )
{
DB::ImageDB::instance()->setDateRange( range, m_dateBar->includeFuzzyCounts() );
m_statusBar->mp_partial->showBrowserMatches( this->selected().size() );
m_browser->reload();
reloadThumbnails( ThumbnailView::MaintainSelection );
}
void MainWindow::Window::clearDateRange()
{
DB::ImageDB::instance()->clearDateRange();
m_browser->reload();
reloadThumbnails( ThumbnailView::MaintainSelection );
}
void MainWindow::Window::showThumbNails(const DB::FileNameList& items)
{
m_thumbnailView->setImageList(items);
m_statusBar->mp_partial->setMatchCount(items.size());
showThumbNails();
}
void MainWindow::Window::slotRecalcCheckSums()
{
DB::ImageDB::instance()->slotRecalcCheckSums( selected() );
}
void MainWindow::Window::slotShowExifInfo()
{
DB::FileNameList items = selectedOnDisk();
if (!items.isEmpty()) {
Exif::InfoDialog* exifDialog = new Exif::InfoDialog(items.at(0), this);
exifDialog->show();
}
}
void MainWindow::Window::showFeatures()
{
FeatureDialog dialog(this);
dialog.exec();
}
void MainWindow::Window::showImage( const DB::FileName& fileName )
{
launchViewer(DB::FileNameList() << fileName, true, false, false);
}
void MainWindow::Window::slotBuildThumbnails()
{
ImageManager::ThumbnailBuilder::instance()->buildAll( ImageManager::StartNow );
}
void MainWindow::Window::slotBuildThumbnailsIfWanted()
{
ImageManager::ThumbnailCache::instance()->flush();
if ( ! Settings::SettingsData::instance()->incrementalThumbnails())
ImageManager::ThumbnailBuilder::instance()->buildAll( ImageManager::StartDelayed );
}
void MainWindow::Window::slotOrderIncr()
{
m_thumbnailView->setSortDirection( ThumbnailView::OldestFirst );
}
void MainWindow::Window::slotOrderDecr()
{
m_thumbnailView->setSortDirection( ThumbnailView::NewestFirst );
}
void MainWindow::Window::showVideos()
{
#if (KIO_VERSION >= ((5<<16)|(31<<8)|(0)))
KRun::runUrl(QUrl(QString::fromLatin1("http://www.kphotoalbum.org/index.php?page=videos"))
, QString::fromLatin1( "text/html" )
, this
, KRun::RunFlags()
);
#else
// this signature is deprecated in newer kio versions
// TODO: remove this when we don't support Ubuntu 16.04 LTS anymore
KRun::runUrl(QUrl(QString::fromLatin1("http://www.kphotoalbum.org/index.php?page=videos"))
, QString::fromLatin1( "text/html" )
, this
);
#endif
}
void MainWindow::Window::slotStatistics()
{
static StatisticsDialog* dialog = new StatisticsDialog(this);
dialog->show();
}
void MainWindow::Window::slotMarkUntagged()
{
if (Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()) {
for (const DB::FileName& newFile : selected()) {
newFile.info()->addCategoryInfo(Settings::SettingsData::instance()->untaggedCategory(),
Settings::SettingsData::instance()->untaggedTag());
}
DirtyIndicator::markDirty();
} else {
// Note: the same dialog text is used in
// Browser::OverviewPage::activateUntaggedImagesAction(),
// so if it is changed, be sure to also change it there!
KMessageBox::information(this,
i18n("You have not yet configured which tag to use for indicating untagged images."
"
"
"Please follow these steps to do so:"
"
- In the menu bar choose Settings
"
"- From there choose Configure KPhotoAlbum
"
"- Now choose the Categories icon
"
"- Now configure section Untagged Images
"),
i18n("Feature has not been configured")
);
}
}
void MainWindow::Window::setupStatusBar()
{
m_statusBar = new MainWindow::StatusBar;
setStatusBar( m_statusBar );
setLocked( Settings::SettingsData::instance()->locked(), true, false );
connect(m_statusBar, &StatusBar::thumbnailSettingsRequested
, [this](){
this->slotOptions();
m_settingsDialog->activatePage(Settings::SettingsPage::ThumbnailsPage);
});
}
void MainWindow::Window::slotRecreateExifDB()
{
Exif::Database::instance()->recreate();
}
void MainWindow::Window::useNextVideoThumbnail()
{
UpdateVideoThumbnail::useNext(selected());
}
void MainWindow::Window::usePreviousVideoThumbnail()
{
UpdateVideoThumbnail::usePrevious(selected());
}
void MainWindow::Window::mergeDuplicates()
{
DuplicateMerger* merger = new DuplicateMerger;
merger->show();
}
void MainWindow::Window::slotThumbnailSizeChanged()
{
QString thumbnailSizeMsg = i18nc( "@info:status",
//xgettext:no-c-format
"Thumbnail width: %1px (storage size: %2px)",
Settings::SettingsData::instance()->actualThumbnailSize(),
Settings::SettingsData::instance()->thumbnailSize()
);
m_statusBar->showMessage( thumbnailSizeMsg, 4000);
}
void MainWindow::Window::createSearchBar()
{
// Set up the search tool bar
SearchBar* bar = new SearchBar( this );
bar->setLineEditEnabled(false);
bar->setObjectName(QString::fromUtf8("searchBar"));
connect(bar, &SearchBar::textChanged, m_browser, &Browser::BrowserWidget::slotLimitToMatch);
connect(bar, &SearchBar::returnPressed, m_browser, &Browser::BrowserWidget::slotInvokeSeleted);
connect(bar, &SearchBar::keyPressed, m_browser, &Browser::BrowserWidget::scrollKeyPressed);
connect(m_browser, &Browser::BrowserWidget::viewChanged, bar, &SearchBar::reset);
connect(m_browser, &Browser::BrowserWidget::isSearchable, bar, &SearchBar::setLineEditEnabled);
}
void MainWindow::Window::executeStartupActions()
{
new ImageManager::ThumbnailBuilder( m_statusBar, this );
if ( ! Settings::SettingsData::instance()->incrementalThumbnails())
ImageManager::ThumbnailBuilder::instance()->buildMissing();
connect( Settings::SettingsData::instance(), SIGNAL(thumbnailSizeChanged(int)), this, SLOT(slotBuildThumbnailsIfWanted()) );
if ( ! FeatureDialog::hasVideoThumbnailer() ) {
BackgroundTaskManager::JobManager::instance()->addJob(
new BackgroundJobs::SearchForVideosWithoutLengthInfo );
BackgroundTaskManager::JobManager::instance()->addJob(
new BackgroundJobs::SearchForVideosWithoutVideoThumbnailsJob );
}
}
void MainWindow::Window::checkIfMplayerIsInstalled()
{
if (Options::the()->demoMode())
return;
if ( !FeatureDialog::hasVideoThumbnailer() ) {
KMessageBox::information( this,
i18n("Unable to find ffmpeg or MPlayer on the system.
"
"Without either of these, KPhotoAlbum will not be able to display video thumbnails and video lengths. "
"Please install the ffmpeg or MPlayer package
"),
i18n("Video thumbnails are not available"), QString::fromLatin1("mplayerNotInstalled"));
} else {
KMessageBox::enableMessage( QString::fromLatin1("mplayerNotInstalled") );
if ( FeatureDialog::ffmpegBinary().isEmpty() && !FeatureDialog::isMplayer2() ) {
KMessageBox::information( this,
i18n("You have MPlayer installed on your system, but it is unfortunately not version 2. "
"MPlayer2 is on most systems a separate package, please install that if at all possible, "
"as that version has much better support for extracting thumbnails from videos.
"),
i18n("MPlayer is too old"), QString::fromLatin1("mplayerVersionTooOld"));
} else
KMessageBox::enableMessage( QString::fromLatin1("mplayerVersionTooOld") );
}
}
bool MainWindow::Window::anyVideosSelected() const
{
Q_FOREACH(const DB::FileName& fileName, selected()) {
if ( Utilities::isVideo(fileName))
return true;
}
return false;
}
void MainWindow::Window::setHistogramVisibilty( bool visible ) const
{
if (visible)
{
m_dateBar->show();
m_dateBarLine->show();
}
else
{
m_dateBar->hide();
m_dateBarLine->hide();
}
}
void MainWindow::Window::slotImageRotated(const DB::FileName& fileName)
{
// An image has been rotated by the annotation dialog or the viewer.
// We have to reload the respective thumbnail to get it in the right angle
ImageManager::ThumbnailCache::instance()->removeThumbnail(fileName);
}
bool MainWindow::Window::dbIsDirty() const
{
return m_statusBar->mp_dirtyIndicator->isSaveDirty();
}
#ifdef HAVE_KGEOMAP
void MainWindow::Window::showPositionBrowser()
{
Browser::PositionBrowserWidget *positionBrowser = positionBrowserWidget();
m_stack->setCurrentWidget(positionBrowser);
updateStates( false );
}
Browser::PositionBrowserWidget* MainWindow::Window::positionBrowserWidget()
{
if (m_positionBrowser == 0) {
m_positionBrowser = createPositionBrowser();
}
return m_positionBrowser;
}
Browser::PositionBrowserWidget* MainWindow::Window::createPositionBrowser()
{
Browser::PositionBrowserWidget* widget = new Browser::PositionBrowserWidget(m_stack);
m_stack->addWidget(widget);
return widget;
}
#endif
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/XMLDB/Database.cpp b/XMLDB/Database.cpp
index ddf74700..08173a12 100644
--- a/XMLDB/Database.cpp
+++ b/XMLDB/Database.cpp
@@ -1,779 +1,780 @@
-/* Copyright (C) 2003-2018 Jesper K. Pedersen
+/* Copyright (C) 2003-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) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "Database.h"
#include "FileReader.h"
#include "FileWriter.h"
#include "XMLCategory.h"
#include "XMLImageDateCollection.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using Utilities::StringSet;
namespace {
void checkForBackupFile( const QString& fileName)
{
QString backupName = QFileInfo( fileName ).absolutePath() + QString::fromLatin1("/.#") + QFileInfo( fileName ).fileName();
QFileInfo backUpFile( backupName);
QFileInfo indexFile( fileName );
if ( !backUpFile.exists() || indexFile.lastModified() > backUpFile.lastModified() || backUpFile.size() == 0 )
return;
int code = KMessageBox::questionYesNo( nullptr, i18n("Autosave file '%1' exists (size %3 KB) and is newer than '%2'. "
"Should the autosave file be used?", backupName, fileName, backUpFile.size() >> 10 ),
i18n("Found Autosave File") );
if ( code == KMessageBox::Yes ) {
QFile in( backupName );
if ( in.open( QIODevice::ReadOnly ) ) {
QFile out( fileName );
if (out.open( QIODevice::WriteOnly ) ) {
char data[1024];
int len;
while ( (len = in.read( data, 1024 ) ) )
out.write( data, len );
}
}
}
}
} // namespace
bool XMLDB::Database::s_anyImageWithEmptySize = false;
-XMLDB::Database::Database( const QString& configFile ):
- m_fileName(configFile)
+XMLDB::Database::Database(const QString& configFile , DB::UIDelegate &delegate)
+ : ImageDB(delegate)
+ , m_fileName(configFile)
{
checkForBackupFile( configFile );
FileReader reader( this );
reader.read( configFile );
m_nextStackId = reader.nextStackId();
connect( categoryCollection(), SIGNAL(itemRemoved(DB::Category*,QString)),
this, SLOT(deleteItem(DB::Category*,QString)) );
connect( categoryCollection(), SIGNAL(itemRenamed(DB::Category*,QString,QString)),
this, SLOT(renameItem(DB::Category*,QString,QString)) );
connect( categoryCollection(), SIGNAL(itemRemoved(DB::Category*,QString)),
&m_members, SLOT(deleteItem(DB::Category*,QString)) );
connect( categoryCollection(), SIGNAL(itemRenamed(DB::Category*,QString,QString)),
&m_members, SLOT(renameItem(DB::Category*,QString,QString)) );
connect( categoryCollection(), SIGNAL(categoryRemoved(QString)),
&m_members, SLOT(deleteCategory(QString)));
}
uint XMLDB::Database::totalCount() const
{
return m_images.count();
}
/**
* I was considering merging the two calls to this method (one for images, one for video), but then I
* realized that all the work is really done after the check for whether the given
* imageInfo is of the right type, and as a match can't be both, this really
* would buy me nothing.
*/
QMap XMLDB::Database::classify( const DB::ImageSearchInfo& info, const QString &category, DB::MediaType typemask )
{
QMap map;
DB::GroupCounter counter( category );
Utilities::StringSet alreadyMatched = info.findAlreadyMatched( category );
DB::ImageSearchInfo noMatchInfo = info;
QString currentMatchTxt = noMatchInfo.categoryMatchText( category );
if ( currentMatchTxt.isEmpty() )
noMatchInfo.setCategoryMatchText( category, DB::ImageDB::NONE() );
else
noMatchInfo.setCategoryMatchText( category, QString::fromLatin1( "%1 & %2" ).arg(currentMatchTxt).arg(DB::ImageDB::NONE()) );
noMatchInfo.setCacheable( false );
// Iterate through the whole database of images.
for( DB::ImageInfoListConstIterator it = m_images.constBegin(); it != m_images.constEnd(); ++it ) {
bool match = ( (*it)->mediaType() & typemask ) && !(*it)->isLocked() && info.match( *it ) && rangeInclude( *it );
if ( match ) { // If the given image is currently matched.
// Now iterate through all the categories the current image
// contains, and increase them in the map mapping from category
// to count.
StringSet items = (*it)->itemsOfCategory(category);
counter.count( items );
for( StringSet::const_iterator it2 = items.begin(); it2 != items.end(); ++it2 ) {
if ( !alreadyMatched.contains(*it2) ) // We do not want to match "Jesper & Jesper"
map[*it2]++;
}
// Find those with no other matches
if ( noMatchInfo.match( *it ) )
map[DB::ImageDB::NONE()]++;
}
}
QMap groups = counter.result();
for( QMap::iterator it= groups.begin(); it != groups.end(); ++it ) {
map[it.key()] = it.value();
}
return map;
}
void XMLDB::Database::renameCategory( const QString& oldName, const QString newName )
{
for( DB::ImageInfoListIterator it = m_images.begin(); it != m_images.end(); ++it ) {
(*it)->renameCategory( oldName, newName );
}
}
void XMLDB::Database::addToBlockList(const DB::FileNameList& list)
{
Q_FOREACH(const DB::FileName& fileName, list) {
m_blockList.insert(fileName);
}
deleteList( list );
}
void XMLDB::Database::deleteList(const DB::FileNameList& list)
{
Q_FOREACH(const DB::FileName& fileName, list) {
DB::ImageInfoPtr inf = fileName.info();
StackMap::iterator found = m_stackMap.find(inf->stackId());
if ( inf->isStacked() && found != m_stackMap.end() ) {
const DB::FileNameList origCache = found.value();
DB::FileNameList newCache;
Q_FOREACH(const DB::FileName& cacheName, origCache) {
if (fileName != cacheName)
newCache.append(cacheName);
}
if (newCache.size() <= 1) {
// we're destroying a stack
Q_FOREACH(const DB::FileName& cacheName, newCache) {
DB::ImageInfoPtr cacheInf = cacheName.info();
cacheInf->setStackId(0);
cacheInf->setStackOrder(0);
}
m_stackMap.remove( inf->stackId() );
} else {
m_stackMap.insert(inf->stackId(), newCache);
}
}
m_imageCache.remove( inf->fileName().absolute() );
m_images.remove( inf );
}
Exif::Database::instance()->remove( list );
emit totalChanged( m_images.count() );
emit imagesDeleted(list);
emit dirty();
}
void XMLDB::Database::renameItem( DB::Category* category, const QString& oldName, const QString& newName )
{
for( DB::ImageInfoListIterator it = m_images.begin(); it != m_images.end(); ++it ) {
(*it)->renameItem( category->name(), oldName, newName );
}
}
void XMLDB::Database::deleteItem( DB::Category* category, const QString& value )
{
for( DB::ImageInfoListIterator it = m_images.begin(); it != m_images.end(); ++it ) {
(*it)->removeCategoryInfo( category->name(), value );
}
}
void XMLDB::Database::lockDB( bool lock, bool exclude )
{
DB::ImageSearchInfo info = Settings::SettingsData::instance()->currentLock();
for( DB::ImageInfoListIterator it = m_images.begin(); it != m_images.end(); ++it ) {
if ( lock ) {
bool match = info.match( *it );
if ( !exclude )
match = !match;
(*it)->setLocked( match );
}
else
(*it)->setLocked( false );
}
}
void XMLDB::Database::clearDelayedImages()
{
m_delayedCache.clear();
m_delayedUpdate.clear();
}
void XMLDB::Database::forceUpdate( const DB::ImageInfoList& images )
{
// FIXME: merge stack information
DB::ImageInfoList newImages = images.sort();
if ( m_images.count() == 0 ) {
// case 1: The existing imagelist is empty.
Q_FOREACH( const DB::ImageInfoPtr& imageInfo, newImages )
m_imageCache.insert( imageInfo->fileName().absolute(), imageInfo );
m_images = newImages;
}
else if ( newImages.count() == 0 ) {
// case 2: No images to merge in - that's easy ;-)
return;
}
else if ( newImages.first()->date().start() > m_images.last()->date().start() ) {
// case 2: The new list is later than the existsing
Q_FOREACH( const DB::ImageInfoPtr& imageInfo, newImages )
m_imageCache.insert( imageInfo->fileName().absolute(), imageInfo );
m_images.appendList(newImages);
}
else if ( m_images.isSorted() ) {
// case 3: The lists overlaps, and the existsing list is sorted
Q_FOREACH( const DB::ImageInfoPtr& imageInfo, newImages )
m_imageCache.insert( imageInfo->fileName().absolute(), imageInfo );
m_images.mergeIn( newImages );
}
else{
// case 4: The lists overlaps, and the existsing list is not sorted in the overlapping range.
Q_FOREACH( const DB::ImageInfoPtr& imageInfo, newImages )
m_imageCache.insert( imageInfo->fileName().absolute(), imageInfo );
m_images.appendList( newImages );
}
}
void XMLDB::Database::addImages( const DB::ImageInfoList& images,
bool doUpdate )
{
Q_FOREACH( const DB::ImageInfoPtr& info, images ) {
info->addCategoryInfo( i18n( "Media Type" ),
info->mediaType() == DB::Image ? i18n( "Image" ) : i18n( "Video" ) );
m_delayedCache.insert( info->fileName().absolute(), info );
m_delayedUpdate << info;
}
if ( doUpdate ) {
commitDelayedImages();
}
}
void XMLDB::Database::commitDelayedImages()
{
uint imagesAdded = m_delayedUpdate.count();
if ( imagesAdded > 0 ) {
forceUpdate(m_delayedUpdate);
m_delayedCache.clear();
m_delayedUpdate.clear();
// It's the responsibility of the caller to add the Exif information.
// It's more efficient from an I/O perspective to minimize the number
// of passes over the images, and with the ability to add the Exif
// data in a transaction, there's no longer any need to read it here.
emit totalChanged( m_images.count() );
emit dirty();
}
}
void XMLDB::Database::renameImage( DB::ImageInfoPtr info, const DB::FileName& newName )
{
info->delaySavingChanges(false);
info->setFileName(newName);
}
DB::ImageInfoPtr XMLDB::Database::info( const DB::FileName& fileName ) const
{
if ( fileName.isNull() )
return DB::ImageInfoPtr();
const QString name = fileName.absolute();
if (m_imageCache.contains( name ))
return m_imageCache[name];
if (m_delayedCache.contains( name ))
return m_delayedCache[name];
Q_FOREACH( const DB::ImageInfoPtr& imageInfo, m_images )
m_imageCache.insert( imageInfo->fileName().absolute(), imageInfo );
if ( m_imageCache.contains( name ) ) {
return m_imageCache[ name ];
}
return DB::ImageInfoPtr();
}
bool XMLDB::Database::rangeInclude( DB::ImageInfoPtr info ) const
{
if (m_selectionRange.start().isNull() )
return true;
DB::ImageDate::MatchType tp = info->date().isIncludedIn( m_selectionRange );
if ( m_includeFuzzyCounts )
return ( tp == DB::ImageDate::ExactMatch || tp == DB::ImageDate::RangeMatch );
else
return ( tp == DB::ImageDate::ExactMatch );
}
DB::MemberMap& XMLDB::Database::memberMap()
{
return m_members;
}
void XMLDB::Database::save( const QString& fileName, bool isAutoSave )
{
FileWriter saver( this );
saver.save( fileName, isAutoSave );
}
DB::MD5Map* XMLDB::Database::md5Map()
{
return &m_md5map;
}
bool XMLDB::Database::isBlocking( const DB::FileName& fileName )
{
return m_blockList.contains( fileName );
}
DB::FileNameList XMLDB::Database::images()
{
return m_images.files();
}
DB::FileNameList XMLDB::Database::search(
const DB::ImageSearchInfo& info,
bool requireOnDisk) const
{
return searchPrivate( info, requireOnDisk, true );
}
DB::FileNameList XMLDB::Database::searchPrivate(
const DB::ImageSearchInfo& info,
bool requireOnDisk,
bool onlyItemsMatchingRange) const
{
// When searching for images counts for the datebar, we want matches outside the range too.
// When searching for images for the thumbnail view, we only want matches inside the range.
DB::FileNameList result;
for( DB::ImageInfoListConstIterator it = m_images.constBegin(); it != m_images.constEnd(); ++it ) {
bool match = !(*it)->isLocked() && info.match( *it ) && ( !onlyItemsMatchingRange || rangeInclude( *it ));
match &= !requireOnDisk || DB::ImageInfo::imageOnDisk( (*it)->fileName() );
if (match)
result.append((*it)->fileName());
}
return result;
}
void XMLDB::Database::sortAndMergeBackIn(const DB::FileNameList& fileNameList)
{
DB::ImageInfoList infoList;
Q_FOREACH( const DB::FileName &fileName, fileNameList )
infoList.append(fileName.info());
m_images.sortAndMergeBackIn(infoList);
}
DB::CategoryCollection* XMLDB::Database::categoryCollection()
{
return &m_categoryCollection;
}
QExplicitlySharedDataPointer XMLDB::Database::rangeCollection()
{
return QExplicitlySharedDataPointer(
new XMLImageDateCollection( searchPrivate( Browser::BrowserWidget::instance()->currentContext(), false, false)));
}
void XMLDB::Database::reorder(
const DB::FileName& item,
const DB::FileNameList& selection,
bool after)
{
Q_ASSERT(!item.isNull());
DB::ImageInfoList list = takeImagesFromSelection(selection);
insertList(item, list, after );
}
// Remove all the images from the database that match the given selection and
// return that sublist.
// This returns the selected and erased images in the order in which they appear
// in the image list itself.
DB::ImageInfoList XMLDB::Database::takeImagesFromSelection(const DB::FileNameList& selection)
{
DB::ImageInfoList result;
if (selection.isEmpty())
return result;
// iterate over all images (expensive!!) TODO: improve?
for( DB::ImageInfoListIterator it = m_images.begin(); it != m_images.end(); /**/ ) {
const DB::FileName imagefile = (*it)->fileName();
DB::FileNameList::const_iterator si = selection.begin();
// for each image, iterate over selection, break on match
for ( /**/; si != selection.end(); ++si ) {
const DB::FileName file = *si;
if ( imagefile == file ) {
break;
}
}
// if image is not in selection, simply advance to next, if not add to result and erase
if (si == selection.end()) {
++it;
} else {
result << *it;
m_imageCache.remove( (*it)->fileName().absolute() );
it = m_images.erase(it);
}
// if all images from selection are in result (size of lists is equal) break.
if (result.size() == selection.size())
break;
}
return result;
}
void XMLDB::Database::insertList(
const DB::FileName& fileName,
const DB::ImageInfoList& list,
bool after)
{
DB::ImageInfoListIterator imageIt = m_images.begin();
for( ; imageIt != m_images.end(); ++imageIt ) {
if ( (*imageIt)->fileName() == fileName ) {
break;
}
}
// since insert() inserts before iterator increment when inserting AFTER image
if ( after )
imageIt++;
for( DB::ImageInfoListConstIterator it = list.begin(); it != list.end(); ++it ) {
// the call to insert() destroys the given iterator so use the new one after the call
imageIt = m_images.insert( imageIt, *it );
m_imageCache.insert( (*it)->fileName().absolute(), *it );
// increment always to retain order of selected images
imageIt++;
}
emit dirty();
}
bool XMLDB::Database::stack(const DB::FileNameList& items)
{
unsigned int changed = 0;
QSet stacks;
QList images;
unsigned int stackOrder = 1;
Q_FOREACH(const DB::FileName& fileName, items) {
DB::ImageInfoPtr imgInfo = fileName.info();
Q_ASSERT( imgInfo );
if ( imgInfo->isStacked() ) {
stacks << imgInfo->stackId();
stackOrder = qMax( stackOrder, imgInfo->stackOrder() + 1 );
} else {
images << imgInfo;
}
}
if ( stacks.size() > 1 )
return false; // images already in different stacks -> can't stack
DB::StackID stackId = ( stacks.size() == 1 ) ? *(stacks.begin() ) : m_nextStackId++;
Q_FOREACH( DB::ImageInfoPtr info, images ) {
info->setStackOrder( stackOrder );
info->setStackId( stackId );
m_stackMap[stackId].append(info->fileName());
++changed;
++stackOrder;
}
if ( changed )
emit dirty();
return changed;
}
void XMLDB::Database::unstack(const DB::FileNameList& items)
{
Q_FOREACH(const DB::FileName& fileName, items) {
DB::FileNameList allInStack = getStackFor(fileName);
if (allInStack.size() <= 2) {
// we're destroying stack here
Q_FOREACH(const DB::FileName& stackFileName, allInStack) {
DB::ImageInfoPtr imgInfo = stackFileName.info();
Q_ASSERT( imgInfo );
if ( imgInfo->isStacked() ) {
m_stackMap.remove( imgInfo->stackId() );
imgInfo->setStackId( 0 );
imgInfo->setStackOrder( 0 );
}
}
} else {
DB::ImageInfoPtr imgInfo = fileName.info();
Q_ASSERT( imgInfo );
if ( imgInfo->isStacked() ) {
m_stackMap[imgInfo->stackId()].removeAll(fileName);
imgInfo->setStackId( 0 );
imgInfo->setStackOrder( 0 );
}
}
}
if (!items.isEmpty())
emit dirty();
}
DB::FileNameList XMLDB::Database::getStackFor(const DB::FileName& referenceImg) const
{
DB::ImageInfoPtr imageInfo = info( referenceImg );
if ( !imageInfo || ! imageInfo->isStacked() )
return DB::FileNameList();
StackMap::iterator found = m_stackMap.find(imageInfo->stackId());
if ( found != m_stackMap.end() )
return found.value();
// it wasn't in the cache -> rebuild it
m_stackMap.clear();
for( DB::ImageInfoListConstIterator it = m_images.constBegin(); it != m_images.constEnd(); ++it ) {
if ( (*it)->isStacked() ) {
DB::StackID stackid = (*it)->stackId();
m_stackMap[stackid].append((*it)->fileName());
}
}
found = m_stackMap.find(imageInfo->stackId());
if ( found != m_stackMap.end() )
return found.value();
else
return DB::FileNameList();
}
void XMLDB::Database::copyData(const DB::FileName &from, const DB::FileName &to)
{
(*info(to)).merge(*info(from));
}
int XMLDB::Database::fileVersion()
{
// File format version, bump it up every time the format for the file changes.
return 8;
}
// During profiling of loading, I found that a significant amount of time was spent in QDateTime::fromString.
// Reviewing the code, I fount that it did a lot of extra checks we don't need (like checking if the string have
// timezone information (which they won't in KPA), this function is a replacement that is faster than the original.
QDateTime dateTimeFromString(const QString& str) {
static QChar T = QChar::fromLatin1('T');
if ( str[10] == T)
return QDateTime(QDate::fromString(str.left(10), Qt::ISODate),QTime::fromString(str.mid(11),Qt::ISODate));
else
return QDateTime::fromString(str,Qt::ISODate);
}
DB::ImageInfoPtr XMLDB::Database::createImageInfo( const DB::FileName& fileName, ReaderPtr reader, Database* db, const QMap *newToOldCategory )
{
static QString _label_ = QString::fromUtf8("label");
static QString _description_ = QString::fromUtf8("description");
static QString _startDate_ = QString::fromUtf8("startDate");
static QString _endDate_ = QString::fromUtf8("endDate");
static QString _yearFrom_ = QString::fromUtf8("yearFrom");
static QString _monthFrom_ = QString::fromUtf8("monthFrom");
static QString _dayFrom_ = QString::fromUtf8("dayFrom");
static QString _hourFrom_ = QString::fromUtf8("hourFrom");
static QString _minuteFrom_ = QString::fromUtf8("minuteFrom");
static QString _secondFrom_ = QString::fromUtf8("secondFrom");
static QString _yearTo_ = QString::fromUtf8("yearTo");
static QString _monthTo_ = QString::fromUtf8("monthTo");
static QString _dayTo_ = QString::fromUtf8("dayTo");
static QString _angle_ = QString::fromUtf8("angle");
static QString _md5sum_ = QString::fromUtf8("md5sum");
static QString _width_ = QString::fromUtf8("width");
static QString _height_ = QString::fromUtf8("height");
static QString _rating_ = QString::fromUtf8("rating");
static QString _stackId_ = QString::fromUtf8("stackId");
static QString _stackOrder_ = QString::fromUtf8("stackOrder");
static QString _gpsPrec_ = QString::fromUtf8("gpsPrec");
static QString _gpsLon_ = QString::fromUtf8("gpsLon");
static QString _gpsLat_ = QString::fromUtf8("gpsLat");
static QString _gpsAlt_ = QString::fromUtf8("gpsAlt");
static QString _videoLength_ = QString::fromUtf8("videoLength");
static QString _options_ = QString::fromUtf8("options");
static QString _0_ = QString::fromUtf8("0");
static QString _minus1_ = QString::fromUtf8("-1");
static QString _MediaType_ = i18n("Media Type");
static QString _Image_ = i18n("Image");
static QString _Video_ = i18n("Video");
QString label;
if (reader->hasAttribute(_label_))
label = reader->attribute(_label_);
else
label = QFileInfo(fileName.relative()).completeBaseName();
QString description;
if ( reader->hasAttribute(_description_) )
description = reader->attribute(_description_);
DB::ImageDate date;
if ( reader->hasAttribute(_startDate_) ) {
QDateTime start;
QString str = reader->attribute( _startDate_ );
if ( !str.isEmpty() )
start = dateTimeFromString( str );
str = reader->attribute( _endDate_ );
if ( !str.isEmpty() )
date = DB::ImageDate( start, dateTimeFromString(str) );
else
date = DB::ImageDate( start );
}
else {
int yearFrom = 0, monthFrom = 0, dayFrom = 0, yearTo = 0, monthTo = 0, dayTo = 0, hourFrom = -1, minuteFrom = -1, secondFrom = -1;
yearFrom = reader->attribute( _yearFrom_, _0_ ).toInt();
monthFrom = reader->attribute( _monthFrom_, _0_ ).toInt();
dayFrom = reader->attribute( _dayFrom_, _0_ ).toInt();
hourFrom = reader->attribute( _hourFrom_, _minus1_ ).toInt();
minuteFrom = reader->attribute( _minuteFrom_, _minus1_ ).toInt();
secondFrom = reader->attribute( _secondFrom_, _minus1_ ).toInt();
yearTo = reader->attribute( _yearTo_, _0_ ).toInt();
monthTo = reader->attribute( _monthTo_, _0_ ).toInt();
dayTo = reader->attribute( _dayTo_, _0_ ).toInt();
date = DB::ImageDate( yearFrom, monthFrom, dayFrom, yearTo, monthTo, dayTo, hourFrom, minuteFrom, secondFrom );
}
int angle = reader->attribute( _angle_, _0_).toInt();
DB::MD5 md5sum(reader->attribute( _md5sum_ ));
s_anyImageWithEmptySize |= !reader->hasAttribute(_width_);
int w = reader->attribute( _width_ , _minus1_ ).toInt();
int h = reader->attribute( _height_ , _minus1_ ).toInt();
QSize size = QSize( w,h );
DB::MediaType mediaType = Utilities::isVideo(fileName) ? DB::Video : DB::Image;
short rating = reader->attribute( _rating_, _minus1_ ).toShort();
DB::StackID stackId = reader->attribute( _stackId_, _0_ ).toULong();
unsigned int stackOrder = reader->attribute( _stackOrder_, _0_ ).toULong();
DB::ImageInfo* info = new DB::ImageInfo( fileName, label, description, date,
angle, md5sum, size, mediaType, rating, stackId, stackOrder );
if ( reader->hasAttribute(_videoLength_))
info->setVideoLength(reader->attribute(_videoLength_).toInt());
DB::ImageInfoPtr result(info);
possibleLoadCompressedCategories( reader, result, db, newToOldCategory );
while( reader->readNextStartOrStopElement(_options_).isStartToken) {
readOptions( result, reader, newToOldCategory );
}
info->addCategoryInfo( _MediaType_,
info->mediaType() == DB::Image ? _Image_ : _Video_ );
return result;
}
void XMLDB::Database::readOptions( DB::ImageInfoPtr info, ReaderPtr reader, const QMap *newToOldCategory )
{
static QString _name_ = QString::fromUtf8("name");
static QString _value_ = QString::fromUtf8("value");
static QString _option_ = QString::fromUtf8("option");
static QString _area_ = QString::fromUtf8("area");
while (reader->readNextStartOrStopElement(_option_).isStartToken) {
QString name = FileReader::unescape( reader->attribute(_name_) );
// If the silent update to db version 6 has been done, use the updated category names.
if (newToOldCategory) {
name = newToOldCategory->key(name,name);
}
if ( !name.isNull() ) {
// Read values
while (reader->readNextStartOrStopElement(_value_).isStartToken) {
QString value = reader->attribute(_value_);
if (reader->hasAttribute(_area_)) {
QStringList areaData = reader->attribute(_area_).split(QString::fromUtf8(" "));
int x = areaData[0].toInt();
int y = areaData[1].toInt();
int w = areaData[2].toInt();
int h = areaData[3].toInt();
QRect area = QRect(QPoint(x, y), QPoint(x + w - 1, y + h - 1));
if (! value.isNull()) {
info->addCategoryInfo(name, value, area);
}
} else {
if (! value.isNull()) {
info->addCategoryInfo(name, value);
}
}
reader->readEndElement();
}
}
}
}
void XMLDB::Database::possibleLoadCompressedCategories( ReaderPtr reader, DB::ImageInfoPtr info, Database* db, const QMap *newToOldCategory )
{
if ( db == nullptr )
return;
Q_FOREACH( const DB::CategoryPtr categoryPtr, db->m_categoryCollection.categories() ) {
QString categoryName = categoryPtr->name();
QString oldCategoryName;
if ( newToOldCategory )
{
// translate to old categoryName, defaulting to the original name if not found:
oldCategoryName = newToOldCategory->value( categoryName, categoryName );
} else {
oldCategoryName = categoryName;
}
QString str = reader->attribute( FileWriter::escape( oldCategoryName ) );
if ( !str.isEmpty() ) {
QStringList list = str.split(QString::fromLatin1( "," ), QString::SkipEmptyParts );
Q_FOREACH( const QString &tagString, list ) {
int id = tagString.toInt();
QString name = static_cast(categoryPtr.data())->nameForId(id);
info->addCategoryInfo( categoryName, name );
}
}
}
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/XMLDB/Database.h b/XMLDB/Database.h
index a58ed673..df7e4e2d 100644
--- a/XMLDB/Database.h
+++ b/XMLDB/Database.h
@@ -1,130 +1,130 @@
-/* Copyright (C) 2003-2010 Jesper K. Pedersen
+/* Copyright (C) 2003-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) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef XMLDB_DATABASE_H
#define XMLDB_DATABASE_H
#include "DB/ImageSearchInfo.h"
#include "DB/ImageInfoList.h"
#include
#include "DB/MemberMap.h"
#include "DB/ImageDB.h"
#include "DB/Category.h"
#include "DB/CategoryCollection.h"
#include "XMLCategoryCollection.h"
#include "DB/MD5Map.h"
#include
#include
#include "FileReader.h"
namespace DB
{
class ImageInfo;
}
namespace XMLDB {
class Database :public DB::ImageDB
{
Q_OBJECT
public:
uint totalCount() const override;
DB::FileNameList search(
const DB::ImageSearchInfo&,
bool requireOnDisk=false) const override;
void renameCategory( const QString& oldName, const QString newName ) override;
QMap classify( const DB::ImageSearchInfo& info, const QString &category, DB::MediaType typemask ) override;
DB::FileNameList images() override;
void addImages( const DB::ImageInfoList& images, bool doUpdate ) override;
void commitDelayedImages() override;
void clearDelayedImages() override;
void renameImage( DB::ImageInfoPtr info, const DB::FileName& newName ) override;
void addToBlockList(const DB::FileNameList& list) override;
bool isBlocking( const DB::FileName& fileName ) override;
void deleteList(const DB::FileNameList& list) override;
DB::ImageInfoPtr info( const DB::FileName& fileName ) const override;
DB::MemberMap& memberMap() override;
void save( const QString& fileName, bool isAutoSave ) override;
DB::MD5Map* md5Map() override;
void sortAndMergeBackIn(const DB::FileNameList& idList) override;
DB::CategoryCollection* categoryCollection() override;
QExplicitlySharedDataPointer rangeCollection() override;
void reorder(
const DB::FileName& item,
const DB::FileNameList& cutList,
bool after) override;
static DB::ImageInfoPtr createImageInfo( const DB::FileName& fileName, ReaderPtr, Database* db = nullptr, const QMap *newToOldCategory = nullptr );
static void possibleLoadCompressedCategories( ReaderPtr reader , DB::ImageInfoPtr info, Database* db, const QMap *newToOldCategory = nullptr );
bool stack(const DB::FileNameList& items) override;
void unstack(const DB::FileNameList& images) override;
DB::FileNameList getStackFor(const DB::FileName& referenceId) const override;
void copyData( const DB::FileName& from, const DB::FileName& to) override;
static int fileVersion();
protected:
DB::FileNameList searchPrivate(
const DB::ImageSearchInfo&,
bool requireOnDisk,
bool onlyItemsMatchingRange) const;
bool rangeInclude( DB::ImageInfoPtr info ) const;
DB::ImageInfoList takeImagesFromSelection(const DB::FileNameList& list);
void insertList( const DB::FileName& id, const DB::ImageInfoList& list, bool after );
static void readOptions( DB::ImageInfoPtr info, ReaderPtr reader, const QMap *newToOldCategory = nullptr );
protected slots:
void renameItem( DB::Category* category, const QString& oldName, const QString& newName );
void deleteItem( DB::Category* category, const QString& option );
void lockDB( bool lock, bool exclude ) override;
private:
friend class DB::ImageDB;
friend class FileReader;
friend class FileWriter;
- Database( const QString& configFile );
+ Database(const QString &configFile, DB::UIDelegate &delegate );
void forceUpdate( const DB::ImageInfoList& );
QString m_fileName;
DB::ImageInfoList m_images;
QSet m_blockList;
DB::ImageInfoList m_missingTimes;
XMLCategoryCollection m_categoryCollection;
DB::MemberMap m_members;
DB::MD5Map m_md5map;
//QMap m_settings;
DB::StackID m_nextStackId;
typedef QMap StackMap;
mutable StackMap m_stackMap;
DB::ImageInfoList m_delayedUpdate;
mutable QHash m_imageCache;
mutable QHash m_delayedCache;
// used for checking if any images are without image attribute from the database.
static bool s_anyImageWithEmptySize;
};
}
#endif /* XMLDB_DATABASE_H */
// vi:expandtab:tabstop=4 shiftwidth=4: