diff --git a/AnnotationDialog/Dialog.cpp b/AnnotationDialog/Dialog.cpp
index d01e942e..3d80e469 100644
--- a/AnnotationDialog/Dialog.cpp
+++ b/AnnotationDialog/Dialog.cpp
@@ -1,1750 +1,1750 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 "Dialog.h"
#include "DescriptionEdit.h"
#include "enums.h"
#include "ImagePreviewWidget.h"
#include "DateEdit.h"
#include "ListSelect.h"
#include "Logging.h"
#include "ResizableFrame.h"
#include "ShortCutManager.h"
#include "ShowSelectionOnlyManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
#include
"),
i18n("Feature has not been configured") );
return nullptr;
}
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 648e07b3..27d2b7b2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,473 +1,473 @@
cmake_minimum_required(VERSION 3.2.0)
project(kphotoalbum VERSION 5.3)
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 REQUIRED COMPONENTS Sql Xml Widgets Network)
find_package(Phonon4Qt5 REQUIRED)
find_package(KF5 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()
find_package(Exiv2 REQUIRED)
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})
+find_package(Marble)
+set_package_properties(Marble
+ PROPERTIES
+ TYPE OPTIONAL
+ PURPOSE "Enables support for geographic map location using embedded GPS information."
+ )
+set(HAVE_MARBLE ${Marble_FOUND})
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/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/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/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/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
)
set(libPlugins_SRCS)
if(KF5Kipi_FOUND)
set(libPlugins_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Interface.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageCollection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageInfo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/CategoryImageCollection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageCollectionSelector.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Settings/PluginsPage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/UploadWidget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/UploadImageCollection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Logging.cpp
)
endif()
set(libViewer_SRCS
${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/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/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
)
set(libUtilities_SRCS
${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/Util.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
)
set(libMainWindow_SRCS
${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/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
)
set(libDB_SRCS
${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/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/FileName.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DB/FileNameList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DB/Logging.cpp
)
set(libImportExport_SRCS
${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/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
)
set(libBrowser_SRCS
${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
)
set(libExif_SRCS
${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}/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/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)
+if(Marble_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/Logging.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Map/GeoCoordinates.cpp
)
-#endif()
+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_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()
+if(Marble_FOUND)
+ target_link_libraries(kphotoalbum Marble)
+endif()
install(TARGETS kphotoalbum ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
########### install files ###############
install(PROGRAMS org.kde.kphotoalbum.desktop org.kde.kphotoalbum-import.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES kphotoalbumrc DESTINATION ${KDE_INSTALL_CONFDIR})
install(FILES tips default-setup DESTINATION ${KDE_INSTALL_DATADIR}/kphotoalbum)
install(FILES kphotoalbumui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kphotoalbum)
install(FILES org.kde.kphotoalbum.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
configure_file(config-kpa-kdcraw.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kdcraw.h)
configure_file(config-kpa-kipi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kipi.h)
-#configure_file(config-kpa-kgeomap.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kgeomap.h)
+configure_file(config-kpa-marble.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-marble.h)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
# vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageInfo.cpp b/DB/ImageInfo.cpp
index 585a9125..df427c9e 100644
--- a/DB/ImageInfo.cpp
+++ b/DB/ImageInfo.cpp
@@ -1,820 +1,820 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 "ImageInfo.h"
#include "FileInfo.h"
#include "Logging.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace DB;
ImageInfo::ImageInfo() :m_null( true ), m_rating(-1), m_stackId(0), m_stackOrder(0)
, m_videoLength(-1)
, m_locked( false ), m_dirty( false ), m_delaySaving( false )
{
}
ImageInfo::ImageInfo( const DB::FileName& fileName, MediaType type, bool readExifInfo )
: m_imageOnDisk( YesOnDisk ), m_null( false ), m_size( -1, -1 ), m_type( type )
, m_rating(-1), m_stackId(0), m_stackOrder(0)
, m_videoLength(-1)
, m_locked(false), m_delaySaving( true )
{
QFileInfo fi( fileName.absolute() );
m_label = fi.completeBaseName();
m_angle = 0;
setFileName(fileName);
// Read EXIF information
if ( readExifInfo )
readExif(fileName, EXIFMODE_INIT);
m_dirty = false;
m_delaySaving = false;
}
/** Change delaying of saving changes.
*
* Will save changes when set to false.
*
* Use this method to set multiple attributes with only one
* database operation.
*
* Example:
* \code
* info.delaySavingChanges(true);
* info.setLabel("Hello");
* info.setDescription("Hello world");
* info.delaySavingChanges(false);
* \endcode
*
* \see saveChanges()
*/
void ImageInfo::delaySavingChanges(bool b)
{
m_delaySaving = b;
if (!b)
saveChanges();
}
void ImageInfo::setLabel( const QString& desc )
{
if (desc != m_label)
m_dirty = true;
m_label = desc;
saveChangesIfNotDelayed();
}
QString ImageInfo::label() const
{
return m_label;
}
void ImageInfo::setDescription( const QString& desc )
{
if (desc != m_description)
m_dirty = true;
m_description = desc.trimmed();
saveChangesIfNotDelayed();
}
QString ImageInfo::description() const
{
return m_description;
}
void ImageInfo::setCategoryInfo( const QString& key, const StringSet& value )
{
// Don't check if really changed, because it's too slow.
m_dirty = true;
m_categoryInfomation[key] = value;
saveChangesIfNotDelayed();
}
bool ImageInfo::hasCategoryInfo( const QString& key, const QString& value ) const
{
return m_categoryInfomation[key].contains(value);
}
bool DB::ImageInfo::hasCategoryInfo( const QString& key, const StringSet& values ) const
{
// OpenSuse leap 42.1 still ships with Qt 5.5
// TODO: remove this version check once we don't care about Qt 5.6 anymore...
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
return values.intersects( m_categoryInfomation[key] );
#else
StringSet tmp = values;
return ! tmp.intersect( m_categoryInfomation[key] ).isEmpty();
#endif
}
StringSet ImageInfo::itemsOfCategory( const QString& key ) const
{
return m_categoryInfomation[key];
}
void ImageInfo::renameItem( const QString& category, const QString& oldValue, const QString& newValue )
{
if (m_taggedAreas.contains(category)) {
if (m_taggedAreas[category].contains(oldValue)) {
m_taggedAreas[category][newValue] = m_taggedAreas[category][oldValue];
m_taggedAreas[category].remove(oldValue);
}
}
StringSet& set = m_categoryInfomation[category];
StringSet::iterator it = set.find( oldValue );
if ( it != set.end() ) {
m_dirty = true;
set.erase( it );
set.insert( newValue );
saveChangesIfNotDelayed();
}
}
DB::FileName ImageInfo::fileName() const
{
return m_fileName;
}
void ImageInfo::setFileName( const DB::FileName& fileName )
{
if (fileName != m_fileName)
m_dirty = true;
m_fileName = fileName;
m_imageOnDisk = Unchecked;
DB::CategoryPtr folderCategory = DB::ImageDB::instance()->categoryCollection()->
categoryForSpecial(DB::Category::FolderCategory);
if (folderCategory) {
DB::MemberMap& map = DB::ImageDB::instance()->memberMap();
createFolderCategoryItem( folderCategory, map );
//ImageDB::instance()->setMemberMap( map );
}
saveChangesIfNotDelayed();
}
void ImageInfo::rotate( int degrees, RotationMode mode )
{
// ensure positive degrees:
degrees += 360;
degrees = degrees % 360;
if ( degrees == 0 )
return;
m_dirty = true;
m_angle = ( m_angle + degrees ) % 360;
if (degrees == 90 || degrees == 270) {
m_size.transpose();
}
// the AnnotationDialog manages this by itself and sets RotateImageInfoOnly:
if ( mode == RotateImageInfoAndAreas )
{
for ( auto& areasOfCategory : m_taggedAreas )
{
for ( auto& area : areasOfCategory )
{
QRect rotatedArea;
// parameter order for QRect::setCoords:
// setCoords( left, top, right, bottom )
// keep in mind that _size is already transposed
switch (degrees) {
case 90:
rotatedArea.setCoords(
m_size.width() - area.bottom(),
area.left(),
m_size.width() - area.top(),
area.right()
);
break;
case 180:
rotatedArea.setCoords(
m_size.width() - area.right(),
m_size.height() - area.bottom(),
m_size.width() - area.left(),
m_size.height() - area.top()
);
break;
case 270:
rotatedArea.setCoords(
area.top(),
m_size.height() - area.right(),
area.bottom(),
m_size.height() - area.left()
);
break;
default:
// degrees==0; "odd" values won't happen.
rotatedArea = area;
break;
}
// update _taggedAreas[category][tag]:
area = rotatedArea;
}
}
}
saveChangesIfNotDelayed();
}
int ImageInfo::angle() const
{
return m_angle;
}
void ImageInfo::setAngle( int angle )
{
if (angle != m_angle)
m_dirty = true;
m_angle = angle;
saveChangesIfNotDelayed();
}
short ImageInfo::rating() const
{
return m_rating;
}
void ImageInfo::setRating( short rating )
{
Q_ASSERT( (rating >= 0 && rating <= 10) || rating == -1 );
if ( rating > 10 )
rating = 10;
if ( rating < -1 )
rating = -1;
if ( m_rating != rating )
m_dirty = true;
m_rating = rating;
saveChangesIfNotDelayed();
}
DB::StackID ImageInfo::stackId() const
{
return m_stackId;
}
void ImageInfo::setStackId( const DB::StackID stackId )
{
if ( stackId != m_stackId )
m_dirty = true;
m_stackId = stackId;
saveChangesIfNotDelayed();
}
unsigned int ImageInfo::stackOrder() const
{
return m_stackOrder;
}
void ImageInfo::setStackOrder( const unsigned int stackOrder )
{
if ( stackOrder != m_stackOrder )
m_dirty = true;
m_stackOrder = stackOrder;
saveChangesIfNotDelayed();
}
void ImageInfo::setVideoLength(int length)
{
if ( m_videoLength != length )
m_dirty = true;
m_videoLength = length;
saveChangesIfNotDelayed();
}
int ImageInfo::videoLength() const
{
return m_videoLength;
}
void ImageInfo::setDate( const ImageDate& date )
{
if (date != m_date)
m_dirty = true;
m_date = date;
saveChangesIfNotDelayed();
}
ImageDate& ImageInfo::date()
{
return m_date;
}
ImageDate ImageInfo::date() const
{
return m_date;
}
bool ImageInfo::operator!=( const ImageInfo& other ) const
{
return !(*this == other);
}
bool ImageInfo::operator==( const ImageInfo& other ) const
{
bool changed =
( m_fileName != other.m_fileName ||
m_label != other.m_label ||
( !m_description.isEmpty() && !other.m_description.isEmpty() && m_description != other.m_description ) || // one might be isNull.
m_date != other.m_date ||
m_angle != other.m_angle ||
m_rating != other.m_rating ||
( m_stackId != other.m_stackId ||
! ( ( m_stackId == 0 ) ? true :
( m_stackOrder == other.m_stackOrder ) ) )
);
if ( !changed ) {
QStringList keys = DB::ImageDB::instance()->categoryCollection()->categoryNames();
for( QStringList::ConstIterator it = keys.constBegin(); it != keys.constEnd(); ++it )
changed |= m_categoryInfomation[*it] != other.m_categoryInfomation[*it];
}
return !changed;
}
void ImageInfo::renameCategory( const QString& oldName, const QString& newName )
{
m_dirty = true;
m_categoryInfomation[newName] = m_categoryInfomation[oldName];
m_categoryInfomation.remove(oldName);
m_taggedAreas[newName] = m_taggedAreas[oldName];
m_taggedAreas.remove(oldName);
saveChangesIfNotDelayed();
}
void ImageInfo::setMD5Sum( const MD5& sum )
{
if (sum != m_md5sum)
{
// if we make a QObject derived class out of imageinfo, we might invalidate thumbnails from here
// file changed -> reload/invalidate metadata:
ExifMode mode = EXIFMODE_ORIENTATION | EXIFMODE_DATABASE_UPDATE;
// fuzzy dates are usually set for a reason
if (!m_date.isFuzzy())
mode |= EXIFMODE_DATE;
// FIXME (ZaJ): the "right" thing to do would be to update the description
// - if it is currently empty (done.)
// - if it has been set from the exif info and not been changed (TODO)
if (m_description.isEmpty())
mode |= EXIFMODE_DESCRIPTION;
readExif( fileName(), mode);
// FIXME (ZaJ): it *should* make sense to set the ImageDB::md5Map() from here, but I want
// to make sure I fully understand everything first...
// this could also be done as signal md5Changed(old,new)
// image size is invalidated by the thumbnail builder, if needed
m_dirty = true;
}
m_md5sum = sum;
saveChangesIfNotDelayed();
}
void ImageInfo::setLocked( bool locked )
{
m_locked = locked;
}
bool ImageInfo::isLocked() const
{
return m_locked;
}
void ImageInfo::readExif(const DB::FileName& fullPath, DB::ExifMode mode)
{
DB::FileInfo exifInfo = DB::FileInfo::read( fullPath, mode );
bool oldDelaySaving = m_delaySaving;
delaySavingChanges(true);
// Date
if ( updateDateInformation(mode) ) {
const ImageDate newDate ( exifInfo.dateTime() );
setDate( newDate );
}
// Orientation
if ( (mode & EXIFMODE_ORIENTATION) && Settings::SettingsData::instance()->useEXIFRotate() ) {
setAngle( exifInfo.angle() );
}
// Description
if ( (mode & EXIFMODE_DESCRIPTION) && Settings::SettingsData::instance()->useEXIFComments() ) {
bool doSetDescription = true;
QString desc = exifInfo.description();
if ( Settings::SettingsData::instance()->stripEXIFComments() ) {
for( const auto& ignoredComment : Settings::SettingsData::instance()->EXIFCommentsToStrip() )
{
if ( desc == ignoredComment )
{
doSetDescription = false;
break;
}
}
}
if (doSetDescription) {
setDescription(desc);
}
}
delaySavingChanges(false);
m_delaySaving = oldDelaySaving;
// Database update
if ( mode & EXIFMODE_DATABASE_UPDATE ) {
Exif::Database::instance()->remove( fullPath );
Exif::Database::instance()->add( fullPath );
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
// GPS coords might have changed...
m_coordsIsSet = false;
#endif
}
}
QStringList ImageInfo::availableCategories() const
{
return m_categoryInfomation.keys();
}
QSize ImageInfo::size() const
{
return m_size;
}
void ImageInfo::setSize( const QSize& size )
{
if (size != m_size)
m_dirty = true;
m_size = size;
saveChangesIfNotDelayed();
}
bool ImageInfo::imageOnDisk( const DB::FileName& fileName )
{
return fileName.exists();
}
ImageInfo::ImageInfo( const DB::FileName& fileName,
const QString& label,
const QString& description,
const ImageDate& date,
int angle,
const MD5& md5sum,
const QSize& size,
MediaType type,
short rating,
unsigned int stackId,
unsigned int stackOrder )
{
m_delaySaving = true;
m_fileName = fileName;
m_label =label;
m_description =description;
m_date = date;
m_angle =angle;
m_md5sum =md5sum;
m_size = size;
m_imageOnDisk = Unchecked;
m_locked = false;
m_null = false;
m_type = type;
m_dirty = true;
delaySavingChanges(false);
if ( rating > 10 )
rating = 10;
if ( rating < -1 )
rating = -1;
m_rating = rating;
m_stackId = stackId;
m_stackOrder = stackOrder;
m_videoLength= -1;
}
// TODO: we should get rid of this operator. It seems only be necessary
// because of the 'delaySavings' field that gets a special value.
// ImageInfo should just be a dumb data object holder and not incorporate
// storing strategies.
ImageInfo& ImageInfo::operator=( const ImageInfo& other )
{
m_fileName = other.m_fileName;
m_label = other.m_label;
m_description = other.m_description;
m_date = other.m_date;
m_categoryInfomation = other.m_categoryInfomation;
m_taggedAreas = other.m_taggedAreas;
m_angle = other.m_angle;
m_imageOnDisk = other.m_imageOnDisk;
m_md5sum = other.m_md5sum;
m_null = other.m_null;
m_size = other.m_size;
m_type = other.m_type;
m_dirty = other.m_dirty;
m_rating = other.m_rating;
m_stackId = other.m_stackId;
m_stackOrder = other.m_stackOrder;
m_videoLength = other.m_videoLength;
delaySavingChanges(false);
return *this;
}
MediaType DB::ImageInfo::mediaType() const
{
return m_type;
}
bool ImageInfo::isVideo() const
{
return m_type == Video;
}
void DB::ImageInfo::createFolderCategoryItem( DB::CategoryPtr folderCategory, DB::MemberMap& memberMap )
{
QString folderName = Utilities::relativeFolderName( m_fileName.relative() );
if ( folderName.isEmpty() )
return;
QStringList directories = folderName.split(QString::fromLatin1( "/" ) );
QString curPath;
for( QStringList::ConstIterator directoryIt = directories.constBegin(); directoryIt != directories.constEnd(); ++directoryIt ) {
if ( curPath.isEmpty() )
curPath = *directoryIt;
else {
QString oldPath = curPath;
curPath = curPath + QString::fromLatin1( "/" ) + *directoryIt;
memberMap.addMemberToGroup( folderCategory->name(), oldPath, curPath );
}
}
m_categoryInfomation.insert( folderCategory->name() , StringSet() << folderName );
folderCategory->addItem( folderName );
}
void DB::ImageInfo::copyExtraData( const DB::ImageInfo& from, bool copyAngle)
{
m_categoryInfomation = from.m_categoryInfomation;
m_description = from.m_description;
// Hmm... what should the date be? orig or modified?
// _date = from._date;
if (copyAngle)
m_angle = from.m_angle;
m_rating = from.m_rating;
}
void DB::ImageInfo::removeExtraData ()
{
m_categoryInfomation.clear();
m_description.clear();
m_rating = -1;
}
void ImageInfo::merge(const ImageInfo &other)
{
// Merge description
if ( !other.description().isEmpty() ) {
if ( m_description.isEmpty() )
m_description = other.description();
else if (m_description != other.description())
m_description += QString::fromUtf8("\n-----------\n") + other.m_description;
}
// Clear untagged tag if one of the images was untagged
const QString untaggedCategory = Settings::SettingsData::instance()->untaggedCategory();
const QString untaggedTag = Settings::SettingsData::instance()->untaggedTag();
const bool isCompleted = !m_categoryInfomation[untaggedCategory].contains(untaggedTag) || !other.m_categoryInfomation[untaggedCategory].contains(untaggedTag);
// Merge tags
QSet keys = QSet::fromList(m_categoryInfomation.keys());
keys.unite(QSet::fromList(other.m_categoryInfomation.keys()));
for( const QString& key : keys) {
m_categoryInfomation[key].unite(other.m_categoryInfomation[key]);
}
// Clear untagged tag if one of the images was untagged
if (isCompleted)
m_categoryInfomation[untaggedCategory].remove(untaggedTag);
// merge stacks:
if (isStacked() || other.isStacked())
{
DB::FileNameList stackImages;
if (!isStacked())
stackImages.append(fileName());
else
stackImages.append(DB::ImageDB::instance()->getStackFor(fileName()));
stackImages.append(DB::ImageDB::instance()->getStackFor(other.fileName()));
DB::ImageDB::instance()->unstack(stackImages);
if (!DB::ImageDB::instance()->stack(stackImages))
qCWarning(DBLog, "Could not merge stacks!");
}
}
void DB::ImageInfo::addCategoryInfo( const QString& category, const StringSet& values )
{
for ( StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt ) {
if (! m_categoryInfomation[category].contains( *valueIt ) ) {
m_dirty = true;
m_categoryInfomation[category].insert( *valueIt );
}
}
saveChangesIfNotDelayed();
}
void DB::ImageInfo::clearAllCategoryInfo()
{
m_categoryInfomation.clear();
m_taggedAreas.clear();
}
void DB::ImageInfo::removeCategoryInfo( const QString& category, const StringSet& values )
{
for ( StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt ) {
if ( m_categoryInfomation[category].contains( *valueIt ) ) {
m_dirty = true;
m_categoryInfomation[category].remove(*valueIt);
m_taggedAreas[category].remove(*valueIt);
}
}
saveChangesIfNotDelayed();
}
void DB::ImageInfo::addCategoryInfo( const QString& category, const QString& value, const QRect& area )
{
if (! m_categoryInfomation[category].contains( value ) ) {
m_dirty = true;
m_categoryInfomation[category].insert( value );
if (area.isValid()) {
m_taggedAreas[category][value] = area;
}
}
saveChangesIfNotDelayed();
}
void DB::ImageInfo::removeCategoryInfo( const QString& category, const QString& value )
{
if ( m_categoryInfomation[category].contains( value ) ) {
m_dirty = true;
m_categoryInfomation[category].remove( value );
m_taggedAreas[category].remove( value );
}
saveChangesIfNotDelayed();
}
void DB::ImageInfo::setPositionedTags(const QString& category, const QMap &positionedTags)
{
m_dirty = true;
m_taggedAreas[category] = positionedTags;
saveChangesIfNotDelayed();
}
bool DB::ImageInfo::updateDateInformation( int mode ) const
{
if ((mode & EXIFMODE_DATE) == 0)
return false;
if ( (mode & EXIFMODE_FORCE) != 0 )
return true;
return true;
}
QMap> DB::ImageInfo::taggedAreas() const
{
return m_taggedAreas;
}
QRect DB::ImageInfo::areaForTag(QString category, QString tag) const
{
// QMap::value returns a default constructed value if the key is not found:
return m_taggedAreas.value(category).value(tag);
}
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Map::GeoCoordinates DB::ImageInfo::coordinates() const
{
if (m_coordsIsSet) {
return m_coordinates;
}
static const int EXIF_GPS_VERSIONID = 0;
static const int EXIF_GPS_LATREF = 1;
static const int EXIF_GPS_LAT = 2;
static const int EXIF_GPS_LONREF = 3;
static const int EXIF_GPS_LON = 4;
static const int EXIF_GPS_ALTREF = 5;
static const int EXIF_GPS_ALT = 6;
static const QString S = QString::fromUtf8("S");
static const QString W = QString::fromUtf8("W");
static QList fields;
if (fields.isEmpty())
{
// the order here matters! we use the named int constants afterwards to refer to them:
fields.append( new Exif::IntExifElement( "Exif.GPSInfo.GPSVersionID" ) ); // actually a byte value
fields.append( new Exif::StringExifElement( "Exif.GPSInfo.GPSLatitudeRef" ) );
fields.append( new Exif::RationalExifElement( "Exif.GPSInfo.GPSLatitude" ) );
fields.append( new Exif::StringExifElement( "Exif.GPSInfo.GPSLongitudeRef" ) );
fields.append( new Exif::RationalExifElement( "Exif.GPSInfo.GPSLongitude" ) );
fields.append( new Exif::IntExifElement( "Exif.GPSInfo.GPSAltitudeRef" ) ); // actually a byte value
fields.append( new Exif::RationalExifElement( "Exif.GPSInfo.GPSAltitude" ) );
}
// read field values from database:
bool foundIt = Exif::Database::instance()->readFields( m_fileName, fields );
// if the Database query result doesn't contain exif GPS info (-> upgraded exifdb from DBVersion < 2), it is null
// if the result is int 0, then there's no exif gps information in the image
// otherwise we can proceed to parse the information
if ( foundIt && fields[EXIF_GPS_VERSIONID]->value().isNull() )
{
// update exif DB and repeat the search:
Exif::Database::instance()->remove( fileName() );
Exif::Database::instance()->add( fileName() );
Exif::Database::instance()->readFields( m_fileName, fields );
Q_ASSERT( !fields[EXIF_GPS_VERSIONID]->value().isNull() );
}
Map::GeoCoordinates coords;
// gps info set?
// don't use the versionid field here, because some cameras use 0 as its value
if ( foundIt && fields[EXIF_GPS_LAT]->value().toInt() != -1.0
&& fields[EXIF_GPS_LON]->value().toInt() != -1.0 )
{
// lat/lon/alt reference determines sign of float:
double latr = (fields[EXIF_GPS_LATREF]->value().toString() == S ) ? -1.0 : 1.0;
double lat = fields[EXIF_GPS_LAT]->value().toFloat();
double lonr = (fields[EXIF_GPS_LONREF]->value().toString() == W ) ? -1.0 : 1.0;
double lon = fields[EXIF_GPS_LON]->value().toFloat();
double altr = (fields[EXIF_GPS_ALTREF]->value().toInt() == 1 ) ? -1.0 : 1.0;
double alt = fields[EXIF_GPS_ALT]->value().toFloat();
if (lat != -1.0 && lon != -1.0) {
coords.setLatLon(latr * lat, lonr * lon);
if (alt != 0.0f) {
coords.setAlt(altr * alt);
}
}
}
m_coordinates = coords;
m_coordsIsSet = true;
return m_coordinates;
}
#endif
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageInfo.h b/DB/ImageInfo.h
index 9f7b8a70..f5823542 100644
--- a/DB/ImageInfo.h
+++ b/DB/ImageInfo.h
@@ -1,242 +1,242 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 IMAGEINFO_H
#define IMAGEINFO_H
#include
#include
#include
#include "ImageDate.h"
#include "Utilities/StringSet.h"
#include "MD5.h"
#include "ExifMode.h"
#include "DB/CategoryPtr.h"
#include
#include
#include "FileName.h"
-#include "config-kpa-kgeomap.h"
-#ifdef HAVE_KGEOMAP
+#include "config-kpa-marble.h"
+#ifdef HAVE_MARBLE
#include "Map/GeoCoordinates.h"
#endif
namespace Plugins
{
class ImageInfo;
}
namespace XMLDB {
class Database;
}
namespace DB
{
enum PathType {
RelativeToImageRoot,
AbsolutePath
};
enum RotationMode {
RotateImageInfoAndAreas,
RotateImageInfoOnly
};
using Utilities::StringSet;
class MemberMap;
enum MediaType { Image = 0x01, Video = 0x02 };
const MediaType anyMediaType = MediaType(Image | Video);
typedef unsigned int StackID;
class ImageInfo :public QSharedData {
public:
ImageInfo();
explicit ImageInfo( const DB::FileName& fileName, MediaType type = Image, bool readExifInfo = true );
ImageInfo( const DB::FileName& fileName,
const QString& label,
const QString& description,
const ImageDate& date,
int angle,
const MD5& md5sum,
const QSize& size,
MediaType type,
short rating = -1,
StackID stackId = 0,
unsigned int stackOrder = 0 );
virtual ~ImageInfo() { saveChanges(); }
FileName fileName() const;
void setFileName( const DB::FileName& relativeFileName );
void setLabel( const QString& );
QString label() const;
void setDescription( const QString& );
QString description() const;
void setDate( const ImageDate& );
ImageDate date() const;
ImageDate& date();
void readExif(const DB::FileName& fullPath, DB::ExifMode mode);
void rotate( int degrees, RotationMode mode=RotateImageInfoAndAreas );
int angle() const;
void setAngle( int angle );
short rating() const;
void setRating( short rating );
bool isStacked() const { return m_stackId != 0; }
StackID stackId() const;
unsigned int stackOrder() const;
void setStackOrder( const unsigned int stackOrder );
void setVideoLength(int seconds);
int videoLength() const;
void setCategoryInfo( const QString& key, const StringSet& value );
void addCategoryInfo( const QString& category, const StringSet& values );
/**
* Enable a tag within a category for this image.
* Optionally, the tag's position can be given (for positionable categories).
* @param category the category name
* @param value the tag name
* @param area the image region that the tag applies to.
*/
void addCategoryInfo(const QString& category, const QString& value, const QRect& area = QRect());
void clearAllCategoryInfo();
void removeCategoryInfo( const QString& category, const StringSet& values );
void removeCategoryInfo( const QString& category, const QString& value );
/**
* Set the tagged areas for the image.
* It is assumed that the positioned tags have already been set to the ImageInfo
* using one of the functions setCategoryInfo
or addCategoryInfo
.
*
* @param category the category name.
* @param positionedTags a mapping of tag names to image areas.
*/
void setPositionedTags(const QString& category, const QMap &positionedTags);
bool hasCategoryInfo( const QString& key, const QString& value ) const;
bool hasCategoryInfo( const QString& key, const StringSet& values ) const;
QStringList availableCategories() const;
StringSet itemsOfCategory( const QString& category ) const;
void renameItem( const QString& key, const QString& oldValue, const QString& newValue );
void renameCategory( const QString& oldName, const QString& newName );
bool operator!=( const ImageInfo& other ) const;
bool operator==( const ImageInfo& other ) const;
ImageInfo& operator=( const ImageInfo& other );
static bool imageOnDisk( const DB::FileName& fileName );
const MD5& MD5Sum() const { return m_md5sum; }
void setMD5Sum( const MD5& sum );
void setLocked( bool );
bool isLocked() const;
bool isNull() const { return m_null; }
QSize size() const;
void setSize( const QSize& size );
MediaType mediaType() const;
void setMediaType( MediaType type ) { if (type != m_type) m_dirty = true; m_type = type; saveChangesIfNotDelayed(); }
bool isVideo() const;
void createFolderCategoryItem( DB::CategoryPtr, DB::MemberMap& memberMap );
void delaySavingChanges(bool b=true);
void copyExtraData( const ImageInfo& from, bool copyAngle = true);
void removeExtraData();
/**
* Merge another ImageInfo into this one.
* The other ImageInfo is not altered in any way or removed.
*/
void merge(const ImageInfo& other);
QMap> taggedAreas() const;
/**
* Return the area associated with a tag.
* @param category the category name
* @param tag the tag name
* @return the associated area, or QRect()
if no association exists.
*/
QRect areaForTag(QString category, QString tag) const;
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Map::GeoCoordinates coordinates() const;
#endif
protected:
/** Save changes to database.
*
* Back-ends, which need changes to be instantly in database,
* should override this.
*/
virtual void saveChanges() {}
void saveChangesIfNotDelayed() { if (!m_delaySaving) saveChanges(); }
void setIsNull(bool b) { m_null = b; }
bool isDirty() const { return m_dirty; }
void setIsDirty(bool b) { m_dirty = b; }
bool updateDateInformation( int mode ) const;
void setStackId( const StackID stackId );
friend class XMLDB::Database;
private:
DB::FileName m_fileName;
QString m_label;
QString m_description;
ImageDate m_date;
QMap m_categoryInfomation;
QMap> m_taggedAreas;
int m_angle;
enum OnDisk { YesOnDisk, NoNotOnDisk, Unchecked };
mutable OnDisk m_imageOnDisk;
MD5 m_md5sum;
bool m_null;
QSize m_size;
MediaType m_type;
short m_rating;
StackID m_stackId;
unsigned int m_stackOrder;
int m_videoLength;
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
mutable Map::GeoCoordinates m_coordinates;
mutable bool m_coordsIsSet = false;
#endif
// Cache information
bool m_locked;
// Will be set to true after every change
bool m_dirty;
bool m_delaySaving;
};
}
#endif /* IMAGEINFO_H */
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageSearchInfo.cpp b/DB/ImageSearchInfo.cpp
index 5be7ab2b..8c0e918f 100644
--- a/DB/ImageSearchInfo.cpp
+++ b/DB/ImageSearchInfo.cpp
@@ -1,597 +1,597 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 "ImageSearchInfo.h"
#include "AndCategoryMatcher.h"
#include "CategoryMatcher.h"
#include "ContainerCategoryMatcher.h"
#include "ExactCategoryMatcher.h"
#include "ImageDB.h"
#include "Logging.h"
#include "NegationCategoryMatcher.h"
#include "NoTagCategoryMatcher.h"
#include "OrCategoryMatcher.h"
#include "ValueCategoryMatcher.h"
#include
#include
#include
#include
#include
#include
#include
using namespace DB;
ImageSearchInfo::ImageSearchInfo( const ImageDate& date,
const QString& label, const QString& description )
: m_date( date), m_label( label ), m_description( description ), m_rating( -1 ), m_megapixel( 0 ), m_max_megapixel( 0 ), m_ratingSearchMode( 0 ), m_searchRAW( false ), m_isNull( false ), m_compiled( false )
{
}
ImageSearchInfo::ImageSearchInfo( const ImageDate& date,
const QString& label, const QString& description,
const QString& fnPattern )
: m_date( date), m_label( label ), m_description( description ), m_fnPattern( fnPattern ), m_rating( -1 ), m_megapixel( 0 ), m_max_megapixel( 0 ), m_ratingSearchMode( 0 ), m_searchRAW( false ), m_isNull( false ), m_compiled( false )
{
}
QString ImageSearchInfo::label() const
{
return m_label;
}
QRegExp ImageSearchInfo::fnPattern() const
{
return m_fnPattern;
}
QString ImageSearchInfo::description() const
{
return m_description;
}
ImageSearchInfo::ImageSearchInfo()
: m_rating( -1 ), m_megapixel( 0 ), m_max_megapixel( 0 ), m_ratingSearchMode( 0 ), m_searchRAW( false ), m_isNull( true ), m_compiled( false )
{
}
bool ImageSearchInfo::isNull() const
{
return m_isNull;
}
bool ImageSearchInfo::match( ImageInfoPtr info ) const
{
if ( m_isNull )
return true;
if ( !m_compiled )
compile();
bool ok = true;
ok = m_exifSearchInfo.matches( info->fileName() );
QDateTime actualStart = info->date().start();
QDateTime actualEnd = info->date().end();
if ( actualEnd <= actualStart ) {
QDateTime tmp = actualStart;
actualStart = actualEnd;
actualEnd = tmp;
}
if ( !m_date.start().isNull() ) {
// Date
// the search date matches the actual date if:
// actual.start <= search.start <= actuel.end or
// actual.start <= search.end <=actuel.end or
// search.start <= actual.start and actual.end <= search.end
bool b1 =( actualStart <= m_date.start() && m_date.start() <= actualEnd );
bool b2 =( actualStart <= m_date.end() && m_date.end() <= actualEnd );
bool b3 = ( m_date.start() <= actualStart && ( actualEnd <= m_date.end() || m_date.end().isNull() ) );
ok = ok && ( ( b1 || b2 || b3 ) );
} else if ( !m_date.end().isNull() ) {
bool b1 = ( actualStart <= m_date.end() && m_date.end() <= actualEnd );
bool b2 = ( actualEnd <= m_date.end() );
ok = ok && ( ( b1 || b2 ) );
}
// -------------------------------------------------- Options
// alreadyMatched map is used to make it possible to search for
// Jesper & None
QMap alreadyMatched;
for (CategoryMatcher* optionMatcher : m_categoryMatchers) {
ok = ok && optionMatcher->eval(info, alreadyMatched);
}
// -------------------------------------------------- Label
ok = ok && ( m_label.isEmpty() || info->label().indexOf(m_label) != -1 );
// -------------------------------------------------- RAW
ok = ok && ( m_searchRAW == false || ImageManager::RAWImageDecoder::isRAW( info->fileName()) );
// -------------------------------------------------- Rating
//ok = ok && (_rating == -1 ) || ( _rating == info->rating() );
if (m_rating != -1) {
switch( m_ratingSearchMode ) {
case 1:
// Image rating at least selected
ok = ok && ( m_rating <= info->rating() );
break;
case 2:
// Image rating less than selected
ok = ok && ( m_rating >= info->rating() );
break;
case 3:
// Image rating not equal
ok = ok && ( m_rating != info->rating() );
break;
default:
ok = ok && ((m_rating == -1 ) || ( m_rating == info->rating() ));
break;
}
}
// -------------------------------------------------- Resolution
if ( m_megapixel )
ok = ok && ( m_megapixel * 1000000 <= info->size().width() * info->size().height() );
if ( m_max_megapixel && m_max_megapixel > m_megapixel )
ok = ok && ( m_max_megapixel * 1000000 > info->size().width() * info->size().height() );
// -------------------------------------------------- Text
QString txt = info->description();
if ( !m_description.isEmpty() ) {
QStringList list = m_description.split(QChar::fromLatin1(' '), QString::SkipEmptyParts);
Q_FOREACH( const QString &word, list ) {
ok = ok && ( txt.indexOf( word, 0, Qt::CaseInsensitive ) != -1 );
}
}
// -------------------------------------------------- File name pattern
ok = ok && ( m_fnPattern.isEmpty() ||
m_fnPattern.indexIn( info->fileName().relative() ) != -1 );
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
// Search for GPS Position
if (ok && m_usingRegionSelection) {
ok = ok && info->coordinates().hasCoordinates();
if (ok) {
float infoLat = info->coordinates().lat();
float infoLon = info->coordinates().lon();
ok = ok
&& m_regionSelectionMinLat <= infoLat
&& infoLat <= m_regionSelectionMaxLat
&& m_regionSelectionMinLon <= infoLon
&& infoLon <= m_regionSelectionMaxLon;
}
}
#endif
return ok;
}
QString ImageSearchInfo::categoryMatchText( const QString& name ) const
{
return m_categoryMatchText[name];
}
void ImageSearchInfo::setCategoryMatchText( const QString& name, const QString& value )
{
m_categoryMatchText[name] = value;
m_isNull = false;
m_compiled = false;
}
void ImageSearchInfo::addAnd( const QString& category, const QString& value )
{
// Escape literal "&"s in value by doubling it
QString escapedValue = value;
escapedValue.replace(QString::fromUtf8("&"), QString::fromUtf8("&&"));
QString val = categoryMatchText( category );
if ( !val.isEmpty() )
val += QString::fromLatin1( " & " ) + escapedValue;
else
val = escapedValue;
setCategoryMatchText( category, val );
m_isNull = false;
m_compiled = false;
}
void ImageSearchInfo::setRating( short rating )
{
m_rating = rating;
m_isNull = false;
m_compiled = false;
}
void ImageSearchInfo::setMegaPixel( short megapixel )
{
m_megapixel = megapixel;
}
void ImageSearchInfo::setMaxMegaPixel( short max_megapixel )
{
m_max_megapixel = max_megapixel;
}
void ImageSearchInfo::setSearchMode(int index)
{
m_ratingSearchMode = index;
}
void ImageSearchInfo::setSearchRAW( bool searchRAW )
{
m_searchRAW = searchRAW;
}
QString ImageSearchInfo::toString() const
{
QString res;
bool first = true;
for( QMap::ConstIterator it= m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it ) {
if ( ! it.value().isEmpty() ) {
if ( first )
first = false;
else
res += QString::fromLatin1( " / " );
QString txt = it.value();
if ( txt == ImageDB::NONE() )
txt = i18nc( "As in No persons, no locations etc. I do realize that translators may have problem with this, "
"but I need some how to indicate the category, and users may create their own categories, so this is "
"the best I can do - Jesper.", "No %1", it.key() );
if ( txt.contains( QString::fromLatin1("|") ) )
txt.replace( QString::fromLatin1( "&" ), QString::fromLatin1( " %1 " ).arg( i18n("and") ) );
else
txt.replace( QString::fromLatin1( "&" ), QString::fromLatin1( " / " ) );
txt.replace( QString::fromLatin1( "|" ), QString::fromLatin1( " %1 " ).arg( i18n("or") ) );
txt.replace( QString::fromLatin1( "!" ), QString::fromLatin1( " %1 " ).arg( i18n("not") ) );
txt.replace( ImageDB::NONE(), i18nc( "As in no other persons, or no other locations. "
"I do realize that translators may have problem with this, "
"but I need some how to indicate the category, and users may create their own categories, so this is "
"the best I can do - Jesper.", "No other %1", it.key() ) );
res += txt.simplified();
}
}
return res;
}
void ImageSearchInfo::debug()
{
for( QMap::Iterator it= m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it ) {
qCDebug(DBCategoryMatcherLog) << it.key() << ", " << it.value();
}
}
// PENDING(blackie) move this into the Options class instead of having it here.
void ImageSearchInfo::saveLock() const
{
KConfigGroup config = KSharedConfig::openConfig()->group( Settings::SettingsData::instance()->groupForDatabase( "Privacy Settings"));
config.writeEntry( QString::fromLatin1("label"), m_label );
config.writeEntry( QString::fromLatin1("description"), m_description );
config.writeEntry( QString::fromLatin1("categories"), m_categoryMatchText.keys() );
for( QMap::ConstIterator it= m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it ) {
config.writeEntry( it.key(), it.value() );
}
config.sync();
}
ImageSearchInfo ImageSearchInfo::loadLock()
{
KConfigGroup config = KSharedConfig::openConfig()->group( Settings::SettingsData::instance()->groupForDatabase( "Privacy Settings" ));
ImageSearchInfo info;
info.m_label = config.readEntry( "label" );
info.m_description = config.readEntry( "description" );
QStringList categories = config.readEntry( QString::fromLatin1("categories"), QStringList() );
for( QStringList::ConstIterator it = categories.constBegin(); it != categories.constEnd(); ++it ) {
info.setCategoryMatchText( *it, config.readEntry( *it, QString() ) );
}
return info;
}
ImageSearchInfo::ImageSearchInfo( const ImageSearchInfo& other )
{
m_date = other.m_date;
m_categoryMatchText = other.m_categoryMatchText;
m_label = other.m_label;
m_description = other.m_description;
m_fnPattern = other.m_fnPattern;
m_isNull = other.m_isNull;
m_compiled = false;
m_rating = other.m_rating;
m_ratingSearchMode = other.m_ratingSearchMode;
m_megapixel = other.m_megapixel;
m_max_megapixel = other.m_max_megapixel;
m_searchRAW = other.m_searchRAW;
m_exifSearchInfo = other.m_exifSearchInfo;
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
m_regionSelection = other.m_regionSelection;
#endif
}
void ImageSearchInfo::compile() const
{
m_exifSearchInfo.search();
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
// Prepare Search for GPS Position
m_usingRegionSelection = m_regionSelection.first.hasCoordinates() && m_regionSelection.second.hasCoordinates();
if (m_usingRegionSelection) {
using std::min;
using std::max;
m_regionSelectionMinLat = min(m_regionSelection.first.lat(), m_regionSelection.second.lat());
m_regionSelectionMaxLat = max(m_regionSelection.first.lat(), m_regionSelection.second.lat());
m_regionSelectionMinLon = min(m_regionSelection.first.lon(), m_regionSelection.second.lon());
m_regionSelectionMaxLon = max(m_regionSelection.first.lon(), m_regionSelection.second.lon());
}
#endif
deleteMatchers();
for( QMap::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it ) {
QString category = it.key();
QString matchText = it.value();
QStringList orParts = matchText.split(QString::fromLatin1("|"), QString::SkipEmptyParts);
DB::ContainerCategoryMatcher* orMatcher = new DB::OrCategoryMatcher;
Q_FOREACH( QString orPart, orParts ) {
// Split by " & ", not only by "&", so that the doubled "&"s won't be used as a split point
QStringList andParts = orPart.split(QString::fromLatin1(" & "), QString::SkipEmptyParts);
DB::ContainerCategoryMatcher* andMatcher;
bool exactMatch=false;
bool negate = false;
andMatcher = new DB::AndCategoryMatcher;
Q_FOREACH( QString str, andParts ) {
static QRegExp regexp( QString::fromLatin1("^\\s*!\\s*(.*)$") );
if ( regexp.exactMatch( str ) )
{ // str is preceded with NOT
negate = true;
str = regexp.cap(1);
}
str = str.trimmed();
CategoryMatcher* valueMatcher;
if ( str == ImageDB::NONE() )
{ // mark AND-group as containing a "No other" condition
exactMatch = true;
continue;
}
else
{
valueMatcher = new DB::ValueCategoryMatcher( category, str );
if ( negate )
valueMatcher = new DB::NegationCategoryMatcher( valueMatcher );
}
andMatcher->addElement( valueMatcher );
}
if ( exactMatch )
{
DB::CategoryMatcher *exactMatcher = nullptr;
// if andMatcher has exactMatch set, but no CategoryMatchers, then
// matching "category / None" is what we want:
if ( andMatcher->mp_elements.count() == 0 )
{
exactMatcher = new DB::NoTagCategoryMatcher( category );
}
else
{
ExactCategoryMatcher *noOtherMatcher = new ExactCategoryMatcher( category );
if ( andMatcher->mp_elements.count() == 1 )
noOtherMatcher->setMatcher( andMatcher->mp_elements[0] );
else
noOtherMatcher->setMatcher( andMatcher );
exactMatcher = noOtherMatcher;
}
if ( negate )
exactMatcher = new DB::NegationCategoryMatcher( exactMatcher );
orMatcher->addElement( exactMatcher );
}
else
if ( andMatcher->mp_elements.count() == 1 )
orMatcher->addElement( andMatcher->mp_elements[0] );
else if ( andMatcher->mp_elements.count() > 1 )
orMatcher->addElement( andMatcher );
}
CategoryMatcher* matcher = nullptr;
if ( orMatcher->mp_elements.count() == 1 )
matcher = orMatcher->mp_elements[0];
else if ( orMatcher->mp_elements.count() > 1 )
matcher = orMatcher;
if ( matcher )
{
m_categoryMatchers.append( matcher );
if ( DBCategoryMatcherLog().isDebugEnabled() )
{
qCDebug(DBCategoryMatcherLog) << "Matching text '" << matchText << "' in category "<< category <<":";
matcher->debug(0);
qCDebug(DBCategoryMatcherLog) << ".";
}
}
}
m_compiled = true;
}
ImageSearchInfo::~ImageSearchInfo()
{
deleteMatchers();
}
void ImageSearchInfo::debugMatcher() const
{
if ( !m_compiled )
compile();
qCDebug(DBCategoryMatcherLog, "And:");
for (CategoryMatcher* optionMatcher : m_categoryMatchers) {
optionMatcher->debug(1);
}
}
QList > ImageSearchInfo::query() const
{
if ( !m_compiled )
compile();
// Combine _optionMachers to one list of lists in Disjunctive
// Normal Form and return it.
QList::Iterator it = m_categoryMatchers.begin();
QList > result;
if ( it == m_categoryMatchers.end() )
return result;
result = convertMatcher( *it );
++it;
for( ; it != m_categoryMatchers.end(); ++it ) {
QList > current = convertMatcher( *it );
QList > oldResult = result;
result.clear();
for (QList resultIt : oldResult) {
for (QList currentIt : current) {
QList tmp;
tmp += resultIt;
tmp += currentIt;
result.append( tmp );
}
}
}
return result;
}
Utilities::StringSet ImageSearchInfo::findAlreadyMatched( const QString &group ) const
{
Utilities::StringSet result;
QString str = categoryMatchText( group );
if ( str.contains( QString::fromLatin1( "|" ) ) ) {
return result;
}
QStringList list = str.split(QString::fromLatin1( "&" ), QString::SkipEmptyParts);
Q_FOREACH( QString part, list ) {
QString nm = part.trimmed();
if (! nm.contains( QString::fromLatin1( "!" ) ) )
result.insert(nm);
}
return result;
}
void ImageSearchInfo::deleteMatchers() const
{
qDeleteAll(m_categoryMatchers);
m_categoryMatchers.clear();
}
QList ImageSearchInfo::extractAndMatcher( CategoryMatcher* matcher ) const
{
QList result;
AndCategoryMatcher* andMatcher;
SimpleCategoryMatcher* simpleMatcher;
if ( ( andMatcher = dynamic_cast( matcher ) ) ) {
for (CategoryMatcher* child : andMatcher->mp_elements) {
SimpleCategoryMatcher* simpleMatcher = dynamic_cast( child );
Q_ASSERT( simpleMatcher );
result.append( simpleMatcher );
}
}
else if ( ( simpleMatcher = dynamic_cast( matcher ) ) )
result.append( simpleMatcher );
else
Q_ASSERT( false );
return result;
}
/** Convert matcher to Disjunctive Normal Form.
*
* @return OR-list of AND-lists. (e.g. OR(AND(a,b),AND(c,d)))
*/
QList > ImageSearchInfo::convertMatcher( CategoryMatcher* item ) const
{
QList > result;
OrCategoryMatcher* orMacther;
if ( ( orMacther = dynamic_cast( item ) ) ) {
for (CategoryMatcher* child : orMacther->mp_elements) {
result.append( extractAndMatcher( child ) );
}
}
else
result.append( extractAndMatcher( item ) );
return result;
}
ImageDate ImageSearchInfo::date() const
{
return m_date;
}
void ImageSearchInfo::addExifSearchInfo( const Exif::SearchInfo info )
{
m_exifSearchInfo = info;
m_isNull = false;
}
void DB::ImageSearchInfo::renameCategory( const QString& oldName, const QString& newName )
{
m_categoryMatchText[newName] = m_categoryMatchText[oldName];
m_categoryMatchText.remove( oldName );
m_compiled = false;
}
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Map::GeoCoordinates::Pair ImageSearchInfo::regionSelection() const
{
return m_regionSelection;
}
void ImageSearchInfo::setRegionSelection(const Map::GeoCoordinates::Pair& actRegionSelection)
{
m_regionSelection = actRegionSelection;
m_compiled = false;
if (m_regionSelection.first.hasCoordinates() && m_regionSelection.second.hasCoordinates()) {
m_isNull = false;
}
}
#endif
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageSearchInfo.h b/DB/ImageSearchInfo.h
index 0833e5ba..daeec279 100644
--- a/DB/ImageSearchInfo.h
+++ b/DB/ImageSearchInfo.h
@@ -1,130 +1,130 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 IMAGESEARCHINFO_H
#define IMAGESEARCHINFO_H
-#include
+#include
#include
#include
#include
#include
#include
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
#include "Map/GeoCoordinates.h"
#endif
#include
namespace DB
{
class SimpleCategoryMatcher;
class ImageInfo;
class CategoryMatcher;
class ImageSearchInfo {
public:
ImageSearchInfo();
~ImageSearchInfo();
ImageSearchInfo( const ImageDate& date,
const QString& label, const QString& description );
ImageSearchInfo( const ImageDate& date,
const QString& label, const QString& description,
const QString& fnPattern );
ImageSearchInfo( const ImageSearchInfo& other );
ImageDate date() const;
QString categoryMatchText( const QString& name ) const;
void setCategoryMatchText( const QString& name, const QString& value );
void renameCategory( const QString& oldName, const QString& newName );
QString label() const;
QRegExp fnPattern() const;
QString description() const;
bool isNull() const;
bool match( ImageInfoPtr ) const;
QList > query() const;
void addAnd( const QString& category, const QString& value );
void setRating( short rating);
QString toString() const;
void setMegaPixel( short megapixel );
void setMaxMegaPixel( short maxmegapixel );
void setSearchRAW( bool m_searchRAW );
void setSearchMode( int index );
void saveLock() const;
static ImageSearchInfo loadLock();
void debug();
void debugMatcher() const;
Utilities::StringSet findAlreadyMatched( const QString &group ) const;
void addExifSearchInfo( const Exif::SearchInfo info );
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Map::GeoCoordinates::Pair regionSelection() const;
void setRegionSelection(const Map::GeoCoordinates::Pair& actRegionSelection);
#endif
protected:
void compile() const;
void deleteMatchers() const;
QList extractAndMatcher( CategoryMatcher* andMatcher ) const;
QList > convertMatcher( CategoryMatcher* ) const;
private:
ImageDate m_date;
QMap m_categoryMatchText;
QString m_label;
QString m_description;
QRegExp m_fnPattern;
short m_rating;
short m_megapixel;
short m_max_megapixel;
int m_ratingSearchMode;
bool m_searchRAW;
bool m_isNull;
mutable bool m_compiled;
mutable QList m_categoryMatchers;
Exif::SearchInfo m_exifSearchInfo;
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Map::GeoCoordinates::Pair m_regionSelection;
mutable bool m_usingRegionSelection = false;
mutable float m_regionSelectionMinLat;
mutable float m_regionSelectionMaxLat;
mutable float m_regionSelectionMinLon;
mutable float m_regionSelectionMaxLon;
#endif
// When adding new instance variable, please notice that this class as an explicit written copy constructor.
};
}
#endif /* IMAGESEARCHINFO_H */
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/FeatureDialog.cpp b/MainWindow/FeatureDialog.cpp
index 9bccd3f1..1b11ab90 100644
--- a/MainWindow/FeatureDialog.cpp
+++ b/MainWindow/FeatureDialog.cpp
@@ -1,246 +1,246 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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
+#include
#include "FeatureDialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Exif/Database.h"
using namespace MainWindow;
FeatureDialog::FeatureDialog( QWidget* parent )
:QDialog( parent )
{
setWindowTitle( i18n("KPhotoAlbum Feature Status") );
QTextBrowser* browser = new QTextBrowser( this );
QString text = i18n("Overview
"
"Below you may see the list of compile- and runtime features KPhotoAlbum has, and their status:
"
"%1", featureString() );
text += i18n( "What can I do if I miss a feature?
"
"If you compiled KPhotoAlbum yourself, then please review the sections below to learn what to install "
"to get the feature in question. If on the other hand you installed KPhotoAlbum from a binary package, please tell "
"whoever made the package about this defect, eventually including the information from the section below.
"
"In case you are missing a feature and you did not compile KPhotoAlbum yourself, please do consider doing so. "
"It really is not that hard. If you need help compiling KPhotoAlbum, feel free to ask on the "
"KPhotoAlbum mailing list
"
"The steps to compile KPhotoAlbum can be seen on "
"the KPhotoAlbum home page. If you have never compiled a KDE application, then please ensure that "
"you have the developer packages installed, in most distributions they go under names like kdelibs-devel
" );
text += i18n( ""
"KPhotoAlbum has a plug-in system with lots of extensions. You may among other things find plug-ins for:"
"
"
"- Writing images to cds or dvd's
"
"- Adjusting timestamps on your images
"
"- Making a calendar featuring your images
"
"- Uploading your images to flickr
"
"- Upload your images to facebook
"
"
"
"The plug-in library is called KIPI, and may be downloaded from the "
"KDE Userbase Wiki
" );
text += i18n( ""
"KPhotoAlbum allows you to search using a certain number of EXIF tags. For this KPhotoAlbum "
"needs an Sqlite database. "
"In addition the qt package for sqlite (e.g.qt-sql-sqlite) must be installed.
");
text += i18n(""
- "If KPhotoAlbum has been built with support for libkgeomap, "
+ "
If KPhotoAlbum has been built with support for Marble, "
"KPhotoAlbum can show images with GPS information on a map."
"
");
text += i18n(""
"KPhotoAlbum relies on Qt's Phonon architecture for displaying videos; this in turn relies on GStreamer. "
"If this feature is not enabled for you, have a look at the "
"KPhotoAlbum wiki article on video support.
");
QStringList mimeTypes = supportedVideoMimeTypes();
mimeTypes.sort();
if ( mimeTypes.isEmpty() )
text += i18n( "No video mime types found, which indicates that either Qt was compiled without phonon support, or there were missing codecs
");
else
text += i18n("Phonon is capable of playing movies of these mime types:
", mimeTypes.join(QString::fromLatin1( "" ) ) );
text += i18n(""
"KPhotoAlbum can use ffmpeg or MPlayer to extract thumbnails from videos. These thumbnails are used to preview "
"videos in the thumbnail viewer.
"
"In the past, MPlayer (in contrast to MPlayer2) often had problems extracting the length "
"of videos and also often fails to extract the thumbnails used for cycling video thumbnails. "
"For that reason, you should prefer ffmpeg or MPlayer2 over MPlayer, if possible.
"
);
text += i18n(""
"KPhotoAlbum can use ffprobe or MPlayer to extract length information from videos."
"
"
"Correct length information is also necessary for correct rendering of video thumbnails.
"
);
browser->setText( text );
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(browser);
this->setLayout(layout);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
buttonBox->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return);
layout->addWidget(buttonBox);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
}
QSize FeatureDialog::sizeHint() const
{
return QSize(800,600);
}
bool MainWindow::FeatureDialog::hasKIPISupport()
{
#ifdef HASKIPI
return true;
#else
return false;
#endif
}
bool MainWindow::FeatureDialog::hasEXIV2DBSupport()
{
return Exif::Database::isAvailable();
}
bool MainWindow::FeatureDialog::hasGeoMapSupport()
{
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
return true;
#else
return false;
#endif
}
QString FeatureDialog::mplayerBinary()
{
QString mplayer = QStandardPaths::findExecutable( QString::fromLatin1("mplayer2"));
if ( mplayer.isNull() )
mplayer = QStandardPaths::findExecutable( QString::fromLatin1("mplayer"));
return mplayer;
}
bool FeatureDialog::isMplayer2()
{
QProcess process;
process.start( mplayerBinary(), QStringList() << QString::fromLatin1("--version"));
process.waitForFinished();
const QString output = QString::fromLocal8Bit(process.readAllStandardOutput().data());
return output.contains(QString::fromLatin1("MPlayer2"));
}
QString FeatureDialog::ffmpegBinary()
{
QString ffmpeg = QStandardPaths::findExecutable( QString::fromLatin1("ffmpeg"));
return ffmpeg;
}
QString FeatureDialog::ffprobeBinary()
{
QString ffprobe = QStandardPaths::findExecutable( QString::fromLatin1("ffprobe"));
return ffprobe;
}
bool FeatureDialog::hasVideoThumbnailer()
{
return ! ( ffmpegBinary().isEmpty() && mplayerBinary().isEmpty());
}
bool FeatureDialog::hasVideoProber()
{
return ! ( ffprobeBinary().isEmpty() && mplayerBinary().isEmpty());
}
bool MainWindow::FeatureDialog::hasAllFeaturesAvailable()
{
// Only answer those that are compile time tests, otherwise we will pay a penalty each time we start up.
return hasKIPISupport() && hasEXIV2DBSupport() && hasGeoMapSupport() && hasVideoThumbnailer() && hasVideoProber();
}
struct Data
{
Data() {}
Data( const QString& title, const QString tag, bool featureFound )
: title( title ), tag( tag ), featureFound( featureFound ) {}
QString title;
QString tag;
bool featureFound;
};
QString MainWindow::FeatureDialog::featureString()
{
QList features;
features << Data( i18n("Plug-ins available"), QString::fromLatin1("#kipi"), hasKIPISupport() );
features << Data( i18n( "Sqlite database support (used for EXIF searches)" ), QString::fromLatin1("#database"), hasEXIV2DBSupport() );
features << Data( i18n( "Map view for geotagged images." ), QString::fromLatin1("#geomap"), hasGeoMapSupport() );
features << Data( i18n( "Video support" ), QString::fromLatin1("#video"), !supportedVideoMimeTypes().isEmpty() );
QString result = QString::fromLatin1("");
const QString red = QString::fromLatin1("%1");
const QString yellow = QString::fromLatin1("%1");
const QString yes = i18nc("Feature available","Yes");
const QString no = red.arg( i18nc("Feature not available","No") );
const QString formatString = QString::fromLatin1( "%2 | %3 |
" );
for( QList::ConstIterator featureIt = features.constBegin(); featureIt != features.constEnd(); ++featureIt ) {
result += formatString
.arg( (*featureIt).tag ).arg( (*featureIt).title ).arg( (*featureIt).featureFound ? yes : no );
}
QString thumbnailSupport = hasVideoThumbnailer() ? ( !ffmpegBinary().isEmpty() || isMplayer2() ? yes : yellow.arg(i18n("Only with MPlayer1"))) : no ;
result += formatString.arg(QString::fromLatin1("#videoPreview")).arg(i18n("Video thumbnail support")).arg(thumbnailSupport);
QString videoinfoSupport = hasVideoProber() ? yes : no;
result += formatString.arg(QString::fromLatin1("#videoInfo")).arg(i18n("Video metadata support")).arg(videoinfoSupport);
result += QString::fromLatin1( "
" );
return result;
}
QStringList MainWindow::FeatureDialog::supportedVideoMimeTypes()
{
return Phonon::BackendCapabilities::availableMimeTypes();
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/Window.cpp b/MainWindow/Window.cpp
index 39a2e51b..dbb26667 100644
--- a/MainWindow/Window.cpp
+++ b/MainWindow/Window.cpp
@@ -1,1984 +1,1984 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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
#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
#ifdef HASKIPI
# include
#endif
#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
+#ifdef HAVE_MARBLE
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.";
createSarchBar();
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);
QTimer::singleShot( 0, this, SLOT(delayedInit()) );
updateContextMenuFromSelectionSize(0);
// Automatically save toolbar settings
setAutoSaveSettings();
checkIfMplayerIsInstalled();
qCInfo(TimingLog) << "MainWindow: misc setup time: " << timer.restart() << "ms.";
executeStartupActions();
qCInfo(TimingLog) << "MainWindow: executeStartupActions " << 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();
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* exifDB = Exif::Database::instance(); // Load the database
if ( exifDB->isAvailable() && !exifDB->isOpen() ) {
KMessageBox::sorry( this, i18n("EXIF database cannot be opened. Check that the image root directory is writable.") );
}
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();
announceAndroidVersion();
}
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") );
}
}
doQuit:
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 );
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 = Utilities::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() );
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->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 );
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 );
// 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 = QUrl::fromLocalFile(info->fileName().absolute());
QList allSelectedFiles;
for (const QString &selectedFile : selected().toStringList(DB::AbsolutePath)) {
allSelectedFiles << QUrl::fromLocalFile(selectedFile);
}
// "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(), pluginInfo->comment() );
}
#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 );
}
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();
createSarchBar();
}
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 {
qDebug() << "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 = Utilities::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 );
}
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::createSarchBar()
{
// 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::announceAndroidVersion()
{
// Don't bother people with this information when they are starting KPA the first time
if (DB::ImageDB::instance()->totalCount() < 100)
return;
const QString doNotShowKey = QString::fromLatin1( "announce_android_version_key" );
const QString txt = i18n("Did you know that there is an Android client for KPhotoAlbum?
"
"With the Android client you can view your images from your desktop.
"
"See youtube video or "
"install from google play
" );
KMessageBox::information( this, txt, QString(), doNotShowKey, KMessageBox::AllowLink );
}
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
+#ifdef HAVE_MARBLE
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/MainWindow/Window.h b/MainWindow/Window.h
index 6d461bda..dae6e7d3 100644
--- a/MainWindow/Window.h
+++ b/MainWindow/Window.h
@@ -1,269 +1,269 @@
-/* Copyright (C) 2003-2010 Jesper K. Pedersen
+/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 MAINWINDOW_WINDOW_H
#define MAINWINDOW_WINDOW_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
#include
#endif
class QAction;
class QCloseEvent;
class QContextMenuEvent;
class QFrame;
class QLabel;
class QMoveEvent;
class QResizeEvent;
class QStackedWidget;
class QTimer;
class KActionMenu;
class KTipDialog;
class KToggleAction;
#ifdef HASKIPI
namespace KIPI { class PluginLoader; }
#endif
namespace AnnotationDialog { class Dialog; }
namespace Browser{ class BrowserWidget; class BreadcrumbList; }
namespace DateBar { class DateBarWidget; }
namespace DB { class ImageInfoList; }
namespace HTMLGenerator { class HTMLDialog; }
namespace Plugins { class Interface; }
namespace Settings { class SettingsDialog; }
namespace ThumbnailView { class ThumbnailFacade; }
class BreadcrumbViewer;
namespace MainWindow
{
class DeleteDialog;
class StatusBar;
class TokenEditor;
class Window :public KXmlGuiWindow
{
Q_OBJECT
public:
explicit Window( QWidget* parent );
~Window();
static void configureImages( const DB::ImageInfoList& list, bool oneAtATime );
static Window* theMainWindow();
DB::FileNameList selected( ThumbnailView::SelectionMode mode = ThumbnailView::ExpandCollapsedStacks ) const;
DB::ImageSearchInfo currentContext();
QString currentBrowseCategory() const;
void setStackHead( const DB::FileName& image );
void setHistogramVisibilty( bool visible ) const;
bool dbIsDirty() const;
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
void showPositionBrowser();
Browser::PositionBrowserWidget* positionBrowserWidget();
#endif
public slots:
void showThumbNails(const DB::FileNameList& items);
void loadPlugins();
void reloadThumbnails( ThumbnailView::SelectionUpdateMethod method = ThumbnailView::MaintainSelection );
void runDemo();
void slotImageRotated(const DB::FileName& fileName);
void slotSave();
protected slots:
void showThumbNails();
bool slotExit();
void slotOptions();
void slotConfigureAllImages();
void slotConfigureImagesOneAtATime();
void slotCreateImageStack();
void slotUnStackImages();
void slotSetStackHead();
void slotCopySelectedURLs();
void slotPasteInformation();
void slotDeleteSelected();
void slotReReadExifInfo();
void slotAutoStackImages();
void slotSearch();
void slotView( bool reuse = true, bool slideShow = false, bool random = false );
void slotViewNewWindow();
void slotSortByDateAndTime();
void slotSortAllByDateAndTime();
void slotLimitToSelected();
void slotExportToHTML();
void slotAutoSave();
void showBrowser();
void slotOptionGroupChanged();
void showTipOfDay();
void lockToDefaultScope();
void setDefaultScopePositive();
void setDefaultScopeNegative();
void unlockFromDefaultScope();
void changePassword();
void slotConfigureKeyBindings();
void slotSetFileName( const DB::FileName& );
void updateContextMenuFromSelectionSize(int selectionSize);
void slotUpdateViewMenu( DB::Category::ViewType );
void slotShowNotOnDisk();
void slotBuildThumbnails();
void slotBuildThumbnailsIfWanted();
void slotRunSlideShow();
void slotRunRandomizedSlideShow();
void slotConfigureToolbars();
void slotNewToolbarConfig();
void slotImport();
void slotExport();
void delayedInit();
void slotReenableMessages();
void slotImagesChanged( const QList& );
void slotSelectionChanged(int count);
void plug();
void slotRemoveTokens();
void slotShowListOfFiles();
void updateDateBar( const Browser::BreadcrumbList& );
void updateDateBar();
void slotShowImagesWithInvalidDate();
void slotShowImagesWithChangedMD5Sum();
void showDateBarTip( const QString& );
void slotJumpToContext();
void setDateRange( const DB::ImageDate& );
void clearDateRange();
void startAutoSaveTimer();
void slotRecalcCheckSums();
void slotShowExifInfo();
void showFeatures();
void showImage( const DB::FileName& fileName );
void slotOrderIncr();
void slotOrderDecr();
void slotRotateSelectedLeft();
void slotRotateSelectedRight();
void rotateSelected( int angle );
void showVideos();
void slotStatistics();
void slotRecreateExifDB();
void useNextVideoThumbnail();
void usePreviousVideoThumbnail();
void mergeDuplicates();
void slotThumbnailSizeChanged();
void slotMarkUntagged();
protected:
void configureImages( bool oneAtATime );
QString welcome();
virtual void closeEvent( QCloseEvent* e );
virtual void resizeEvent( QResizeEvent* );
virtual void moveEvent ( QMoveEvent * );
void setupMenuBar();
void createAnnotationDialog();
bool load();
virtual void contextMenuEvent( QContextMenuEvent* e );
void setLocked( bool b, bool force, bool recount=true );
void configImages( const DB::ImageInfoList& list, bool oneAtATime );
void updateStates( bool thumbNailView );
DB::FileNameList selectedOnDisk();
void setupPluginMenu();
void launchViewer(const DB::FileNameList& mediaList, bool reuse, bool slideShow, bool random);
void setupStatusBar();
void setPluginMenuState( const char* name, const QList& actions );
void createSarchBar();
void executeStartupActions();
void checkIfMplayerIsInstalled();
bool anyVideosSelected() const;
void announceAndroidVersion();
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Browser::PositionBrowserWidget* createPositionBrowser();
#endif
private:
static Window* s_instance;
ThumbnailView::ThumbnailFacade* m_thumbnailView;
Settings::SettingsDialog* m_settingsDialog;
QPointer m_annotationDialog;
QStackedWidget* m_stack;
QTimer* m_autoSaveTimer;
Browser::BrowserWidget* m_browser;
DeleteDialog* m_deleteDialog;
QAction* m_lock;
QAction* m_unlock;
QAction* m_setDefaultPos;
QAction* m_setDefaultNeg;
QAction* m_jumpToContext;
HTMLGenerator::HTMLDialog* m_htmlDialog;
QAction* m_configOneAtATime;
QAction* m_configAllSimultaniously;
QAction* m_createImageStack;
QAction* m_unStackImages;
QAction* m_setStackHead;
QAction* m_view;
QAction* m_rotLeft;
QAction* m_rotRight;
QAction* m_sortByDateAndTime;
QAction* m_sortAllByDateAndTime;
QAction* m_AutoStackImages;
QAction* m_viewInNewWindow;
KActionMenu* m_viewMenu;
KToggleAction* m_smallListView;
KToggleAction* m_largeListView;
KToggleAction* m_largeIconView;
QAction* m_generateHtml;
QAction* m_copy;
QAction* m_paste;
QAction* m_deleteSelected;
QAction* m_limitToMarked;
QAction* m_selectAll;
QAction* m_runSlideShow;
QAction* m_runRandomSlideShow;
Plugins::Interface* m_pluginInterface;
QAction* m_showExifDialog;
#ifdef HASKIPI
KIPI::PluginLoader* m_pluginLoader;
#endif
QAction* m_recreateThumbnails;
QAction* m_useNextVideoThumbnail;
QAction* m_usePreviousVideoThumbnail;
QAction* m_markUntagged;
TokenEditor* m_tokenEditor;
DateBar::DateBarWidget* m_dateBar;
QFrame* m_dateBarLine;
bool m_hasLoadedPlugins;
QMap > m_viewerInputMacros;
MainWindow::StatusBar* m_statusBar;
QString m_lastTarget;
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Browser::PositionBrowserWidget* m_positionBrowser;
#endif
};
}
#endif /* MAINWINDOW_WINDOW_H */
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Plugins/ImageInfo.cpp b/Plugins/ImageInfo.cpp
index a8f8c89a..99767246 100644
--- a/Plugins/ImageInfo.cpp
+++ b/Plugins/ImageInfo.cpp
@@ -1,393 +1,393 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 "ImageInfo.h"
#include "Logging.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Map/GeoCoordinates.h"
#define KEXIV_ORIENTATION_UNSPECIFIED 0
#define KEXIV_ORIENTATION_NORMAL 1
#define KEXIV_ORIENTATION_HFLIP 2
#define KEXIV_ORIENTATION_ROT_180 3
#define KEXIV_ORIENTATION_VFLIP 4
#define KEXIV_ORIENTATION_ROT_90_HFLIP 5
#define KEXIV_ORIENTATION_ROT_90 6
#define KEXIV_ORIENTATION_ROT_90_VFLIP 7
#define KEXIV_ORIENTATION_ROT_270 8
/**
* Convert a rotation in degrees to a KExiv2::ImageOrientation value.
*/
static int deg2KexivOrientation( int deg)
{
deg = (deg + 360) % 360;;
switch (deg)
{
case 0:
return KEXIV_ORIENTATION_NORMAL;
case 90:
return KEXIV_ORIENTATION_ROT_90;
case 180:
return KEXIV_ORIENTATION_ROT_180;
case 270:
return KEXIV_ORIENTATION_ROT_270;
default:
qCWarning(PluginsLog) << "Rotation of " << deg << "degrees can't be mapped to KExiv2::ImageOrientation value.";
return KEXIV_ORIENTATION_UNSPECIFIED;
}
}
/**
* Convert a KExiv2::ImageOrientation value into a degrees angle.
*/
static int kexivOrientation2deg( int orient)
{
switch (orient)
{
case KEXIV_ORIENTATION_NORMAL:
return 0;
case KEXIV_ORIENTATION_ROT_90:
return 90;
case KEXIV_ORIENTATION_ROT_180:
return 280;
case KEXIV_ORIENTATION_ROT_270:
return 270;
default:
qCWarning(PluginsLog) << "KExiv2::ImageOrientation value " << orient << " not a pure rotation. Discarding orientation info.";
return 0;
}
}
Plugins::ImageInfo::ImageInfo( KIPI::Interface* interface, const QUrl &url )
: KIPI::ImageInfoShared( interface, url )
{
m_info = DB::ImageDB::instance()->info( DB::FileName::fromAbsolutePath(_url.path()));
}
QMap Plugins::ImageInfo::attributes()
{
if (m_info == nullptr) {
// This can happen if we're trying to access an image that
// has been deleted on-disc, but not yet the database
return QMap();
}
Q_ASSERT( m_info );
QMap res;
res.insert(QString::fromLatin1("name"), QFileInfo(m_info->fileName().absolute()).baseName());
res.insert(QString::fromLatin1("comment"), m_info->description());
res.insert(QLatin1String("date"), m_info->date().start());
res.insert(QLatin1String("dateto"), m_info->date().end());
res.insert(QLatin1String("isexactdate"), m_info->date().start() == m_info->date().end());
res.insert(QString::fromLatin1("orientation"), deg2KexivOrientation(m_info->angle()) );
res.insert(QString::fromLatin1("angle"), deg2KexivOrientation(m_info->angle()) ); // for compatibility with older versions. Now called orientation.
res.insert(QString::fromLatin1("title"), m_info->label());
res.insert(QString::fromLatin1("rating"), m_info->rating());
// not supported:
//res.insert(QString::fromLatin1("colorlabel"), xxx );
//res.insert(QString::fromLatin1("picklabel"), xxx );
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
Map::GeoCoordinates position = m_info->coordinates();
if (position.hasCoordinates()) {
res.insert(QString::fromLatin1("longitude"), QVariant(position.lon()));
res.insert(QString::fromLatin1("latitude"), QVariant(position.lat()));
if (position.hasAltitude())
res.insert(QString::fromLatin1("altitude"), QVariant(position.alt()));
}
#endif
// Flickr plug-in expects the item tags, so we better give them.
QString text;
QList categories = DB::ImageDB::instance()->categoryCollection()->categories();
QStringList tags;
QStringList tagspath;
const QLatin1String sep ("/");
Q_FOREACH( const DB::CategoryPtr category, categories ) {
QString categoryName = category->name();
if ( category->isSpecialCategory() )
continue;
// I don't know why any categories except the above should be excluded
//if ( category->doShow() ) {
Utilities::StringSet items = m_info->itemsOfCategory( categoryName );
Q_FOREACH( const QString &tag, items ) {
tags.append( tag );
// digikam compatible tag path:
// note: this produces a semi-flattened hierarchy.
// instead of "Places/France/Paris" this will yield "Places/Paris"
tagspath.append( categoryName + sep + tag );
}
//}
}
res.insert(QString::fromLatin1( "tagspath" ), tagspath );
res.insert(QString::fromLatin1( "keywords" ), tags );
res.insert(QString::fromLatin1( "tags" ), tags ); // for compatibility with older versions. Now called keywords.
// TODO: implement this:
//res.insert(QString::fromLatin1( "filesize" ), xxx );
// not supported:
//res.insert(QString::fromLatin1( "creators" ), xxx );
//res.insert(QString::fromLatin1( "credit" ), xxx );
//res.insert(QString::fromLatin1( "rights" ), xxx );
//res.insert(QString::fromLatin1( "source" ), xxx );
return res;
}
void Plugins::ImageInfo::clearAttributes()
{
if( m_info ) {
// official behaviour is to delete all officially supported attributes:
QStringList attr;
attr.append( QString::fromLatin1("comment") );
attr.append( QString::fromLatin1("date") );
attr.append( QString::fromLatin1("title") );
attr.append( QString::fromLatin1("orientation") );
attr.append( QString::fromLatin1("tagspath") );
attr.append( QString::fromLatin1("rating") );
attr.append( QString::fromLatin1("colorlabel") );
attr.append( QString::fromLatin1("picklabel") );
attr.append( QString::fromLatin1("gpslocation") );
attr.append( QString::fromLatin1("copyrights") );
delAttributes(attr);
}
}
void Plugins::ImageInfo::addAttributes( const QMap& amap )
{
if ( m_info && ! amap.empty() ) {
QMap map = amap;
if ( map.contains(QLatin1String("name")) )
{
// plugin renamed the item
// TODO: implement this
qCWarning(PluginsLog, "File renaming by kipi-plugin not supported.");
//map.remove(QLatin1String("name"));
}
if ( map.contains(QLatin1String("comment")) )
{
// is it save to do that? digikam seems to allow multiple comments on a single image
// if a plugin assumes that it is adding a comment, not setting it, things might go badly...
m_info->setDescription( map[QLatin1String("comment")].toString() );
map.remove(QLatin1String("comment"));
}
// note: this probably won't work as expected because according to the spec,
// "isexactdate" is supposed to be readonly and therefore never set here:
if (map.contains(QLatin1String("isexactdate")) && map.contains(QLatin1String("date"))) {
m_info->setDate(DB::ImageDate(map[QLatin1String("date")].toDateTime()));
map.remove(QLatin1String("date"));
} else if (map.contains(QLatin1String("date")) && map.contains(QLatin1String("dateto"))) {
m_info->setDate(DB::ImageDate(map[QLatin1String("date")].toDateTime(), map[QLatin1String("dateto")].toDateTime()));
map.remove(QLatin1String("date"));
map.remove(QLatin1String("dateto"));
} else if (map.contains(QLatin1String("date"))) {
m_info->setDate(DB::ImageDate(map[QLatin1String("date")].toDateTime()));
map.remove(QLatin1String("date"));
}
if ( map.contains(QLatin1String("angle")) )
{
qCWarning(PluginsLog, "Kipi-plugin uses deprecated attribute \"angle\".");
m_info->setAngle( kexivOrientation2deg( map[QLatin1String("angle")].toInt() ) );
map.remove(QLatin1String("angle"));
}
if ( map.contains(QLatin1String("orientation")) )
{
m_info->setAngle( kexivOrientation2deg( map[QLatin1String("orientation")].toInt() ) );
map.remove(QLatin1String("orientation"));
}
if ( map.contains(QLatin1String("title")) )
{
m_info->setLabel( map[QLatin1String("title")].toString() );
map.remove(QLatin1String("title"));
}
if ( map.contains(QLatin1String("rating")) )
{
m_info->setRating( map[QLatin1String("rating")].toInt() );
map.remove(QLatin1String("rating"));
}
if ( map.contains(QLatin1String("tagspath")) )
{
const QStringList tagspaths = map[QLatin1String("tagspath")].toStringList();
const DB::CategoryCollection *categories = DB::ImageDB::instance()->categoryCollection();
DB::MemberMap &memberMap = DB::ImageDB::instance()->memberMap();
Q_FOREACH( const QString &path, tagspaths )
{
qCDebug(PluginsLog) << "Adding tags: " << path;
QStringList tagpath = path.split( QLatin1String("/"), QString::SkipEmptyParts );
// Note: maybe tagspaths with only one component or with unknown first component
// should be added to the "keywords"/"Events" category?
if ( tagpath.size() < 2 )
{
qCWarning(PluginsLog) << "Ignoring incompatible tag: " << path;
continue;
}
// first component is the category,
const QString categoryName = tagpath.takeFirst();
DB::CategoryPtr cat = categories->categoryForName( categoryName );
if ( cat )
{
QString previousTag;
// last component is the tag:
// others define hierarchy:
Q_FOREACH( const QString ¤tTag, tagpath )
{
if ( ! cat->items().contains( currentTag ) )
{
qCDebug(PluginsLog) << "Adding tag " << currentTag << " to category " << categoryName;
// before we can use a tag, we have to add it
cat->addItem( currentTag );
}
if ( ! previousTag.isNull() )
{
if ( ! memberMap.isGroup( categoryName, previousTag ) )
{
// create a group for the parent tag, so we can add a sub-category
memberMap.addGroup( categoryName, previousTag );
}
if ( memberMap.canAddMemberToGroup( categoryName, previousTag, currentTag ) )
{
// make currentTag a member of the previousTag group
memberMap.addMemberToGroup( categoryName, previousTag, currentTag );
} else {
qCWarning(PluginsLog) << "Cannot make " << currentTag << " a subcategory of "
<< categoryName << "/" << previousTag << "!";
}
}
previousTag = currentTag;
}
qCDebug(PluginsLog) << "Adding tag " << previousTag << " in category " << categoryName
<< " to image " << m_info->label();
// previousTag must be a valid category (see addItem() above...)
m_info->addCategoryInfo( categoryName, previousTag );
} else {
qCWarning(PluginsLog) << "Unknown category: " << categoryName;
}
}
map.remove(QLatin1String("tagspath"));
}
// remove read-only keywords:
map.remove(QLatin1String("filesize"));
map.remove(QLatin1String("isexactdate"));
map.remove(QLatin1String("keywords"));
map.remove(QLatin1String("tags"));
map.remove(QLatin1String("altitude"));
map.remove(QLatin1String("longitude"));
map.remove(QLatin1String("latitude"));
// colorlabel
// picklabel
// creators
// credit
// rights
// source
MainWindow::DirtyIndicator::markDirty();
if ( ! map.isEmpty() )
{
qCWarning(PluginsLog) << "The following attributes are not (yet) supported by the KPhotoAlbum KIPI interface:" << map;
}
}
}
void Plugins::ImageInfo::delAttributes( const QStringList& attrs)
{
if ( m_info && ! attrs.empty() ) {
QStringList delAttrs = attrs;
if ( delAttrs.contains(QLatin1String("comment")))
{
m_info->setDescription( QString() );
delAttrs.removeAll(QLatin1String("comment"));
}
// not supported: date
if ( delAttrs.contains(QLatin1String("orientation")) ||
delAttrs.contains(QLatin1String("angle")) )
{
m_info->setAngle( 0 );
delAttrs.removeAll(QLatin1String("orientation"));
delAttrs.removeAll(QLatin1String("angle"));
}
if ( delAttrs.contains(QLatin1String("rating")))
{
m_info->setRating( -1 );
delAttrs.removeAll(QLatin1String("rating"));
}
if ( delAttrs.contains(QLatin1String("title")))
{
m_info->setLabel( QString() );
delAttrs.removeAll(QLatin1String("title"));
}
// TODO:
// (colorlabel)
// (picklabel)
// copyrights
// not supported: gpslocation
if ( delAttrs.contains(QLatin1String("tags")) ||
delAttrs.contains(QLatin1String("tagspath")))
{
m_info->clearAllCategoryInfo();
delAttrs.removeAll(QLatin1String("tags"));
delAttrs.removeAll(QLatin1String("tagspath"));
}
MainWindow::DirtyIndicator::markDirty();
if ( ! delAttrs.isEmpty() )
{
qCWarning(PluginsLog) << "The following attributes are not (yet) supported by the KPhotoAlbum KIPI interface:" << delAttrs;
}
}
}
void Plugins::ImageInfo::cloneData( ImageInfoShared* const other )
{
ImageInfoShared::cloneData( other );
if ( m_info ) {
Plugins::ImageInfo* inf = static_cast( other );
m_info->setDate( inf->m_info->date() );
MainWindow::DirtyIndicator::markDirty();
}
}
bool Plugins::ImageInfo::isPositionAttribute(const QString &key)
{
return (key == QString::fromLatin1("longitude") || key == QString::fromLatin1("latitude") || key == QString::fromLatin1("altitude") || key == QString::fromLatin1("positionPrecision"));
}
bool Plugins::ImageInfo::isCategoryAttribute(const QString &key)
{
return (key != QString::fromLatin1("tags") && !isPositionAttribute(key));
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Viewer/InfoBox.cpp b/Viewer/InfoBox.cpp
index dd1ee717..77ebc64a 100644
--- a/Viewer/InfoBox.cpp
+++ b/Viewer/InfoBox.cpp
@@ -1,346 +1,346 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
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 "InfoBox.h"
// Qt includes
#include
#include
#include
#include
#include
#include
// KDE includes
#include
#include
#include
// Local includes
#include
#include
#include
#include
-#ifdef HAVE_KGEOMAP
+#ifdef HAVE_MARBLE
#include