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: