diff --git a/NEWS b/NEWS index 91bbb9bbcb..34d132765b 100644 --- a/NEWS +++ b/NEWS @@ -1,247 +1,251 @@ digiKam 6.0.0 - Release date: 2018-xx-xx ***************************************************************************************************** NEW FEATURES: ImageEditor : New clone tool to replace old CImg in-painting tool. ImageEditor : Add Web services Import and Export tools. Showfoto : Add Web services Import and Export tools. LightTable : Add Web services Import and Export tools. Database : Similarity database has been moved to a dedicated file to not bloat core database with computed finger-prints. This will speed-up query in core database in case of Similarity feature is used. Database : New video metadata parser based on ffmpeg to populate database. Search : Add video support to find files based on properties registered on database. General : Add QWebEngine support. General : Update internal Libraw to last 0.18.12. General : Fix all Krazy static analyzer reports. Tags : Add possibility to merge tags by drag & drop. HTML Gallery : New Vanilla theme to emulate export to HTML from Adobe LightRoom. HTML Gallery : New Blue Frame theme. HTML Gallery : New Kiosk-Mode theme. ***************************************************************************************************** BUGFIXES: 001 ==> 172650 - No export tools available. 002 ==> 149591 - The export menu is blank. 003 ==> 300424 - Export tools not detected. 004 ==> 327743 - MediaWiki export not displayed. 005 ==> 348146 - Export tools configuration module. 006 ==> 243275 - Crashing when calling configuration. 007 ==> 257134 - Crashes when entering its settings. 008 ==> 230666 - Crash during start. 009 ==> 306698 - Crashes after update to KDE. 010 ==> 207640 - Crashes immediately at startup, sometimes at closing. 011 ==> 245628 - Crash when enabling/disabling Facebook import/export. 012 ==> 097317 - sigsegv [New Thread 1100241184 (LWP 5665)]. 013 ==> 245776 - Crashes when selecting Settings. 014 ==> 245775 - Crashes even without export tools installed. 015 ==> 254283 - Crash as soon as i click settings. 016 ==> 255733 - crash when reopening configuration dialog. 017 ==> 262050 - Crash after new install and scan to mysql database. 018 ==> 263871 - Crashed after searching for duplicates. 019 ==> 268242 - Crashes after I clicked on 'Settings'. 020 ==> 276882 - Add export tool buttons to toolbar. 021 ==> 284801 - Export to picasaweb crashes. 022 ==> 277669 - Crash after files moved. 023 ==> 281250 - Crash after disabling Export tools. 024 ==> 202637 - Crash trying to start export tool. 025 ==> 306693 - after update to KDE host application crashes on startup. 026 ==> 282781 - Crashes when reopening configuration dialog after disabling export tools. 027 ==> 285499 - Crash when settings window opened. 028 ==> 297629 - Crashed when importing from Nikon P7000. 029 ==> 303338 - Crashes when clicking "send to" button. 030 ==> 307619 - Refuses to load Export tools. 031 ==> 311812 - Export tools not loading, SO version not defined. 032 ==> 313186 - Crashes on attempt to use the "Send to" menu. 033 ==> 313356 - Crashed when clicking the "send to" button. 034 ==> 313577 - Crashes when pressing the "send to" button. 035 ==> 313890 - Crash when clicking "Send to...". 036 ==> 315033 - Crashes on pressing Send To... button. 037 ==> 315914 - The facebook tool crashes everytime on initialization. 038 ==> 326556 - Export tools are not loaded when starting host application for second time. 039 ==> 095175 - crash on loading, signal 11 SIGSEGV. 040 ==> 175844 - Crashes at startup loading Export tools. 041 ==> 306511 - Crash during start. 042 ==> 234021 - Crash on loading. 043 ==> 219772 - Opening the application causes crash. 044 ==> 294173 - Crash after Image resize start. 045 ==> 306881 - Crashed when attempting to open Export tools. 046 ==> 306495 - Crash changing settings linux ubuntu lucid. 047 ==> 306497 - Crash after changing settings segmentation fault possible 2nd report? 048 ==> 185470 - "Import from facebook" is listed twice in import menu. 049 ==> 334045 - MediaWiki option not available in Export menu when plugin activated. 050 ==> 142112 - Can't save on my webspace with ShowFoto. 051 ==> 167417 - Showfoto cannot save files of CIFS mount. 052 ==> 125164 - Flickr export tool should respect host application selection. 053 ==> 238927 - Host application quits when uploading to Flickr. 054 ==> 326740 - Selection of tools is set to default after each update. 055 ==> 233063 - Add progress indicator when moving or copy files [patch]. 056 ==> 361829 - Rotated MP4 video with "Orientation" flag are not played back in the correct rotation angle. 057 ==> 329854 - digiKam doesn't rotate video thumbnail. 058 ==> 376661 - When importing ~200,000 video files Digikam crashes in about 2-5 seconds of starting. 059 ==> 377072 - Cannot read video metadata. 060 ==> 374453 - Extract right video date from MP4 files metadata. 061 ==> 377177 - Geolocation / GPS information doesn't appear in video metadata. 062 ==> 383588 - Imported video files have time shifted exactly 2 hours later. 063 ==> 380847 - MP4 video not importing with correct date. 064 ==> 373682 - geolocalisation filter does not take care of the videos geolocalisation tags. 065 ==> 340925 - digiKam crash when start it. 066 ==> 331506 - digiKam crashes on startup. 067 ==> 335816 - Crash when trying to add a big collection. 068 ==> 353295 - digiKam repeatedly crashes while importing pictures. 069 ==> 341433 - Crash when opening digiKam application. 070 ==> 375562 - digiKam crashes while scanning images into sqlite database. 071 ==> 334782 - Crash while doing nothing. 072 ==> 362672 - Crash on start of digiKam. 073 ==> 341023 - Crash after startup during check for updated images. 074 ==> 386891 - Crashed while adding pictures. 075 ==> 342666 - digiKam crashes during find new items. 076 ==> 341274 - digiKam crash on startup. 077 ==> 343708 - Crash when scanning album. 078 ==> 332721 - Crash when reading a certain MP4 video file. 079 ==> 343736 - Crashes when rebuilding thumbnails from database. 080 ==> 346356 - digiKam crashes when adding 90.000 pictures to library. 081 ==> 343714 - digiKam crash when scanning for new items. 082 ==> 341091 - digiKam crashes when updating the MySQL database of a a hudge photo collection. 083 ==> 340879 - digiKam crashes after getting unexpected but reasonable output from libexiv2. 084 ==> 342712 - Crash on collection scanning. 085 ==> 356704 - digiKam still crashes while scanning a new photo directory and subdirs. 086 ==> 339269 - Segfault when opening a folder that contains unknown file types (mov, avi, xcf). 087 ==> 364635 - digiKam crashes on startup. 088 ==> 357356 - digiKam crash on startup while scanning photos. 089 ==> 341554 - digiKam crashed by Data-Import from NFS. 090 ==> 345457 - digiKam crashes at "loading tools". 091 ==> 349635 - Crash of digiKam - moving album. 092 ==> 342604 - digiKam crash. 093 ==> 331450 - Signal 8 on album opening. 094 ==> 342030 - digiKam crashes when checking an AVI video file using exiv2. 095 ==> 352777 - Crash during scan. 096 ==> 352944 - digiKam crashes on start. 097 ==> 343643 - digiKam crashes while perform initial scanning of custom photo folder. 098 ==> 342000 - digiKam crash when opening folder with Videos (Album or SD Card import). 099 ==> 353447 - digiKam crashes when scanning files. 100 ==> 346807 - Crashes on startup. 101 ==> 364639 - digiKam crashed while opening database. 102 ==> 341504 - Crash while using application. 103 ==> 367691 - Searching for pictures crashes at 30% every time. 104 ==> 334604 - Crash after changing disk partions. 105 ==> 351689 - Crash on opening digiKam. 106 ==> 149267 - digiKam crashes after finding avi and so on. 107 ==> 170387 - Add movies management. 108 ==> 369629 - digiKam does not use GPS data from video files. 109 ==> 367880 - Nexus 5X videos show up upside-down in digiKam. 110 ==> 330116 - digiKam does not take care about GPS info stored in MP4 video files. 111 ==> 339150 - digikam crashes when trying to display video file. 112 ==> 344406 - Crash at start. 113 ==> 339909 - digiKam Segmentation Fault on open. 114 ==> 343231 - Crash at scanning for new fotos. 115 ==> 340373 - Crash on scanning video directory. 116 ==> 134679 - Video and audio files are not imported. 117 ==> 375357 - No video preview. 118 ==> 261773 - Batch renaming does not complete when MP4 video file is processed. 119 ==> 185915 - Album View: "Created" time of video set to "00:00". 120 ==> 303218 - digiKam import crashes when you select video files. 121 ==> 374241 - Bad video date rename. 122 ==> 375646 - Be able to scan only photo, not video and audio. 123 ==> 262499 - Cannot rename .AVI files. 124 ==> 199261 - Import avi movies from sdcard wrong date and no thumbnail. 125 ==> 181521 - NEF's in descending order, AVI in ascending order in import from SD-card. 126 ==> 392019 - Two persons can point to the same face tag in pictures. 127 ==> 392013 - Metadata explorer does not show XMP face rectangles. 128 ==> 389508 - Dates Side Menu Is Not Updated Automatically After Exif Date Change [patch]. 129 ==> 331864 - Merge Tags with same name when moving to same hierarchy level. 130 ==> 347302 - Reassign name to face. 131 ==> 391747 - BQM Tool "Remove Metadata" doesn't remove all metadata from image. 132 ==> 285683 - Already imported pictures not recognized after daylight savings time. 133 ==> 392309 - Icons are pixelated when my display scale factor is 1.2 134 ==> 392405 - Function 'getImageIdsFromArea' argument order different. 135 ==> 386224 - Metadata is not updated when moving tags. 136 ==> 370245 - Be able to rename tags which have been setted in pictures. 137 ==> 374470 - Deleted tags are not removed from file metadata. 138 ==> 374516 - Persons metadata are not updated after a tag removed. 139 ==> 392436 - Count Files in directory. 140 ==> 363859 - digiKam core port from QWebKit to QWebEngine [patch]. 141 ==> 392427 - Cannot add collection on network drive. 142 ==> 392022 - Position of a face tag appears on top of bottom of the list, instead of being sorted alphabetically. 143 ==> 372763 - Rename does not give options on Conflict. 144 ==> 391533 - Feature request: add "NOT" tag matching condition in "Filters" panel. 145 ==> 381222 - digiKam crash on fuzzy search. 146 ==> 386275 - Crash caused by QtAV. 147 ==> 372342 - Face tag area is very short [patch]. 148 ==> 391348 - People Side Menu Shows Only Faces Not People Tagged Images. 149 ==> 385630 - Views Requiring Maps Takes ~30s to Launch. 150 ==> 192908 - Allow to split icon-view in order to show multiple albums at the same time. 151 ==> 339088 - GIT master: crash when clicking through images in preview, with face recognition running in background. 152 ==> 341605 - Crash if I attempt to use left-sidebar tags tab. 153 ==> 227266 - Handle Video Date from metadata. 154 ==> 227259 - Needs to Edit Video Date. 155 ==> 373284 - digiKam crashed with SIGSEGV in QSortFilterProxyModel::parent(). 156 ==> 384807 - digikam 5.7.0 AppImage bundle : provide a more recent ffmpeg version for video support. 157 ==> 391835 - Deleted pictures still appear in group. 158 ==> 387483 - Elegant theme: Selected frame colors swapped [patch]. 159 ==> 375424 - Thumbnails are not being removed from AlbumsView after moving the images to Trash. 160 ==> 368796 - Problem with Exif-tags: ImageDescription and UserComment. 161 ==> 392417 - AppImage (5.9.0-01-x86-64) does not support "--install" cli parameter. 162 ==> 392922 - digikam-6.0.0 fail to start. 163 ==> 391399 - Not possible to add location bookmarks in Digikam >5.6.0. 164 ==> 380876 - Tags in Digikam DB maintained after being removed from file and file re-scanned. 165 ==> 392017 - Merging, renaming and removing face tags. 166 ==> 352711 - Externally removed tags are not removed from digiKam. 167 ==> 393108 - Tags not always visible when selecting multiple pictures in a group. 168 ==> 392656 - Selecting a root album for face scan doesn't include subfolders, but rather scans an unexpected album set. 169 ==> 329438 - Rename function with Date & Time does not work with NTFS. 170 ==> 376473 - Can"t set empty IPTC country code when using metadata templates. 171 ==> 380289 - Cannot write to Albums residing on NFS. 172 ==> 384465 - With Compact Flash Card Created date in thumbnails is wrong. 173 ==> 381958 - Cannot add additional collection. 174 ==> 383747 - "Rotate only by setting a flag" Changes Image Instead. 175 ==> 387977 - No icon only view of "Extras sidebar": sidebar taking up a lot of space. 176 ==> 277502 - All versions of version set always displayed in Album view [patch]. 177 ==> 393283 - Caption not updating Exif.Image.ImageDescription field. 178 ==> 391060 - Crashes on undo of very large tif. 179 ==> 366305 - Add a message at startup about the lack of temporary space to perform Undo operations. 180 ==> 366391 - Rotating an image seems to forget to reset the orientation flag. 181 ==> 393654 - Not able to select gpx file. 182 ==> 367596 - Sub-folder count images but don't show them (unsupported JPEG file?). 183 ==> 379922 - Digikam won't remove tags set by Windows Explorer. 184 ==> 379081 - GPS data are in file but geolocation indicator is not shown and map view empty. 185 ==> 354819 - Specific pictures not showing up in digikam. 186 ==> 393855 - MySQL/MariaDB upgrade fails. 187 ==> 384603 - Camera Creation Date not set from EXIF data. 188 ==> 386959 - Properties view: wrong creation date [patch]. 189 ==> 393970 - No mts video thumbnails. 190 ==> 393728 - Reread metadata from Video uses sidecar only. 191 ==> 393925 - UpdateSchemaFromV7ToV9 fails due to duplicate key in album_2. 192 ==> 393773 - showfoto crashes when geotagging. 193 ==> 388199 - No context menu to copy coordinates from map. 194 ==> 393399 - Windows defender freaks out in windows 10 and Edge. 195 ==> 392134 - SIGSEGV While Scanning Faces [patch]. 196 ==> 394168 - OSM Search Yields No Results. 197 ==> 377719 - Cannot rename file with overwrite [patch]. 198 ==> 388002 - remove kio related legacy [patch] 199 ==> 394242 - Import settings unneccesarily asks to overwrite image database, and crashes when I decline. 200 ==> 394278 - A slideshow theme for kiosk mode. 201 ==> 340389 - digiKam crashes while editing pictures for color balancing on OSX [patch]. 202 ==> 394413 - Unify group handling [patch]. 203 ==> 394573 - Revers geodata from open street map does not work. 204 ==> 394590 - Feature request: being able to filter on all metadatas fields. 205 ==> 394671 - Distortion on Panasonic DMC-LX15. 206 ==> 393205 - Advanced rename very slow. 207 ==> 382474 - Thumbnail regeneration. 208 ==> 394865 - digikam suspicious crash on exit. 209 ==> 390541 - Tooltip background cannot be changed. 210 ==> 391521 - "Tool-tip" box difficult to read due to default color scheme. 211 ==> 377849 - Albums disappear when the network is interrupted. 212 ==> 394988 - PgDown and PgUp hardcoded in preview mode. 213 ==> 366312 - Efficient photo tagging workflow got lost in transition from 4.x to 5. 214 ==> 395093 - Being able to export a list of paths from a selection of thumbnails. 215 ==> 395144 - When zooming in preview, face tags show on wrong places. 216 ==> 275671 - Scan single image for faces. -217 ==> +217 ==> 395199 - Uploading large video files to flickr fails. +218 ==> 348274 - "Change Account" immediately opens web page in browser, before I click Continue +219 ==> 263347 - Print wizard ignores selected paper size, reverts to A4. +220 ==> 395579 - Only one tag being exported to flickr. +221 ==> diff --git a/core/app/views/digikamview.cpp b/core/app/views/digikamview.cpp index da052115ae..a9cb03648f 100644 --- a/core/app/views/digikamview.cpp +++ b/core/app/views/digikamview.cpp @@ -1,2780 +1,2780 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2002-16-10 * Description : implementation of album view interface. * * Copyright (C) 2002-2005 by Renchi Raju * Copyright (C) 2002-2018 by Gilles Caulier * Copyright (C) 2009-2011 by Johannes Wienke * Copyright (C) 2010-2011 by Andi Clemens * Copyright (C) 2011-2013 by Michael G. Hansen * Copyright (C) 2014-2015 by Mohamed_Anwer * Copyright (C) 2017 by Simon Frei * * 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, 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. * * ============================================================ */ #include "digikamview.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "albumhistory.h" #include "albumlabelstreeview.h" #include "coredbsearchxml.h" #include "digikam_config.h" #include "digikam_debug.h" #include "digikam_globals.h" #include "digikamapp.h" #include "digikamimageview.h" #include "dmessagebox.h" #include "dzoombar.h" #include "dtrashitemmodel.h" #include "facescansettings.h" #include "facesdetector.h" #include "fileactionmngr.h" #include "fileactionprogress.h" #include "filtersidebarwidget.h" #include "filterstatusbar.h" #include "imagealbummodel.h" #include "imagedescedittab.h" #include "imagepreviewview.h" #include "imagepropertiessidebardb.h" #include "imagepropertiesversionstab.h" #include "imagethumbnailbar.h" #include "imageviewutilities.h" #include "leftsidebarwidgets.h" #include "loadingcacheinterface.h" #include "metadatahub.h" #include "metadatasettings.h" #include "metadatasynchronizer.h" #include "newitemsfinder.h" #include "presentationmngr.h" #include "queuemgrwindow.h" #include "scancontroller.h" #include "setup.h" #include "sidebar.h" #include "slideshow.h" #include "slideshowbuilder.h" #include "statusprogressbar.h" #include "tableview.h" #include "tagmodificationhelper.h" #include "tagsactionmngr.h" #include "tagscache.h" #include "tagsmanager.h" #include "thumbsgenerator.h" #include "trashview.h" #include "versionmanagersettings.h" #include "contextmenuhelper.h" #ifdef HAVE_MEDIAPLAYER # include "mediaplayerview.h" #endif //HAVE_MEDIAPLAYER #ifdef HAVE_MARBLE # include "mapwidgetview.h" #endif // HAVE_MARBLE namespace Digikam { class DigikamView::Private { public: explicit Private() : needDispatchSelection(false), useAlbumHistory(false), initialAlbumID(0), thumbSize(ThumbnailSize::Medium), dockArea(0), splitter(0), selectionTimer(0), thumbSizeTimer(0), albumFolderSideBar(0), tagViewSideBar(0), labelsSideBar(0), dateViewSideBar(0), timelineSideBar(0), searchSideBar(0), fuzzySearchSideBar(0), #ifdef HAVE_MARBLE gpsSearchSideBar(0), mapView(0), #endif // HAVE_MARBLE peopleSideBar(0), parent(0), iconView(0), tableView(0), trashView(0), utilities(0), albumManager(0), albumHistory(0), stackedview(0), lastViewMode(StackedView::IconViewMode), albumModificationHelper(0), tagModificationHelper(0), searchModificationHelper(0), leftSideBar(0), rightSideBar(0), filterWidget(0), optionAlbumViewPrefix(QLatin1String("AlbumView")), modelCollection(0), labelsSearchHandler(0) { } QString userPresentableAlbumTitle(const QString& album) const; void addPageUpDownActions(DigikamView* const q, QWidget* const w); public: bool needDispatchSelection; bool useAlbumHistory; int initialAlbumID; int thumbSize; QMainWindow* dockArea; SidebarSplitter* splitter; QTimer* selectionTimer; QTimer* thumbSizeTimer; // left side bar AlbumFolderViewSideBarWidget* albumFolderSideBar; TagViewSideBarWidget* tagViewSideBar; LabelsSideBarWidget* labelsSideBar; DateFolderViewSideBarWidget* dateViewSideBar; TimelineSideBarWidget* timelineSideBar; SearchSideBarWidget* searchSideBar; FuzzySearchSideBarWidget* fuzzySearchSideBar; #ifdef HAVE_MARBLE GPSSearchSideBarWidget* gpsSearchSideBar; MapWidgetView* mapView; #endif // HAVE_MARBLE PeopleSideBarWidget* peopleSideBar; DigikamApp* parent; DigikamImageView* iconView; TableView* tableView; TrashView* trashView; ImageViewUtilities* utilities; AlbumManager* albumManager; AlbumHistory* albumHistory; StackedView* stackedview; StackedView::StackedViewMode lastViewMode; AlbumModificationHelper* albumModificationHelper; TagModificationHelper* tagModificationHelper; SearchModificationHelper* searchModificationHelper; Sidebar* leftSideBar; ImagePropertiesSideBarDB* rightSideBar; FilterSideBarWidget* filterWidget; QString optionAlbumViewPrefix; QList leftSideBarWidgets; DigikamModelCollection* modelCollection; AlbumLabelsSearchHandler* labelsSearchHandler; }; QString DigikamView::Private::userPresentableAlbumTitle(const QString& title) const { if (title == SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch)) { return i18n("Fuzzy Sketch Search"); } else if (title == SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch)) { return i18n("Fuzzy Image Search"); } else if (title == SAlbum::getTemporaryTitle(DatabaseSearch::MapSearch)) { return i18n("Map Search"); } else if (title == SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch) || title == SAlbum::getTemporaryTitle(DatabaseSearch::KeywordSearch)) { return i18n("Last Search"); } else if (title == SAlbum::getTemporaryTitle(DatabaseSearch::TimeLineSearch)) { return i18n("Timeline"); } return title; } void DigikamView::Private::addPageUpDownActions(DigikamView* const q, QWidget* const w) { defineShortcut(w, Qt::Key_PageDown, q, SLOT(slotNextItem())); defineShortcut(w, Qt::Key_Down, q, SLOT(slotNextItem())); defineShortcut(w, Qt::Key_Right, q, SLOT(slotNextItem())); defineShortcut(w, Qt::Key_PageUp, q, SLOT(slotPrevItem())); defineShortcut(w, Qt::Key_Up, q, SLOT(slotPrevItem())); defineShortcut(w, Qt::Key_Left, q, SLOT(slotPrevItem())); } // ------------------------------------------------------------------------------------------- DigikamView::DigikamView(QWidget* const parent, DigikamModelCollection* const modelCollection) : DHBox(parent), d(new Private) { qRegisterMetaType("SlideShowSettings"); d->parent = static_cast(parent); d->modelCollection = modelCollection; d->albumManager = AlbumManager::instance(); d->albumModificationHelper = new AlbumModificationHelper(this, this); d->tagModificationHelper = new TagModificationHelper(this, this); d->searchModificationHelper = new SearchModificationHelper(this, this); d->splitter = new SidebarSplitter; d->splitter->setFrameStyle( QFrame::NoFrame ); d->splitter->setFrameShadow( QFrame::Plain ); d->splitter->setFrameShape( QFrame::NoFrame ); d->splitter->setOpaqueResize(false); d->leftSideBar = new Sidebar(this, d->splitter, Qt::LeftEdge); d->leftSideBar->setObjectName(QLatin1String("Digikam Left Sidebar")); d->splitter->setParent(this); // The dock area where the thumbnail bar is allowed to go. d->dockArea = new QMainWindow(this, Qt::Widget); d->splitter->addWidget(d->dockArea); d->stackedview = new StackedView(d->dockArea); d->dockArea->setCentralWidget(d->stackedview); d->stackedview->setDockArea(d->dockArea); d->iconView = d->stackedview->imageIconView(); #ifdef HAVE_MARBLE d->mapView = d->stackedview->mapWidgetView(); #endif // HAVE_MARBLE d->tableView = d->stackedview->tableView(); d->trashView = d->stackedview->trashView(); d->utilities = d->iconView->utilities(); d->addPageUpDownActions(this, d->stackedview->imagePreviewView()); d->addPageUpDownActions(this, d->stackedview->thumbBar()); #ifdef HAVE_MEDIAPLAYER d->addPageUpDownActions(this, d->stackedview->mediaPlayerView()); #endif //HAVE_MEDIAPLAYER d->rightSideBar = new ImagePropertiesSideBarDB(this, d->splitter, Qt::RightEdge, true); d->rightSideBar->setObjectName(QLatin1String("Digikam Right Sidebar")); // album folder view d->albumFolderSideBar = new AlbumFolderViewSideBarWidget(d->leftSideBar, d->modelCollection->getAlbumModel(), d->albumModificationHelper); d->leftSideBarWidgets << d->albumFolderSideBar; connect(d->albumFolderSideBar, SIGNAL(signalFindDuplicates(PAlbum*)), this, SLOT(slotNewDuplicatesSearch(PAlbum*))); // Tags sidebar tab contents. d->tagViewSideBar = new TagViewSideBarWidget(d->leftSideBar, d->modelCollection->getTagModel()); d->leftSideBarWidgets << d->tagViewSideBar; connect(d->tagViewSideBar, SIGNAL(signalFindDuplicates(QList)), this, SLOT(slotNewDuplicatesSearch(QList))); // Labels sidebar d->labelsSideBar = new LabelsSideBarWidget(d->leftSideBar); d->leftSideBarWidgets << d->labelsSideBar; d->labelsSearchHandler = new AlbumLabelsSearchHandler(d->labelsSideBar->labelsTree()); // date view d->dateViewSideBar = new DateFolderViewSideBarWidget(d->leftSideBar, d->modelCollection->getDateAlbumModel(), d->iconView->imageAlbumFilterModel()); d->leftSideBarWidgets << d->dateViewSideBar; // timeline side bar d->timelineSideBar = new TimelineSideBarWidget(d->leftSideBar, d->modelCollection->getSearchModel(), d->searchModificationHelper); d->leftSideBarWidgets << d->timelineSideBar; // Search sidebar tab contents. d->searchSideBar = new SearchSideBarWidget(d->leftSideBar, d->modelCollection->getSearchModel(), d->searchModificationHelper); d->leftSideBarWidgets << d->searchSideBar; // Fuzzy search d->fuzzySearchSideBar = new FuzzySearchSideBarWidget(d->leftSideBar, d->modelCollection->getSearchModel(), d->searchModificationHelper); d->leftSideBarWidgets << d->fuzzySearchSideBar; connect(d->fuzzySearchSideBar,SIGNAL(signalActive(bool)), this, SIGNAL(signalFuzzySidebarActive(bool))); #ifdef HAVE_MARBLE d->gpsSearchSideBar = new GPSSearchSideBarWidget(d->leftSideBar, d->modelCollection->getSearchModel(), d->searchModificationHelper, d->iconView->imageFilterModel(), d->iconView->getSelectionModel()); d->leftSideBarWidgets << d->gpsSearchSideBar; #endif // HAVE_MARBLE // People Sidebar d->peopleSideBar = new PeopleSideBarWidget(d->leftSideBar, d->modelCollection->getTagFacesModel(), d->searchModificationHelper); connect(d->peopleSideBar, SIGNAL(requestFaceMode(bool)), d->iconView, SLOT(setFaceMode(bool))); connect(d->peopleSideBar, SIGNAL(signalFindDuplicates(QList)), this, SLOT(slotNewDuplicatesSearch(QList))); d->leftSideBarWidgets << d->peopleSideBar; foreach(SidebarWidget* const leftWidget, d->leftSideBarWidgets) { d->leftSideBar->appendTab(leftWidget, leftWidget->getIcon(), leftWidget->getCaption()); connect(leftWidget, SIGNAL(requestActiveTab(SidebarWidget*)), this, SLOT(slotLeftSideBarActivate(SidebarWidget*))); } // add only page up and down to work correctly with QCompleter defineShortcut(d->rightSideBar->imageDescEditTab(), Qt::Key_PageDown, this, SLOT(slotNextItem())); defineShortcut(d->rightSideBar->imageDescEditTab(), Qt::Key_PageUp, this, SLOT(slotPrevItem())); // Tags Filter sidebar tab contents. d->filterWidget = new FilterSideBarWidget(d->rightSideBar, d->modelCollection->getTagFilterModel()); d->rightSideBar->appendTab(d->filterWidget, QIcon::fromTheme(QLatin1String("view-filter")), i18n("Filters")); // Versions sidebar overlays d->rightSideBar->getFiltersHistoryTab()->addOpenAlbumAction(d->iconView->imageModel()); d->rightSideBar->getFiltersHistoryTab()->addShowHideOverlay(); d->selectionTimer = new QTimer(this); d->selectionTimer->setSingleShot(true); d->selectionTimer->setInterval(75); d->thumbSizeTimer = new QTimer(this); d->thumbSizeTimer->setSingleShot(true); d->thumbSizeTimer->setInterval(300); d->albumHistory = new AlbumHistory(); slotSidebarTabTitleStyleChanged(); setupConnections(); connect(d->rightSideBar->imageDescEditTab()->getNewTagEdit(), SIGNAL(taggingActionFinished()), this, SLOT(slotFocusAndNextImage())); connect(d->rightSideBar, SIGNAL(signalSetupMetadataFilters(int)), this, SLOT(slotSetupMetadataFilters(int))); } DigikamView::~DigikamView() { saveViewState(); delete d->labelsSearchHandler; delete d->albumHistory; delete d; } void DigikamView::applySettings() { foreach(SidebarWidget* const sidebarWidget, d->leftSideBarWidgets) { sidebarWidget->applySettings(); } d->iconView->imageFilterModel()->setVersionImageFilterSettings(VersionImageFilterSettings(ApplicationSettings::instance()->getVersionManagerSettings())); refreshView(); } void DigikamView::refreshView() { d->rightSideBar->refreshTagsView(); } void DigikamView::setupConnections() { // -- DigikamApp connections ---------------------------------- connect(d->parent, SIGNAL(signalEscapePressed()), this, SLOT(slotEscapePreview())); connect(d->parent, SIGNAL(signalEscapePressed()), d->stackedview, SLOT(slotEscapePreview())); connect(d->parent, SIGNAL(signalNextItem()), this, SLOT(slotNextItem())); connect(d->parent, SIGNAL(signalPrevItem()), this, SLOT(slotPrevItem())); connect(d->parent, SIGNAL(signalFirstItem()), this, SLOT(slotFirstItem())); connect(d->parent, SIGNAL(signalLastItem()), this, SLOT(slotLastItem())); connect(d->parent, SIGNAL(signalCutAlbumItemsSelection()), d->iconView, SLOT(cut())); connect(d->parent, SIGNAL(signalCopyAlbumItemsSelection()), d->iconView, SLOT(copy())); connect(d->parent, SIGNAL(signalPasteAlbumItemsSelection()), this, SLOT(slotImagePaste())); // -- AlbumManager connections -------------------------------- connect(d->albumManager, SIGNAL(signalAlbumCurrentChanged(QList)), this, SLOT(slotAlbumSelected(QList))); connect(d->albumManager, SIGNAL(signalAllAlbumsLoaded()), this, SLOT(slotAllAlbumsLoaded())); connect(d->albumManager, SIGNAL(signalAlbumsCleared()), this, SLOT(slotAlbumsCleared())); // -- IconView Connections ------------------------------------- connect(d->iconView->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotImageSelected())); connect(d->iconView->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotImageSelected())); connect(d->iconView->model(), SIGNAL(layoutChanged()), this, SLOT(slotImageSelected())); connect(d->iconView, SIGNAL(selectionChanged()), this, SLOT(slotImageSelected())); connect(d->iconView, SIGNAL(previewRequested(ImageInfo)), this, SLOT(slotTogglePreviewMode(ImageInfo))); connect(d->iconView, SIGNAL(fullscreenRequested(ImageInfo)), this, SLOT(slotSlideShowManualFrom(ImageInfo))); connect(d->iconView, SIGNAL(zoomOutStep()), this, SLOT(slotZoomOut())); connect(d->iconView, SIGNAL(zoomInStep()), this, SLOT(slotZoomIn())); connect(d->iconView, SIGNAL(signalShowContextMenu(QContextMenuEvent*,QList)), this, SLOT(slotShowContextMenu(QContextMenuEvent*,QList))); connect(d->iconView, SIGNAL(signalShowContextMenuOnInfo(QContextMenuEvent*,ImageInfo,QList,ImageFilterModel*)), this, SLOT(slotShowContextMenuOnInfo(QContextMenuEvent*,ImageInfo,QList,ImageFilterModel*))); connect(d->iconView, SIGNAL(signalShowGroupContextMenu(QContextMenuEvent*,QList,ImageFilterModel*)), this, SLOT(slotShowGroupContextMenu(QContextMenuEvent*,QList,ImageFilterModel*))); // -- TableView Connections ----------------------------------- connect(d->tableView, SIGNAL(signalPreviewRequested(ImageInfo)), this, SLOT(slotTogglePreviewMode(ImageInfo))); connect(d->tableView, SIGNAL(signalZoomOutStep()), this, SLOT(slotZoomOut())); connect(d->tableView, SIGNAL(signalZoomInStep()), this, SLOT(slotZoomIn())); connect(d->tableView, SIGNAL(signalShowContextMenu(QContextMenuEvent*,QList)), this, SLOT(slotShowContextMenu(QContextMenuEvent*,QList))); connect(d->tableView, SIGNAL(signalShowContextMenuOnInfo(QContextMenuEvent*,ImageInfo,QList,ImageFilterModel*)), this, SLOT(slotShowContextMenuOnInfo(QContextMenuEvent*,ImageInfo,QList,ImageFilterModel*))); // TableView::signalItemsChanged is emitted when something changes in the model that // DigikamView should care about, not only the selection. connect(d->tableView, SIGNAL(signalItemsChanged()), this, SLOT(slotImageSelected())); // -- Trash View Connections ---------------------------------- connect(d->trashView, SIGNAL(selectionChanged()), this, SLOT(slotImageSelected())); // -- Sidebar Connections ------------------------------------- connect(d->leftSideBar, SIGNAL(signalChangedTab(QWidget*)), this, SLOT(slotLeftSidebarChangedTab(QWidget*))); connect(d->rightSideBar, SIGNAL(signalFirstItem()), this, SLOT(slotFirstItem())); connect(d->rightSideBar, SIGNAL(signalNextItem()), this, SLOT(slotNextItem())); connect(d->rightSideBar, SIGNAL(signalPrevItem()), this, SLOT(slotPrevItem())); connect(d->rightSideBar, SIGNAL(signalLastItem()), this, SLOT(slotLastItem())); connect(this, SIGNAL(signalNoCurrentItem()), d->rightSideBar, SLOT(slotNoCurrentItem())); #ifdef HAVE_MARBLE connect(d->gpsSearchSideBar, SIGNAL(signalMapSoloItems(QList,QString)), d->iconView->imageFilterModel(), SLOT(setIdWhitelist(QList,QString))); #endif // HAVE_MARBLE // -- Filter Bars Connections --------------------------------- ImageAlbumFilterModel* const model = d->iconView->imageAlbumFilterModel(); connect(d->filterWidget, SIGNAL(signalTagFilterChanged(QList,QList,ImageFilterSettings::MatchingCondition,bool,QList,QList)), d->iconView->imageFilterModel(), SLOT(setTagFilter(QList,QList,ImageFilterSettings::MatchingCondition,bool,QList,QList))); connect(d->filterWidget, SIGNAL(signalRatingFilterChanged(int,ImageFilterSettings::RatingCondition,bool)), model, SLOT(setRatingFilter(int,ImageFilterSettings::RatingCondition,bool))); connect(d->filterWidget, SIGNAL(signalSearchTextFilterChanged(SearchTextFilterSettings)), model, SLOT(setTextFilter(SearchTextFilterSettings))); connect(model, SIGNAL(filterMatchesForText(bool)), d->filterWidget, SLOT(slotFilterMatchesForText(bool))); connect(d->filterWidget, SIGNAL(signalMimeTypeFilterChanged(int)), model, SLOT(setMimeTypeFilter(int))); connect(d->filterWidget, SIGNAL(signalGeolocationFilterChanged(ImageFilterSettings::GeolocationCondition)), model, SLOT(setGeolocationFilter(ImageFilterSettings::GeolocationCondition))); // -- Preview image widget Connections ------------------------ connect(d->stackedview, SIGNAL(signalNextItem()), this, SLOT(slotNextItem())); connect(d->stackedview, SIGNAL(signalPrevItem()), this, SLOT(slotPrevItem())); connect(d->stackedview, SIGNAL(signalDeleteItem()), this, SLOT(slotImageDelete())); connect(d->stackedview, SIGNAL(signalViewModeChanged()), this, SLOT(slotViewModeChanged())); connect(d->stackedview, SIGNAL(signalEscapePreview()), this, SLOT(slotEscapePreview())); connect(d->stackedview, SIGNAL(signalSlideShowCurrent()), this, SLOT(slotSlideShowManualFromCurrent())); connect(d->stackedview, SIGNAL(signalZoomFactorChanged(double)), this, SLOT(slotZoomFactorChanged(double))); connect(d->stackedview, SIGNAL(signalAddToExistingQueue(int)), this, SLOT(slotImageAddToExistingQueue(int))); connect(d->stackedview, SIGNAL(signalGotoAlbumAndItem(ImageInfo)), this, SLOT(slotGotoAlbumAndItem(ImageInfo))); connect(d->stackedview, SIGNAL(signalGotoDateAndItem(ImageInfo)), this, SLOT(slotGotoDateAndItem(ImageInfo))); connect(d->stackedview, SIGNAL(signalGotoTagAndItem(int)), this, SLOT(slotGotoTagAndItem(int))); connect(d->stackedview, SIGNAL(signalPopupTagsView()), d->rightSideBar, SLOT(slotPopupTagsView())); // -- FileActionMngr progress --------------- connect(FileActionMngr::instance(), SIGNAL(signalImageChangeFailed(QString,QStringList)), this, SLOT(slotImageChangeFailed(QString,QStringList))); // -- timers --------------- connect(d->selectionTimer, SIGNAL(timeout()), this, SLOT(slotDispatchImageSelected())); connect(d->thumbSizeTimer, SIGNAL(timeout()), this, SLOT(slotThumbSizeEffect()) ); // -- Album Settings ---------------- connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotSidebarTabTitleStyleChanged())); // -- Album History ----------------- connect(this, SIGNAL(signalAlbumSelected(Album*)), d->albumHistory, SLOT(slotAlbumSelected())); connect(this, SIGNAL(signalImageSelected(ImageInfoList,ImageInfoList)), d->albumHistory, SLOT(slotImageSelected(ImageInfoList))); connect(d->iconView, SIGNAL(currentChanged(ImageInfo)), d->albumHistory, SLOT(slotCurrentChange(ImageInfo))); connect(d->iconView->imageModel(), SIGNAL(imageInfosAdded(QList)), d->albumHistory, SLOT(slotAlbumCurrentChanged())); connect(d->albumHistory, SIGNAL(signalSetCurrent(qlonglong)), this, SLOT(slotSetCurrentWhenAvailable(qlonglong))); connect(d->albumHistory, SIGNAL(signalSetSelectedInfos(QList)), d->iconView, SLOT(setSelectedImageInfos(QList))); connect(d->albumManager, SIGNAL(signalAlbumDeleted(Album*)), d->albumHistory, SLOT(slotAlbumDeleted(Album*))); connect(d->albumManager, SIGNAL(signalAlbumsCleared()), d->albumHistory, SLOT(slotAlbumsCleared())); // -- Image versions ---------------- connect(d->rightSideBar->getFiltersHistoryTab(), SIGNAL(imageSelected(ImageInfo)), d->iconView, SLOT(hintAt(ImageInfo))); connect(d->rightSideBar->getFiltersHistoryTab(), SIGNAL(actionTriggered(ImageInfo)), this, SLOT(slotGotoAlbumAndItem(ImageInfo))); } void DigikamView::connectIconViewFilter(FilterStatusBar* const filterbar) { ImageAlbumFilterModel* const model = d->iconView->imageAlbumFilterModel(); connect(model, SIGNAL(filterMatches(bool)), filterbar, SLOT(slotFilterMatches(bool))); connect(model, SIGNAL(filterSettingsChanged(ImageFilterSettings)), filterbar, SLOT(slotFilterSettingsChanged(ImageFilterSettings))); connect(filterbar, SIGNAL(signalResetFilters()), d->filterWidget, SLOT(slotResetFilters())); connect(filterbar, SIGNAL(signalPopupFiltersView()), this, SLOT(slotPopupFiltersView())); } void DigikamView::slotPopupFiltersView() { d->rightSideBar->setActiveTab(d->filterWidget); d->filterWidget->setFocusToTextFilter(); } void DigikamView::loadViewState() { foreach(SidebarWidget* const widget, d->leftSideBarWidgets) { widget->loadState(); } d->filterWidget->loadState(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("MainWindow")); // Restore the splitter d->splitter->restoreState(group); // Restore the thumbnail bar dock. QByteArray thumbbarState; thumbbarState = group.readEntry(QLatin1String("ThumbbarState"), thumbbarState); d->dockArea->restoreState(QByteArray::fromBase64(thumbbarState)); d->initialAlbumID = group.readEntry(QLatin1String("InitialAlbumID"), 0); #ifdef HAVE_MARBLE d->mapView->loadState(); #endif // HAVE_MARBLE d->tableView->loadState(); d->rightSideBar->loadState(); } void DigikamView::saveViewState() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("MainWindow")); foreach(SidebarWidget* const widget, d->leftSideBarWidgets) { widget->saveState(); } d->filterWidget->saveState(); // Save the splitter states. d->splitter->saveState(group); // Save the position and size of the thumbnail bar. The thumbnail bar dock // needs to be closed explicitly, because when it is floating and visible // (when the user is in image preview mode) when the layout is saved, it // also reappears when restoring the view, while it should always be hidden. d->stackedview->thumbBarDock()->close(); group.writeEntry(QLatin1String("ThumbbarState"), d->dockArea->saveState().toBase64()); QList albumList = AlbumManager::instance()->currentAlbums(); Album* album = 0; if(!albumList.isEmpty()) { album = albumList.first(); } if (album) { group.writeEntry(QLatin1String("InitialAlbumID"), album->globalID()); } else { group.writeEntry(QLatin1String("InitialAlbumID"), 0); } #ifdef HAVE_MARBLE d->mapView->saveState(); #endif // HAVE_MARBLE d->tableView->saveState(); d->rightSideBar->saveState(); } QList DigikamView::leftSidebarWidgets() const { return d->leftSideBarWidgets; } QList DigikamView::allUrls(bool grouping) const { /// @todo This functions seems not to be used anywhere right now return allInfo(grouping).toImageUrlList(); } QList DigikamView::selectedUrls(bool grouping) const { return selectedInfoList(grouping).toImageUrlList(); } QList DigikamView::selectedUrls(const ApplicationSettings::OperationType type) const { return selectedInfoList(type).toImageUrlList(); } void DigikamView::showSideBars() { d->leftSideBar->restore(); d->rightSideBar->restore(); } void DigikamView::hideSideBars() { d->leftSideBar->backup(); d->rightSideBar->backup(); } void DigikamView::toggleLeftSidebar() { d->leftSideBar->isExpanded() ? d->leftSideBar->shrink() : d->leftSideBar->expand(); } void DigikamView::toggleRightSidebar() { d->rightSideBar->isExpanded() ? d->rightSideBar->shrink() : d->rightSideBar->expand(); } void DigikamView::previousLeftSideBarTab() { d->leftSideBar->activePreviousTab(); } void DigikamView::nextLeftSideBarTab() { d->leftSideBar->activeNextTab(); } void DigikamView::previousRightSideBarTab() { d->rightSideBar->activePreviousTab(); } void DigikamView::nextRightSideBarTab() { d->rightSideBar->activeNextTab(); } void DigikamView::slotFirstItem() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotGoToRow(0, false); break; default: // all other views are tied to IconView's selection model d->iconView->toFirstIndex(); } } void DigikamView::slotPrevItem() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotGoToRow(-1, true); break; default: // all other views are tied to IconView's selection model d->iconView->toPreviousIndex(); } } void DigikamView::slotNextItem() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotGoToRow(1, true); break; default: // all other views are tied to IconView's selection model d->iconView->toNextIndex(); } } void DigikamView::slotLastItem() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotGoToRow(-1, false); break; default: // all other views are tied to IconView's selection model d->iconView->toLastIndex(); } } void DigikamView::slotSelectItemByUrl(const QUrl& url) { /// @todo This functions seems not to be used anywhere right now /// @todo Adapt to TableView d->iconView->toIndex(url); } void DigikamView::slotAllAlbumsLoaded() { disconnect(d->albumManager, SIGNAL(signalAllAlbumsLoaded()), this, SLOT(slotAllAlbumsLoaded())); loadViewState(); d->leftSideBar->loadState(); d->rightSideBar->loadState(); d->rightSideBar->populateTags(); // now that all albums have been loaded, activate the albumHistory d->useAlbumHistory = true; Album* const album = d->albumManager->findAlbum(d->initialAlbumID); d->albumManager->setCurrentAlbums(QList() << album); } void DigikamView::slotSortAlbums(int role) { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } settings->setAlbumSortRole((ApplicationSettings::AlbumSortRole) role); settings->saveSettings(); //A dummy way to force the tree view to resort if the album sort role changed PAlbum* const albumBeforeSorting = d->albumFolderSideBar->currentAlbum(); settings->setAlbumSortChanged(true); d->albumFolderSideBar->doSaveState(); d->albumFolderSideBar->doLoadState(); d->albumFolderSideBar->doSaveState(); d->albumFolderSideBar->doLoadState(); settings->setAlbumSortChanged(false); if (d->leftSideBar->getActiveTab() == d->albumFolderSideBar) { d->albumFolderSideBar->setCurrentAlbum(albumBeforeSorting); } } void DigikamView::slotNewAlbum() { // TODO use the selection model of the view instead d->albumModificationHelper->slotAlbumNew(d->albumFolderSideBar->currentAlbum()); } void DigikamView::slotDeleteAlbum() { d->albumModificationHelper->slotAlbumDelete(d->albumFolderSideBar->currentAlbum()); } void DigikamView::slotRenameAlbum() { d->albumModificationHelper->slotAlbumRename(d->albumFolderSideBar->currentAlbum()); } void DigikamView::slotNewTag() { QList talbums = AlbumManager::instance()->currentTAlbums(); if (!talbums.isEmpty()) d->tagModificationHelper->slotTagNew(talbums.first()); } void DigikamView::slotDeleteTag() { QList talbums = AlbumManager::instance()->currentTAlbums(); if (!talbums.isEmpty()) d->tagModificationHelper->slotTagDelete(talbums.first()); } void DigikamView::slotEditTag() { QList talbums = AlbumManager::instance()->currentTAlbums(); if (!talbums.isEmpty()) d->tagModificationHelper->slotTagEdit(talbums.first()); } void DigikamView::slotOpenTagsManager() { TagsManager* const tagMngr = TagsManager::instance(); tagMngr->show(); tagMngr->activateWindow(); tagMngr->raise(); } void DigikamView::slotAssignTag() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToNewTagEdit(); } void DigikamView::slotNewKeywordSearch() { slotLeftSideBarActivate(d->searchSideBar); d->searchSideBar->newKeywordSearch(); } void DigikamView::slotNewAdvancedSearch() { slotLeftSideBarActivate(d->searchSideBar); d->searchSideBar->newAdvancedSearch(); } void DigikamView::slotNewDuplicatesSearch(PAlbum* album) { slotLeftSideBarActivate(d->fuzzySearchSideBar); d->fuzzySearchSideBar->newDuplicatesSearch(album); } -void DigikamView::slotNewDuplicatesSearch(QList albums) +void DigikamView::slotNewDuplicatesSearch(const QList& albums) { slotLeftSideBarActivate(d->fuzzySearchSideBar); d->fuzzySearchSideBar->newDuplicatesSearch(albums); } -void DigikamView::slotNewDuplicatesSearch(QList albums) +void DigikamView::slotNewDuplicatesSearch(const QList& albums) { slotLeftSideBarActivate(d->fuzzySearchSideBar); d->fuzzySearchSideBar->newDuplicatesSearch(albums); } void DigikamView::slotAlbumsCleared() { emit signalAlbumSelected(0); } void DigikamView::slotAlbumHistoryBack(int steps) { QList albums; QWidget* widget = 0; d->albumHistory->back(albums, &widget, steps); changeAlbumFromHistory(albums, widget); } void DigikamView::slotAlbumHistoryForward(int steps) { QList albums; QWidget* widget = 0; d->albumHistory->forward(albums, &widget, steps); changeAlbumFromHistory(albums , widget); } // TODO update, use SideBarWidget instead of QWidget void DigikamView::changeAlbumFromHistory(QList album, QWidget* const widget) { if (!(album.isEmpty()) && widget) { // TODO update, temporary casting until signature is changed SidebarWidget* const sideBarWidget = dynamic_cast(widget); if (sideBarWidget) { sideBarWidget->changeAlbumFromHistory(album); slotLeftSideBarActivate(sideBarWidget); if (sideBarWidget == d->labelsSideBar) { d->labelsSearchHandler->restoreSelectionFromHistory(d->albumHistory->neededLabels()); } } d->parent->enableAlbumBackwardHistory(d->useAlbumHistory && !d->albumHistory->isBackwardEmpty()); d->parent->enableAlbumForwardHistory(d->useAlbumHistory && !d->albumHistory->isForwardEmpty()); } } void DigikamView::clearHistory() { d->albumHistory->clearHistory(); d->parent->enableAlbumBackwardHistory(false); d->parent->enableAlbumForwardHistory(false); } void DigikamView::getBackwardHistory(QStringList& titles) { d->albumHistory->getBackwardHistory(titles); - for (int i = 0; i < titles.size(); ++i) + for (int i = 0 ; i < titles.size() ; ++i) { titles[i] = d->userPresentableAlbumTitle(titles.at(i)); } } void DigikamView::getForwardHistory(QStringList& titles) { d->albumHistory->getForwardHistory(titles); - for (int i = 0; i < titles.size(); ++i) + for (int i = 0 ; i < titles.size() ; ++i) { titles[i] = d->userPresentableAlbumTitle(titles.at(i)); } } void DigikamView::slotGotoAlbumAndItem(const ImageInfo& imageInfo) { qCDebug(DIGIKAM_GENERAL_LOG) << "going to " << imageInfo; emit signalNoCurrentItem(); PAlbum* const album = AlbumManager::instance()->findPAlbum(imageInfo.albumId()); d->albumFolderSideBar->setCurrentAlbum(album); slotLeftSideBarActivate(d->albumFolderSideBar); // Set the activate item url to find in the Album View after // all items have be reloaded. slotSetCurrentWhenAvailable(imageInfo.id()); // And finally toggle album manager to handle album history and // reload all items. d->albumManager->setCurrentAlbums(QList() << album); } void DigikamView::slotGotoDateAndItem(const ImageInfo& imageInfo) { QDate date = imageInfo.dateTime().date(); emit signalNoCurrentItem(); // Change to Date Album view. // Note, that this also opens the side bar if it is closed; this is // considered as a feature, because it highlights that the view was changed. slotLeftSideBarActivate(d->dateViewSideBar); // Set the activate item url to find in the Album View after // all items have be reloaded. slotSetCurrentWhenAvailable(imageInfo.id()); // Change the year and month of the iconItem (day is unused). d->dateViewSideBar->gotoDate(date); } void DigikamView::slotGotoTagAndItem(int tagID) { // FIXME: Arnd: don't know yet how to get the iconItem passed through ... // then we would know how to use the following ... // KURL url( iconItem->imageInfo()->kurl() ); // url.cleanPath(); emit signalNoCurrentItem(); // Change to Tag Folder view. // Note, that this also opens the side bar if it is closed; this is // considered as a feature, because it highlights that the view was changed. slotLeftSideBarActivate(d->tagViewSideBar); // Set the current tag in the tag folder view. // TODO this slot should use a TAlbum pointer directly TAlbum* const tag = AlbumManager::instance()->findTAlbum(tagID); if (tag) { d->tagViewSideBar->setCurrentAlbum(tag); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find a tag album for tag id " << tagID; } // Set the activate item url to find in the Tag View after // all items have be reloaded. // FIXME: see above // d->iconView->setAlbumItemToFind(url); } void DigikamView::slotSelectAlbum(const QUrl& url) { PAlbum* const album = d->albumManager->findPAlbum(url); if (!album) { qCWarning(DIGIKAM_GENERAL_LOG) << "Unable to find album for " << url; return; } slotLeftSideBarActivate(d->albumFolderSideBar); d->albumFolderSideBar->setCurrentAlbum(album); } -void DigikamView::slotAlbumSelected(QList albums) +void DigikamView::slotAlbumSelected(const QList& albums) { emit signalNoCurrentItem(); emit signalAlbumSelected(0); if (albums.isEmpty() || !albums.first()) { d->iconView->openAlbum(QList()); #ifdef HAVE_MARBLE d->mapView->openAlbum(0); #endif // HAVE_MARBLE slotTogglePreviewMode(ImageInfo()); return; } Album* const album = albums.first(); emit signalAlbumSelected(album); if (d->useAlbumHistory && !d->labelsSearchHandler->isRestoringSelectionFromHistory()) { if (!(d->leftSideBar->getActiveTab() == d->labelsSideBar)) { d->albumHistory->addAlbums(albums, d->leftSideBar->getActiveTab()); } else { if (albums.first()->isUsedByLabelsTree()) { d->albumHistory->addAlbums(albums, d->leftSideBar->getActiveTab(), d->labelsSideBar->selectedLabels()); } } } d->parent->enableAlbumBackwardHistory(d->useAlbumHistory && !d->albumHistory->isBackwardEmpty()); d->parent->enableAlbumForwardHistory(d->useAlbumHistory && !d->albumHistory->isForwardEmpty()); d->iconView->openAlbum(albums); if (album->isRoot()) { d->stackedview->setViewMode(StackedView::WelcomePageMode); } else if (album->isTrashAlbum()) { PAlbum* const palbum = d->albumManager->findPAlbum(album->parent()->id()); if (palbum) { QUrl url = palbum->fileUrl(); url = url.adjusted(QUrl::StripTrailingSlash); d->trashView->model()->loadItemsForCollection(url.toLocalFile()); d->filterWidget->setEnabled(false); d->stackedview->setViewMode(StackedView::TrashViewMode); } } else { switch (viewMode()) { case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: case StackedView::WelcomePageMode: case StackedView::TrashViewMode: slotTogglePreviewMode(ImageInfo()); break; default: break; } d->filterWidget->setEnabled(true); } } void DigikamView::slotAlbumOpenInFileManager() { Album* const album = d->albumManager->currentAlbums().first(); if (!album || album->type() != Album::PHYSICAL) { return; } if (album->isRoot()) { QMessageBox::critical(this, qApp->applicationName(), i18n("Cannot open the root album. It is not a physical location.")); return; } PAlbum* const palbum = dynamic_cast(album); if (palbum) { QDesktopServices::openUrl(QUrl::fromLocalFile(palbum->folderPath())); } } void DigikamView::slotRefresh() { switch (viewMode()) { case StackedView::PreviewImageMode: d->stackedview->imagePreviewView()->reload(); break; #ifdef HAVE_MEDIAPLAYER case StackedView::MediaPlayerMode: d->stackedview->mediaPlayerView()->reload(); break; #endif //HAVE_MEDIAPLAYER default: Album* const album = currentAlbum(); if (!album) return; // force reloading of thumbnails LoadingCacheInterface::cleanThumbnailCache(); ThumbsGenerator* const tool = new ThumbsGenerator(true, album->id()); tool->start(); // if physical album, schedule a collection scan of current album's path if (album->type() == Album::PHYSICAL) { NewItemsFinder* const tool = new NewItemsFinder(NewItemsFinder::ScheduleCollectionScan, QStringList() << static_cast(album)->folderPath()); connect(tool, SIGNAL(signalComplete()), this, SLOT(slotAlbumRefreshComplete())); tool->start(); } break; } } void DigikamView::slotAlbumRefreshComplete() { // force reload. Should normally not be necessary, but we may have bugs qlonglong currentId = currentInfo().id(); d->iconView->imageAlbumModel()->refresh(); if (currentId != -1) { slotSetCurrentWhenAvailable(currentId); } } void DigikamView::slotImageSelected() { // delay to slotDispatchImageSelected d->needDispatchSelection = true; d->selectionTimer->start(); switch (viewMode()) { case StackedView::TableViewMode: emit signalSelectionChanged(d->tableView->numberOfSelectedItems()); break; default: emit signalSelectionChanged(d->iconView->numberOfSelectedIndexes()); } } void DigikamView::slotDispatchImageSelected() { if (viewMode() == StackedView::TrashViewMode) { d->rightSideBar->itemChanged(d->trashView->lastSelectedItemUrl()); return; } if (d->needDispatchSelection) { // the list of ImageInfos of currently selected items, currentItem first const ImageInfoList list = selectedInfoList(true, true); const ImageInfoList allImages = allInfo(true); if (list.isEmpty()) { d->stackedview->setPreviewItem(); emit signalImageSelected(list, allImages); emit signalNoCurrentItem(); } else { d->rightSideBar->itemChanged(list); ImageInfo previousInfo; ImageInfo nextInfo; if (viewMode() == StackedView::TableViewMode) { previousInfo = d->tableView->previousInfo(); nextInfo = d->tableView->nextInfo(); } else { previousInfo = d->iconView->previousInfo(list.first()); nextInfo = d->iconView->nextInfo(list.first()); } if ((viewMode() != StackedView::IconViewMode) && (viewMode() != StackedView::MapWidgetMode) && (viewMode() != StackedView::TableViewMode) ) { d->stackedview->setPreviewItem(list.first(), previousInfo, nextInfo); } emit signalImageSelected(list, allImages); } d->needDispatchSelection = false; } } double DigikamView::zoomMin() const { return d->stackedview->zoomMin(); } double DigikamView::zoomMax() const { return d->stackedview->zoomMax(); } void DigikamView::setZoomFactor(double zoom) { d->stackedview->setZoomFactorSnapped(zoom); } void DigikamView::slotZoomFactorChanged(double zoom) { toggleZoomActions(); emit signalZoomChanged(zoom); } void DigikamView::setThumbSize(int size) { if (viewMode() == StackedView::PreviewImageMode) { double z = DZoomBar::zoomFromSize(size, zoomMin(), zoomMax()); setZoomFactor(z); } else if ( (viewMode() == StackedView::IconViewMode) || (viewMode() == StackedView::TableViewMode) || (viewMode() == StackedView::TrashViewMode)) { if (size > ThumbnailSize::maxThumbsSize()) { d->thumbSize = ThumbnailSize::maxThumbsSize(); } else if (size < ThumbnailSize::Small) { d->thumbSize = ThumbnailSize::Small; } else { d->thumbSize = size; } emit signalThumbSizeChanged(d->thumbSize); d->thumbSizeTimer->start(); } } void DigikamView::slotThumbSizeEffect() { d->iconView->setThumbnailSize(ThumbnailSize(d->thumbSize)); d->tableView->setThumbnailSize(ThumbnailSize(d->thumbSize)); d->trashView->setThumbnailSize(ThumbnailSize(d->thumbSize)); toggleZoomActions(); ApplicationSettings::instance()->setDefaultIconSize(d->thumbSize); } void DigikamView::toggleZoomActions() { if (viewMode() == StackedView::PreviewImageMode) { d->parent->enableZoomMinusAction(true); d->parent->enableZoomPlusAction(true); if (d->stackedview->maxZoom()) { d->parent->enableZoomPlusAction(false); } if (d->stackedview->minZoom()) { d->parent->enableZoomMinusAction(false); } } else if ((viewMode() == StackedView::IconViewMode) || (viewMode() == StackedView::TableViewMode)) { d->parent->enableZoomMinusAction(true); d->parent->enableZoomPlusAction(true); if (d->thumbSize >= ThumbnailSize::maxThumbsSize()) { d->parent->enableZoomPlusAction(false); } if (d->thumbSize <= ThumbnailSize::Small) { d->parent->enableZoomMinusAction(false); } } else { d->parent->enableZoomMinusAction(false); d->parent->enableZoomPlusAction(false); } } void DigikamView::slotZoomIn() { if ( (viewMode() == StackedView::IconViewMode) || (viewMode() == StackedView::TableViewMode) ) { setThumbSize(d->thumbSize + ThumbnailSize::Step); toggleZoomActions(); emit signalThumbSizeChanged(d->thumbSize); } else if (viewMode() == StackedView::PreviewImageMode) { d->stackedview->increaseZoom(); } } void DigikamView::slotZoomOut() { if ( (viewMode() == StackedView::IconViewMode) || (viewMode() == StackedView::TableViewMode) ) { setThumbSize(d->thumbSize - ThumbnailSize::Step); toggleZoomActions(); emit signalThumbSizeChanged(d->thumbSize); } else if (viewMode() == StackedView::PreviewImageMode) { d->stackedview->decreaseZoom(); } } void DigikamView::slotZoomTo100Percents() { if (viewMode() == StackedView::PreviewImageMode) { d->stackedview->toggleFitToWindowOr100(); } } void DigikamView::slotFitToWindow() { if (viewMode() == StackedView::TableViewMode) { /// @todo We should choose an appropriate thumbnail size here } else if (viewMode() == StackedView::IconViewMode) { int nts = d->iconView->fitToWidthIcons(); qCDebug(DIGIKAM_GENERAL_LOG) << "new thumb size = " << nts; setThumbSize(nts); toggleZoomActions(); emit signalThumbSizeChanged(d->thumbSize); } else if (viewMode() == StackedView::PreviewImageMode) { d->stackedview->fitToWindow(); } } void DigikamView::slotAlbumPropsEdit() { d->albumModificationHelper->slotAlbumEdit(d->albumManager->currentPAlbum()); } void DigikamView::slotAlbumWriteMetadata() { Album* const album = d->albumManager->currentAlbums().first(); if (!album) { return; } MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList() << album, MetadataSynchronizer::WriteFromDatabaseToFile); tool->start(); } void DigikamView::slotAlbumReadMetadata() { Album* const album = d->albumManager->currentAlbums().first(); if (!album) { return; } MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList() << album, MetadataSynchronizer::ReadFromFileToDatabase); tool->start(); } void DigikamView::slotImageWriteMetadata() { const ImageInfoList selected = selectedInfoList(ApplicationSettings::Metadata); MetadataSynchronizer* const tool = new MetadataSynchronizer(selected, MetadataSynchronizer::WriteFromDatabaseToFile); tool->start(); } void DigikamView::slotImageReadMetadata() { const ImageInfoList selected = selectedInfoList(ApplicationSettings::Metadata); MetadataSynchronizer* const tool = new MetadataSynchronizer(selected, MetadataSynchronizer::ReadFromFileToDatabase); tool->start(); } // ---------------------------------------------------------------- void DigikamView::slotEscapePreview() { if (viewMode() == StackedView::IconViewMode || viewMode() == StackedView::MapWidgetMode || viewMode() == StackedView::TableViewMode || viewMode() == StackedView::WelcomePageMode) { return; } // pass a null image info, because we want to fall back to the old // view mode slotTogglePreviewMode(ImageInfo()); } void DigikamView::slotMapWidgetView() { d->stackedview->setViewMode(StackedView::MapWidgetMode); } void DigikamView::slotTableView() { d->stackedview->setViewMode(StackedView::TableViewMode); } void DigikamView::slotIconView() { if (viewMode() == StackedView::PreviewImageMode) { emit signalThumbSizeChanged(d->thumbSize); } // and switch to icon view d->stackedview->setViewMode(StackedView::IconViewMode); // make sure the next/previous buttons are updated slotImageSelected(); } void DigikamView::slotImagePreview() { slotTogglePreviewMode(currentInfo()); } /** * @brief This method toggles between AlbumView/MapWidgetView and ImagePreview modes, depending on the context. */ void DigikamView::slotTogglePreviewMode(const ImageInfo& info) { if ( (viewMode() == StackedView::IconViewMode || viewMode() == StackedView::TableViewMode || viewMode() == StackedView::MapWidgetMode) && !info.isNull() ) { if (info.isLocationAvailable()) { d->lastViewMode = viewMode(); if (viewMode() == StackedView::IconViewMode) { d->stackedview->setPreviewItem(info, d->iconView->previousInfo(info), d->iconView->nextInfo(info)); } else { d->stackedview->setPreviewItem(info, ImageInfo(), ImageInfo()); } } else { QModelIndex index = d->iconView->indexForInfo(info); d->iconView->showIndexNotification(index, i18nc("@info", "The storage location of this image
is currently not available
")); } } else { // go back to either AlbumViewMode or MapWidgetMode d->stackedview->setViewMode( d->lastViewMode ); } // make sure the next/previous buttons are updated slotImageSelected(); } void DigikamView::slotViewModeChanged() { toggleZoomActions(); switch (viewMode()) { case StackedView::IconViewMode: emit signalSwitchedToIconView(); emit signalThumbSizeChanged(d->thumbSize); break; case StackedView::PreviewImageMode: emit signalSwitchedToPreview(); slotZoomFactorChanged(d->stackedview->zoomFactor()); break; case StackedView::WelcomePageMode: emit signalSwitchedToIconView(); break; case StackedView::MediaPlayerMode: emit signalSwitchedToPreview(); break; case StackedView::MapWidgetMode: emit signalSwitchedToMapView(); //TODO: connect map view's zoom buttons to main status bar zoom buttons break; case StackedView::TableViewMode: emit signalSwitchedToTableView(); emit signalThumbSizeChanged(d->thumbSize); break; case StackedView::TrashViewMode: emit signalSwitchedToTrashView(); break; } } void DigikamView::slotImageFindSimilar() { const ImageInfo current = currentInfo(); if (!current.isNull()) { d->fuzzySearchSideBar->newSimilarSearch(current); slotLeftSideBarActivate(d->fuzzySearchSideBar); } } void DigikamView::slotImageScanForFaces() { FaceScanSettings settings; settings.accuracy = ApplicationSettings::instance()->getFaceDetectionAccuracy(); settings.recognizeAlgorithm = RecognitionDatabase::RecognizeAlgorithm::LBP; settings.task = FaceScanSettings::DetectAndRecognize; settings.alreadyScannedHandling = FaceScanSettings::Rescan; settings.infos = selectedInfoList(ApplicationSettings::Tools); FacesDetector* const tool = new FacesDetector(settings); connect(tool, SIGNAL(signalComplete()), this, SLOT(slotRefreshImagePreview())); tool->start(); } void DigikamView::slotRefreshImagePreview() { if (viewMode() == StackedView::PreviewImageMode) { d->stackedview->imagePreviewView()->reload(); } } void DigikamView::slotEditor() { const ImageInfoList imageInfoList = selectedInfoList(ApplicationSettings::Tools); ImageInfo singleInfo = currentInfo(); if (singleInfo.isNull() && !imageInfoList.isEmpty()) { singleInfo = imageInfoList.first(); } Album* const current = currentAlbum(); d->utilities->openInfos(singleInfo, imageInfoList, current); } void DigikamView::slotFileWithDefaultApplication() { d->utilities->openInfosWithDefaultApplication(selectedInfoList(ApplicationSettings::Tools)); } void DigikamView::slotLightTable() { bool grouping = selectedNeedGroupResolving(ApplicationSettings::LightTable); const ImageInfoList selectedList = selectedInfoList(false, grouping); if (selectedList.isEmpty()) { grouping = allNeedGroupResolving(ApplicationSettings::LightTable); } const ImageInfoList allInfoList = allInfo(grouping); const ImageInfo currentImageInfo = currentInfo(); d->utilities->insertToLightTableAuto(allInfoList, selectedList, currentImageInfo); } void DigikamView::slotQueueMgr() { bool grouping = selectedNeedGroupResolving(ApplicationSettings::BQM); ImageInfoList imageInfoList = selectedInfoList(false, grouping); ImageInfo singleInfo = currentInfo(); if (singleInfo.isNull() && !imageInfoList.isEmpty()) { singleInfo = imageInfoList.first(); } if (singleInfo.isNull()) { grouping = allNeedGroupResolving(ApplicationSettings::BQM); const ImageInfoList allItems = allInfo(grouping); if (!allItems.isEmpty()) { singleInfo = allItems.first(); } } d->utilities->insertToQueueManager(imageInfoList, singleInfo, true); } void DigikamView::slotImageEdit() { // Where is the difference to slotEditor? slotEditor(); } void DigikamView::slotImageLightTable() { const ImageInfoList selectedList = selectedInfoList(ApplicationSettings::LightTable); const ImageInfo currentImageInfo = currentInfo(); // replace images in light table d->utilities->insertToLightTable(selectedList, currentImageInfo, false); } void DigikamView::slotImageAddToLightTable() { const ImageInfoList selectedList = selectedInfoList(ApplicationSettings::LightTable); const ImageInfo currentImageInfo = currentInfo(); // add to images in light table d->utilities->insertToLightTable(selectedList, currentImageInfo, true); } void DigikamView::slotImageAddToCurrentQueue() { const ImageInfoList selectedList = selectedInfoList(ApplicationSettings::BQM); const ImageInfo currentImageInfo = currentInfo(); d->utilities->insertToQueueManager(selectedList, currentImageInfo, false); } void DigikamView::slotImageAddToNewQueue() { const bool newQueue = QueueMgrWindow::queueManagerWindowCreated() && !QueueMgrWindow::queueManagerWindow()->queuesMap().isEmpty(); const ImageInfoList selectedList = selectedInfoList(ApplicationSettings::BQM); const ImageInfo currentImageInfo = currentInfo(); d->utilities->insertToQueueManager(selectedList, currentImageInfo, newQueue); } void DigikamView::slotImageAddToExistingQueue(int queueid) { const ImageInfoList selectedList = selectedInfoList(ApplicationSettings::BQM); const ImageInfo currentImageInfo = currentInfo(); if (!selectedList.isEmpty()) { d->utilities->insertSilentToQueueManager(selectedList, currentImageInfo, queueid); } } void DigikamView::slotImageRename() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->rename(); break; default: d->iconView->rename(); } } void DigikamView::slotImageDelete() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotDeleteSelected(ImageViewUtilities::DeleteUseTrash); break; default: d->iconView->deleteSelected(ImageViewUtilities::DeleteUseTrash); } } void DigikamView::slotImageDeletePermanently() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotDeleteSelected(ImageViewUtilities::DeletePermanently); break; default: d->iconView->deleteSelected(ImageViewUtilities::DeletePermanently); } } void DigikamView::slotImageDeletePermanentlyDirectly() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotDeleteSelectedWithoutConfirmation(ImageViewUtilities::DeletePermanently); break; default: d->iconView->deleteSelectedDirectly(ImageViewUtilities::DeletePermanently); } } void DigikamView::slotImageTrashDirectly() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotDeleteSelectedWithoutConfirmation(ImageViewUtilities::DeleteUseTrash); break; default: d->iconView->deleteSelectedDirectly(ImageViewUtilities::DeleteUseTrash); } } void DigikamView::slotSelectAll() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->selectAll(); break; default: d->iconView->selectAll(); } } void DigikamView::slotSelectNone() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->clearSelection(); break; default: d->iconView->clearSelection(); } } void DigikamView::slotSelectInvert() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->invertSelection(); break; default: d->iconView->invertSelection(); } } void DigikamView::slotSortImages(int sortRole) { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } settings->setImageSortOrder(sortRole); d->iconView->imageFilterModel()->setSortRole((ImageSortSettings::SortRole) sortRole); settings->emitSetupChanged(); } void DigikamView::slotSortImagesOrder(int order) { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } settings->setImageSorting(order); d->iconView->imageFilterModel()->setSortOrder((ImageSortSettings::SortOrder) order); settings->emitSetupChanged(); } void DigikamView::slotSeparateImages(int categoryMode) { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } settings->setImageSeparationMode(categoryMode); d->iconView->imageFilterModel()->setCategorizationMode((ImageSortSettings::CategorizationMode) categoryMode); } void DigikamView::slotImageSeparationSortOrder(int order) { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } settings->setImageSeparationSortOrder(order); d->iconView->imageFilterModel()->setCategorizationSortOrder((ImageSortSettings::SortOrder) order); } void DigikamView::slotMoveSelectionToAlbum() { d->utilities->createNewAlbumForInfos(selectedInfoList(false, true), currentAlbum()); } void DigikamView::slotImagePaste() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotPaste(); break; default: d->iconView->paste(); } } void DigikamView::slotLeftSidebarChangedTab(QWidget* w) { // TODO update, temporary cast SidebarWidget* const widget = dynamic_cast(w); foreach(SidebarWidget* const sideBarWidget, d->leftSideBarWidgets) { bool active = (widget && (widget == sideBarWidget)); sideBarWidget->setActive(active); } } void DigikamView::toggleTag(int tagID) { ImageInfoList tagToRemove, tagToAssign; const ImageInfoList selectedList = selectedInfoList(ApplicationSettings::Metadata); foreach(const ImageInfo& info, selectedList) { if (info.tagIds().contains(tagID)) tagToRemove.append(info); else tagToAssign.append(info); } FileActionMngr::instance()->assignTag(tagToAssign, tagID); FileActionMngr::instance()->removeTag(tagToRemove, tagID); } void DigikamView::slotAssignPickLabel(int pickId) { FileActionMngr::instance()->assignPickLabel(selectedInfoList(ApplicationSettings::Metadata), pickId); } void DigikamView::slotAssignColorLabel(int colorId) { FileActionMngr::instance()->assignColorLabel(selectedInfoList(ApplicationSettings::Metadata), colorId); } void DigikamView::slotAssignRating(int rating) { FileActionMngr::instance()->assignRating(selectedInfoList(ApplicationSettings::Metadata), rating); } void DigikamView::slotAssignTag(int tagID) { FileActionMngr::instance()->assignTags(selectedInfoList(ApplicationSettings::Metadata), QList() << tagID); } void DigikamView::slotRemoveTag(int tagID) { FileActionMngr::instance()->removeTags(selectedInfoList(ApplicationSettings::Metadata), QList() << tagID); } void DigikamView::slotSlideShowAll() { slideShow(allInfo(ApplicationSettings::Slideshow)); } void DigikamView::slotSlideShowSelection() { slideShow(selectedInfoList(ApplicationSettings::Slideshow)); } void DigikamView::slotSlideShowRecursive() { QList albumList = AlbumManager::instance()->currentAlbums(); Album* album = 0; if (!albumList.isEmpty()) { album = albumList.first(); } if (album) { SlideShowBuilder* const builder = new SlideShowBuilder(album); connect(builder, SIGNAL(signalComplete(SlideShowSettings)), this, SLOT(slotSlideShowBuilderComplete(SlideShowSettings))); builder->run(); } } void DigikamView::slotSlideShowManualFromCurrent() { slotSlideShowManualFrom(currentInfo()); } void DigikamView::slotSlideShowManualFrom(const ImageInfo& info) { SlideShowBuilder* const builder = new SlideShowBuilder(allInfo(ApplicationSettings::Slideshow)); builder->setOverrideStartFrom(info); builder->setAutoPlayEnabled(false); connect(builder, SIGNAL(signalComplete(SlideShowSettings)), this, SLOT(slotSlideShowBuilderComplete(SlideShowSettings))); builder->run(); } void DigikamView::presentation() { PresentationMngr* const mngr = new PresentationMngr(this); foreach(const ImageInfo& info, selectedInfoList(ApplicationSettings::Slideshow)) { mngr->addFile(info.fileUrl(), info.comment()); qApp->processEvents(); } mngr->showConfigDialog(); } void DigikamView::slideShow(const ImageInfoList& infoList) { SlideShowBuilder* const builder = new SlideShowBuilder(infoList); connect(builder, SIGNAL(signalComplete(SlideShowSettings)), this, SLOT(slotSlideShowBuilderComplete(SlideShowSettings))); builder->run(); } void DigikamView::slotSlideShowBuilderComplete(const SlideShowSettings& settings) { SlideShow* const slide = new SlideShow(settings); TagsActionMngr::defaultManager()->registerActionsToWidget(slide); if (settings.imageUrl.isValid()) { slide->setCurrentItem(settings.imageUrl); } else if (settings.startWithCurrent) { slide->setCurrentItem(currentInfo().fileUrl()); } connect(slide, SIGNAL(signalRatingChanged(QUrl,int)), this, SLOT(slotRatingChanged(QUrl,int))); connect(slide, SIGNAL(signalColorLabelChanged(QUrl,int)), this, SLOT(slotColorLabelChanged(QUrl,int))); connect(slide, SIGNAL(signalPickLabelChanged(QUrl,int)), this, SLOT(slotPickLabelChanged(QUrl,int))); connect(slide, SIGNAL(signalToggleTag(QUrl,int)), this, SLOT(slotToggleTag(QUrl,int))); connect(slide, SIGNAL(signalLastItemUrl(QUrl)), d->iconView, SLOT(setCurrentUrl(QUrl))); slide->show(); } void DigikamView::toggleShowBar(bool b) { d->stackedview->thumbBarDock()->showThumbBar(b); // See bug #319876 : force to reload current view mode to set thumbbar visibility properly. d->stackedview->setViewMode(viewMode()); } void DigikamView::setRecurseAlbums(bool recursive) { d->iconView->imageAlbumModel()->setRecurseAlbums(recursive); } void DigikamView::setRecurseTags(bool recursive) { d->iconView->imageAlbumModel()->setRecurseTags(recursive); } void DigikamView::slotSidebarTabTitleStyleChanged() { d->leftSideBar->setStyle(ApplicationSettings::instance()->getSidebarTitleStyle()); d->rightSideBar->setStyle(ApplicationSettings::instance()->getSidebarTitleStyle()); /// @todo Which settings actually have to be reloaded? // d->rightSideBar->applySettings(); } void DigikamView::slotImageChangeFailed(const QString& message, const QStringList& fileNames) { if (fileNames.isEmpty()) { return; } DMessageBox::showInformationList(QMessageBox::Critical, qApp->activeWindow(), qApp->applicationName(), message, fileNames); } void DigikamView::slotLeftSideBarActivateAlbums() { d->leftSideBar->setActiveTab(d->albumFolderSideBar); } void DigikamView::slotLeftSideBarActivateTags() { d->leftSideBar->setActiveTab(d->tagViewSideBar); } void DigikamView::slotLeftSideBarActivate(SidebarWidget* widget) { d->leftSideBar->setActiveTab(widget); } void DigikamView::slotLeftSideBarActivate(QWidget* widget) { slotLeftSideBarActivate(static_cast(widget)); } void DigikamView::slotRightSideBarActivateTitles() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void DigikamView::slotRightSideBarActivateComments() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void DigikamView::slotRightSideBarActivateAssignedTags() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->activateAssignedTagsButton(); } void DigikamView::slotRatingChanged(const QUrl& url, int rating) { rating = qMin(RatingMax, qMax(RatingMin, rating)); ImageInfo info = ImageInfo::fromUrl(url); if (!info.isNull()) { FileActionMngr::instance()->assignRating(info, rating); } } void DigikamView::slotColorLabelChanged(const QUrl& url, int color) { ImageInfo info = ImageInfo::fromUrl(url); if (!info.isNull()) { FileActionMngr::instance()->assignColorLabel(info, color); } } void DigikamView::slotPickLabelChanged(const QUrl& url, int pick) { ImageInfo info = ImageInfo::fromUrl(url); if (!info.isNull()) { FileActionMngr::instance()->assignPickLabel(info, pick); } } void DigikamView::slotToggleTag(const QUrl& url, int tagID) { ImageInfo info = ImageInfo::fromUrl(url); if (!info.isNull()) { if (info.tagIds().contains(tagID)) FileActionMngr::instance()->removeTag(info, tagID); else FileActionMngr::instance()->assignTag(info, tagID); } } bool DigikamView::hasCurrentItem() const { return !currentInfo().isNull(); } void DigikamView::slotFocusAndNextImage() { //slot is called on pressing "return" a second time after assigning a tag d->stackedview->currentWidget()->setFocus(); //select next image, since the user is probably done tagging the current image slotNextItem(); } void DigikamView::slotImageExifOrientation(int orientation) { FileActionMngr::instance()->setExifOrientation( selectedInfoList(ApplicationSettings::Metadata), orientation); } void DigikamView::imageTransform(MetaEngineRotation::TransformationAction transform) { FileActionMngr::instance()->transform( selectedInfoList(ApplicationSettings::Metadata), transform); } ImageInfo DigikamView::currentInfo() const { switch (viewMode()) { case StackedView::TableViewMode: return d->tableView->currentInfo(); #ifdef HAVE_MARBLE case StackedView::MapWidgetMode: return d->mapView->currentImageInfo(); #endif // HAVE_MARBLE case StackedView::MediaPlayerMode: case StackedView::PreviewImageMode: case StackedView::IconViewMode: // all of these modes use the same selection model and data as the IconViewMode return d->iconView->currentInfo(); default: return ImageInfo(); } } Album* DigikamView::currentAlbum() const { switch (viewMode()) { case StackedView::TableViewMode: return d->tableView->currentAlbum(); case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: case StackedView::MapWidgetMode: case StackedView::IconViewMode: // all of these modes use the same selection model and data as the IconViewMode return d->iconView->currentAlbum(); default: return 0; } } ImageInfoList DigikamView::selectedInfoList(const bool currentFirst, const bool grouping) const { switch (viewMode()) { case StackedView::TableViewMode: if (currentFirst) { return d->tableView->selectedImageInfosCurrentFirst(grouping); } return d->tableView->selectedImageInfos(grouping); case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: case StackedView::MapWidgetMode: case StackedView::IconViewMode: // all of these modes use the same selection model and data as the IconViewMode if (currentFirst) { return d->iconView->selectedImageInfosCurrentFirst(grouping); } return d->iconView->selectedImageInfos(grouping); default: return QList(); } } ImageInfoList DigikamView::selectedInfoList(const ApplicationSettings::OperationType type, const bool currentFirst) const { return selectedInfoList(currentFirst, selectedNeedGroupResolving(type)); } ImageInfoList DigikamView::allInfo(const bool grouping) const { switch (viewMode()) { case StackedView::TableViewMode: return d->tableView->allImageInfos(grouping); case StackedView::MapWidgetMode: case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: case StackedView::IconViewMode: // all of these modes use the same selection model and data as the IconViewMode return d->iconView->allImageInfos(grouping); default: return QList(); } } ImageInfoList DigikamView::allInfo(const ApplicationSettings::OperationType type) const { return allInfo(allNeedGroupResolving(type)); } bool DigikamView::allNeedGroupResolving(const ApplicationSettings::OperationType type) const { switch (viewMode()) { case StackedView::TableViewMode: return d->tableView->allNeedGroupResolving(type); case StackedView::MapWidgetMode: case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: case StackedView::IconViewMode: // all of these modes use the same selection model and data as the IconViewMode return d->iconView->allNeedGroupResolving(type); default: return false; } } bool DigikamView::selectedNeedGroupResolving(const ApplicationSettings::OperationType type) const { switch (viewMode()) { case StackedView::TableViewMode: return d->tableView->selectedNeedGroupResolving(type); case StackedView::MapWidgetMode: case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: case StackedView::IconViewMode: // all of these modes use the same selection model and data as the IconViewMode return d->iconView->selectedNeedGroupResolving(type); default: return false; } } QUrl DigikamView::currentUrl() const { const ImageInfo cInfo = currentInfo(); return cInfo.fileUrl(); } void DigikamView::slotSetCurrentWhenAvailable(const qlonglong id) { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotSetCurrentWhenAvailable(id); break; default: d->iconView->setCurrentWhenAvailable(id); } } void DigikamView::slotAwayFromSelection() { switch (viewMode()) { case StackedView::TableViewMode: d->tableView->slotAwayFromSelection(); break; default: d->iconView->awayFromSelection(); } } StackedView::StackedViewMode DigikamView::viewMode() const { return d->stackedview->viewMode(); } void DigikamView::slotSetupMetadataFilters(int tab) { Setup::execMetadataFilters(this, tab); } void DigikamView::toggleFullScreen(bool set) { d->stackedview->imagePreviewView()->toggleFullScreen(set); } void DigikamView::setToolsIconView(DCategorizedView* const view) { d->rightSideBar->appendTab(view, QIcon::fromTheme(QLatin1String("document-edit")), i18n("Tools")); } void DigikamView::slotShowContextMenu(QContextMenuEvent* event, const QList& extraGroupingActions) { Album* const album = currentAlbum(); if (!album || album->isRoot() || (album->type() != Album::PHYSICAL && album->type() != Album::TAG) ) { return; } QMenu menu(this); ContextMenuHelper cmHelper(&menu); cmHelper.addAction(QLatin1String("full_screen")); cmHelper.addAction(QLatin1String("options_show_menubar")); cmHelper.addSeparator(); // -------------------------------------------------------- cmHelper.addStandardActionPaste(this, SLOT(slotImagePaste())); // -------------------------------------------------------- if (!extraGroupingActions.isEmpty()) { cmHelper.addSeparator(); cmHelper.addGroupMenu(QList(), extraGroupingActions); } cmHelper.exec(event->globalPos()); } void DigikamView::slotShowContextMenuOnInfo(QContextMenuEvent* event, const ImageInfo& info, const QList& extraGroupingActions, ImageFilterModel* imageFilterModel) { QList selectedImageIds = selectedInfoList(true, true).toImageIdList(); // -------------------------------------------------------- QMenu menu(this); ContextMenuHelper cmHelper(&menu); cmHelper.setImageFilterModel(imageFilterModel); cmHelper.addAction(QLatin1String("full_screen")); cmHelper.addAction(QLatin1String("options_show_menubar")); cmHelper.addSeparator(); // -------------------------------------------------------- QAction* const viewAction = new QAction(i18nc("View the selected image", "Preview"), this); viewAction->setIcon(QIcon::fromTheme(QLatin1String("view-preview"))); viewAction->setEnabled(selectedImageIds.count() == 1); cmHelper.addAction(viewAction); cmHelper.addOpenAndNavigateActions(selectedImageIds); cmHelper.addSeparator(); // -------------------------------------------------------- cmHelper.addAction(QLatin1String("image_scan_for_faces")); cmHelper.addAction(QLatin1String("image_find_similar")); cmHelper.addStandardActionLightTable(); cmHelper.addQueueManagerMenu(); cmHelper.addSeparator(); // -------------------------------------------------------- cmHelper.addAction(QLatin1String("image_rotate")); cmHelper.addAction(QLatin1String("cut_album_selection")); cmHelper.addAction(QLatin1String("copy_album_selection")); cmHelper.addAction(QLatin1String("paste_album_selection")); cmHelper.addAction(QLatin1String("image_rename")); cmHelper.addStandardActionItemDelete(this, SLOT(slotImageDelete()), selectedImageIds.count()); cmHelper.addSeparator(); // -------------------------------------------------------- cmHelper.addStandardActionThumbnail(selectedImageIds, currentAlbum()); cmHelper.addAssignTagsMenu(selectedImageIds); cmHelper.addRemoveTagsMenu(selectedImageIds); cmHelper.addLabelsAction(); if (d->leftSideBar->getActiveTab() != d->peopleSideBar) { cmHelper.addSeparator(); cmHelper.addGroupMenu(selectedImageIds, extraGroupingActions); } // special action handling -------------------------------- connect(&cmHelper, SIGNAL(signalAssignColorLabel(int)), this, SLOT(slotAssignColorLabel(int))); connect(&cmHelper, SIGNAL(signalAssignPickLabel(int)), this, SLOT(slotAssignPickLabel(int))); connect(&cmHelper, SIGNAL(signalAssignRating(int)), this, SLOT(slotAssignRating(int))); connect(&cmHelper, SIGNAL(signalAssignTag(int)), this, SLOT(slotAssignTag(int))); connect(&cmHelper, SIGNAL(signalRemoveTag(int)), this, SLOT(slotRemoveTag(int))); connect(&cmHelper, SIGNAL(signalPopupTagsView()), d->rightSideBar, SLOT(slotPopupTagsView())); connect(&cmHelper, SIGNAL(signalGotoTag(int)), this, SLOT(slotGotoTagAndItem(int))); connect(&cmHelper, SIGNAL(signalGotoTag(int)), d->albumHistory, SLOT(slotClearSelectTAlbum(int))); connect(&cmHelper, SIGNAL(signalGotoAlbum(ImageInfo)), this, SLOT(slotGotoAlbumAndItem(ImageInfo))); connect(&cmHelper, SIGNAL(signalGotoAlbum(ImageInfo)), d->albumHistory, SLOT(slotClearSelectPAlbum(ImageInfo))); connect(&cmHelper, SIGNAL(signalGotoDate(ImageInfo)), this, SLOT(slotGotoDateAndItem(ImageInfo))); connect(&cmHelper, SIGNAL(signalSetThumbnail(ImageInfo)), this, SLOT(slotSetAsAlbumThumbnail(ImageInfo))); connect(&cmHelper, SIGNAL(signalAddToExistingQueue(int)), this, SLOT(slotImageAddToExistingQueue(int))); connect(&cmHelper, SIGNAL(signalCreateGroup()), this, SLOT(slotCreateGroupFromSelection())); connect(&cmHelper, SIGNAL(signalCreateGroupByTime()), this, SLOT(slotCreateGroupByTimeFromSelection())); connect(&cmHelper, SIGNAL(signalCreateGroupByFilename()), this, SLOT(slotCreateGroupByFilenameFromSelection())); connect(&cmHelper, SIGNAL(signalRemoveFromGroup()), this, SLOT(slotRemoveSelectedFromGroup())); connect(&cmHelper, SIGNAL(signalUngroup()), this, SLOT(slotUngroupSelected())); // -------------------------------------------------------- QAction* const choice = cmHelper.exec(event->globalPos()); if (choice && (choice == viewAction)) { slotTogglePreviewMode(info); } } void DigikamView::slotShowGroupContextMenu(QContextMenuEvent* event, const QList& selectedInfos, ImageFilterModel* imageFilterModel) { QList selectedImageIDs; foreach(const ImageInfo& info, selectedInfos) { selectedImageIDs << info.id(); } QMenu popmenu(this); ContextMenuHelper cmhelper(&popmenu); cmhelper.setImageFilterModel(imageFilterModel); cmhelper.addGroupActions(selectedImageIDs); // special action handling -------------------------------- connect(&cmhelper, SIGNAL(signalCreateGroup()), this, SLOT(slotCreateGroupFromSelection())); connect(&cmhelper, SIGNAL(signalCreateGroupByTime()), this, SLOT(slotCreateGroupByTimeFromSelection())); connect(&cmhelper, SIGNAL(signalCreateGroupByFilename()), this, SLOT(slotCreateGroupByFilenameFromSelection())); connect(&cmhelper, SIGNAL(signalUngroup()), this, SLOT(slotUngroupSelected())); connect(&cmhelper, SIGNAL(signalRemoveFromGroup()), this, SLOT(slotRemoveSelectedFromGroup())); cmhelper.exec(event->globalPos()); } void DigikamView::slotSetAsAlbumThumbnail(const ImageInfo& info) { d->utilities->setAsAlbumThumbnail(currentAlbum(), info); } void DigikamView::slotCreateGroupFromSelection() { FileActionMngr::instance()->addToGroup(currentInfo(), selectedInfoList(false, true)); } void DigikamView::slotCreateGroupByTimeFromSelection() { d->utilities->createGroupByTimeFromInfoList(selectedInfoList(false, true)); } void DigikamView::slotCreateGroupByFilenameFromSelection() { d->utilities->createGroupByFilenameFromInfoList(selectedInfoList(false, true)); } void DigikamView::slotRemoveSelectedFromGroup() { FileActionMngr::instance()->removeFromGroup(selectedInfoList(false, true)); } void DigikamView::slotUngroupSelected() { FileActionMngr::instance()->ungroup(selectedInfoList(false, true)); } } // namespace Digikam diff --git a/core/app/views/digikamview.h b/core/app/views/digikamview.h index f5756451bf..4e158245f8 100644 --- a/core/app/views/digikamview.h +++ b/core/app/views/digikamview.h @@ -1,343 +1,343 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2002-16-10 * Description : implementation of album view interface. * * Copyright (C) 2002-2005 by Renchi Raju * Copyright (C) 2002-2018 by Gilles Caulier * Copyright (C) 2009-2011 by Johannes Wienke * Copyright (C) 2010-2011 by Andi Clemens * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_VIEW_H #define DIGIKAM_VIEW_H // Qt includes #include #include // Local includes #include "applicationsettings.h" #include "metaengine_rotation.h" #include "digikam_config.h" #include "searchtextbar.h" #include "imageinfo.h" #include "digikammodelcollection.h" #include "sidebarwidget.h" #include "stackedview.h" #include "dlayoutbox.h" namespace Digikam { class AlbumIconItem; class Album; class PAlbum; class TAlbum; class BatchSyncMetadata; class FilterStatusBar; class SlideShowSettings; class DCategorizedView; class ImageFilterModel; class DigikamView : public DHBox { Q_OBJECT public: explicit DigikamView(QWidget* const parent, DigikamModelCollection* const modelCollection); ~DigikamView(); void applySettings(); void refreshView(); void clearHistory(); void getForwardHistory(QStringList& titles); void getBackwardHistory(QStringList& titles); void showSideBars(); void hideSideBars(); void toggleLeftSidebar(); void toggleRightSidebar(); void previousLeftSideBarTab(); void nextLeftSideBarTab(); void previousRightSideBarTab(); void nextRightSideBarTab(); void setToolsIconView(DCategorizedView* const view); void setThumbSize(int size); void toggleShowBar(bool); void setRecurseAlbums(bool recursive); void setRecurseTags(bool recursive); void imageTransform(MetaEngineRotation::TransformationAction transform); void connectIconViewFilter(FilterStatusBar* const filter); QUrl currentUrl() const; bool hasCurrentItem() const; ImageInfo currentInfo() const; Album* currentAlbum() const; /** * Get currently selected items. By default only the first images in groups are * given, while all can be obtained by setting the grouping parameter to true. * Given an operation, it will be determined from settings/user query whether * only the first or all items in a group are returned. * Ideally only the latter (giving an operation) is used. */ QList selectedUrls(bool grouping = false) const; QList selectedUrls(const ApplicationSettings::OperationType type) const; ImageInfoList selectedInfoList(const bool currentFirst = false, const bool grouping = false) const; ImageInfoList selectedInfoList(const ApplicationSettings::OperationType type, const bool currentFirst = false) const; /** * Get all items in the current view. * Whether only the first or all grouped items are returned is determined * as described above. */ QList allUrls(bool grouping = false) const; ImageInfoList allInfo(const bool grouping = false) const; ImageInfoList allInfo(const ApplicationSettings::OperationType type) const; /** * Query whether the operation to be performed on currently selected or all * all items in the currently active view should be performed on all * grouped items or just the first. */ bool allNeedGroupResolving(const ApplicationSettings::OperationType type) const; bool selectedNeedGroupResolving(const ApplicationSettings::OperationType type) const; double zoomMin() const; double zoomMax() const; void presentation(); void toggleTag(int tagID); void toggleFullScreen(bool set); QList leftSidebarWidgets() const; StackedView::StackedViewMode viewMode() const; Q_SIGNALS: void signalAlbumSelected(Album*); void signalImageSelected(const ImageInfoList& selectedImage, const ImageInfoList& allImages); void signalNoCurrentItem(); void signalSelectionChanged(int numberOfSelectedItems); void signalThumbSizeChanged(int); void signalZoomChanged(double); void signalSwitchedToPreview(); void signalSwitchedToIconView(); void signalSwitchedToMapView(); void signalSwitchedToTableView(); void signalSwitchedToTrashView(); void signalGotoAlbumAndItem(const ImageInfo&); void signalGotoDateAndItem(AlbumIconItem*); void signalGotoTagAndItem(int tagID); void signalChangedTab(QWidget*); void signalFuzzySidebarActive(bool active); public Q_SLOTS: void setZoomFactor(double zoom); // View Action slots void slotZoomIn(); void slotZoomOut(); void slotZoomTo100Percents(); void slotFitToWindow(); void slotSlideShowAll(); void slotSlideShowSelection(); void slotSlideShowRecursive(); void slotSlideShowManualFromCurrent(); void slotSlideShowManualFrom(const ImageInfo& info); // Album action slots void slotRefresh(); void slotNewAlbum(); void slotSortAlbums(int role); void slotDeleteAlbum(); void slotRenameAlbum(); void slotAlbumPropsEdit(); void slotAlbumOpenInFileManager(); void slotAlbumHistoryBack(int steps=1); void slotAlbumHistoryForward(int steps=1); void slotAlbumWriteMetadata(); void slotAlbumReadMetadata(); - void slotAlbumSelected(QList albums); + void slotAlbumSelected(const QList& albums); void slotGotoAlbumAndItem(const ImageInfo& imageInfo); void slotGotoDateAndItem(const ImageInfo& imageInfo); void slotGotoTagAndItem(int tagID); void slotSelectAlbum(const QUrl& url); void slotSetCurrentWhenAvailable(const qlonglong id); void slotSetAsAlbumThumbnail(const ImageInfo& info); // Tag action slots void slotNewTag(); void slotDeleteTag(); void slotEditTag(); void slotOpenTagsManager(); void slotAssignTag(); // Search action slots void slotNewKeywordSearch(); void slotNewAdvancedSearch(); - void slotNewDuplicatesSearch(PAlbum* album=0); - void slotNewDuplicatesSearch(QList albums); - void slotNewDuplicatesSearch(QList albums); + void slotNewDuplicatesSearch(PAlbum* album = 0); + void slotNewDuplicatesSearch(const QList& albums); + void slotNewDuplicatesSearch(const QList& albums); // Image action slots void slotImageLightTable(); void slotImageAddToLightTable(); void slotImageAddToCurrentQueue(); void slotImageAddToNewQueue(); void slotImageAddToExistingQueue(int); void slotImagePreview(); void slotMapWidgetView(); void slotTableView(); void slotIconView(); void slotImageEdit(); void slotImageFindSimilar(); void slotImageScanForFaces(); void slotImageExifOrientation(int orientation); void slotImageRename(); void slotImageDelete(); void slotImageDeletePermanently(); void slotImageDeletePermanentlyDirectly(); void slotImageTrashDirectly(); void slotImageWriteMetadata(); void slotImageReadMetadata(); void slotSelectAll(); void slotSelectNone(); void slotSelectInvert(); void slotSortImages(int order); void slotSortImagesOrder(int order); void slotSeparateImages(int mode); void slotImageSeparationSortOrder(int order); void slotMoveSelectionToAlbum(); void slotImagePaste(); void slotAssignPickLabel(int pickId); void slotAssignColorLabel(int colorId); void slotAssignRating(int rating); void slotAssignTag(int tagID); void slotRemoveTag(int tagID); // Tools action slots. void slotEditor(); void slotLightTable(); void slotQueueMgr(); void slotFileWithDefaultApplication(); void slotLeftSideBarActivate(QWidget* widget); void slotLeftSideBarActivate(SidebarWidget* widget); void slotLeftSideBarActivateAlbums(); void slotLeftSideBarActivateTags(); void slotRightSideBarActivateTitles(); void slotRightSideBarActivateComments(); void slotRightSideBarActivateAssignedTags(); void slotFocusAndNextImage(); void slotCreateGroupFromSelection(); void slotCreateGroupByTimeFromSelection(); void slotCreateGroupByFilenameFromSelection(); void slotRemoveSelectedFromGroup(); void slotUngroupSelected(); private: void toggleZoomActions(); void setupConnections(); void loadViewState(); void saveViewState(); void changeAlbumFromHistory(QList album, QWidget* const widget); void slideShow(const ImageInfoList& infoList); private Q_SLOTS: void slotAllAlbumsLoaded(); void slotAlbumsCleared(); void slotImageSelected(); void slotTogglePreviewMode(const ImageInfo& info); void slotDispatchImageSelected(); void slotLeftSidebarChangedTab(QWidget* w); void slotFirstItem(); void slotPrevItem(); void slotNextItem(); void slotLastItem(); void slotSelectItemByUrl(const QUrl&); void slotAwayFromSelection(); void slotViewModeChanged(); void slotEscapePreview(); void slotSlideShowBuilderComplete(const SlideShowSettings& settings); void slotThumbSizeEffect(); void slotZoomFactorChanged(double); void slotSidebarTabTitleStyleChanged(); void slotImageChangeFailed(const QString& message, const QStringList& fileNames); void slotRatingChanged(const QUrl&, int); void slotColorLabelChanged(const QUrl&, int); void slotPickLabelChanged(const QUrl&, int); void slotToggleTag(const QUrl&, int); void slotPopupFiltersView(); void slotSetupMetadataFilters(int); void slotAlbumRefreshComplete(); void slotRefreshImagePreview(); void slotShowContextMenu(QContextMenuEvent* event, const QList& extraGroupingActions = QList()); void slotShowContextMenuOnInfo(QContextMenuEvent* event, const ImageInfo& info, const QList& extraGroupingActions = QList(), ImageFilterModel* imageFilterModel = 0); void slotShowGroupContextMenu(QContextMenuEvent* event, const QList& selectedInfos, ImageFilterModel* imageFilterModel = 0); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_VIEW_H diff --git a/core/app/views/leftsidebarwidgets.cpp b/core/app/views/leftsidebarwidgets.cpp index 7e0e14bf06..932f583d5f 100644 --- a/core/app/views/leftsidebarwidgets.cpp +++ b/core/app/views/leftsidebarwidgets.cpp @@ -1,1501 +1,1501 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : left sidebar widgets * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2018 by Gilles Caulier * Copyright (C) 2012 by Andi Clemens * Copyright (C) 2014 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #include "leftsidebarwidgets.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "albummodificationhelper.h" #include "albumselectiontreeview.h" #include "applicationsettings.h" #include "datefolderview.h" #include "editablesearchtreeview.h" #include "fuzzysearchview.h" #include "searchfolderview.h" #include "searchtabheader.h" #include "searchtextbar.h" #include "coredbsearchxml.h" #include "tagfolderview.h" #include "timelinewidget.h" #include "facescandialog.h" #include "facesdetector.h" #include "tagsmanager.h" #include "albumlabelstreeview.h" #include "coredb.h" #include "dexpanderbox.h" namespace Digikam { class AlbumFolderViewSideBarWidget::Private { public: explicit Private() : albumModificationHelper(0), albumFolderView(0), searchTextBar(0) { } AlbumModificationHelper* albumModificationHelper; AlbumSelectionTreeView* albumFolderView; SearchTextBar* searchTextBar; }; AlbumFolderViewSideBarWidget::AlbumFolderViewSideBarWidget(QWidget* const parent, AlbumModel* const model, AlbumModificationHelper* const albumModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("AlbumFolderView Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F1); d->albumModificationHelper = albumModificationHelper; QVBoxLayout* const layout = new QVBoxLayout(this); d->albumFolderView = new AlbumSelectionTreeView(this, model, d->albumModificationHelper); d->albumFolderView->setObjectName(QLatin1String("AlbumFolderView")); d->albumFolderView->setConfigGroup(getConfigGroup()); d->albumFolderView->setExpandNewCurrentItem(true); d->albumFolderView->setAlbumManagerCurrentAlbum(true); d->searchTextBar = new SearchTextBar(this, QLatin1String("DigikamViewFolderSearchBar")); d->searchTextBar->setHighlightOnResult(true); d->searchTextBar->setModel(model, AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchTextBar->setFilterModel(d->albumFolderView->albumFilterModel()); layout->addWidget(d->albumFolderView); layout->addWidget(d->searchTextBar); // setup connection connect(d->albumFolderView, SIGNAL(signalFindDuplicates(PAlbum*)), this, SIGNAL(signalFindDuplicates(PAlbum*))); } AlbumFolderViewSideBarWidget::~AlbumFolderViewSideBarWidget() { delete d; } void AlbumFolderViewSideBarWidget::setActive(bool active) { if (active) { AlbumManager::instance()->setCurrentAlbums(QList() << d->albumFolderView->currentAlbum()); } } void AlbumFolderViewSideBarWidget::doLoadState() { d->albumFolderView->loadState(); } void AlbumFolderViewSideBarWidget::doSaveState() { d->albumFolderView->saveState(); } void AlbumFolderViewSideBarWidget::applySettings() { ApplicationSettings* const settings = ApplicationSettings::instance(); d->albumFolderView->setEnableToolTips(settings->getShowAlbumToolTips()); } void AlbumFolderViewSideBarWidget::changeAlbumFromHistory(QList album) { d->albumFolderView->setCurrentAlbums(album); } AlbumPointer AlbumFolderViewSideBarWidget::currentAlbum() const { return AlbumPointer (d->albumFolderView->currentAlbum()); } void AlbumFolderViewSideBarWidget::setCurrentAlbum(PAlbum* album) { // Change the current album in list view. d->albumFolderView->setCurrentAlbums(QList() << album); } const QIcon AlbumFolderViewSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("folder-pictures")); } const QString AlbumFolderViewSideBarWidget::getCaption() { return i18n("Albums"); } // ----------------------------------------------------------------------------- class TagViewSideBarWidget::Private { public: enum TagsSource { NoTags = 0, ExistingTags }; public: explicit Private() : openTagMngr(0), tagSearchBar(0), tagFolderView(0), btnGroup(0), noTagsBtn(0), tagsBtn(0), noTagsWasChecked(false), ExistingTagsWasChecked(false) { } public: QPushButton* openTagMngr; SearchTextBar* tagSearchBar; TagFolderView* tagFolderView; QButtonGroup* btnGroup; QRadioButton* noTagsBtn; QRadioButton* tagsBtn; bool noTagsWasChecked; bool ExistingTagsWasChecked; QString noTagsSearchXml; static const QString configTagsSourceEntry; }; const QString TagViewSideBarWidget::Private::configTagsSourceEntry(QLatin1String("TagsSource")); TagViewSideBarWidget::TagViewSideBarWidget(QWidget* const parent, TagModel* const model) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("TagView Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F2); QVBoxLayout* const layout = new QVBoxLayout(this); d->openTagMngr = new QPushButton( i18n("Open Tag Manager")); d->noTagsBtn = new QRadioButton(i18n("No Tags"), this); d->tagsBtn = new QRadioButton(i18n("Existing Tags"), this); d->btnGroup = new QButtonGroup(this); d->btnGroup->addButton(d->noTagsBtn); d->btnGroup->addButton(d->tagsBtn); d->btnGroup->setId(d->noTagsBtn, 0); d->btnGroup->setId(d->tagsBtn, 1); d->btnGroup->setExclusive(true); d->tagFolderView = new TagFolderView(this, model); d->tagFolderView->setConfigGroup(getConfigGroup()); d->tagFolderView->setExpandNewCurrentItem(true); d->tagFolderView->setAlbumManagerCurrentAlbum(true); d->tagSearchBar = new SearchTextBar(this, QLatin1String("DigikamViewTagSearchBar")); d->tagSearchBar->setHighlightOnResult(true); d->tagSearchBar->setModel(model, AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->tagSearchBar->setFilterModel(d->tagFolderView->albumFilterModel()); layout->addWidget(d->openTagMngr); layout->addWidget(d->noTagsBtn); layout->addWidget(d->tagsBtn); layout->addWidget(d->tagFolderView); layout->addWidget(d->tagSearchBar); connect(d->openTagMngr, SIGNAL(clicked()), this,SLOT(slotOpenTagManager())); connect(d->tagFolderView, SIGNAL(signalFindDuplicates(QList)), this, SIGNAL(signalFindDuplicates(QList))); connect(d->btnGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotToggleTagsSelection(int))); } TagViewSideBarWidget::~TagViewSideBarWidget() { delete d; } void TagViewSideBarWidget::setActive(bool active) { if (active) { if(d->noTagsBtn->isChecked()) { setNoTagsAlbum(); } else { AlbumManager::instance()->setCurrentAlbums(d->tagFolderView->selectedTags()); } } } void TagViewSideBarWidget::doLoadState() { KConfigGroup group = getConfigGroup(); bool noTagsBtnWasChecked = group.readEntry(d->configTagsSourceEntry, false); d->noTagsBtn->setChecked(noTagsBtnWasChecked); d->tagsBtn->setChecked(!noTagsBtnWasChecked); d->noTagsWasChecked = noTagsBtnWasChecked; d->ExistingTagsWasChecked = !noTagsBtnWasChecked; d->tagFolderView->loadState(); d->tagFolderView->setDisabled(noTagsBtnWasChecked); } void TagViewSideBarWidget::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(d->configTagsSourceEntry, d->noTagsBtn->isChecked()); d->tagFolderView->saveState(); group.sync(); } void TagViewSideBarWidget::applySettings() { } void TagViewSideBarWidget::changeAlbumFromHistory(QList album) { if (album.first()->type() == Album::TAG) { d->tagsBtn->setChecked(true); d->tagFolderView->setEnabled(true); d->ExistingTagsWasChecked = true; d->noTagsWasChecked = false; d->tagFolderView->setCurrentAlbums(album); } else { d->noTagsBtn->setChecked(true); d->tagFolderView->setDisabled(true); d->noTagsWasChecked = true; d->ExistingTagsWasChecked = false; } } AlbumPointer TagViewSideBarWidget::currentAlbum() const { return AlbumPointer (d->tagFolderView->currentAlbum()); } void TagViewSideBarWidget::setNoTagsAlbum() { if (d->noTagsSearchXml.isEmpty()) { SearchXmlWriter writer; writer.setFieldOperator((SearchXml::standardFieldOperator())); writer.writeGroup(); writer.writeField(QLatin1String("notag"), SearchXml::Equal); writer.finishField(); writer.finishGroup(); writer.finish(); d->noTagsSearchXml = writer.xml(); } QString title = SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch); SAlbum* album = AlbumManager::instance()->findSAlbum(title); int id; if (album) { id = album->id(); CoreDbAccess().db()->updateSearch(id,DatabaseSearch::AdvancedSearch, SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->noTagsSearchXml); } else { id = CoreDbAccess().db()->addSearch(DatabaseSearch::AdvancedSearch, SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->noTagsSearchXml); } album = new SAlbum(i18n("No Tags Album"), id); if (album) { AlbumManager::instance()->setCurrentAlbums(QList() << album); } } const QIcon TagViewSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("tag")); } const QString TagViewSideBarWidget::getCaption() { return i18n("Tags"); } void TagViewSideBarWidget::setCurrentAlbum(TAlbum* album) { d->tagFolderView->setCurrentAlbums(QList() << album); } void TagViewSideBarWidget::slotOpenTagManager() { TagsManager* const tagMngr = TagsManager::instance(); tagMngr->show(); tagMngr->activateWindow(); tagMngr->raise(); } void TagViewSideBarWidget::slotToggleTagsSelection(int radioClicked) { switch (Private::TagsSource(radioClicked)) { case Private::NoTags: { if (!d->noTagsWasChecked) { setNoTagsAlbum(); d->tagFolderView->setDisabled(true); d->noTagsWasChecked = d->noTagsBtn->isChecked(); d->ExistingTagsWasChecked = d->tagsBtn->isChecked(); } break; } case Private::ExistingTags: { if (!d->ExistingTagsWasChecked) { d->tagFolderView->setEnabled(true); setActive(true); d->noTagsWasChecked = d->noTagsBtn->isChecked(); d->ExistingTagsWasChecked = d->tagsBtn->isChecked(); } break; } } } // ----------------------------------------------------------------------------- class LabelsSideBarWidget::Private { public: explicit Private() : labelsTree(0) { } AlbumLabelsTreeView* labelsTree; }; LabelsSideBarWidget::LabelsSideBarWidget(QWidget* const parent) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("Labels Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F3); QVBoxLayout* const layout = new QVBoxLayout(this); d->labelsTree = new AlbumLabelsTreeView(this); d->labelsTree->setConfigGroup(getConfigGroup()); layout->addWidget(d->labelsTree); } LabelsSideBarWidget::~LabelsSideBarWidget() { delete d; } AlbumLabelsTreeView *LabelsSideBarWidget::labelsTree() { return d->labelsTree; } void LabelsSideBarWidget::setActive(bool active) { if (active) { d->labelsTree->setCurrentAlbum(); } } void LabelsSideBarWidget::applySettings() { } void LabelsSideBarWidget::changeAlbumFromHistory(QList album) { Q_UNUSED(album); } void LabelsSideBarWidget::doLoadState() { d->labelsTree->doLoadState(); } void LabelsSideBarWidget::doSaveState() { d->labelsTree->doSaveState(); } const QIcon LabelsSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("folder-favorites")); } const QString LabelsSideBarWidget::getCaption() { return i18n("Labels"); } QHash > LabelsSideBarWidget::selectedLabels() { return d->labelsTree->selectedLabels(); } // ----------------------------------------------------------------------------- class DateFolderViewSideBarWidget::Private { public: explicit Private() : dateFolderView(0) { } DateFolderView* dateFolderView; }; DateFolderViewSideBarWidget::DateFolderViewSideBarWidget(QWidget* const parent, DateAlbumModel* const model, ImageAlbumFilterModel* const imageFilterModel) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("DateFolderView Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F4); QVBoxLayout* const layout = new QVBoxLayout(this); d->dateFolderView = new DateFolderView(this, model); d->dateFolderView->setConfigGroup(getConfigGroup()); d->dateFolderView->setImageModel(imageFilterModel); layout->addWidget(d->dateFolderView); } DateFolderViewSideBarWidget::~DateFolderViewSideBarWidget() { delete d; } void DateFolderViewSideBarWidget::setActive(bool active) { d->dateFolderView->setActive(active); } void DateFolderViewSideBarWidget::doLoadState() { d->dateFolderView->loadState(); } void DateFolderViewSideBarWidget::doSaveState() { d->dateFolderView->saveState(); } void DateFolderViewSideBarWidget::applySettings() { } void DateFolderViewSideBarWidget::changeAlbumFromHistory(QList album) { d->dateFolderView->changeAlbumFromHistory(dynamic_cast(album.first())); } AlbumPointer DateFolderViewSideBarWidget::currentAlbum() const { return d->dateFolderView->currentAlbum(); } void DateFolderViewSideBarWidget::gotoDate(const QDate& date) { d->dateFolderView->gotoDate(date); } const QIcon DateFolderViewSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("view-calendar-list")); } const QString DateFolderViewSideBarWidget::getCaption() { return i18n("Dates"); } // ----------------------------------------------------------------------------- class TimelineSideBarWidget::Private { public: explicit Private() : scaleBG(0), cursorCountLabel(0), scrollBar(0), timer(0), resetButton(0), saveButton(0), timeUnitCB(0), nameEdit(0), cursorDateLabel(0), searchDateBar(0), timeLineFolderView(0), timeLineWidget(0), searchModificationHelper(0) { } static const QString configHistogramTimeUnitEntry; static const QString configHistogramScaleEntry; static const QString configCursorPositionEntry; QButtonGroup* scaleBG; QLabel* cursorCountLabel; QScrollBar* scrollBar; QTimer* timer; QToolButton* resetButton; QToolButton* saveButton; QComboBox* timeUnitCB; QLineEdit* nameEdit; DAdjustableLabel* cursorDateLabel; SearchTextBar* searchDateBar; EditableSearchTreeView* timeLineFolderView; TimeLineWidget* timeLineWidget; SearchModificationHelper* searchModificationHelper; AlbumPointer currentTimelineSearch; }; const QString TimelineSideBarWidget::Private::configHistogramTimeUnitEntry(QLatin1String("Histogram TimeUnit")); const QString TimelineSideBarWidget::Private::configHistogramScaleEntry(QLatin1String("Histogram Scale")); const QString TimelineSideBarWidget::Private::configCursorPositionEntry(QLatin1String("Cursor Position")); // -------------------------------------------------------- TimelineSideBarWidget::TimelineSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("TimeLine Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F5); d->searchModificationHelper = searchModificationHelper; d->timer = new QTimer(this); setAttribute(Qt::WA_DeleteOnClose); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const vlay = new QVBoxLayout(this); QFrame* const panel = new QFrame(this); panel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); panel->setLineWidth(1); QGridLayout* const grid = new QGridLayout(panel); // --------------------------------------------------------------- QWidget* const hbox1 = new QWidget(panel); QHBoxLayout* const hlay = new QHBoxLayout(hbox1); QLabel* const label1 = new QLabel(i18n("Time Unit:"), hbox1); d->timeUnitCB = new QComboBox(hbox1); d->timeUnitCB->addItem(i18n("Day"), TimeLineWidget::Day); d->timeUnitCB->addItem(i18n("Week"), TimeLineWidget::Week); d->timeUnitCB->addItem(i18n("Month"), TimeLineWidget::Month); d->timeUnitCB->addItem(i18n("Year"), TimeLineWidget::Year); d->timeUnitCB->setCurrentIndex((int)TimeLineWidget::Month); d->timeUnitCB->setFocusPolicy(Qt::NoFocus); d->timeUnitCB->setWhatsThis(i18n("

Select the histogram time unit.

" "

You can change the graph decade to zoom in or zoom out over time.

")); QWidget* const scaleBox = new QWidget(hbox1); QHBoxLayout* const hlay2 = new QHBoxLayout(scaleBox); d->scaleBG = new QButtonGroup(scaleBox); d->scaleBG->setExclusive(true); scaleBox->setWhatsThis( i18n("

Select the histogram scale.

" "

If the date's maximal counts are small, you can use the linear scale.

" "

Logarithmic scale can be used when the maximal counts are big; " "if it is used, all values (small and large) will be visible on the " "graph.

")); QToolButton* const linHistoButton = new QToolButton(scaleBox); linHistoButton->setToolTip( i18n( "Linear" ) ); linHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-linear"))); linHistoButton->setCheckable(true); d->scaleBG->addButton(linHistoButton, TimeLineWidget::LinScale); QToolButton* const logHistoButton = new QToolButton(scaleBox); logHistoButton->setToolTip( i18n( "Logarithmic" ) ); logHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-logarithmic"))); logHistoButton->setCheckable(true); d->scaleBG->addButton(logHistoButton, TimeLineWidget::LogScale); hlay2->setContentsMargins(QMargins()); hlay2->setSpacing(0); hlay2->addWidget(linHistoButton); hlay2->addWidget(logHistoButton); hlay->setContentsMargins(QMargins()); hlay->setSpacing(spacing); hlay->addWidget(label1); hlay->addWidget(d->timeUnitCB); hlay->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum)); hlay->addWidget(scaleBox); // --------------------------------------------------------------- d->timeLineWidget = new TimeLineWidget(panel); d->scrollBar = new QScrollBar(panel); d->scrollBar->setOrientation(Qt::Horizontal); d->scrollBar->setMinimum(0); d->scrollBar->setSingleStep(1); d->cursorDateLabel = new DAdjustableLabel(panel); d->cursorCountLabel = new QLabel(panel); d->cursorCountLabel->setAlignment(Qt::AlignRight); // --------------------------------------------------------------- DHBox* const hbox2 = new DHBox(panel); hbox2->setContentsMargins(QMargins()); hbox2->setSpacing(spacing); d->resetButton = new QToolButton(hbox2); d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert"))); d->resetButton->setToolTip(i18n("Clear current selection")); d->resetButton->setWhatsThis(i18n("If you press this button, the current date selection on the time-line will be cleared.")); d->nameEdit = new QLineEdit(hbox2); d->nameEdit->setClearButtonEnabled(true); d->nameEdit->setWhatsThis(i18n("Enter the name of the current dates search to save in the " "\"Searches\" view")); d->saveButton = new QToolButton(hbox2); d->saveButton->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); d->saveButton->setEnabled(false); d->saveButton->setToolTip(i18n("Save current selection to a new virtual Album")); d->saveButton->setWhatsThis(i18n("If you press this button, the dates selected on the time-line will be " "saved to a new search virtual Album using the name set on the left.")); // --------------------------------------------------------------- grid->addWidget(hbox1, 0, 0, 1, 4); grid->addWidget(d->cursorDateLabel, 1, 0, 1, 3); grid->addWidget(d->cursorCountLabel, 1, 3, 1, 1); grid->addWidget(d->timeLineWidget, 2, 0, 1, 4); grid->addWidget(d->scrollBar, 3, 0, 1, 4); grid->addWidget(hbox2, 4, 0, 1, 4); grid->setColumnStretch(2, 10); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); // --------------------------------------------------------------- d->timeLineFolderView = new EditableSearchTreeView(this, searchModel, searchModificationHelper); d->timeLineFolderView->setConfigGroup(getConfigGroup()); d->timeLineFolderView->filteredModel()->listTimelineSearches(); d->timeLineFolderView->filteredModel()->setListTemporarySearches(false); d->timeLineFolderView->setAlbumManagerCurrentAlbum(false); d->searchDateBar = new SearchTextBar(this, QLatin1String("TimeLineViewSearchDateBar")); d->searchDateBar->setModel(d->timeLineFolderView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchDateBar->setFilterModel(d->timeLineFolderView->albumFilterModel()); vlay->addWidget(panel); vlay->addWidget(d->timeLineFolderView); vlay->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Minimum, QSizePolicy::Minimum)); vlay->addWidget(d->searchDateBar); vlay->setContentsMargins(QMargins()); vlay->setSpacing(0); // --------------------------------------------------------------- connect(AlbumManager::instance(), SIGNAL(signalDatesMapDirty(QMap)), d->timeLineWidget, SLOT(slotDatesMap(QMap))); connect(d->timeLineFolderView, SIGNAL(currentAlbumChanged(Album*)), this, SLOT(slotAlbumSelected(Album*))); connect(d->timeUnitCB, SIGNAL(activated(int)), this, SLOT(slotTimeUnitChanged(int))); connect(d->scaleBG, SIGNAL(buttonReleased(int)), this, SLOT(slotScaleChanged(int))); connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), this, SLOT(slotInit())); connect(d->timeLineWidget, SIGNAL(signalCursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); connect(d->timeLineWidget, SIGNAL(signalSelectionChanged()), this, SLOT(slotSelectionChanged())); connect(d->timeLineWidget, SIGNAL(signalRefDateTimeChanged()), this, SLOT(slotRefDateTimeChanged())); connect(d->timer, SIGNAL(timeout()), this, SLOT(slotUpdateCurrentDateSearchAlbum())); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotResetSelection())); connect(d->saveButton, SIGNAL(clicked()), this, SLOT(slotSaveSelection())); connect(d->scrollBar, SIGNAL(valueChanged(int)), this, SLOT(slotScrollBarValueChanged(int))); connect(d->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(slotCheckAboutSelection())); connect(d->nameEdit, SIGNAL(returnPressed()), d->saveButton, SLOT(animateClick())); } TimelineSideBarWidget::~TimelineSideBarWidget() { delete d; } void TimelineSideBarWidget::slotInit() { // Date Maps are loaded from AlbumManager to TimeLineWidget after than GUI is initialized. // AlbumManager query Date KIO slave to stats items from database and it can take a while. // We waiting than TimeLineWidget is ready before to set last config from users. loadState(); disconnect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), this, SLOT(slotInit())); connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), this, SLOT(slotCursorPositionChanged())); } void TimelineSideBarWidget::setActive(bool active) { if (active) { if (!d->currentTimelineSearch) { d->currentTimelineSearch = d->timeLineFolderView->currentAlbum(); } if (d->currentTimelineSearch) { AlbumManager::instance()->setCurrentAlbums(QList() << d->currentTimelineSearch); } else { slotUpdateCurrentDateSearchAlbum(); } } } void TimelineSideBarWidget::doLoadState() { KConfigGroup group = getConfigGroup(); d->timeUnitCB->setCurrentIndex(group.readEntry(d->configHistogramTimeUnitEntry, (int)TimeLineWidget::Month)); slotTimeUnitChanged(d->timeUnitCB->currentIndex()); int id = group.readEntry(d->configHistogramScaleEntry, (int)TimeLineWidget::LinScale); if (d->scaleBG->button(id)) { d->scaleBG->button(id)->setChecked(true); } slotScaleChanged(d->scaleBG->checkedId()); QDateTime now = QDateTime::currentDateTime(); d->timeLineWidget->setCursorDateTime(group.readEntry(d->configCursorPositionEntry, now)); d->timeLineWidget->setCurrentIndex(d->timeLineWidget->indexForCursorDateTime()); d->timeLineFolderView->loadState(); } void TimelineSideBarWidget::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(d->configHistogramTimeUnitEntry, d->timeUnitCB->currentIndex()); group.writeEntry(d->configHistogramScaleEntry, d->scaleBG->checkedId()); group.writeEntry(d->configCursorPositionEntry, d->timeLineWidget->cursorDateTime()); d->timeLineFolderView->saveState(); group.sync(); } void TimelineSideBarWidget::applySettings() { // nothing to do here right now } void TimelineSideBarWidget::changeAlbumFromHistory(QList album) { d->timeLineFolderView->setCurrentAlbums(album); } const QIcon TimelineSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("player-time")); } const QString TimelineSideBarWidget::getCaption() { return i18n("Timeline"); } void TimelineSideBarWidget::slotRefDateTimeChanged() { d->scrollBar->blockSignals(true); d->scrollBar->setMaximum(d->timeLineWidget->totalIndex()-1); d->scrollBar->setValue(d->timeLineWidget->indexForRefDateTime()-1); d->scrollBar->blockSignals(false); } void TimelineSideBarWidget::slotTimeUnitChanged(int mode) { d->timeLineWidget->setTimeUnit((TimeLineWidget::TimeUnit)mode); } void TimelineSideBarWidget::slotScrollBarValueChanged(int val) { d->timeLineWidget->setCurrentIndex(val); } void TimelineSideBarWidget::slotScaleChanged(int mode) { d->timeLineWidget->setScaleMode((TimeLineWidget::ScaleMode)mode); } void TimelineSideBarWidget::slotCursorPositionChanged() { QString txt; int val = d->timeLineWidget->cursorInfo(txt); d->cursorDateLabel->setAdjustedText(txt); d->cursorCountLabel->setText((val == 0) ? i18n("no item") : i18np("1 item", "%1 items", val)); } void TimelineSideBarWidget::slotSelectionChanged() { d->timer->setSingleShot(true); d->timer->start(500); } /** Called from d->timer event.*/ void TimelineSideBarWidget::slotUpdateCurrentDateSearchAlbum() { slotCheckAboutSelection(); int totalCount = 0; DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount); d->currentTimelineSearch = d->searchModificationHelper-> slotCreateTimeLineSearch(SAlbum::getTemporaryTitle(DatabaseSearch::TimeLineSearch), dateRanges, true); d->timeLineFolderView->setCurrentAlbum(0); // "temporary" search is not listed in view } void TimelineSideBarWidget::slotSaveSelection() { QString name = d->nameEdit->text(); int totalCount = 0; DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount); d->currentTimelineSearch = d->searchModificationHelper->slotCreateTimeLineSearch(name, dateRanges); } void TimelineSideBarWidget::slotAlbumSelected(Album* album) { if (d->currentTimelineSearch == album) { return; } SAlbum* const salbum = dynamic_cast(album); if (!salbum) { return; } d->currentTimelineSearch = salbum; AlbumManager::instance()->setCurrentAlbums(QList() << salbum); SearchXmlReader reader(salbum->query()); // The timeline query consists of groups, with two date time fields each DateRangeList list; while (!reader.atEnd()) { // read groups if (reader.readNext() == SearchXml::Group) { QDateTime start, end; int numberOfFields = 0; while (!reader.atEnd()) { // read fields reader.readNext(); if (reader.isEndElement()) { break; } if (reader.isFieldElement()) { if (numberOfFields == 0) { start = reader.valueToDateTime(); } else if (numberOfFields == 1) { end = reader.valueToDateTime(); } ++numberOfFields; } } if (numberOfFields) { list << DateRange(start, end); } } } d->timeLineWidget->setSelectedDateRange(list); } void TimelineSideBarWidget::slotResetSelection() { d->timeLineWidget->slotResetSelection(); slotCheckAboutSelection(); AlbumManager::instance()->setCurrentAlbums(QList()); } void TimelineSideBarWidget::slotCheckAboutSelection() { int totalCount = 0; DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount); if (!list.isEmpty()) { d->nameEdit->setEnabled(true); if (!d->nameEdit->text().isEmpty()) { d->saveButton->setEnabled(true); } } else { d->nameEdit->setEnabled(false); d->saveButton->setEnabled(false); } } // ----------------------------------------------------------------------------- class SearchSideBarWidget::Private { public: explicit Private() : searchSearchBar(0), searchTreeView(0), searchTabHeader(0) { } SearchTextBar* searchSearchBar; NormalSearchTreeView* searchTreeView; SearchTabHeader* searchTabHeader; }; SearchSideBarWidget::SearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("Search Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F6); QVBoxLayout* const layout = new QVBoxLayout(this); d->searchTabHeader = new SearchTabHeader(this); d->searchTreeView = new NormalSearchTreeView(this, searchModel, searchModificationHelper); d->searchTreeView->setConfigGroup(getConfigGroup()); d->searchTreeView->filteredModel()->listNormalSearches(); d->searchTreeView->filteredModel()->setListTemporarySearches(true); d->searchTreeView->setAlbumManagerCurrentAlbum(true); d->searchSearchBar = new SearchTextBar(this, QLatin1String("DigikamViewSearchSearchBar")); d->searchSearchBar->setModel(d->searchTreeView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchSearchBar->setFilterModel(d->searchTreeView->albumFilterModel()); layout->addWidget(d->searchTabHeader); layout->addWidget(d->searchTreeView); layout->setStretchFactor(d->searchTreeView, 1); layout->addWidget(d->searchSearchBar); connect(d->searchTreeView, SIGNAL(newSearch()), d->searchTabHeader, SLOT(newAdvancedSearch())); connect(d->searchTreeView, SIGNAL(editSearch(SAlbum*)), d->searchTabHeader, SLOT(editSearch(SAlbum*))); connect(d->searchTreeView, SIGNAL(currentAlbumChanged(Album*)), d->searchTabHeader, SLOT(selectedSearchChanged(Album*))); connect(d->searchTabHeader, SIGNAL(searchShallBeSelected(QList)), d->searchTreeView, SLOT(setCurrentAlbums(QList))); } SearchSideBarWidget::~SearchSideBarWidget() { delete d; } void SearchSideBarWidget::setActive(bool active) { if (active) { AlbumManager::instance()->setCurrentAlbums(QList() << d->searchTreeView->currentAlbum()); } } void SearchSideBarWidget::doLoadState() { d->searchTreeView->loadState(); } void SearchSideBarWidget::doSaveState() { d->searchTreeView->saveState(); } void SearchSideBarWidget::applySettings() { } void SearchSideBarWidget::changeAlbumFromHistory(QList album) { d->searchTreeView->setCurrentAlbums(album); } const QIcon SearchSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("edit-find")); } const QString SearchSideBarWidget::getCaption() { return i18nc("Avanced search images, access stored searches", "Search"); } void SearchSideBarWidget::newKeywordSearch() { d->searchTabHeader->newKeywordSearch(); } void SearchSideBarWidget::newAdvancedSearch() { d->searchTabHeader->newAdvancedSearch(); } // ----------------------------------------------------------------------------- class FuzzySearchSideBarWidget::Private { public: explicit Private() : fuzzySearchView(0), searchModificationHelper(0) { } FuzzySearchView* fuzzySearchView; SearchModificationHelper* searchModificationHelper; }; FuzzySearchSideBarWidget::FuzzySearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("Fuzzy Search Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F7); d->fuzzySearchView = new FuzzySearchView(searchModel, searchModificationHelper, this); d->fuzzySearchView->setConfigGroup(getConfigGroup()); QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(d->fuzzySearchView); } FuzzySearchSideBarWidget::~FuzzySearchSideBarWidget() { delete d; } void FuzzySearchSideBarWidget::setActive(bool active) { d->fuzzySearchView->setActive(active); if (active) { AlbumManager::instance()->setCurrentAlbums(QList() << d->fuzzySearchView->currentAlbum()); } emit signalActive(active); } void FuzzySearchSideBarWidget::doLoadState() { d->fuzzySearchView->loadState(); } void FuzzySearchSideBarWidget::doSaveState() { d->fuzzySearchView->saveState(); } void FuzzySearchSideBarWidget::applySettings() { } void FuzzySearchSideBarWidget::changeAlbumFromHistory(QList album) { SAlbum* const salbum = dynamic_cast(album.first()); d->fuzzySearchView->setCurrentAlbum(salbum); } const QIcon FuzzySearchSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("tools-wizard")); } const QString FuzzySearchSideBarWidget::getCaption() { return i18nc("Fuzzy Search images, as dupplicates, sketch, searches by similarities", "Similarity"); } void FuzzySearchSideBarWidget::newDuplicatesSearch(PAlbum* album) { d->fuzzySearchView->newDuplicatesSearch(album); } -void FuzzySearchSideBarWidget::newDuplicatesSearch(QList albums) +void FuzzySearchSideBarWidget::newDuplicatesSearch(const QList& albums) { d->fuzzySearchView->newDuplicatesSearch(albums); } -void FuzzySearchSideBarWidget::newDuplicatesSearch(QList albums) +void FuzzySearchSideBarWidget::newDuplicatesSearch(const QList& albums) { d->fuzzySearchView->newDuplicatesSearch(albums); } void FuzzySearchSideBarWidget::newSimilarSearch(const ImageInfo& imageInfo) { if (imageInfo.isNull()) { return; } d->fuzzySearchView->setImageInfo(imageInfo); } // ----------------------------------------------------------------------------- #ifdef HAVE_MARBLE class GPSSearchSideBarWidget::Private { public: explicit Private() : gpsSearchView(0) { } GPSSearchView* gpsSearchView; }; GPSSearchSideBarWidget::GPSSearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper, ImageFilterModel* const imageFilterModel, QItemSelectionModel* const itemSelectionModel) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("GPS Search Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F8); d->gpsSearchView = new GPSSearchView(this, searchModel, searchModificationHelper, imageFilterModel, itemSelectionModel); d->gpsSearchView->setConfigGroup(getConfigGroup()); QScrollArea* const scrollArea = new QScrollArea(this); QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(scrollArea); scrollArea->setWidget(d->gpsSearchView); scrollArea->setWidgetResizable(true); connect(d->gpsSearchView, SIGNAL(signalMapSoloItems(QList,QString)), this, SIGNAL(signalMapSoloItems(QList,QString))); } GPSSearchSideBarWidget::~GPSSearchSideBarWidget() { delete d; } void GPSSearchSideBarWidget::setActive(bool active) { d->gpsSearchView->setActive(active); } void GPSSearchSideBarWidget::doLoadState() { d->gpsSearchView->loadState(); } void GPSSearchSideBarWidget::doSaveState() { d->gpsSearchView->saveState(); } void GPSSearchSideBarWidget::applySettings() { } void GPSSearchSideBarWidget::changeAlbumFromHistory(QList album) { d->gpsSearchView->changeAlbumFromHistory(dynamic_cast(album.first())); } const QIcon GPSSearchSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("globe")); } const QString GPSSearchSideBarWidget::getCaption() { return i18nc("Search images on a map", "Map"); } #endif // HAVE_MARBLE // ----------------------------------------------------------------------------- class PeopleSideBarWidget::Private : public TagViewSideBarWidget::Private { public: explicit Private() { personIcon = 0; textLabel = 0; rescanButton = 0; searchModificationHelper = 0; } QLabel* personIcon; QLabel* textLabel; QPushButton* rescanButton; SearchModificationHelper* searchModificationHelper; }; PeopleSideBarWidget::PeopleSideBarWidget(QWidget* const parent, TagModel* const model, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("People Sidebar")); setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F9); d->searchModificationHelper = searchModificationHelper; QVBoxLayout* const layout = new QVBoxLayout; QHBoxLayout* const hlay = new QHBoxLayout; d->tagFolderView = new TagFolderView(this, model); d->tagFolderView->setConfigGroup(getConfigGroup()); d->tagFolderView->setExpandNewCurrentItem(true); d->tagFolderView->setAlbumManagerCurrentAlbum(true); d->tagFolderView->setShowDeleteFaceTagsAction(true); d->tagFolderView->filteredModel()->listOnlyTagsWithProperty(TagPropertyName::person()); d->tagFolderView->filteredModel()->setFilterBehavior(AlbumFilterModel::StrictFiltering); d->tagSearchBar = new SearchTextBar(this, QLatin1String("DigikamViewPeopleSearchBar")); d->tagSearchBar->setHighlightOnResult(true); d->tagSearchBar->setModel(d->tagFolderView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->tagSearchBar->setFilterModel(d->tagFolderView->albumFilterModel()); d->rescanButton = new QPushButton; d->rescanButton->setText(i18n("Scan collection for faces")); d->personIcon = new QLabel; d->personIcon->setPixmap(QIcon::fromTheme(QLatin1String("edit-image-face-show")).pixmap(48)); d->textLabel = new QLabel(i18n("People Tags")); hlay->addWidget(d->personIcon); hlay->addWidget(d->textLabel); layout->addLayout(hlay); layout->addWidget(d->rescanButton); layout->addWidget(d->tagFolderView); layout->addWidget(d->tagSearchBar); setLayout(layout); connect(d->tagFolderView, SIGNAL(signalFindDuplicates(QList)), this, SIGNAL(signalFindDuplicates(QList))); connect(d->rescanButton, SIGNAL(pressed()), this, SLOT(slotScanForFaces()) ); } PeopleSideBarWidget::~PeopleSideBarWidget() { delete d; } void PeopleSideBarWidget::slotInit() { loadState(); } void PeopleSideBarWidget::setActive(bool active) { emit requestFaceMode(active); if (active) { d->tagFolderView->setCurrentAlbums(QList() << d->tagFolderView->currentAlbum()); } } void PeopleSideBarWidget::doLoadState() { d->tagFolderView->loadState(); } void PeopleSideBarWidget::doSaveState() { d->tagFolderView->saveState(); } void PeopleSideBarWidget::applySettings() { } void PeopleSideBarWidget::changeAlbumFromHistory(QList album) { d->tagFolderView->setCurrentAlbums(album); } void PeopleSideBarWidget::slotScanForFaces() { FaceScanDialog dialog; if (dialog.exec() == QDialog::Accepted) { FacesDetector* const tool = new FacesDetector(dialog.settings()); tool->start(); } } const QIcon PeopleSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("edit-image-face-show")); } const QString PeopleSideBarWidget::getCaption() { return i18nc("Browse images sorted by depicted people", "People"); } } // namespace Digikam diff --git a/core/app/views/leftsidebarwidgets.h b/core/app/views/leftsidebarwidgets.h index 42568f87ae..4639a734ab 100644 --- a/core/app/views/leftsidebarwidgets.h +++ b/core/app/views/leftsidebarwidgets.h @@ -1,425 +1,425 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : left sidebar widgets * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2018 by Gilles Caulier * Copyright (C) 2014 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_LEFT_SIDE_BAR_WIDGETS_H #define DIGIKAM_LEFT_SIDE_BAR_WIDGETS_H // KDE includes #include // Local includes #include "digikam_config.h" #include "albummodel.h" #include "albummodificationhelper.h" #include "imagealbumfiltermodel.h" #include "searchmodificationhelper.h" #include "sidebarwidget.h" #include "imagefiltermodel.h" #include "albumlabelstreeview.h" #ifdef HAVE_MARBLE # include "gpssearchview.h" #endif // HAVE_MARBLE namespace Digikam { template class AlbumPointer; /** * SideBarWidget for the folder view. * * @author jwienke */ class AlbumFolderViewSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit AlbumFolderViewSideBarWidget(QWidget* const parent, AlbumModel* const model, AlbumModificationHelper* const albumModificationHelper); virtual ~AlbumFolderViewSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); AlbumPointer currentAlbum() const; public Q_SLOTS: void setCurrentAlbum(PAlbum* album); Q_SIGNALS: - void signalFindDuplicates(PAlbum*); + void signalFindDuplicates(PAlbum* album); private: class Private; Private* const d; }; // ----------------------------------------------------------------------------------------- /** * SideBarWidget for the tag view. * * @author jwienke */ class TagViewSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit TagViewSideBarWidget(QWidget* const parent, TagModel* const model); virtual ~TagViewSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); AlbumPointer currentAlbum() const; private: void setNoTagsAlbum(); public Q_SLOTS: void setCurrentAlbum(TAlbum* album); void slotOpenTagManager(); void slotToggleTagsSelection(int radioClicked); Q_SIGNALS: - void signalFindDuplicates(QList albums); + void signalFindDuplicates(const QList& albums); public: // Declared as public due to use by Private class Private; private: Private* const d; }; // ----------------------------------------------------------------------------------------- /** * SideBarWidget for the Labels. * * @author Mohamed_Anwer */ class LabelsSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit LabelsSideBarWidget(QWidget* const parent); virtual ~LabelsSideBarWidget(); AlbumLabelsTreeView* labelsTree(); void setActive(bool active); void applySettings(); void changeAlbumFromHistory(QList album); void doLoadState(); void doSaveState(); const QIcon getIcon(); const QString getCaption(); QHash > selectedLabels(); private: class Private; Private* const d; }; // ----------------------------------------------------------------------------------------- /** * SideBarWidget for the date folder view. * * @author jwienke */ class DateFolderViewSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit DateFolderViewSideBarWidget(QWidget* const parent, DateAlbumModel* const model, ImageAlbumFilterModel* const imageFilterModel); virtual ~DateFolderViewSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); AlbumPointer currentAlbum() const; void gotoDate(const QDate& date); private: class Private; Private* const d; }; // ----------------------------------------------------------------------------------------- /** * SideBarWidget for the date folder view. * * @author jwienke */ class TimelineSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit TimelineSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper); virtual ~TimelineSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); private Q_SLOTS: void slotInit(); void slotScrollBarValueChanged(int); void slotRefDateTimeChanged(); void slotScaleChanged(int); void slotTimeUnitChanged(int); void slotCursorPositionChanged(); void slotSelectionChanged(); void slotResetSelection(); void slotSaveSelection(); void slotUpdateCurrentDateSearchAlbum(); void slotAlbumSelected(Album*); void slotCheckAboutSelection(); private: class Private; Private* const d; }; // ----------------------------------------------------------------------------------------- /** * SideBarWidget for the search. * * @author jwienke */ class SearchSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit SearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper); virtual ~SearchSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); void newKeywordSearch(); void newAdvancedSearch(); private: class Private; Private* const d; }; // ----------------------------------------------------------------------------------------- /** * SideBarWidget for the fuzzy search. * * @author jwienke */ class FuzzySearchSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit FuzzySearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper); virtual ~FuzzySearchSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); void newDuplicatesSearch(PAlbum* album); - void newDuplicatesSearch(QList albums); - void newDuplicatesSearch(QList albums); + void newDuplicatesSearch(const QList& albums); + void newDuplicatesSearch(const QList& albums); void newSimilarSearch(const ImageInfo& imageInfo); Q_SIGNALS: void signalActive(bool); private: class Private; Private* const d; }; // ----------------------------------------------------------------------------------------- #ifdef HAVE_MARBLE /** * SideBarWidget for the gps search. * * @author jwienke */ class GPSSearchSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit GPSSearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper, ImageFilterModel* const imageFilterModel, QItemSelectionModel* const itemSelectionModel); virtual ~GPSSearchSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); Q_SIGNALS: void signalMapSoloItems(const QList&, const QString&); private: class Private; Private* const d; }; #endif // HAVE_MARBLE // ----------------------------------------------------------------------------------------- /** * SideBarWidget for People * * @author Aditya Bhatt */ class PeopleSideBarWidget : public SidebarWidget { Q_OBJECT public: explicit PeopleSideBarWidget(QWidget* const parent, TagModel* const tagModel, SearchModificationHelper* const searchModificationHelper); virtual ~PeopleSideBarWidget(); void setActive(bool active); void doLoadState(); void doSaveState(); void applySettings(); void changeAlbumFromHistory(QList album); const QIcon getIcon(); const QString getCaption(); private Q_SLOTS: void slotInit(); void slotScanForFaces(); Q_SIGNALS: void requestFaceMode(bool on); - void signalFindDuplicates(QList); + void signalFindDuplicates(const QList& albums); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_LEFT_SIDE_BAR_WIDGETS_H diff --git a/core/data/database/dbconfig.xml.cmake.in b/core/data/database/dbconfig.xml.cmake.in index 334aaa895e..4dff8c5698 100644 --- a/core/data/database/dbconfig.xml.cmake.in +++ b/core/data/database/dbconfig.xml.cmake.in @@ -1,2600 +1,2613 @@ QSQLITE ${DBCORECONFIG_XML_VERSION} TestHost DatabaseName UserName Password Port ConnectOptions CREATE TRIGGER privcheck_trigger DELETE ON PrivCheck BEGIN SELECT * FROM PrivCheck; END; DROP TRIGGER privcheck_trigger; CREATE TABLE PrivCheck (id INT, name VARCHAR(35)); ALTER TABLE PrivCheck ADD COLUMN addedColumn; DROP TABLE PrivCheck; DROP TABLE IF EXISTS PrivCheck; CREATE TABLE AlbumRoots (id INTEGER PRIMARY KEY, label TEXT, status INTEGER NOT NULL, type INTEGER NOT NULL, identifier TEXT, specificPath TEXT, UNIQUE(identifier, specificPath)); CREATE TABLE Albums (id INTEGER PRIMARY KEY, albumRoot INTEGER NOT NULL, relativePath TEXT NOT NULL, date DATE, caption TEXT, collection TEXT, icon INTEGER, UNIQUE(albumRoot, relativePath)); CREATE TABLE Images (id INTEGER PRIMARY KEY, album INTEGER, name TEXT NOT NULL, status INTEGER NOT NULL, category INTEGER NOT NULL, modificationDate DATETIME, fileSize INTEGER, uniqueHash TEXT, + manualOrder INTEGER, UNIQUE (album, name)); CREATE TABLE ImageInformation (imageid INTEGER PRIMARY KEY, rating INTEGER, creationDate DATETIME, digitizationDate DATETIME, orientation INTEGER, width INTEGER, height INTEGER, format TEXT, colorDepth INTEGER, colorModel INTEGER); CREATE TABLE ImageMetadata (imageid INTEGER PRIMARY KEY, make TEXT, model TEXT, lens TEXT, aperture REAL, focalLength REAL, focalLength35 REAL, exposureTime REAL, exposureProgram INTEGER, exposureMode INTEGER, sensitivity INTEGER, flash INTEGER, whiteBalance INTEGER, whiteBalanceColorTemperature INTEGER, meteringMode INTEGER, subjectDistance REAL, subjectDistanceCategory INTEGER); CREATE TABLE VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT); CREATE TABLE ImagePositions (imageid INTEGER PRIMARY KEY, latitude TEXT, latitudeNumber REAL, longitude TEXT, longitudeNumber REAL, altitude REAL, orientation REAL, tilt REAL, roll REAL, accuracy REAL, description TEXT); CREATE TABLE ImageComments (id INTEGER PRIMARY KEY, imageid INTEGER, type INTEGER, language TEXT, author TEXT, date DATETIME, comment TEXT, UNIQUE(imageid, type, language, author)); CREATE TABLE ImageCopyright (id INTEGER PRIMARY KEY, imageid INTEGER, property TEXT, value TEXT, extraValue TEXT, UNIQUE(imageid, property, value, extraValue)); CREATE TABLE IF NOT EXISTS Tags (id INTEGER PRIMARY KEY, pid INTEGER, name TEXT NOT NULL, icon INTEGER, iconkde TEXT, UNIQUE (name, pid)); CREATE TABLE IF NOT EXISTS TagsTree (id INTEGER NOT NULL, pid INTEGER NOT NULL, UNIQUE (id, pid)); CREATE TABLE IF NOT EXISTS ImageTags (imageid INTEGER NOT NULL, tagid INTEGER NOT NULL, UNIQUE (imageid, tagid)); CREATE TABLE IF NOT EXISTS ImageProperties (imageid INTEGER NOT NULL, property TEXT NOT NULL, value TEXT NOT NULL, UNIQUE (imageid, property)); CREATE TABLE IF NOT EXISTS Searches (id INTEGER PRIMARY KEY, type INTEGER, name TEXT NOT NULL, query TEXT NOT NULL); CREATE TABLE DownloadHistory (id INTEGER PRIMARY KEY, identifier TEXT, filename TEXT, filesize INTEGER, filedate DATETIME, UNIQUE(identifier, filename, filesize, filedate)); CREATE TABLE IF NOT EXISTS Settings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE TABLE ImageHistory (imageid INTEGER PRIMARY KEY, uuid TEXT, history TEXT); CREATE TABLE ImageRelations (subject INTEGER, object INTEGER, type INTEGER, UNIQUE(subject, object, type)); CREATE TABLE TagProperties (tagid INTEGER, property TEXT, value TEXT); CREATE TABLE ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT, value TEXT); CREATE INDEX dir_index ON Images (album); CREATE INDEX hash_index ON Images (uniqueHash); CREATE INDEX tag_index ON ImageTags (tagid); CREATE INDEX tag_id_index ON ImageTags (imageid); CREATE INDEX image_name_index ON Images (name); CREATE INDEX creationdate_index ON ImageInformation (creationDate); CREATE INDEX comments_imageid_index ON ImageComments (imageid); CREATE INDEX copyright_imageid_index ON ImageCopyright (imageid); CREATE INDEX uuid_index ON ImageHistory (uuid); CREATE INDEX subject_relations_index ON ImageRelations (subject); CREATE INDEX object_relations_index ON ImageRelations (object); CREATE INDEX tagproperties_index ON TagProperties (tagid); CREATE INDEX imagetagproperties_index ON ImageTagProperties (imageid, tagid); CREATE INDEX imagetagproperties_imageid_index ON ImageTagProperties (imageid); CREATE INDEX imagetagproperties_tagid_index ON ImageTagProperties (tagid); CREATE TRIGGER delete_albumroot DELETE ON AlbumRoots BEGIN DELETE FROM Albums WHERE Albums.albumRoot = OLD.id; END; CREATE TRIGGER delete_album DELETE ON Albums BEGIN DELETE FROM Images WHERE Images.album = OLD.id; END; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; CREATE TRIGGER delete_tag DELETE ON Tags BEGIN DELETE FROM ImageTags WHERE tagid=OLD.id; DELETE FROM TagProperties WHERE tagid=OLD.id; DELETE FROM ImageTagProperties WHERE tagid=OLD.id; END; CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags BEGIN INSERT INTO TagsTree SELECT NEW.id, NEW.pid UNION SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid; END; CREATE TRIGGER delete_tagstree DELETE ON Tags BEGIN DELETE FROM Tags WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id); DELETE FROM TagsTree WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id); DELETE FROM TagsTree WHERE id=OLD.id; END; CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags BEGIN DELETE FROM TagsTree WHERE ((id = OLD.id) OR id IN (SELECT id FROM TagsTree WHERE pid=OLD.id)) AND pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id); INSERT INTO TagsTree SELECT NEW.id, NEW.pid UNION SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid UNION SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id UNION SELECT A.id, B.pid FROM TagsTree A, TagsTree B WHERE A.pid = NEW.id AND B.id = NEW.pid; END; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Images.name COLLATE NOCASE; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Albums.relativePath,Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.creationDate; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.rating DESC; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID; + + INSERT OR IGNORE INTO Images ( id, :fieldList ) VALUES ( :id, :valueList ); + UPDATE Images SET :fieldValueList WHERE id=:id; + + INSERT OR IGNORE INTO ImageInformation ( imageid, :fieldList ) VALUES ( :id, :valueList ); UPDATE ImageInformation SET :fieldValueList WHERE imageid=:id; INSERT OR IGNORE INTO ImageHistory ( imageid, :fieldList ) VALUES ( :id, :valueList ); UPDATE ImageHistory SET :fieldValueList WHERE imageid=:id; INSERT INTO Tags (pid, name) VALUES( :tagPID, :tagname); DELETE FROM Tags WHERE id=:tagID; DELETE FROM Albums WHERE albumRoot=:albumRoot; DELETE FROM Albums WHERE albumRoot=:albumRoot AND relativePath=:relativePath; DELETE FROM Albums WHERE Albums.id=:albumId; SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID OR tagid IN (SELECT id FROM TagsTree WHERE pid=:tagID2) ); SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID); SELECT imageid FROM ImageTags JOIN Images ON ImageTags.imageid=Images.id WHERE Images.status=1 AND ( tagid=:tagID OR tagid IN (SELECT id FROM TagsTree WHERE pid=:tagPID) ); SELECT imageid FROM ImageTags JOIN Images ON ImageTags.imageid=Images.id WHERE Images.status=1 AND tagid=:tagID; SELECT DISTINCT Images.id, Images.name, Images.album, Albums.albumRoot, ImageInformation.rating, Images.category, ImageInformation.format, ImageInformation.creationDate, Images.modificationDate, Images.fileSize, ImageInformation.width, ImageInformation.height FROM Images INNER JOIN ImageInformation ON Images.id=ImageInformation.imageid INNER JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID OR tagid IN (SELECT id FROM TagsTree WHERE pid=:tagPID)); SELECT DISTINCT Images.id, Images.name, Images.album, Albums.albumRoot, ImageInformation.rating, Images.category, ImageInformation.format, ImageInformation.creationDate, Images.modificationDate, Images.fileSize, ImageInformation.width, ImageInformation.height FROM Images INNER JOIN ImageInformation ON Images.id=ImageInformation.imageid INNER JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID ); CREATE TABLE Thumbnails (id INTEGER PRIMARY KEY, type INTEGER, modificationDate DATETIME, orientationHint INTEGER, data BLOB); CREATE TABLE UniqueHashes (uniqueHash TEXT, fileSize INTEGER, thumbId INTEGER, UNIQUE(uniqueHash, fileSize)); CREATE TABLE FilePaths (path TEXT, thumbId INTEGER, UNIQUE(path)); CREATE TABLE CustomIdentifiers (identifier TEXT, thumbId INTEGER, UNIQUE(identifier)); CREATE TABLE IF NOT EXISTS Settings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE INDEX id_uniqueHashes ON UniqueHashes (thumbId); CREATE INDEX id_filePaths ON FilePaths (thumbId); CREATE INDEX id_customIdentifiers ON CustomIdentifiers (thumbId); CREATE TRIGGER delete_thumbnails DELETE ON Thumbnails BEGIN DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = OLD.id; DELETE FROM FilePaths WHERE FilePaths.thumbId = OLD.id; DELETE FROM CustomIdentifiers WHERE CustomIdentifiers.thumbId = OLD.id; END; SELECT value FROM Settings WHERE keyword=:keyword; SELECT value FROM Settings WHERE keyword=:keyword; REPLACE INTO Settings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS Settings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE TABLE Identities (id INTEGER PRIMARY KEY, type INTEGER); CREATE TABLE IdentityAttributes (id INTEGER, attribute TEXT, value TEXT); CREATE TABLE OpenCVLBPHRecognizer (id INTEGER PRIMARY KEY, version INTEGER, radius INTEGER, neighbors INTEGER, grid_x INTEGER, grid_y INTEGER); CREATE TABLE OpenCVLBPHistograms (id INTEGER PRIMARY KEY, recognizerid INTEGER, identity INTEGER, context TEXT, type INTEGER, rows INTEGER, cols INTEGER, data BLOB); CREATE TABLE FaceMatrices (id INTEGER PRIMARY KEY, identity INTEGER, context TEXT, type INTEGER, rows INTEGER, cols INTEGER, data BLOB, vecdata BLOB); CREATE INDEX attribute_index ON IdentityAttributes (id); CREATE TRIGGER delete_identities DELETE ON Identities BEGIN DELETE FROM IdentityAttributes WHERE IdentityAttributes.id = OLD.id; END; SELECT value FROM Settings WHERE keyword=:keyword; REPLACE INTO Settings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS ImageSimilarity (imageid1 INTEGER NOT NULL, imageid2 INTEGER NOT NULL, algorithm INTEGER, value DOUBLE, CONSTRAINT Similar UNIQUE(imageid1, imageid2, algorithm)); CREATE TABLE IF NOT EXISTS ImageHaarMatrix (imageid INTEGER PRIMARY KEY, modificationDate DATETIME, uniqueHash TEXT, matrix BLOB); CREATE TABLE IF NOT EXISTS SimilaritySettings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE TRIGGER IF NOT EXISTS delete_similarities DELETE ON ImageHaarMatrix BEGIN DELETE FROM ImageSimilarity WHERE ( ImageSimilarity.imageid1=OLD.imageid OR ImageSimilarity.imageid2=OLD.imageid ) AND ( ImageSimilarity.algorithm=1 ); END; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; REPLACE INTO SimilaritySettings VALUES (:keyword, :value); SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots; INSERT OR IGNORE INTO AlbumRoots (id, label, status, type, identifier, specificPath) VALUES (:id, :label, :status, :type, :identifier, :specificPath); SELECT id, albumRoot, relativePath, date, caption, collection FROM Albums WHERE albumRoot IN (SELECT id FROM AlbumRoots); INSERT OR IGNORE INTO Albums (id, albumRoot, relativePath, date, caption, collection, icon) VALUES (:id, :albumRoot, :relativePath, :date, :caption, :collection, NULL); SELECT id, icon FROM Albums WHERE icon IS NOT NULL AND icon != 0; UPDATE Albums set icon = :icon WHERE id = :id; - SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash FROM Images + SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder FROM Images WHERE album IN (SELECT id FROM Albums); - INSERT OR IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash); + INSERT OR IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash, :manualOrder); SELECT imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel FROM ImageInformation WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageInformation (imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel) VALUES (:imageid, :rating, :creationDate, :digitizationDate, :orientation, :width, :height, :format, :colorDepth, :colorModel); SELECT imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory FROM ImageMetadata WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageMetadata (imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) VALUES (:imageid, :make, :model, :lens, :aperture, :focalLength, :focalLength35, :exposureTime, :exposureProgram, :exposureMode, :sensitivity, :flash, :whiteBalance, :whiteBalanceColorTemperature, :meteringMode, :subjectDistance, :subjectDistanceCategory); SELECT imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec FROM VideoMetadata WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO VideoMetadata (imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec) VALUES (:imageid, :aspectRatio, :audioBitRate, :audioChannelType, :audioCompressor, :duration, :frameRate, :videoCodec); SELECT imageid, tagid, property, value FROM ImageTagProperties WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageTagProperties (imageid, tagid, property, value) VALUES (:imageid, :tagid, :property, :value); SELECT tagid, property, value FROM TagProperties; INSERT OR IGNORE INTO TagProperties (tagid, property, value) VALUES (:tagid, :property, :value); SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description FROM ImagePositions WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImagePositions (imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description) VALUES (:imageid, :latitude, :latitudeNumber, :longitude, :longitudeNumber, :altitude, :orientation, :tilt, :roll, :accuracy, :description); SELECT id, imageid, type, language, author, date, comment FROM ImageComments WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageComments (id, imageid, type, language, author, date, comment) VALUES (:id, :imageid, :type, :language, :author, :date, :comment); SELECT id, imageid, property, value, extraValue FROM ImageCopyright WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageCopyright (id, imageid, property, value, extraValue) VALUES (:id, :imageid, :property, :value, :extraValue); SELECT id, pid, name, CASE WHEN icon = 0 THEN NULL ELSE icon END AS icon, iconkde FROM Tags WHERE id != 0; INSERT OR REPLACE INTO Tags (id, pid, name, icon, iconkde) VALUES (:id, :pid, :name, :icon, :iconkde); SELECT imageid, tagid FROM ImageTags WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageTags (imageid, tagid) VALUES (:imageid, :tagid); SELECT imageid, property, value FROM ImageProperties WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageProperties (imageid, property, value) VALUES (:imageid, :property, :value); SELECT imageid, uuid, history FROM ImageHistory WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageHistory (imageid, uuid, history) VALUES (:imageid, :uuid, :history); SELECT subject, object, type FROM ImageRelations INNER JOIN Images ON subject = Images.id WHERE object IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageRelations (subject, object, type) VALUES (:subject, :object, :type); SELECT id, type, name, query FROM Searches; INSERT OR IGNORE INTO Searches (id, type, name, query) VALUES (:id, :type, :name, :query); SELECT id, identifier, filename, filesize, filedate FROM DownloadHistory; INSERT OR IGNORE INTO DownloadHistory (id, identifier, filename, filesize, filedate) VALUES (:id, :identifier, :filename, :filesize, :filedate); SELECT keyword, value FROM Settings WHERE keyword = 'Locale'; INSERT OR IGNORE INTO Settings (keyword, value) VALUES (:keyword, :value); DELETE FROM Thumbnails WHERE id IN (SELECT thumbId FROM FilePaths WHERE path=:path); DELETE FROM Thumbnails WHERE id IN (SELECT thumbId FROM UniqueHashes WHERE uniqueHash=:uniqueHash AND fileSize=:filesize); DELETE FROM Thumbnails WHERE id IN (SELECT thumbId FROM CustomIdentifiers WHERE identifier=:identifier); CREATE TABLE ImageHistory (imageid INTEGER PRIMARY KEY, uuid TEXT, history TEXT); CREATE TABLE ImageRelations (subject INTEGER, object INTEGER, type INTEGER, UNIQUE(subject, object, type)); CREATE TABLE TagProperties (tagid INTEGER, property TEXT, value TEXT); CREATE TABLE ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT, value TEXT); CREATE INDEX tag_id_index ON ImageTags (imageid); CREATE INDEX image_name_index ON Images (name); CREATE INDEX creationdate_index ON ImageInformation (creationDate); CREATE INDEX comments_imageid_index ON ImageComments (imageid); CREATE INDEX copyright_imageid_index ON ImageCopyright (imageid); CREATE INDEX uuid_index ON ImageHistory (uuid); CREATE INDEX subject_relations_index ON ImageRelations (subject); CREATE INDEX object_relations_index ON ImageRelations (object); CREATE INDEX tagproperties_index ON TagProperties (tagid); CREATE INDEX imagetagproperties_index ON ImageTagProperties (imageid, tagid); CREATE INDEX imagetagproperties_imageid_index ON ImageTagProperties (imageid); CREATE INDEX imagetagproperties_tagid_index ON ImageTagProperties (tagid); DROP TRIGGER delete_image; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP TRIGGER delete_tag; CREATE TRIGGER delete_tag DELETE ON Tags BEGIN DELETE FROM ImageTags WHERE tagid=OLD.id; DELETE FROM TagProperties WHERE tagid=OLD.id; DELETE FROM ImageTagProperties WHERE tagid=OLD.id; END; CREATE TABLE VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT); DROP TRIGGER delete_image; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP TABLE IF EXISTS ImageHaarMatrix; DROP TRIGGER delete_image; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; + ALTER TABLE Images ADD manualOrder INTEGER; CREATE TABLE CustomIdentifiers (identifier TEXT, thumbId INTEGER, UNIQUE(identifier)); CREATE INDEX id_customIdentifiers ON CustomIdentifiers (thumbId); DROP TRIGGER delete_thumbnails; CREATE TRIGGER delete_thumbnails DELETE ON Thumbnails BEGIN DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = OLD.id; DELETE FROM FilePaths WHERE FilePaths.thumbId = OLD.id; DELETE FROM CustomIdentifiers WHERE CustomIdentifiers.thumbId = OLD.id; END; VACUUM; VACUUM; VACUUM; VACUUM; pragma integrity_check; pragma integrity_check; pragma integrity_check; pragma integrity_check; $$DBHOSTNAME$$ digikam root $$DBPORT$$ $$DBOPTIONS$$ CREATE TABLE IF NOT EXISTS PrivCheck (id INT, name VARCHAR(35)) ENGINE InnoDB; ALTER TABLE PrivCheck DROP COLUMN name; DROP TABLE PrivCheck; DROP TABLE IF EXISTS PrivCheck; CREATE TABLE IF NOT EXISTS AlbumRoots (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, label LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, status INTEGER NOT NULL, type INTEGER NOT NULL, identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, specificPath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(identifier(127), specificPath(128))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Albums (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, albumRoot INTEGER NOT NULL, relativePath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, date DATE, caption LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, collection LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, icon INTEGER, CONSTRAINT Albums_AlbumRoots FOREIGN KEY (albumRoot) REFERENCES AlbumRoots (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(albumRoot, relativePath(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Images (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, album INTEGER, name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, status INTEGER NOT NULL, category INTEGER NOT NULL, modificationDate DATETIME(3), fileSize BIGINT, uniqueHash VARCHAR(128), + manualOrder INTEGER, CONSTRAINT Images_Albums FOREIGN KEY (album) REFERENCES Albums (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (album, name(255))) ENGINE InnoDB; ALTER TABLE Albums ADD CONSTRAINT Albums_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE; CREATE TABLE IF NOT EXISTS ImageInformation (imageid INTEGER PRIMARY KEY, rating INTEGER, creationDate DATETIME(3), digitizationDate DATETIME(3), orientation INTEGER, width INTEGER, height INTEGER, format LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, colorDepth INTEGER, colorModel INTEGER, CONSTRAINT ImageInformation_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageMetadata (imageid INTEGER PRIMARY KEY, make LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, model LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, lens LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, aperture REAL, focalLength REAL, focalLength35 REAL, exposureTime REAL, exposureProgram INTEGER, exposureMode INTEGER, sensitivity INTEGER, flash INTEGER, whiteBalance INTEGER, whiteBalanceColorTemperature INTEGER, meteringMode INTEGER, subjectDistance REAL, subjectDistanceCategory INTEGER, CONSTRAINT ImageMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT, CONSTRAINT VideoMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImagePositions (imageid INTEGER PRIMARY KEY, latitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, latitudeNumber REAL, longitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, longitudeNumber REAL, altitude REAL, orientation REAL, tilt REAL, roll REAL, accuracy REAL, description LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImagePositions_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageComments (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, imageid INTEGER, type INTEGER, language VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci, author LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, date DATETIME(3), comment LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageComments_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(imageid, type, language, author(202))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageCopyright (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, imageid INTEGER, property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, extraValue LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageCopyright_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(imageid, property(110), value(111), extraValue(111))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Tags (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, pid INTEGER, name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, icon INTEGER, iconkde LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, lft INT NOT NULL, rgt INT NOT NULL, CONSTRAINT Tags_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE, UNIQUE(pid, name(100))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageTags (imageid INTEGER NOT NULL, tagid INTEGER NOT NULL, CONSTRAINT ImageTags_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT ImageTags_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (imageid, tagid)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageProperties (imageid INTEGER NOT NULL, property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, CONSTRAINT ImageProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (imageid, property(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Searches (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, type INTEGER, name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, query LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS DownloadHistory (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, filename LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, filesize BIGINT, filedate DATETIME(3), UNIQUE(identifier(164), filename(165), filesize, filedate)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Settings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageHistory (imageid INTEGER PRIMARY KEY, uuid VARCHAR(128), history LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageHistory_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageRelations (subject INTEGER, object INTEGER, type INTEGER, CONSTRAINT ImageRelations_ImagesS FOREIGN KEY (subject) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT ImageRelations_ImagesO FOREIGN KEY (object) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(subject, object, type)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS TagProperties (tagid INTEGER, property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT TagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageTagProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT ImageTagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE OR REPLACE VIEW TagsTree AS SELECT id, pid FROM Tags; DROP PROCEDURE IF EXISTS create_index_if_not_exists; CREATE PROCEDURE create_index_if_not_exists(table_name_vc varchar(50), index_name_vc varchar(50), field_list_vc varchar(1024)) BEGIN set @Index_cnt = ( SELECT COUNT(1) cnt FROM INFORMATION_SCHEMA.STATISTICS WHERE CONVERT(DATABASE() USING latin1) = CONVERT(TABLE_SCHEMA USING latin1) AND CONVERT(table_name USING latin1) = CONVERT(table_name_vc USING latin1) AND CONVERT(index_name USING latin1) = CONVERT(index_name_vc USING latin1) ); IF IFNULL(@Index_cnt, 0) = 0 THEN set @index_sql = CONCAT( CONVERT( 'ALTER TABLE ' USING latin1), CONVERT( table_name_vc USING latin1), CONVERT( ' ADD INDEX ' USING latin1), CONVERT( index_name_vc USING latin1), CONVERT( '(' USING latin1), CONVERT( field_list_vc USING latin1), CONVERT( ');' USING latin1) ); PREPARE stmt FROM @index_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; CALL create_index_if_not_exists('Images','dir_index','album'); CALL create_index_if_not_exists('Images','hash_index','uniqueHash'); CALL create_index_if_not_exists('ImageTags','tag_index','tagid'); CALL create_index_if_not_exists('ImageTags','tag_id_index','imageid'); CALL create_index_if_not_exists('Images','image_name_index','name(255)'); CALL create_index_if_not_exists('ImageInformation','creationdate_index','creationDate'); CALL create_index_if_not_exists('ImageComments','comments_imageid_index','imageid'); CALL create_index_if_not_exists('ImageCopyright','copyright_imageid_index','imageid'); CALL create_index_if_not_exists('ImageHistory','uuid_index','uuid'); CALL create_index_if_not_exists('ImageRelations','subject_relations_index','subject'); CALL create_index_if_not_exists('ImageRelations','object_relations_index','object'); CALL create_index_if_not_exists('TagProperties','tagproperties_index','tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_index','imageid, tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_imageid_index','imageid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_tagid_index','tagid'); SELECT @minLeft := IF(ISNULL(MIN(lft)), 1, MIN(lft)-1), @maxRight := IF(ISNULL(MAX(rgt)), 2, MAX(rgt)+1) FROM Tags WHERE id >= 0 AND pid >= 0; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; REPLACE INTO Tags (id, pid, name, icon, iconkde, lft, rgt) VALUES (0, -1, '_Digikam_root_tag_', NULL, NULL, @minLeft, @maxRight); SET SQL_MODE=@OLD_SQL_MODE; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Albums.relativePath,Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.creationDate; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.rating DESC; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID; + + INSERT INTO Images ( id, :fieldList ) VALUES ( :id, :valueList ) ON DUPLICATE KEY UPDATE :fieldValueList; + + INSERT INTO ImageInformation ( imageid, :fieldList ) VALUES ( :id, :valueList ) ON DUPLICATE KEY UPDATE :fieldValueList; INSERT INTO ImageHistory( imageid, :fieldList ) VALUES ( :id, :valueList ) ON DUPLICATE KEY UPDATE :fieldValueList; SELECT @myLeft := lft FROM Tags WHERE id = :tagPID; SELECT @myLeft := IF (@myLeft is null, 0, @myLeft); UPDATE Tags SET rgt = rgt + 2 WHERE rgt > @myLeft; UPDATE Tags SET lft = lft + 2 WHERE lft > @myLeft; INSERT INTO Tags(name, pid, lft, rgt) VALUES(:tagname, :tagPID, @myLeft + 1, @myLeft + 2); SELECT @myLeft := lft FROM Tags WHERE id = :tagID; SELECT @myLeft := IF (@myLeft is null, 0, @myLeft); DELETE FROM Tags WHERE id = :tagID; UPDATE Tags SET rgt = rgt - 2 WHERE rgt > @myLeft; UPDATE Tags SET lft = lft - 2 WHERE lft > @myLeft; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM Tags WHERE id = :tagID; UPDATE Tags SET rgt = rgt * -1, lft = lft * -1 WHERE lft BETWEEN @myLeft AND @myRight; UPDATE Tags SET rgt = rgt - @myWidth WHERE rgt > @myRight; UPDATE Tags SET lft = lft - @myWidth WHERE lft > @myRight; SELECT @myNewLeft := lft FROM Tags WHERE id = :newTagPID; SELECT @myNewLeft := IF (@myNewLeft is null, 0, @myNewLeft); UPDATE Tags SET rgt = rgt + @myWidth WHERE rgt > @myNewLeft; UPDATE Tags SET lft = lft + @myWidth WHERE lft > @myNewLeft; UPDATE Tags SET lft = lft * -1 - @myLeft + @myNewLeft + 1, rgt = rgt * -1 - @myLeft + @myNewLeft + 1 WHERE lft * -1 BETWEEN @myLeft AND @myRight; SELECT @albumID:=id FROM Albums WHERE albumRoot=:albumRoot; DELETE FROM Albums WHERE albumRoot=:albumRoot; DELETE FROM Images WHERE Images.album=@albumID; SELECT @albumID:=id FROM Albums WHERE albumRoot=:albumRoot AND relativePath=:relativePath; DELETE FROM Albums WHERE albumRoot=:albumRoot AND relativePath=:relativePath; DELETE FROM Images WHERE Images.album=@albumID; SELECT @albumID:=id FROM Albums WHERE Albums.id=:albumId; DELETE FROM Albums WHERE Albums.id=:albumId; DELETE FROM Images WHERE Images.album=@albumID; SELECT DISTINCT alb.albumRoot, alb.relativePath, img.name FROM ( Images AS img JOIN Albums AS alb ON alb.id = img.album JOIN ImageTags AS ita ON ita.imageid = img.id ) JOIN ( Tags As tp JOIN Tags As tc ON tc.lft BETWEEN tp.lft AND tp.rgt ) ON tc.id = ita.tagID WHERE img.status = 1 AND tp.id = :tagID /* AND tp.id = :tagID2 */ ORDER BY img.name; SELECT alb.albumRoot, alb.relativePath, img.name FROM Albums AS alb JOIN Images AS img ON alb.id = img.album JOIN ImageTags AS it ON it.imageid = img.id WHERE img.status = 1 AND it.tagid = :tagID; SELECT DISTINCT ita.imageid FROM ( Images AS img JOIN ImageTags AS ita ON ita.imageid = img.id ) JOIN ( Tags As tp JOIN Tags As tc ON tc.lft BETWEEN tp.lft AND tp.rgt ) ON tc.id = ita.tagID WHERE img.status = 1 AND tp.id = :tagID ORDER BY img.name; SELECT imageid FROM ImageTags JOIN Images ON ImageTags.imageid=Images.id WHERE Images.status=1 AND tagid=:tagID; SELECT DISTINCT img.id, img.name, img.album, alb.albumRoot, inf.rating, img.category, inf.format, inf.creationDate, img.modificationDate, img.fileSize, inf.width, inf.height FROM ( Images AS img JOIN ImageInformation AS inf ON img.id=inf.imageid JOIN Albums AS alb ON alb.id=img.album JOIN ImageTags AS ita ON ita.imageid = img.id ) JOIN ( Tags As tp JOIN Tags As tc ON tc.lft BETWEEN tp.lft AND tp.rgt ) ON tc.id = ita.tagID WHERE img.status = 1 AND tp.id = :tagID ORDER BY inf.rating DESC, img.name ASC; SELECT DISTINCT img.id, img.name, img.album, alb.albumRoot, inf.rating, img.category, inf.format, inf.creationDate, img.modificationDate, img.fileSize, inf.width, inf.height FROM Images AS img JOIN ImageInformation AS inf ON img.id=inf.imageid JOIN Albums AS alb ON alb.id=img.album JOIN ImageTags AS ita ON ita.imageid = img.id WHERE img.status = 1 AND ita.tagID = :tagID ORDER BY inf.rating DESC, img.name ASC; CREATE TABLE IF NOT EXISTS Thumbnails (id INTEGER PRIMARY KEY AUTO_INCREMENT, type INTEGER, modificationDate DATETIME(3), orientationHint INTEGER, data LONGBLOB) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS UniqueHashes (uniqueHash VARCHAR(128), fileSize BIGINT, thumbId INTEGER, CONSTRAINT UniqueHashes_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(uniqueHash, fileSize)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS FilePaths (path LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, thumbId INTEGER, CONSTRAINT FilePaths_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(path(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS CustomIdentifiers (identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, thumbId INTEGER, CONSTRAINT CustomIdentifiers_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(identifier(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ThumbSettings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; DROP PROCEDURE IF EXISTS create_index_if_not_exists; CREATE PROCEDURE create_index_if_not_exists(table_name_vc varchar(50), index_name_vc varchar(50), field_list_vc varchar(1024)) BEGIN set @Index_cnt = ( SELECT COUNT(1) cnt FROM INFORMATION_SCHEMA.STATISTICS WHERE CONVERT(DATABASE() USING latin1) = CONVERT(TABLE_SCHEMA USING latin1) AND CONVERT(table_name USING latin1) = CONVERT(table_name_vc USING latin1) AND CONVERT(index_name USING latin1) = CONVERT(index_name_vc USING latin1) ); IF IFNULL(@Index_cnt, 0) = 0 THEN set @index_sql = CONCAT( CONVERT( 'ALTER TABLE ' USING latin1), CONVERT( table_name_vc USING latin1), CONVERT( ' ADD INDEX ' USING latin1), CONVERT( index_name_vc USING latin1), CONVERT( '(' USING latin1), CONVERT( field_list_vc USING latin1), CONVERT( ');' USING latin1) ); PREPARE stmt FROM @index_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; CALL create_index_if_not_exists('UniqueHashes','id_uniqueHashes','thumbId'); CALL create_index_if_not_exists('FilePaths','id_filePaths','thumbId'); CALL create_index_if_not_exists('CustomIdentifiers','id_customIdentifiers','thumbId'); SELECT value FROM ThumbSettings WHERE keyword=:keyword; SELECT value FROM Settings WHERE keyword=:keyword; REPLACE INTO ThumbSettings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS Identities (id INTEGER PRIMARY KEY AUTO_INCREMENT, `type` INTEGER) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS IdentityAttributes (id INTEGER, `type` INTEGER, attribute LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, `value` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT IdentityAttributes_Identities FOREIGN KEY (id) REFERENCES Identities (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS FaceSettings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `value` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS OpenCVLBPHRecognizer (id INTEGER PRIMARY KEY AUTO_INCREMENT, version INTEGER, radius INTEGER, neighbors INTEGER, grid_x INTEGER, grid_y INTEGER) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS OpenCVLBPHistograms (id INTEGER PRIMARY KEY AUTO_INCREMENT, recognizerid INTEGER, identity INTEGER, `context` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, `type` INTEGER, `rows` INTEGER, `cols` INTEGER, `data` LONGBLOB) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS FaceMatrices (id INTEGER PRIMARY KEY AUTO_INCREMENT, identity INTEGER, `context` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, `type` INTEGER, `rows` INTEGER, `cols` INTEGER, `data` LONGBLOB, vecdata LONGBLOB) ENGINE InnoDB; DROP PROCEDURE IF EXISTS create_index_if_not_exists; CREATE PROCEDURE create_index_if_not_exists(table_name_vc varchar(50), index_name_vc varchar(50), field_list_vc varchar(1024)) BEGIN set @Index_cnt = ( SELECT COUNT(1) cnt FROM INFORMATION_SCHEMA.STATISTICS WHERE CONVERT(DATABASE() USING latin1) = CONVERT(TABLE_SCHEMA USING latin1) AND CONVERT(table_name USING latin1) = CONVERT(table_name_vc USING latin1) AND CONVERT(index_name USING latin1) = CONVERT(index_name_vc USING latin1) ); IF IFNULL(@Index_cnt, 0) = 0 THEN set @index_sql = CONCAT( CONVERT( 'ALTER TABLE ' USING latin1), CONVERT( table_name_vc USING latin1), CONVERT( ' ADD INDEX ' USING latin1), CONVERT( index_name_vc USING latin1), CONVERT( '(' USING latin1), CONVERT( field_list_vc USING latin1), CONVERT( ');' USING latin1) ); PREPARE stmt FROM @index_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; CALL create_index_if_not_exists('IdentityAttributes','identityattributes_index','id'); SELECT value FROM FaceSettings WHERE keyword=:keyword; REPLACE INTO FaceSettings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS ImageSimilarity (imageid1 INTEGER NOT NULL, imageid2 INTEGER NOT NULL, algorithm INTEGER, value DOUBLE, CONSTRAINT Similar UNIQUE(imageid1, imageid2, algorithm)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageHaarMatrix (imageid INTEGER PRIMARY KEY, modificationDate DATETIME(3), uniqueHash LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, matrix LONGBLOB) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS SimilaritySettings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `value` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; REPLACE INTO SimilaritySettings VALUES (:keyword, :value); ALTER TABLE Albums DROP FOREIGN KEY Albums_Images; SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots; INSERT IGNORE INTO AlbumRoots (id, label, status, type, identifier, specificPath) VALUES (:id, :label, :status, :type, :identifier, :specificPath); SELECT id, albumRoot, relativePath, date, caption, collection FROM Albums WHERE albumRoot IN (SELECT id FROM AlbumRoots); INSERT IGNORE INTO Albums (id, albumRoot, relativePath, date, caption, collection, icon) VALUES (:id, :albumRoot, :relativePath, :date, :caption, :collection, NULL); SELECT id, icon FROM Albums WHERE icon IS NOT NULL AND icon != 0; UPDATE Albums set icon = :icon WHERE id = :id; - SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash FROM Images + SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder FROM Images WHERE album IN (SELECT id FROM Albums); - INSERT IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash); + INSERT IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash, :manualOrder); SELECT imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel FROM ImageInformation WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageInformation (imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel) VALUES (:imageid, :rating, :creationDate, :digitizationDate, :orientation, :width, :height, :format, :colorDepth, :colorModel); SELECT imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory FROM ImageMetadata WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageMetadata (imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) VALUES (:imageid, :make, :model, :lens, :aperture, :focalLength, :focalLength35, :exposureTime, :exposureProgram, :exposureMode, :sensitivity, :flash, :whiteBalance, :whiteBalanceColorTemperature, :meteringMode, :subjectDistance, :subjectDistanceCategory); SELECT imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec FROM VideoMetadata WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO VideoMetadata (imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec) VALUES (:imageid, :aspectRatio, :audioBitRate, :audioChannelType, :audioCompressor, :duration, :frameRate, :videoCodec); SELECT imageid, tagid, property, value FROM ImageTagProperties WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageTagProperties (imageid, tagid, property, value) VALUES (:imageid, :tagid, :property, :value); SELECT tagid, property, value FROM TagProperties WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO TagProperties (tagid, property, value) VALUES (:tagid, :property, :value); SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description FROM ImagePositions WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImagePositions (imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description) VALUES (:imageid, :latitude, :latitudeNumber, :longitude, :longitudeNumber, :altitude, :orientation, :tilt, :roll, :accuracy, :description); SELECT id, imageid, type, language, author, date, comment FROM ImageComments WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageComments (id, imageid, type, language, author, date, comment) VALUES (:id, :imageid, :type, :language, :author, :date, :comment); SELECT id, imageid, property, value, extraValue FROM ImageCopyright WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageCopyright (id, imageid, property, value, extraValue) VALUES (:id, :imageid, :property, :value, :extraValue); SELECT id, pid, name, CASE WHEN icon = 0 THEN NULL ELSE icon END AS icon, iconkde FROM Tags WHERE id != 0; SELECT @myLeft := lft FROM Tags WHERE id = :pid; SELECT @myLeft := IF (@myLeft is null, 0, @myLeft); UPDATE Tags SET rgt = rgt + 2 WHERE rgt > @myLeft; UPDATE Tags SET lft = lft + 2 WHERE lft > @myLeft; REPLACE INTO Tags(id, pid, name, icon, iconkde, lft, rgt) VALUES(:id, :pid, :name, :icon, :iconkde, @myLeft + 1, @myLeft + 2); SELECT imageid, tagid FROM ImageTags WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageTags (imageid, tagid) VALUES (:imageid, :tagid); SELECT imageid, property, value FROM ImageProperties WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageProperties (imageid, property, value) VALUES (:imageid, :property, :value); SELECT imageid, uuid, history FROM ImageHistory WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageHistory (imageid, uuid, history) VALUES (:imageid, :uuid, :history); SELECT subject, object, type FROM ImageRelations INNER JOIN Images ON subject = Images.id WHERE object IN (SELECT id FROM Images); INSERT IGNORE INTO ImageRelations (subject, object, type) VALUES (:subject, :object, :type); SELECT id, type, name, query FROM Searches; INSERT IGNORE INTO Searches (id, type, name, query) VALUES (:id, :type, :name, :query); SELECT id, identifier, filename, filesize, filedate FROM DownloadHistory; INSERT IGNORE INTO DownloadHistory (id, identifier, filename, filesize, filedate) VALUES (:id, :identifier, :filename, :filesize, :filedate); SELECT keyword, value FROM Settings WHERE keyword = 'Locale'; INSERT IGNORE INTO Settings (keyword, value) VALUES (:keyword, :value); SELECT @thumbsId := thumbId FROM FilePaths WHERE path=:path; DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = @thumbsId; DELETE FROM FilePaths WHERE FilePaths.thumbId = @thumbsId; DELETE FROM Thumbnails WHERE id = @thumbsId; SELECT @thumbsId := thumbId FROM UniqueHashes WHERE uniqueHash=:uniqueHash AND fileSize=:filesize; DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = @thumbsId; DELETE FROM FilePaths WHERE FilePaths.thumbId = @thumbsId; DELETE FROM Thumbnails WHERE id = @thumbsId; SELECT @thumbsId := thumbId FROM CustomIdentifiers WHERE identifier=:identifier; DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = @thumbsId; DELETE FROM FilePaths WHERE FilePaths.thumbId = @thumbsId; DELETE FROM CustomIdentifiers WHERE CustomIdentifiers.thumbId = @thumbsId; DELETE FROM Thumbnails WHERE id = @thumbsId; CREATE TABLE IF NOT EXISTS ImageHistory (imageid INTEGER PRIMARY KEY, uuid VARCHAR(128), history LONGTEXT CHARACTER SET utf8); CREATE TABLE IF NOT EXISTS ImageRelations (subject INTEGER, object INTEGER, type INTEGER, UNIQUE(subject, object, type)); CREATE TABLE IF NOT EXISTS TagProperties (tagid INTEGER, property TEXT CHARACTER SET utf8, value LONGTEXT CHARACTER SET utf8); CREATE TABLE IF NOT EXISTS ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT CHARACTER SET utf8, value LONGTEXT CHARACTER SET utf8); CALL create_index_if_not_exists('ImageTags','tag_id_index','imageid'); CALL create_index_if_not_exists('Images','image_name_index','name(255)'); CALL create_index_if_not_exists('ImageInformation','creationdate_index','creationDate'); CALL create_index_if_not_exists('ImageComments','comments_imageid_index','imageid'); CALL create_index_if_not_exists('ImageCopyright','copyright_imageid_index','imageid'); CALL create_index_if_not_exists('ImageHistory','uuid_index','uuid'); CALL create_index_if_not_exists('ImageRelations','subject_relations_index','subject'); CALL create_index_if_not_exists('ImageRelations','object_relations_index','object'); CALL create_index_if_not_exists('TagProperties','tagproperties_index','tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_index','imageid,tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_imageid_index','imageid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_tagid_index','tagid'); ALTER TABLE Images CHANGE uniqueHash uniqueHash VARCHAR(128); DROP TRIGGER IF EXISTS delete_image; CREATE TRIGGER delete_image AFTER DELETE ON Images FOR EACH ROW BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP TRIGGER IF EXISTS delete_tag; CREATE TRIGGER delete_tag AFTER DELETE ON Tags FOR EACH ROW BEGIN DELETE FROM ImageTags WHERE tagid=OLD.id; DELETE FROM TagProperties WHERE tagid=OLD.id; DELETE FROM ImageTagProperties WHERE tagid=OLD.id; END; CREATE TABLE IF NOT EXISTS VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT); DROP TRIGGER IF EXISTS delete_image; CREATE TRIGGER delete_image AFTER DELETE ON Images FOR EACH ROW BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP PROCEDURE IF EXISTS drop_foreign_key; CREATE PROCEDURE drop_foreign_key(IN tableName VARCHAR(64), IN constraintName VARCHAR(64)) BEGIN IF EXISTS( SELECT * FROM information_schema.table_constraints WHERE table_schema = DATABASE() AND table_name = CONVERT(tableName USING latin1) AND constraint_name = CONVERT(constraintName USING latin1) AND constraint_type = 'FOREIGN KEY') THEN SET @query = CONCAT( CONVERT('ALTER TABLE ' USING latin1), CONVERT(tableName USING latin1), CONVERT(' DROP FOREIGN KEY ' USING latin1), CONVERT(constraintName USING latin1), CONVERT(';'USING latin1) ); PREPARE stmt FROM @query; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; DROP PROCEDURE IF EXISTS drop_index_if_exists; CREATE PROCEDURE drop_index_if_exists(in tableName varchar(64), in indexName varchar(64)) BEGIN IF((SELECT COUNT(*) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = CONVERT(tableName USING latin1) AND index_name = CONVERT(indexName USING latin1)) > 0) THEN SET @query = CONCAT( CONVERT('DROP INDEX ' USING latin1), CONVERT(indexName USING latin1), CONVERT(' ON ' USING latin1), CONVERT(tableName USING latin1) ); PREPARE stmt FROM @query; EXECUTE stmt; END IF; END; DROP TRIGGER IF EXISTS delete_image; DROP TRIGGER IF EXISTS delete_tag; DROP TRIGGER IF EXISTS move_tagstree; CALL drop_index_if_exists('AlbumRoots', 'identifier'); CALL drop_foreign_key('Albums', 'Albums_AlbumRoots'); CALL drop_index_if_exists('Albums', 'albumRoot'); CALL drop_foreign_key('Images', 'Images_Albums'); CALL drop_index_if_exists('Images', 'album'); CALL drop_index_if_exists('Images', 'album_2'); CALL drop_index_if_exists('Images', 'album_3'); CALL drop_foreign_key('Albums', 'Albums_Images'); CALL drop_foreign_key('ImageHaarMatrix', 'ImageHaarMatrix_Images'); CALL drop_foreign_key('ImageInformation', 'ImageInformation_Images'); CALL drop_foreign_key('ImageMetadata', 'ImageMetadata_Images'); CALL drop_foreign_key('VideoMetadata', 'VideoMetadata_Images'); CALL drop_foreign_key('ImagePositions', 'ImagePositions_Images'); CALL drop_foreign_key('ImageComments', 'ImageComments_Images'); CALL drop_index_if_exists('ImageComments', 'imageid'); CALL drop_foreign_key('ImageCopyright', 'ImageCopyright_Images'); CALL drop_index_if_exists('ImageCopyright', 'imageid'); CALL drop_foreign_key('Tags', 'Tags_Images'); CALL drop_index_if_exists('Tags', 'pid'); CALL drop_foreign_key('ImageTags', 'ImageTags_Images'); CALL drop_foreign_key('ImageTags', 'ImageTags_Tags'); CALL drop_foreign_key('ImageProperties', 'ImageProperties_Images'); CALL drop_index_if_exists('ImageProperties', 'imageid'); CALL drop_index_if_exists('DownloadHistory', 'identifier'); CALL drop_index_if_exists('Settings', 'keyword'); CALL drop_foreign_key('ImageHistory', 'ImageHistory_Images'); CALL drop_foreign_key('ImageRelations', 'ImageRelations_ImagesS'); CALL drop_foreign_key('ImageRelations', 'ImageRelations_ImagesO'); CALL drop_foreign_key('TagProperties', 'TagProperties_Tags'); CALL drop_foreign_key('ImageTagProperties', 'ImageTagProperties_Images'); CALL drop_foreign_key('ImageTagProperties', 'ImageTagProperties_Tags'); ALTER TABLE AlbumRoots MODIFY COLUMN label LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE AlbumRoots MODIFY COLUMN identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE AlbumRoots MODIFY COLUMN specificPath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Albums MODIFY COLUMN relativePath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Albums MODIFY COLUMN caption LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Albums MODIFY COLUMN collection LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Images MODIFY COLUMN name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Images MODIFY COLUMN fileSize BIGINT; ALTER TABLE ImageHaarMatrix MODIFY COLUMN uniqueHash LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageInformation MODIFY COLUMN format LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageMetadata MODIFY COLUMN make LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageMetadata MODIFY COLUMN model LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageMetadata MODIFY COLUMN lens LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImagePositions MODIFY COLUMN latitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImagePositions MODIFY COLUMN longitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImagePositions MODIFY COLUMN description LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageComments MODIFY COLUMN language VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageComments MODIFY COLUMN author LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageComments MODIFY COLUMN comment LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageCopyright MODIFY COLUMN property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageCopyright MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageCopyright MODIFY COLUMN extraValue LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Tags MODIFY COLUMN name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Tags MODIFY COLUMN iconkde LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageProperties MODIFY COLUMN property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE ImageProperties MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Searches MODIFY COLUMN name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Searches MODIFY COLUMN query LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE DownloadHistory MODIFY COLUMN identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE DownloadHistory MODIFY COLUMN filename LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE DownloadHistory MODIFY COLUMN filesize BIGINT; ALTER TABLE Settings MODIFY COLUMN keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Settings MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageHistory MODIFY COLUMN history LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE TagProperties MODIFY COLUMN property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE TagProperties MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageTagProperties MODIFY COLUMN property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageTagProperties MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; DROP TABLE IF EXISTS ImageTagProperties_old; DROP TABLE IF EXISTS TagProperties_old; DROP TABLE IF EXISTS ImageRelations_old; DROP TABLE IF EXISTS ImageHistory_old; DROP TABLE IF EXISTS Settings_old; DROP TABLE IF EXISTS DownloadHistory_old; DROP TABLE IF EXISTS ImageProperties_old; DROP TABLE IF EXISTS ImageTags_old; DROP TABLE IF EXISTS Tags_old; DROP TABLE IF EXISTS ImageCopyright_old; DROP TABLE IF EXISTS ImageComments_old; DROP TABLE IF EXISTS ImagePositions_old; DROP TABLE IF EXISTS VideoMetadata_old; DROP TABLE IF EXISTS ImageMetadata_old; DROP TABLE IF EXISTS ImageInformation_old; DROP TABLE IF EXISTS ImageHaarMatrix_old; DROP TABLE IF EXISTS Images_old; DROP TABLE IF EXISTS Albums_old; DROP TABLE IF EXISTS AlbumRoots_old; RENAME TABLE AlbumRoots TO AlbumRoots_old; CREATE TABLE AlbumRoots LIKE AlbumRoots_old; ALTER TABLE AlbumRoots ADD UNIQUE (identifier(127), specificPath(128)), ENGINE InnoDB; INSERT IGNORE INTO AlbumRoots SELECT * FROM AlbumRoots_old; RENAME TABLE Albums TO Albums_old; CREATE TABLE Albums LIKE Albums_old; ALTER TABLE Albums ADD CONSTRAINT Albums_AlbumRoots FOREIGN KEY (albumRoot) REFERENCES AlbumRoots (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE (albumRoot, relativePath(255)), ENGINE InnoDB; INSERT IGNORE INTO Albums SELECT * FROM Albums_old; UPDATE Albums SET icon = NULL WHERE icon = 0; RENAME TABLE Images TO Images_old; CREATE TABLE Images LIKE Images_old; ALTER TABLE Images ADD CONSTRAINT Images_Albums FOREIGN KEY (album) REFERENCES Albums (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE (album, name(255)), ENGINE InnoDB; INSERT IGNORE INTO Images SELECT * FROM Images_old; ALTER TABLE Albums ADD CONSTRAINT Albums_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE; RENAME TABLE ImageHaarMatrix TO ImageHaarMatrix_old; CREATE TABLE ImageHaarMatrix LIKE ImageHaarMatrix_old; ALTER TABLE ImageHaarMatrix ADD CONSTRAINT ImageHaarMatrix_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageHaarMatrix SELECT * FROM ImageHaarMatrix_old; RENAME TABLE ImageInformation TO ImageInformation_old; CREATE TABLE ImageInformation LIKE ImageInformation_old; ALTER TABLE ImageInformation ADD CONSTRAINT ImageInformation_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageInformation SELECT * FROM ImageInformation_old; RENAME TABLE ImageMetadata TO ImageMetadata_old; CREATE TABLE ImageMetadata LIKE ImageMetadata_old; ALTER TABLE ImageMetadata ADD CONSTRAINT ImageMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageMetadata SELECT * FROM ImageMetadata_old; RENAME TABLE VideoMetadata TO VideoMetadata_old; CREATE TABLE VideoMetadata LIKE VideoMetadata_old; ALTER TABLE VideoMetadata ADD CONSTRAINT VideoMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO VideoMetadata SELECT * FROM VideoMetadata_old; RENAME TABLE ImagePositions TO ImagePositions_old; CREATE TABLE ImagePositions LIKE ImagePositions_old; ALTER TABLE ImagePositions ADD CONSTRAINT ImagePositions_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImagePositions SELECT * FROM ImagePositions_old; RENAME TABLE ImageComments TO ImageComments_old; CREATE TABLE ImageComments LIKE ImageComments_old; ALTER TABLE ImageComments ADD CONSTRAINT ImageComments_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE(imageid, type, language, author(202)), ENGINE InnoDB; INSERT IGNORE INTO ImageComments SELECT * FROM ImageComments_old; RENAME TABLE ImageCopyright TO ImageCopyright_old; CREATE TABLE ImageCopyright LIKE ImageCopyright_old; ALTER TABLE ImageCopyright ADD CONSTRAINT ImageCopyright_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE(imageid, property(110), value(111), extraValue(111)), ENGINE InnoDB; INSERT IGNORE INTO ImageCopyright SELECT * FROM ImageCopyright_old; SET FOREIGN_KEY_CHECKS=0; RENAME TABLE Tags TO Tags_old; CREATE TABLE Tags LIKE Tags_old; ALTER TABLE Tags ADD CONSTRAINT Tags_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE, ADD UNIQUE(pid, name(100)), ENGINE InnoDB; REPLACE INTO Tags SELECT * FROM Tags_old; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; REPLACE INTO Tags (id, pid, name, icon, iconkde, lft, rgt) VALUES (0, -1, '_Digikam_root_tag_', NULL, NULL, (SELECT MIN(tl.lft) FROM Tags AS tl), (SELECT MAX(tr.rgt) FROM Tags AS tr)); SET SQL_MODE=@OLD_SQL_MODE; UPDATE Tags SET icon = NULL WHERE icon = 0; SET FOREIGN_KEY_CHECKS=1; RENAME TABLE ImageTags TO ImageTags_old; CREATE TABLE ImageTags LIKE ImageTags_old; ALTER TABLE ImageTags ADD CONSTRAINT ImageTags_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT ImageTags_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageTags SELECT * FROM ImageTags_old; RENAME TABLE ImageProperties TO ImageProperties_old; CREATE TABLE ImageProperties LIKE ImageProperties_old; ALTER TABLE ImageProperties ADD CONSTRAINT ImageProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE (imageid, property(255)), ENGINE InnoDB; INSERT IGNORE INTO ImageProperties SELECT * FROM ImageProperties_old; ALTER TABLE Searches ENGINE InnoDB; RENAME TABLE DownloadHistory TO DownloadHistory_old; CREATE TABLE DownloadHistory LIKE DownloadHistory_old; ALTER TABLE DownloadHistory ADD UNIQUE(identifier(164), filename(165), filesize, filedate), ENGINE InnoDB; INSERT IGNORE INTO DownloadHistory SELECT * FROM DownloadHistory_old; RENAME TABLE Settings TO Settings_old; CREATE TABLE Settings LIKE Settings_old; ALTER TABLE Settings ADD UNIQUE(keyword(255)), ENGINE InnoDB; INSERT IGNORE INTO Settings SELECT * FROM Settings_old; RENAME TABLE ImageHistory TO ImageHistory_old; CREATE TABLE ImageHistory LIKE ImageHistory_old; ALTER TABLE ImageHistory ADD CONSTRAINT ImageHistory_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageHistory SELECT * FROM ImageHistory_old; RENAME TABLE ImageRelations TO ImageRelations_old; CREATE TABLE ImageRelations LIKE ImageRelations_old; ALTER TABLE ImageRelations ADD CONSTRAINT ImageRelations_ImagesS FOREIGN KEY (subject) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT ImageRelations_ImagesO FOREIGN KEY (object) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageRelations SELECT * FROM ImageRelations_old; RENAME TABLE TagProperties TO TagProperties_old; CREATE TABLE TagProperties LIKE TagProperties_old; ALTER TABLE TagProperties ADD CONSTRAINT TagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO TagProperties SELECT * FROM TagProperties_old; RENAME TABLE ImageTagProperties TO ImageTagProperties_old; CREATE TABLE ImageTagProperties LIKE ImageTagProperties_old; ALTER TABLE ImageTagProperties ADD CONSTRAINT ImageTagProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT ImageTagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageTagProperties SELECT * FROM ImageTagProperties_old; DROP TABLE ImageTagProperties_old; DROP TABLE TagProperties_old; DROP TABLE ImageRelations_old; DROP TABLE ImageHistory_old; DROP TABLE Settings_old; DROP TABLE DownloadHistory_old; DROP TABLE ImageProperties_old; DROP TABLE ImageTags_old; DROP TABLE Tags_old; DROP TABLE ImageCopyright_old; DROP TABLE ImageComments_old; DROP TABLE ImagePositions_old; DROP TABLE VideoMetadata_old; DROP TABLE ImageMetadata_old; DROP TABLE ImageInformation_old; DROP TABLE ImageHaarMatrix_old; DROP TABLE Images_old; DROP TABLE Albums_old; DROP TABLE AlbumRoots_old; DROP TABLE IF EXISTS ImageHaarMatrix; + ALTER TABLE Images ADD manualOrder INTEGER; ALTER TABLE UniqueHashes CHANGE uniqueHash uniqueHash VARCHAR(128); CREATE TABLE IF NOT EXISTS CustomIdentifiers (identifier LONGTEXT CHARACTER SET utf8, thumbId INTEGER, UNIQUE(identifier(333))); CALL create_index_if_not_exists('CustomIdentifiers','id_customIdentifiers','thumbId'); ALTER TABLE Settings RENAME ThumbSettings; ALTER TABLE ThumbSettings MODIFY COLUMN keyword VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ThumbSettings ENGINE InnoDB; ALTER TABLE Thumbnails ENGINE InnoDB; ALTER TABLE UniqueHashes ENGINE InnoDB; ALTER TABLE FilePaths MODIFY COLUMN path VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE FilePaths ENGINE InnoDB; ALTER TABLE CustomIdentifiers MODIFY COLUMN identifier VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE CustomIdentifiers ENGINE InnoDB; ALTER TABLE UniqueHashes ADD CONSTRAINT UniqueHashes_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE FilePaths ADD CONSTRAINT FilePaths_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE CustomIdentifiers ADD CONSTRAINT CustomIdentifiers_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE; OPTIMIZE TABLE Albums, Images, ImageInformation, ImageMetadata, VideoMetadata, ImagePositions, ImageComments, ImageCopyright, ImageProperties, ImageHistory, ImageRelations, Tags, ImageTags, ImageTagProperties; OPTIMIZE TABLE Thumbnails, UniqueHashes, FilePaths, CustomIdentifiers; OPTIMIZE TABLE Identities, IdentityAttributes; OPTIMIZE TABLE ImageSimilarity, ImageHaarMatrix, SimilaritySettings; CHECK TABLE Albums, Images, ImageInformation, ImageMetadata, VideoMetadata, ImagePositions, ImageComments, ImageCopyright, ImageProperties, ImageHistory, ImageRelations, Tags, ImageTags, ImageTagProperties; CHECK TABLE Thumbnails, UniqueHashes, FilePaths, CustomIdentifiers; CHECK TABLE Identities, IdentityAttributes; CHECK TABLE ImageSimilarity, ImageHaarMatrix, SimilaritySettings; diff --git a/core/libs/album/albummanager.h b/core/libs/album/albummanager.h index 63823c9aeb..3f8029f8ba 100644 --- a/core/libs/album/albummanager.h +++ b/core/libs/album/albummanager.h @@ -1,913 +1,913 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-06-15 * Description : Albums manager interface. * * Copyright (C) 2004 by Renchi Raju * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2006-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ /** @file albummanager.h */ #ifndef DIGIKAM_ALBUM_MANAGER_H #define DIGIKAM_ALBUM_MANAGER_H // Qt includes #include #include #include #include #include // Local includes #include "album.h" #include "coredbalbuminfo.h" #include "dbengineparameters.h" #include "digikam_export.h" #include "imagelisterrecord.h" class QDate; namespace Digikam { class FAlbum; class CollectionLocation; class AlbumChangeset; class TagChangeset; class SearchChangeset; class CollectionImageChangeset; class ImageTagChangeset; /** * \class AlbumManager * * AlbumManager manages albums: does listing of albums and controls the lifetime of it. * For PAlbums and TAlbums, the listing is done by reading the db directly and * building the hierarchy of the albums. For DAlbums, since the listing takes * time, the work is delegated to a dbjob. Interested frontend entities can * connect to the albummanager to receive notifications of new Albums, when * Albums are deleted and when the current album is changed. * * Additional operations are provided for: creating/deleting/rename Albums, * updating icons and moving Albums. * */ class DIGIKAM_EXPORT AlbumManager : public QObject { Q_OBJECT public: /** * A convenience function to get the instance of the AlbumManager */ static AlbumManager* instance(); /** @name Library path And Scanning */ //@{ /** * Initialize. Informs the user about failures. * Returns true on success, false on failure. * A return value of false during startup indicates termination of the program * (user is informed) */ bool setDatabase(const DbEngineParameters& params, bool priority, const QString& suggestedAlbumRoot = QString()); /** * Some checks for settings done in first run wizard in case of QSlite Database. */ static void checkDatabaseDirsAfterFirstRun(const QString& dbPath, const QString& albumPath); /** * Sets new database when chosen by the user in setup. * Handles user notification about problems. * Call this instead of setDatabase when digiKam is up and running. */ void changeDatabase(const DbEngineParameters& params); /** * Stop ongoing operations, prepare for application shutdown */ void cleanUp(); /** * Checks if the given database path is equal to the current one */ bool databaseEqual(const DbEngineParameters& parameters) const; /** * starts scanning the libraryPath and listing the albums. If the * libraryPath has not changed since the last scan, then nothing happens * @see setLibraryPath * @see refresh */ void startScan(); /** * This is similar to startScan, except that it assumes you have run * startScan at least once. It checks the database to see if any new albums * have been added and updates them accordingly. Use this when a change in the * filesystem is detected (but the album library path hasn't changed) * @see startScan */ void refresh(); /** * Ensures that valid item counts for physical and tag albums are available */ void prepareItemCounts(); //@} /** @name List of Albums and current Album */ //@{ /** * @return a list of all PAlbums */ AlbumList allPAlbums() const; /** * @return a list of all TAlbums */ AlbumList allTAlbums() const; /** * @return a list of all SAlbums */ AlbumList allSAlbums() const; /** * @return a list of all DAlbums */ AlbumList allDAlbums() const; /** * @return a list of all FAlbums */ AlbumList allFAlbums() const; /** * set current album to @p albums. It's similar to setCurrentAlbum, * but supports multiple selected albums */ void setCurrentAlbums(QList albums); /** * @returns current albums, previously set up by setCurrentAlbums */ AlbumList currentAlbums() const; /** * @returns the current PAlbum or null if no one is selected */ PAlbum* currentPAlbum() const; /** * @returns the current TAlbum or null if no one is selected */ QList currentTAlbums() const; /** * @returns the current FAlbum or null if no one is selected */ FAlbum* currentFAlbum() const; //@} /** @name Finding Albums */ //@{ /** * Given a complete file url (kde url with file protocol), it will try to find * a PAlbum corresponding to it. * \warning This should not be used, unless really necessary * @return PAlbum corresponding to supplied @p url * @param url the url we need to check */ PAlbum* findPAlbum(const QUrl& url) const; /** * @return a PAlbum with given ID * @param id the id for the PAlbum */ PAlbum* findPAlbum(int id) const; /** * @return a TAlbum with given ID * @param id the id for the TAlbum */ TAlbum* findTAlbum(int id) const; /** * @return a SAlbum with given ID * @param id the id for the SAlbum */ SAlbum* findSAlbum(int id) const; /** * @return a DAlbum with given ID * @param id the id for the DAlbum */ DAlbum* findDAlbum(int id) const; /** * @return a FAlbum with given name * @param name the name for the FAlbum (name of the person which the FAlbum corresponds to */ FAlbum* findFAlbum(const QString& name) const; /** * @return a Album with the given globalID * @param gid the global id for the album */ Album* findAlbum(int gid) const; /** * @return a Album with the given type and id * @param id the id for the album (not the global id) */ Album* findAlbum(Album::Type type, int id) const; /** * @return a TAlbum with given tag path, or 0 if not found * @param tagPath the tag path ("People/Friend/John") */ TAlbum* findTAlbum(const QString& tagPath) const; /** * @return a SAlbum with given name, or 0 if not found * @param name the name of the search */ SAlbum* findSAlbum(const QString& name) const; /** * @return SAlbums with given type, empty list if not found * @param searchType the type of the search */ QList< SAlbum* > findSAlbumsBySearchType(int searchType) const; //@} /** @name Operations on PAlbum */ //@{ /** * Create a new PAlbum with supplied properties as a child of the parent * This is equivalent to creating a new folder on the disk with supplied * name in the parent's folder path. Also the supplied attributes are written * out to the database * \note the signalAlbumAdded will be fired before this function returns. Its * recommended to connect to that signal to get notification of new album added * @return the newly created PAlbum or 0 if it fails * @param parent The parent album under which to create the new Album. * Parent must not be root. Otherwise, use the other variants of this method. * If parent is root, the albumRootPath must be supplied. * @param name the name of the new album * @param caption the caption for the new album * @param date the date for the new album * @param errMsg this will contain the error message describing why the * operation failed */ PAlbum* createPAlbum(PAlbum* parent, const QString& name, const QString& caption, const QDate& date, const QString& category, QString& errMsg); /** * Overloaded method. Here you can supply an albumRootPath which must * correspond to an available collection location. */ PAlbum* createPAlbum(const QString& albumRootPath, const QString& name, const QString& caption, const QDate& date, const QString& category, QString& errMsg); /** * Overloaded method. Here you can supply a collection location (which * must be available). * * @param location the collection for the new album */ PAlbum* createPAlbum(const CollectionLocation& location, const QString& name, const QString& caption, const QDate& date, const QString& category, QString& errMsg); /** * Renames a PAlbum. This is equivalent to actually renaming the corresponding * folder on the disk. * @return true if the operation succeeds, false otherwise * @param album the Album which should be renamed * @param newName the new name for the album * @param errMsg this will contain the error message describing why the * operation failed */ bool renamePAlbum(PAlbum* album, const QString& newName, QString& errMsg); /** * Update the icon for an album. The @p icon is the name (and not full path) * of the file in the album * @return true if the operation succeeds, false otherwise * @param album the album for which icon should be changed * @param iconID the filename of the new icon * @param errMsg if the operation fails, this will contain the error message * describing why the operation failed */ bool updatePAlbumIcon(PAlbum* album, qlonglong iconID, QString& errMsg); /** * Returns the id of the item with the given filename in * the given PAlbum. * @param album The albumId in which we search the item. * @param fileName The name of the item file. * @return The item id or -1 if not existent. */ qlonglong getItemFromAlbum(PAlbum* album, const QString& fileName); //@} /** * @return A hash with the titles for all album IDs. */ QHash albumTitles() const; /** @name Operations on TAlbum */ //@{ /** * Create a new TAlbum with supplied properties as a child of the parent * The tag is added to the database * \note the signalAlbumAdded will be fired before this function returns. Its * recommended to connect to that signal to get notification of new album added * @return the newly created TAlbum or 0 if it fails * @param parent the parent album under which to create the new Album * @param name the name of the new album * @param iconkde the iconkde for the new album (this is a filename which * kde iconloader can load up * @param errMsg this will contain the error message describing why the * operation failed */ TAlbum* createTAlbum(TAlbum* parent, const QString& name, const QString& iconkde, QString& errMsg); /** * A list of tag paths is supplied. * If no corresponding TAlbum exists, a new one will be created. * @param tagPaths A list of tag paths * @returns A list of all TAlbums for the list (already existing or newly created) */ AlbumList findOrCreateTAlbums(const QStringList& tagPaths); /** * Delete a TAlbum. * The tag is removed from the database * \note the signalAlbumDeleted will be fired before this function returns. Its * recommended to connect to that signal to get notification of album deletes * @return true if the operation succeeds or false otherwise * @param album the TAlbum to delete * @param errMsg this will contain the error message describing why the * @param askUser ask user to write metadata to images * operation failed */ bool deleteTAlbum(TAlbum* album, QString& errMsg, bool askUser = true); /** * Renames a TAlbum. * This updates the tag name in the database * @return true if the operation succeeds, false otherwise * @param album the Album which should be renamed * @param name the new name for the album * @param errMsg this will contain the error message describing why the * operation failed */ bool renameTAlbum(TAlbum* album, const QString& name, QString& errMsg); /** * Move a TAlbum to a new parent. * This updates the tag parent ID in the database * @return true if the operation succeeds, false otherwise * @param album the Album which should be moved * @param newParent the Parent Album to which album should be moved * @param errMsg this will contain the error message describing why the * operation failed */ bool moveTAlbum(TAlbum* album, TAlbum* newParent, QString& errMsg); /** * Merge a TAlbum to a TAlbum. * This updates the image tags in the database * @return true if the operation succeeds, false otherwise * @param album the Album which should be merged * @param destAlbum the Album to which album should be merged * @param dialog show dialog to ask the user if he wants to merge * @param errMsg this will contain the error message describing why the * operation failed */ bool mergeTAlbum(TAlbum* album, TAlbum* destAlbum, bool dialog, QString& errMsg); /** * Update the icon for a TAlbum. * @return true if the operation succeeds, false otherwise * @param album the album for which icon should be changed * @param iconKDE a simple filename which can be loaded by KIconLoader * @param iconID id of the icon image file * @param errMsg this will contain the error message describing why the * operation failed * \note if iconKDE is not empty then iconID is used. So if you want to set * the icon to a file which can be loaded by KIconLoader, pass it in as * iconKDE. otherwise pass a null QString to iconKDE and set iconID */ bool updateTAlbumIcon(TAlbum* album, const QString& iconKDE, qlonglong iconID, QString& errMsg); /** * Get a list of recently assigned tags (only last 6 tags are listed) * @return the list of recently assigned TAlbums * @param includeInternal include internal tags in the returned list, or skip them */ AlbumList getRecentlyAssignedTags(bool includeInternal = false) const; /** * @return A list with the tag paths for a list of tag IDs. * @param tagIDs list of tag album IDs * @param leadingSlash if true return tags with a leading slash * @param includeInternal include internal tags in the returned list, or skip them */ QStringList tagPaths(const QList& tagIDs, bool leadingSlash=true, bool includeInternal = false) const; /** * @return A list with the tag names for a list of tag IDs. * @param tagIDs list of tag album IDs */ QStringList tagNames(const QList& tagIDs, bool includeInternal = false) const; /** * @return A hash with the tag paths for all tag IDs. */ QHash tagPaths(bool leadingSlash=true, bool includeInternal = false) const; /** * @return A hash with the tag names for all tag IDs. */ QHash tagNames(bool includeInternal = false) const; /** * Returns a list of TAlbums which have the given property, * or the given property/value combination. */ AlbumList findTagsWithProperty(const QString& property); AlbumList findTagsWithProperty(const QString& property, const QString& value); /** * TODO */ QList subTags(int tagId, bool recursive = false); //@} /** @name Operations on SAlbum */ //@{ /** * Create a new SAlbum with supplied url. If an existing SAlbum with same name * exists this function will return a pointer to that album, instead of creating * a new one. A newly created search album is added to the database. For an * existing SAlbum, the url is updated and written out to the database * \note the signalAlbumAdded will be fired before this function returns. Its * recommended to connect to that signal to get notification of new album added * @return the newly created SAlbum or an existing SAlbum with same name * @param name name for the new search * @param type the type of the search * @param query search query to use */ SAlbum* createSAlbum(const QString& name, DatabaseSearch::Type type, const QString& query); /** * Update the url for a SAlbum * @return true if the operation succeeds, false otherwise * @param album the album to update * @param changedQuery the new query data of the album * @param changedName a new name, or null to keep the current name * @param type a new type, or UndefinedType to keep the current type */ bool updateSAlbum(SAlbum* album, const QString& changedQuery, const QString& changedName = QString(), DatabaseSearch::Type type = DatabaseSearch::UndefinedType); /** * Delete a SAlbum from the database * \note the signalAlbumDeleted will be fired before this function returns. Its * recommended to connect to that signal to get notification of album deletes * @return true if the operation succeeds, false otherwise * @param album the album to delete */ bool deleteSAlbum(SAlbum* album); //@} /** @name Operations on TAlbum */ //@{ /** * Create a new FAlbum with supplied properties as a child of the parent * The person is added to the database * \note the signalAlbumAdded will be fired before this function returns. Its * recommended to connect to that signal to get notification of new album added * @return the newly created FAlbum or 0 if it fails * @param parent the parent album under which to create the new FAlbum * @param name the name of the new album * @param iconkde the iconkde for the new album (this is a filename which * kde iconloader can load up * @param errMsg this will contain the error message describing why the * operation failed */ FAlbum* createFAlbum(FAlbum* parent, const QString& name, const QString& iconkde, QString& errMsg); /** * Delete a FAlbum. * The person is removed from the database * \note the signalAlbumDeleted will be fired before this function returns. Its * recommended to connect to that signal to get notification of album deletes * @return true if the operation succeeds or false otherwise * @param album the FAlbum to delete * @param errMsg this will contain the error message describing why the * operation failed */ bool deleteFAlbum(FAlbum* album, QString& errMsg); /** * Renames a FAlbum. * This updates the person name in the database * @return true if the operation succeeds, false otherwise * @param album the Album which should be renamed * @param name the new name for the album * @param errMsg this will contain the error message describing why the * operation failed */ bool renameFAlbum(FAlbum* album, const QString& name, QString& errMsg); /** * Update the icon for a FAlbum. * @return true if the operation succeeds, false otherwise * @param album the album for which icon should be changed * @param iconKDE a simple filename which can be loaded by KIconLoader * @param iconID id of the icon image file * @param errMsg this will contain the error message describing why the * operation failed * \note if iconKDE is not empty then iconID is used. So if you want to set * the icon to a file which can be loaded by KIconLoader, pass it in as * iconKDE. otherwise pass a null QString to iconKDE and set iconID */ bool updateFAlbumIcon(FAlbum* album, const QString& iconKDE, qlonglong iconID, QString& errMsg); /** * @return A list with the name paths for a list of tag names. * @param names list of tag album names * @param leadingSlash if true return name paths with a leading slash */ QStringList namePaths(const QList& tagNames, bool leadingSlash=true) const; //@} /** @name Accessors to counting maps */ //@{ /** * Returns the latest count for PAlbums as also emitted via * signalPAlbumsDirty. * * @return count map for PAlbums */ QMap getPAlbumsCount() const; /** * Returns the latest count for TAlbums as also emitted via * signalTAlbumsDirty. * * @return count map for TAlbums */ QMap getTAlbumsCount() const; /** * Returns the latest count for DAlbums as also emitted via * signalDAlbumsDirty. * * @return count map for DAlbums */ QMap getDAlbumsCount() const; /** * Returns the latest count for faces as also emitted via * signalFaceCountsDirty. * * @return count map for faces (confirmed and unconfirmed combined) */ QMap getFaceCount() const; /** * Returns if the given album is currently being moved, that is, * if this album is in between signalAlbumAboutToBeMoved and * signalAlbumMoved. In this case, you can preserve state of such an album * because the object is guaranteed not to be deleted, even if * signalAlbumAboutToBeDeleted is emitted. */ bool isMovingAlbum(Album* album) const; bool isShowingOnlyAvailableAlbums() const; void setShowOnlyAvailableAlbums(bool onlyAvailable); void removeWatchedPAlbums(const PAlbum* const album); void addFakeConnection(); void removeFakeConnection(); //@} Q_SIGNALS: /// An album is about to be added to the given parent (0 if album is root) /// after the item given by prev (prev is 0 if parent has no children yet) void signalAlbumAboutToBeAdded(Album* album, Album* parent, Album* prev); /// The album has been added. void signalAlbumAdded(Album* album); /// The album is about to be deleted, but is still fully valid. void signalAlbumAboutToBeDeleted(Album* album); /// The album is deleted, but the object can still be accessed. void signalAlbumDeleted(Album* album); /// The album is deleted, the object can no longer be accessed. /// For identification purposes, the former album pointer is passed. void signalAlbumHasBeenDeleted(quintptr); void signalAlbumsCleared(); - void signalAlbumCurrentChanged(QList album); + void signalAlbumCurrentChanged(const QList& albums); void signalAllAlbumsLoaded(); void signalAllDAlbumsLoaded(); void signalAlbumIconChanged(Album* album); void signalAlbumRenamed(Album* album); void signalAlbumNewPath(Album* album); void signalSearchUpdated(SAlbum* album); /// Indicates that an album is about to be moved. Signals for deleting and adding will be /// sent afterwards, but the album object is guaranteed not to be deleted until after signalAlbumMoved. void signalAlbumAboutToBeMoved(Album* album); /// Emitted when the album is moved to its new parent. After signalAlbumAboutToBeMoved, /// all four signals for first deleting and then adding will have been sent. void signalAlbumMoved(Album* album); void signalPAlbumsDirty(const QMap&); void signalTAlbumsDirty(const QMap&); void signalDAlbumsDirty(const QMap&); void signalFaceCountsDirty(const QMap&); void signalDatesMapDirty(const QMap&); void signalTagPropertiesChanged(TAlbum* album); void signalAlbumsUpdated(int type); void signalUpdateDuplicatesAlbums(const QList& modifiedAlbums, const QList& deletedImages); // Signals a change in this property. Please note that affected albums may appear or disappear after this signal has been emitted. void signalShowOnlyAvailableAlbumsChanged(bool showsOnlyAvailableAlbums); private Q_SLOTS: void slotDatesJobResult(); void slotDatesJobData(const QMap& datesStatMap); void slotAlbumsJobResult(); void slotAlbumsJobData(const QMap& albumsStatMap); void slotTagsJobResult(); void slotTagsJobData(const QMap& tagsStatMap); void slotPeopleJobResult(); void slotPeopleJobData(const QMap >& facesStatMap); void slotCollectionLocationStatusChanged(const CollectionLocation&, int); void slotCollectionLocationPropertiesChanged(const CollectionLocation& location); void slotAlbumChange(const AlbumChangeset& changeset); void slotTagChange(const TagChangeset& changeset); void slotSearchChange(const SearchChangeset& changeset); void slotCollectionImageChange(const CollectionImageChangeset& changeset); void slotImageTagChange(const ImageTagChangeset& changeset); void slotImagesDeleted(const QList& imageIds); /** * Scan albums directly from database and creates new PAlbums * It only creates those PAlbums which haven't already been * created. */ void scanPAlbums(); void updateChangedPAlbums(); /** * Scan tags directly from database and creates new TAlbums * It only creates those TAlbums which haven't already been * created. */ void scanTAlbums(); /** * Scan searches directly from database and creates new SAlbums * It only creates those SAlbums which haven't already been * created. */ void scanSAlbums(); /** * Scan dates from the database (via IOSlave) and * updates the DAlbums. */ void scanDAlbumsScheduled(); void scanDAlbums(); void getAlbumItemsCount(); void getTagItemsCount(); void tagItemsCount(); void personItemsCount(); private: friend class AlbumManagerCreator; AlbumManager(); ~AlbumManager(); /** * Checks whether an Album has a direct child with the given name. * * @param parent album to check children for * @param title title to search for * @return true if there is a child with name, else * false */ void askUserForWriteChangedTAlbumToFiles(TAlbum* const album); void askUserForWriteChangedTAlbumToFiles(const QList& imageIds); bool hasDirectChildAlbumWithTitle(Album* parent, const QString& title); bool handleCollectionStatusChange(const CollectionLocation& location, int oldStatus); void insertPAlbum(PAlbum* album, PAlbum* parent); void removePAlbum(PAlbum* album); void insertTAlbum(TAlbum* album, TAlbum* parent); void removeTAlbum(TAlbum* album); void updateAlbumPathHash(); void notifyAlbumDeletion(Album* album); void addAlbumRoot(const CollectionLocation& location); void removeAlbumRoot(const CollectionLocation& location); void addGuardedPointer(Album* a, Album** pointer); void removeGuardedPointer(Album* a, Album** pointer); void changeGuardedPointer(Album* oldAlbum, Album* a, Album** pointer); void invalidateGuardedPointers(Album* album); static AlbumManager* internalInstance; public: // Declared public because it used in ChangingDB class. class Private; private: Private* const d; template friend class AlbumPointer; friend class Album; }; // ------------------------------------------------------------------------------------ /** * You can use AlbumPointer to store a guarded pointer to Album * or one of the subclasses (use template parameter). * The pointer will be set to 0 when the album object is deleted. */ template class AlbumPointer { public: AlbumPointer() : album(0) { } AlbumPointer(T* const a) // krazy:exclude=explicit : album(a) { AlbumManager::instance()->addGuardedPointer(album, &album); } AlbumPointer(const AlbumPointer& p) // krazy:exclude=explicit : album(p.album) { AlbumManager::instance()->addGuardedPointer(album, &album); } ~AlbumPointer() { AlbumManager::instance()->removeGuardedPointer(album, &album); } AlbumPointer& operator=(T* const a) { Album* const oldAlbum = album; album = a; AlbumManager::instance()->changeGuardedPointer(oldAlbum, album, &album); return *this; } AlbumPointer& operator=(const AlbumPointer& p) { Album* const oldAlbum = album; album = p.album; AlbumManager::instance()->changeGuardedPointer(oldAlbum, album, &album); return *this; } T* operator->() const { return static_cast(const_cast(album)); } T& operator*() const { return *static_cast(const_cast(album)); } operator T* () const { return static_cast(const_cast(album)); } bool operator!() const { return !album; } private: friend class AlbumManager; Album* album; }; // ------------------------------------------------------------------------------------ template class AlbumPointerList : public QList > { public: AlbumPointerList() { } explicit AlbumPointerList(const AlbumPointerList& list) : QList >(list) { } explicit AlbumPointerList(const QList& list) { operator=(list); } AlbumPointerList& operator=(const AlbumPointerList& list) { return QList >::operator=(list); } AlbumPointerList& operator=(const QList& list) { foreach(T* const t, list) { this->append(AlbumPointer(t)); } return *this; } }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::AlbumPointer<>) Q_DECLARE_METATYPE(Digikam::AlbumPointer) Q_DECLARE_METATYPE(Digikam::AlbumPointer) Q_DECLARE_METATYPE(Digikam::AlbumPointer) Q_DECLARE_METATYPE(Digikam::AlbumPointer) Q_DECLARE_METATYPE(QList) #endif // DIGIKAM_ALBUM_MANAGER_H diff --git a/core/libs/database/collection/collectionmanager.cpp b/core/libs/database/collection/collectionmanager.cpp index 46bb787399..945186d3d8 100644 --- a/core/libs/database/collection/collectionmanager.cpp +++ b/core/libs/database/collection/collectionmanager.cpp @@ -1,1803 +1,1803 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-04-09 * Description : Collection location management * * Copyright (C) 2007-2009 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "collectionmanager.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "coredbaccess.h" #include "coredbchangesets.h" #include "coredbtransaction.h" #include "coredb.h" #include "collectionscanner.h" #include "collectionlocation.h" namespace Digikam { class AlbumRootLocation : public CollectionLocation { public: AlbumRootLocation() : available(false), hidden(false) { } explicit AlbumRootLocation(const AlbumRootInfo& info) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new Location " << info.specificPath << " uuid " << info.identifier; m_id = info.id; m_type = (Type)info.type; specificPath = info.specificPath; identifier = info.identifier; m_label = info.label; m_path.clear(); setStatus((CollectionLocation::Status)info.status); } void setStatusFromFlags() { if (hidden) { m_status = CollectionLocation::LocationHidden; } else { if (available) { m_status = CollectionLocation::LocationAvailable; } else { m_status = CollectionLocation::LocationUnavailable; } } } void setStatus(CollectionLocation::Status s) { m_status = s; // status is exclusive, and Hidden wins // but really both states are independent // - a hidden location might or might not be available if (m_status == CollectionLocation::LocationAvailable) { available = true; hidden = false; } else if (m_status == CollectionLocation::LocationHidden) { available = false; hidden = true; } else // Unavailable, Null, Deleted { available = false; hidden = false; } } void setId(int id) { m_id = id; } void setAbsolutePath(const QString& path) { m_path = path; } void setType(Type type) { m_type = type; } void setLabel(const QString& label) { m_label = label; } public: QString identifier; QString specificPath; bool available; bool hidden; }; // ------------------------------------------------- class SolidVolumeInfo { public: SolidVolumeInfo() : isRemovable(false), isOpticalDisc(false), isMounted(false) { } bool isNull() const { return path.isNull(); } public: QString udi; // Solid device UDI of the StorageAccess device QString path; // mount path of volume, with trailing slash QString uuid; // UUID as from Solid QString label; // volume label (think of CDs) bool isRemovable; // may be removed bool isOpticalDisc; // is an optical disk device as CD/DVD/BR bool isMounted; // is mounted on File System. }; // ------------------------------------------------- class CollectionManagerPrivate { public: explicit CollectionManagerPrivate(CollectionManager* s); // hack for Solid's threading problems QList actuallyListVolumes(); void slotTriggerUpdateVolumesList(); QList volumesListCache; /// Access Solid and return a list of storage volumes QList listVolumes(); /** * Find from a given list (usually the result of listVolumes) the volume * corresponding to the location */ SolidVolumeInfo findVolumeForLocation(const AlbumRootLocation* location, const QList volumes); /** * Find from a given list (usually the result of listVolumes) the volume * on which the file path specified by the url is located. */ SolidVolumeInfo findVolumeForUrl(const QUrl& fileUrl, const QList volumes); /// Create the volume identifier for the given volume info static QString volumeIdentifier(const SolidVolumeInfo& info); /// Create a volume identifier based on the path only QString volumeIdentifier(const QString& path); /// Create a network share identifier based on the mountpath QString networkShareIdentifier(const QString& path); /// Return the path, if location has a path-only identifier. Else returns a null string. QString pathFromIdentifier(const AlbumRootLocation* location); /// Return the path, if location has a path-only identifier. Else returns a null string. QStringList networkShareMountPathsFromIdentifier(const AlbumRootLocation* location); /// Create an MD5 hash of the top-level entries (file names, not file content) of the given path static QString directoryHash(const QString& path); /// Check if a location for specified path exists, assuming the given list of locations was deleted bool checkIfExists(const QString& path, QList assumeDeleted); /// Make a user presentable description, regardless of current location status QString technicalDescription(const AlbumRootLocation* location); public: QMap locations; bool changingDB; QStringList udisToWatch; bool watchEnabled; CollectionManager* s; }; // ------------------------------------------------- class ChangingDB { public: explicit ChangingDB(CollectionManagerPrivate* d) : d(d) { d->changingDB = true; } ~ChangingDB() { d->changingDB = false; } public: CollectionManagerPrivate* const d; }; } // namespace Digikam // This is because of the private slot; we'd want a collectionmanager_p.h #include "collectionmanager.h" // krazy:exclude=includes namespace Digikam { CollectionManagerPrivate::CollectionManagerPrivate(CollectionManager* s) : changingDB(false), watchEnabled(false), s(s) { QObject::connect(s, SIGNAL(triggerUpdateVolumesList()), s, SLOT(slotTriggerUpdateVolumesList()), Qt::BlockingQueuedConnection); } QList CollectionManagerPrivate::listVolumes() { // Move calls to Solid to the main thread. // Solid was meant to be thread-safe, but it is not (KDE4.0), // calling from a non-UI thread leads to a reversible // lock-up of variable length. if (QThread::currentThread() == QCoreApplication::instance()->thread()) { return actuallyListVolumes(); } else { // emit a blocking queued signal to move call to main thread emit(s->triggerUpdateVolumesList()); return volumesListCache; } } void CollectionManagerPrivate::slotTriggerUpdateVolumesList() { volumesListCache = actuallyListVolumes(); } QList CollectionManagerPrivate::actuallyListVolumes() { QList volumes; //qCDebug(DIGIKAM_DATABASE_LOG) << "listFromType"; QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); //qCDebug(DIGIKAM_DATABASE_LOG) << "got listFromType"; udisToWatch.clear(); foreach(const Solid::Device& accessDevice, devices) { // check for StorageAccess if (!accessDevice.is()) { continue; } // mark as a device of principal interest udisToWatch << accessDevice.udi(); const Solid::StorageAccess* access = accessDevice.as(); // watch mount status (remove previous connections) QObject::disconnect(access, SIGNAL(accessibilityChanged(bool,QString)), s, SLOT(accessibilityChanged(bool,QString))); QObject::connect(access, SIGNAL(accessibilityChanged(bool,QString)), s, SLOT(accessibilityChanged(bool,QString))); if (!access->isAccessible()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } /* * We cannot require a drive, some logical volumes may not have "one" drive as parent * See bug 273369 if (!driveDevice.isValid()) { continue; } */ Solid::StorageDrive* drive = driveDevice.as(); // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } Solid::StorageVolume* const volume = volumeDevice.as(); SolidVolumeInfo info; info.udi = accessDevice.udi(); info.path = QDir::fromNativeSeparators(access->filePath()); info.isMounted = access->isAccessible(); if (!info.path.isEmpty() && !info.path.endsWith(QLatin1Char('/'))) { info.path += QLatin1Char('/'); } info.uuid = volume->uuid(); info.label = volume->label(); if (drive) { info.isRemovable = drive->isHotpluggable() || drive->isRemovable(); } else { // impossible to know, but probably not hotpluggable (see comment above) info.isRemovable = false; } info.isOpticalDisc = volumeDevice.is(); volumes << info; } // This is the central place where the watch is enabled watchEnabled = true; return volumes; } QString CollectionManagerPrivate::volumeIdentifier(const SolidVolumeInfo& volume) { QUrl url; url.setScheme(QLatin1String("volumeid")); // On changing these, please update the checkLocation() code bool identifyByUUID = !volume.uuid.isEmpty(); bool identifyByLabel = !identifyByUUID && !volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable); bool addDirectoryHash = identifyByLabel && volume.isOpticalDisc; bool identifyByMountPath = !identifyByUUID && !identifyByLabel; if (identifyByUUID) { QUrlQuery q(url); q.addQueryItem(QLatin1String("uuid"), volume.uuid); url.setQuery(q); } if (identifyByLabel) { QUrlQuery q(url); q.addQueryItem(QLatin1String("label"), volume.label); url.setQuery(q); } if (addDirectoryHash) { // for CDs, we store a hash of the root directory. May be useful. QString dirHash = directoryHash(volume.path); if (!dirHash.isNull()) { QUrlQuery q(url); q.addQueryItem(QLatin1String("directoryhash"), dirHash); url.setQuery(q); } } if (identifyByMountPath) { QUrlQuery q(url); q.addQueryItem(QLatin1String("mountpath"), volume.path); url.setQuery(q); } return url.url(); } QString CollectionManagerPrivate::volumeIdentifier(const QString& path) { QUrl url; url.setScheme(QLatin1String("volumeid")); QUrlQuery q(url); q.addQueryItem(QLatin1String("path"), path); url.setQuery(q); return url.url(); } QString CollectionManagerPrivate::networkShareIdentifier(const QString& path) { QUrl url; url.setScheme(QLatin1String("networkshareid")); QUrlQuery q(url); q.addQueryItem(QLatin1String("mountpath"), path); url.setQuery(q); return url.url(); } QString CollectionManagerPrivate::pathFromIdentifier(const AlbumRootLocation* location) { QUrl url(location->identifier); if (url.scheme() != QLatin1String("volumeid")) { return QString(); } return QUrlQuery(url).queryItemValue(QLatin1String("path")); } QStringList CollectionManagerPrivate::networkShareMountPathsFromIdentifier(const AlbumRootLocation* location) { // using a QUrl because QUrl cannot handle duplicate query items QUrl url(location->identifier); if (url.scheme() != QLatin1String("networkshareid")) { return QStringList(); } return QUrlQuery(url).allQueryItemValues(QLatin1String("mountpath")); } QString CollectionManagerPrivate::directoryHash(const QString& path) { QDir dir(path); if (dir.isReadable()) { QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); QCryptographicHash md5(QCryptographicHash::Md5); foreach(const QString& entry, entries) { md5.addData(entry.toUtf8()); } return QString::fromUtf8(md5.result().toHex()); } return QString(); } SolidVolumeInfo CollectionManagerPrivate::findVolumeForLocation(const AlbumRootLocation* location, const QList volumes) { QUrl url(location->identifier); QString queryItem; if (url.scheme() != QLatin1String("volumeid")) { return SolidVolumeInfo(); } if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("uuid"))).isNull()) { foreach(const SolidVolumeInfo& volume, volumes) { if (volume.uuid.compare(queryItem, Qt::CaseInsensitive) == 0) { return volume; } } return SolidVolumeInfo(); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("label"))).isNull()) { // This one is a bit more difficult, as we take into account the possibility // that the label is not unique, and we take some care to make it work anyway. // find all available volumes with the given label (usually one) QList candidateVolumes; foreach(const SolidVolumeInfo& volume, volumes) { if (volume.label == queryItem) { candidateVolumes << volume; } } if (candidateVolumes.isEmpty()) { return SolidVolumeInfo(); } // find out of there is another location with the same label (usually not) bool hasOtherLocation = false; foreach(AlbumRootLocation* const otherLocation, locations) { if (otherLocation == location) { continue; } QUrl otherUrl(otherLocation->identifier); if (otherUrl.scheme() == QLatin1String("volumeid") && QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == queryItem) { hasOtherLocation = true; break; } } // the usual, easy case if (candidateVolumes.size() == 1 && !hasOtherLocation) { return candidateVolumes.first(); } else { // not unique: try to use the directoryhash QString dirHash = QUrlQuery(url).queryItemValue(QLatin1String("directoryhash")); // bail out if not provided if (dirHash.isNull()) { qCDebug(DIGIKAM_DATABASE_LOG) << "No directory hash specified for the non-unique Label" << queryItem << "Resorting to returning the first match."; return candidateVolumes.first(); } // match against directory hash foreach(const SolidVolumeInfo& volume, candidateVolumes) { QString volumeDirHash = directoryHash(volume.path); if (volumeDirHash == dirHash) { return volume; } } } return SolidVolumeInfo(); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { foreach(const SolidVolumeInfo& volume, volumes) { if (volume.isMounted && volume.path == queryItem) { return volume; } } return SolidVolumeInfo(); } return SolidVolumeInfo(); } QString CollectionManagerPrivate::technicalDescription(const AlbumRootLocation* albumLoc) { QUrl url(albumLoc->identifier); QString queryItem; if (url.scheme() == QLatin1String("volumeid")) { if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("uuid"))).isNull()) { return i18nc("\"relative path\" on harddisk partition with \"UUID\"", "Folder \"%1\" on the volume with the id \"%2\"", QDir::toNativeSeparators(albumLoc->specificPath), queryItem); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("label"))).isNull()) { return i18nc("\"relative path\" on harddisk partition with \"label\"", "Folder \"%1\" on the volume labeled \"%2\"", QDir::toNativeSeparators(albumLoc->specificPath), queryItem); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { return QString::fromUtf8("\"%1\"").arg(queryItem); } } else if (url.scheme() == QLatin1String("networkshareid")) { if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { return i18nc("@info", "Shared directory mounted at %1", QDir::toNativeSeparators(queryItem)); } } return QString(); } SolidVolumeInfo CollectionManagerPrivate::findVolumeForUrl(const QUrl& fileUrl, const QList volumes) { SolidVolumeInfo volume; // v.path is specified to have a trailing slash. path needs one as well. - QString path = fileUrl.toLocalFile() + QLatin1String("/"); + QString path = fileUrl.toLocalFile() + QLatin1Char('/'); int volumeMatch = 0; //FIXME: Network shares! Here we get only the volume of the mount path... // This is probably not really clean. But Solid does not help us. foreach(const SolidVolumeInfo& v, volumes) { if (v.isMounted && !v.path.isEmpty() && path.startsWith(v.path)) { int length = v.path.length(); if (length > volumeMatch) { volumeMatch = v.path.length(); volume = v; } } } if (!volumeMatch) { qCDebug(DIGIKAM_DATABASE_LOG) << "Failed to detect a storage volume for path " << path << " with Solid"; } return volume; } bool CollectionManagerPrivate::checkIfExists(const QString& filePath, QList assumeDeleted) { const QUrl filePathUrl = QUrl::fromLocalFile(filePath); CoreDbAccess access; foreach(AlbumRootLocation* const location, locations) { const QUrl locationPathUrl = QUrl::fromLocalFile(location->albumRootPath()); //qCDebug(DIGIKAM_DATABASE_LOG) << filePathUrl << locationPathUrl; // make sure filePathUrl is neither a child nor a parent // of an existing collection if (!locationPathUrl.isEmpty() && (filePathUrl.isParentOf(locationPathUrl) || locationPathUrl.isParentOf(filePathUrl)) ) { bool isDeleted = false; foreach(const CollectionLocation& deletedLoc, assumeDeleted) { if (deletedLoc.id() == location->id()) { isDeleted = true; break; } } if (!isDeleted) { return true; } } } return false; } // ------------------------------------------------- CollectionManager* CollectionManager::m_instance = 0; CollectionManager* CollectionManager::instance() { if (!m_instance) { m_instance = new CollectionManager; } return m_instance; } void CollectionManager::cleanUp() { delete m_instance; m_instance = 0; } CollectionManager::CollectionManager() : d(new CollectionManagerPrivate(this)) { qRegisterMetaType("CollectionLocation"); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString))); // CoreDbWatch slot is connected at construction of CoreDbWatch, which may be later. } CollectionManager::~CollectionManager() { qDeleteAll(d->locations.values()); delete d; } void CollectionManager::refresh() { { // if called from the CoreDbAccess constructor itself, it will // hold a flag to prevent endless recursion CoreDbAccess access; clear_locked(); } updateLocations(); } void CollectionManager::setWatchDisabled() { d->watchEnabled = false; } CollectionLocation CollectionManager::addLocation(const QUrl& fileUrl, const QString& label) { qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation " << fileUrl; QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); if (!locationForPath(path).isNull()) { return CollectionLocation(); } QList volumes = d->listVolumes(); SolidVolumeInfo volume = d->findVolumeForUrl(fileUrl, volumes); if (!volume.isNull()) { CoreDbAccess access; // volume.path has a trailing slash. We want to split in front of this. QString specificPath = path.mid(volume.path.length() - 1); AlbumRoot::Type type; if (volume.isRemovable) { type = AlbumRoot::VolumeRemovable; } else { type = AlbumRoot::VolumeHardWired; } ChangingDB changing(d); access.db()->addAlbumRoot(type, d->volumeIdentifier(volume), specificPath, label); } else { // Empty volumes indicates that Solid is not working correctly. if (volumes.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "Solid did not return any storage volumes on your system."; qCDebug(DIGIKAM_DATABASE_LOG) << "This indicates a missing implementation or a problem with your installation"; qCDebug(DIGIKAM_DATABASE_LOG) << "On Linux, check that Solid and HAL are working correctly." "Problems with RAID partitions have been reported, if you have RAID this error may be normal."; qCDebug(DIGIKAM_DATABASE_LOG) << "On Windows, Solid may not be fully implemented, if you are running Windows this error may be normal."; } // fall back qCWarning(DIGIKAM_DATABASE_LOG) << "Unable to identify a path with Solid. Adding the location with path only."; ChangingDB changing(d); CoreDbAccess().db()->addAlbumRoot(AlbumRoot::VolumeHardWired, d->volumeIdentifier(path), QLatin1String("/"), label); } // Do not emit the locationAdded signal here, it is done in updateLocations() updateLocations(); return locationForPath(path); } CollectionLocation CollectionManager::addNetworkLocation(const QUrl& fileUrl, const QString& label) { qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation " << fileUrl; QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); if (!locationForPath(path).isNull()) { return CollectionLocation(); } ChangingDB changing(d); CoreDbAccess().db()->addAlbumRoot(AlbumRoot::Network, d->networkShareIdentifier(path), QLatin1String("/"), label); // Do not emit the locationAdded signal here, it is done in updateLocations() updateLocations(); return locationForPath(path); } CollectionManager::LocationCheckResult CollectionManager::checkLocation(const QUrl& fileUrl, QList assumeDeleted, QString* message, QString* iconName) { if (!fileUrl.isLocalFile()) { if (message) { *message = i18n("Sorry, digiKam does not support remote URLs as collections."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); QDir dir(path); if (!dir.isReadable()) { if (message) { *message = i18n("The selected folder does not exist or is not readable"); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (d->checkIfExists(path, assumeDeleted)) { if (message) { *message = i18n("There is already a collection containing the folder \"%1\"", QDir::toNativeSeparators(path)); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QList volumes = d->listVolumes(); SolidVolumeInfo volume = d->findVolumeForUrl(fileUrl, volumes); if (!volume.isNull()) { if (!volume.uuid.isEmpty()) { if (volume.isRemovable) { if (message) { *message = i18n("The storage media can be uniquely identified."); } if (iconName) { *iconName = QLatin1String("drive-removable-media"); } } else { if (message) { *message = i18n("The collection is located on your harddisk"); } if (iconName) { *iconName = QLatin1String("drive-harddisk"); } } return LocationAllRight; } else if (!volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable)) { if (volume.isOpticalDisc) { bool hasOtherLocation = false; foreach(AlbumRootLocation* const otherLocation, d->locations) { QUrl otherUrl(otherLocation->identifier); if (otherUrl.scheme() == QLatin1String("volumeid") && QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == volume.label) { hasOtherLocation = true; break; } } if (iconName) { *iconName = QLatin1String("media-optical"); } if (hasOtherLocation) { if (message) *message = i18n("This is a CD/DVD, which is identified by the label " "that you can set in your CD burning application. " "There is already another entry with the same label. " "The two will be distinguished by the files in the top directory, " "so please do not append files to the CD, or it will not be recognized. " "In the future, please set a unique label on your CDs and DVDs " "if you intend to use them with digiKam."); return LocationHasProblems; } else { if (message) *message = i18n("This is a CD/DVD. It will be identified by the label (\"%1\")" "that you have set in your CD burning application. " "If you create further CDs for use with digikam in the future, " "please remember to give them a unique label as well.", volume.label); return LocationAllRight; } } else { // Which situation? HasProblems or AllRight? if (message) *message = i18n("This is a removable storage medium that will be identified by its label (\"%1\")", volume.label); if (iconName) { *iconName = QLatin1String("drive-removable-media"); } return LocationAllRight; } } else { if (message) *message = i18n("This entry will only be identified by the path where it is found on your system (\"%1\"). " "No more specific means of identification (UUID, label) is available.", QDir::toNativeSeparators(volume.path)); if (iconName) { *iconName = QLatin1String("drive-removale-media"); } return LocationHasProblems; } } else { if (message) *message = i18n("It is not possible on your system to identify the storage medium of this path. " "It will be added using the file path as the only identifier. " "This will work well for your local hard disk."); if (iconName) { *iconName = QLatin1String("folder-important"); } return LocationHasProblems; } } CollectionManager::LocationCheckResult CollectionManager::checkNetworkLocation(const QUrl& fileUrl, QList assumeDeleted, QString* message, QString* iconName) { if (!fileUrl.isLocalFile()) { if (message) { if (fileUrl.scheme() == QLatin1String("smb")) *message = i18n("You need to locally mount your Samba share. " "Sorry, digiKam does currently not support smb:// URLs. "); else *message = i18n("Your network storage must be set up to be accessible " "as files and folders through the operating system. " "DigiKam does not support remote URLs."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); QDir dir(path); if (!dir.isReadable()) { if (message) { *message = i18n("The selected folder does not exist or is not readable"); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (d->checkIfExists(path, assumeDeleted)) { if (message) { *message = i18n("There is already a collection for a network share with the same path."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (message) *message = i18n("The network share will be identified by the path you selected. " "If the path is empty, the share will be considered unavailable."); if (iconName) { *iconName = QLatin1String("network-wired-activated"); } return LocationAllRight; } void CollectionManager::removeLocation(const CollectionLocation& location) { { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // Ensure that all albums are set to orphan and no images will be permanently deleted, // as would do only calling deleteAlbumRoot by a Trigger QList albumIds = access.db()->getAlbumsOnAlbumRoot(albumLoc->id()); ChangingDB changing(d); CollectionScanner scanner; CoreDbTransaction transaction(&access); scanner.safelyRemoveAlbums(albumIds); access.db()->deleteAlbumRoot(albumLoc->id()); } // Do not emit the locationRemoved signal here, it is done in updateLocations() updateLocations(); } QList CollectionManager::checkHardWiredLocations() { QList disappearedLocations; QList volumes = d->listVolumes(); CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { // Hardwired and unavailable? if (location->type() == CollectionLocation::TypeVolumeHardWired && location->status() == CollectionLocation::LocationUnavailable) { disappearedLocations << *location; } } return disappearedLocations; } void CollectionManager::migrationCandidates(const CollectionLocation& location, QString* const description, QStringList* const candidateIdentifiers, QStringList* const candidateDescriptions) { description->clear(); candidateIdentifiers->clear(); candidateDescriptions->clear(); QList volumes = d->listVolumes(); CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } *description = d->technicalDescription(albumLoc); // Find possible new volumes where the specific path is found. foreach(const SolidVolumeInfo& info, volumes) { if (info.isMounted && !info.path.isEmpty()) { QDir dir(info.path + albumLoc->specificPath); if (dir.exists()) { *candidateIdentifiers << d->volumeIdentifier(info); *candidateDescriptions << dir.absolutePath(); } } } } void CollectionManager::migrateToVolume(const CollectionLocation& location, const QString& identifier) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->migrateAlbumRoot(albumLoc->id(), identifier); albumLoc->identifier = identifier; updateLocations(); } void CollectionManager::setLabel(const CollectionLocation& location, const QString& label) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->setAlbumRootLabel(albumLoc->id(), label); // update local structure albumLoc->setLabel(label); emit locationPropertiesChanged(*albumLoc); } void CollectionManager::changeType(const CollectionLocation& location, int type) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->changeAlbumRootType(albumLoc->id(), (AlbumRoot::Type)type); // update local structure albumLoc->setType((CollectionLocation::Type)type); emit locationPropertiesChanged(*albumLoc); } QList CollectionManager::allLocations() { CoreDbAccess access; QList list; foreach(AlbumRootLocation* const location, d->locations) { list << *location; } return list; } QList CollectionManager::allAvailableLocations() { CoreDbAccess access; QList list; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { list << *location; } } return list; } QStringList CollectionManager::allAvailableAlbumRootPaths() { CoreDbAccess access; QStringList list; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { list << location->albumRootPath(); } } return list; } CollectionLocation CollectionManager::locationForAlbumRootId(int id) { CoreDbAccess access; AlbumRootLocation* const location = d->locations.value(id); if (location) { return *location; } else { return CollectionLocation(); } } CollectionLocation CollectionManager::locationForAlbumRoot(const QUrl& fileUrl) { return locationForAlbumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } CollectionLocation CollectionManager::locationForAlbumRootPath(const QString& albumRootPath) { if (!QFileInfo::exists(albumRootPath)) { qCWarning(DIGIKAM_DATABASE_LOG) << "Album root path not exist" << albumRootPath; qCWarning(DIGIKAM_DATABASE_LOG) << "Drive or network connection broken?"; updateLocations(); } CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (location->albumRootPath() == albumRootPath) { return *location; } } return CollectionLocation(); } CollectionLocation CollectionManager::locationForUrl(const QUrl& fileUrl) { return locationForPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } CollectionLocation CollectionManager::locationForPath(const QString& givenPath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString rootPath = location->albumRootPath(); QString filePath = QDir::fromNativeSeparators(givenPath); if (!rootPath.isEmpty() && filePath.startsWith(rootPath)) { // see also bug #221155 for extra checks if (filePath == rootPath || filePath.startsWith(rootPath + QLatin1Char('/'))) { return *location; } } } return CollectionLocation(); } QString CollectionManager::albumRootPath(int id) { CoreDbAccess access; CollectionLocation* const location = d->locations.value(id); if (location && location->status() == CollectionLocation::LocationAvailable) { return location->albumRootPath(); } return QString(); } QString CollectionManager::albumRootLabel(int id) { CoreDbAccess access; CollectionLocation* const location = d->locations.value(id); if (location && location->status() == CollectionLocation::LocationAvailable) { return location->label(); } return QString(); } QUrl CollectionManager::albumRoot(const QUrl& fileUrl) { return QUrl::fromLocalFile(albumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile())); } QString CollectionManager::albumRootPath(const QUrl& fileUrl) { return albumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::albumRootPath(const QString& givenPath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString rootPath = location->albumRootPath(); QString filePath = QDir::fromNativeSeparators(givenPath); if (!rootPath.isEmpty() && filePath.startsWith(rootPath)) { // see also bug #221155 for extra checks if (filePath == rootPath || filePath.startsWith(rootPath + QLatin1Char('/'))) { return location->albumRootPath(); } } } return QString(); } bool CollectionManager::isAlbumRoot(const QUrl& fileUrl) { return isAlbumRoot(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } bool CollectionManager::isAlbumRoot(const QString& filePath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (filePath == location->albumRootPath()) { return true; } } return false; } QString CollectionManager::album(const QUrl& fileUrl) { return album(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::album(const QString& filePath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString absolutePath = location->albumRootPath(); if (absolutePath.isEmpty()) { continue; } QString firstPart = filePath.left(absolutePath.length()); if (firstPart == absolutePath) { if (filePath == absolutePath || (filePath.length() == absolutePath.length() + 1 && filePath.right(1) == QLatin1String("/"))) { return QLatin1String("/"); } else { QString album = filePath.mid(absolutePath.length()); if (album.endsWith(QLatin1Char('/'))) { album.chop(1); } return album; } } } return QString(); } QString CollectionManager::album(const CollectionLocation& location, const QUrl& fileUrl) { return album(location, fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::album(const CollectionLocation& location, const QString& filePath) { if (location.isNull()) { return QString(); } QString absolutePath = location.albumRootPath(); if (filePath == absolutePath) { return QLatin1String("/"); } else { QString album = filePath.mid(absolutePath.length()); if (album.endsWith(QLatin1Char('/'))) { album.chop(1); } return album; } } QUrl CollectionManager::oneAlbumRoot() { return QUrl::fromLocalFile(oneAlbumRootPath()); } QString CollectionManager::oneAlbumRootPath() { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { return location->albumRootPath(); } } return QString(); } void CollectionManager::deviceAdded(const QString& udi) { if (!d->watchEnabled) { return; } Solid::Device device(udi); if (device.is()) { updateLocations(); } } void CollectionManager::deviceRemoved(const QString& udi) { if (!d->watchEnabled) { return; } // we can't access the Solid::Device to check because it is removed CoreDbAccess access; if (!d->udisToWatch.contains(udi)) { return; } updateLocations(); } void CollectionManager::accessibilityChanged(bool accessible, const QString& udi) { Q_UNUSED(accessible); Q_UNUSED(udi); updateLocations(); } void CollectionManager::updateLocations() { // get information from Solid QList volumes; { // Absolutely ensure that the db mutex is not held when emitting the blocking queued signal! Deadlock! CoreDbAccessUnlock unlock; volumes = d->listVolumes(); } { CoreDbAccess access; // read information from database QList infos = access.db()->getAlbumRoots(); // synchronize map with database QMap locs = d->locations; d->locations.clear(); foreach(const AlbumRootInfo& info, infos) { if (locs.contains(info.id)) { d->locations[info.id] = locs.value(info.id); locs.remove(info.id); } else { d->locations[info.id] = new AlbumRootLocation(info); } } // delete old locations foreach(AlbumRootLocation* const location, locs) { CollectionLocation::Status oldStatus = location->status(); location->setStatus(CollectionLocation::LocationDeleted); emit locationStatusChanged(*location, oldStatus); delete location; } // update status with current access state, store old status in list QList oldStatus; foreach(AlbumRootLocation* const location, d->locations) { oldStatus << location->status(); bool available = false; QString absolutePath; if (location->type() == CollectionLocation::TypeNetwork) { foreach(const QString& path, d->networkShareMountPathsFromIdentifier(location)) { QDir dir(path); available = dir.isReadable() && dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot).count() > 0; absolutePath = path; if (available) { break; } } } else { SolidVolumeInfo info = d->findVolumeForLocation(location, volumes); if (!info.isNull()) { available = info.isMounted; QString volumePath = info.path; // volume.path has a trailing slash (and this is good) // but specific path has a leading slash, so remove it volumePath.chop(1); // volumePath is the mount point of the volume; // specific path is the path on the file system of the volume. absolutePath = volumePath + location->specificPath; } else { QString path = d->pathFromIdentifier(location); if (!path.isNull()) { available = true; // Here we have the absolute path as definition of the volume. // specificPath is "/" as per convention, but ignored, // absolute path shall not have a trailing slash. absolutePath = path; } } } // set values in location // Don't touch location->status, do not interfere with "hidden" setting location->available = available; location->setAbsolutePath(absolutePath); qCDebug(DIGIKAM_DATABASE_LOG) << "location for " << absolutePath << " is available " << available; // set the status depending on "hidden" and "available" location->setStatusFromFlags(); } // emit status changes (and new locations) int i = 0; foreach(AlbumRootLocation* const location, d->locations) { if (oldStatus.at(i) != location->status()) { emit locationStatusChanged(*location, oldStatus.at(i)); } ++i; } } } void CollectionManager::clear_locked() { // Internal method: Called with mutex locked // Cave: Difficult recursions with CoreDbAccess constructor and setParameters foreach(AlbumRootLocation* const location, d->locations) { CollectionLocation::Status oldStatus = location->status(); location->setStatus(CollectionLocation::LocationDeleted); emit locationStatusChanged(*location, oldStatus); delete location; } d->locations.clear(); } void CollectionManager::slotAlbumRootChange(const AlbumRootChangeset& changeset) { if (d->changingDB) { return; } switch (changeset.operation()) { case AlbumRootChangeset::Added: case AlbumRootChangeset::Deleted: updateLocations(); break; case AlbumRootChangeset::PropertiesChanged: // label has changed { CollectionLocation toBeEmitted; { CoreDbAccess access; AlbumRootLocation* const location = d->locations.value(changeset.albumRootId()); if (location) { QList infos = access.db()->getAlbumRoots(); foreach(const AlbumRootInfo& info, infos) { if (info.id == location->id()) { location->setLabel(info.label); toBeEmitted = *location; break; } } } } if (!toBeEmitted.isNull()) { emit locationPropertiesChanged(toBeEmitted); } } break; case AlbumRootChangeset::Unknown: break; } } } // namespace Digikam #include "moc_collectionmanager.cpp" diff --git a/core/libs/database/coredb/coredbschemaupdater.cpp b/core/libs/database/coredb/coredbschemaupdater.cpp index 7cf3139e2b..52843ee6da 100644 --- a/core/libs/database/coredb/coredbschemaupdater.cpp +++ b/core/libs/database/coredb/coredbschemaupdater.cpp @@ -1,1580 +1,1583 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-04-16 * Description : Core database Schema updater * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2009-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "coredbschemaupdater.h" // Qt includes #include #include #include #include #include // KDE includes #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "coredbbackend.h" #include "coredbtransaction.h" #include "coredbchecker.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "collectionscanner.h" #include "imagequerybuilder.h" #include "collectionscannerobserver.h" #include "digikam_config.h" namespace Digikam { int CoreDbSchemaUpdater::schemaVersion() { - return 9; + return 10; } int CoreDbSchemaUpdater::filterSettingsVersion() { return 7; } int CoreDbSchemaUpdater::uniqueHashVersion() { return 2; } bool CoreDbSchemaUpdater::isUniqueHashUpToDate() { return CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion(); } // -------------------------------------------------------------------------------------- class CoreDbSchemaUpdater::Private { public: explicit Private() : setError(false), backend(0), albumDB(0), dbAccess(0), observer(0) { } bool setError; QVariant currentVersion; QVariant currentRequiredVersion; CoreDbBackend* backend; CoreDB* albumDB; DbEngineParameters parameters; // legacy CoreDbAccess* dbAccess; QString lastErrorMessage; InitializationObserver* observer; }; CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB, CoreDbBackend* const backend, DbEngineParameters parameters) : d(new Private) { d->backend = backend; d->albumDB = albumDB; d->parameters = parameters; } CoreDbSchemaUpdater::~CoreDbSchemaUpdater() { delete d; } void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess) { d->dbAccess = dbAccess; } const QString CoreDbSchemaUpdater::getLastErrorMessage() { return d->lastErrorMessage; } bool CoreDbSchemaUpdater::update() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update"; bool success = startUpdates(); // cancelled? if (d->observer && !d->observer->continueQuery()) { return false; } // even on failure, try to set current version - it may have incremented setVersionSettings(); if (!success) { return false; } updateFilterSettings(); if (d->observer) { d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess); } return success; } void CoreDbSchemaUpdater::setVersionSettings() { if (d->currentVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersion"), QString::number(d->currentVersion.toInt())); } if (d->currentRequiredVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersionRequired"), QString::number(d->currentRequiredVersion.toInt())); } } static QVariant safeToVariant(const QString& s) { if (s.isEmpty()) { return QVariant(); } else { return s.toInt(); } } void CoreDbSchemaUpdater::readVersionSettings() { d->currentVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion"))); d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired"))); } void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool CoreDbSchemaUpdater::startUpdates() { if (!d->parameters.isSQLite()) { // Do we have sufficient privileges QStringList insufficientRights; CoreDbPrivilegesChecker checker(d->parameters); if (!checker.checkPrivileges(insufficientRights)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database."; QString errorMsg = i18n("You have insufficient privileges on the database.\n" "Following privileges are not assigned to you:\n %1\n" "Check your privileges on the database and restart digiKam.", insufficientRights.join(QLatin1String(",\n"))); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } // First step: do we have an empty database? QStringList tables = d->backend->tables(); if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive)) { // Find out schema version of db file readVersionSettings(); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt(); // We absolutely require the DBVersion setting if (!d->currentVersion.isValid()) { // Something is damaged. Give up. qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. if (d->currentVersion.toInt() > schemaVersion()) { // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is too recent.) " "Please use the more recent version of digiKam that you used before. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available"; // Legacy handling? // first test if there are older files that need to be upgraded. // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9, // all only SQLite databases. // Version numbers used in this source file are a bit confused for the historic versions. // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2), // Version 3 is 0.8-0.9, // Version 3 wrote the setting "DBVersion", "1", // Version 4 is 0.10, the digikam3.db file copied to digikam4.db, // no schema changes. // Version 4 writes "4", and from now on version x writes "x". // Version 5 includes the schema changes from 0.9 to 0.10 // Version 6 brought new tables for history and ImageTagProperties, with version 2.0 // Version 7 brought the VideoMetadata table with 3.0 if (d->parameters.isSQLite()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db")); if (digikam3DB.exists()) { if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath())) { return false; } // d->currentVersion is now 4; return makeUpdates(); } } // No legacy handling: start with a fresh db if (!createDatabase() || !createFilterSettings()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->backend->lastError(); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep() { if (!d->backend->beginTransaction()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". " "This is unusual. Please check that you can access the file and no " "other process has currently locked the file. " "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QDir::toNativeSeparators(currentDBFile.filePath())); d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); return false; } return true; } bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg) { if (!stepOperationSuccess) { d->backend->rollbackTransaction(); if (d->observer) { // error or cancelled? if (!d->observer->continueQuery()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user"; } else if (!d->setError) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } } return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); d->backend->commitTransaction(); return true; } bool CoreDbSchemaUpdater::makeUpdates() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion(); if (d->currentVersion.toInt() < schemaVersion()) { if (d->currentVersion.toInt() < 5) { if (!beginWrapSchemaUpdateStep()) { return false; } // v4 was always SQLite QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, " "caused by an error that we did not expect. " "You can try to discard your old database and start with an empty one. " "(In this case, please move the database files " "\"%1\" and \"%2\" from the directory \"%3\"). " "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QLatin1String("digikam3.db"), QLatin1String("digikam4.db"), QDir::toNativeSeparators(currentDBFile.dir().path())); if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6"; // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set setLegacySettingEntries(); } // Incremental updates, starting from version 5 for (int v = d->currentVersion.toInt(); v < schemaVersion(); v++) { int targetVersion = v + 1; if (!beginWrapSchemaUpdateStep()) { return false; } QString errorMsg = i18n("Failed to update the database schema from version %1 to version %2. " "Please read the error messages printed on the console and " "report this error as a bug at bugs.kde.org. ", d->currentVersion.toInt(), targetVersion); if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); } // add future updates here } return true; } void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultImageFilter, QStringList& defaultVideoFilter, QStringList& defaultAudioFilter) { //NOTE for updating: //When changing anything here, just increment filterSettingsVersion() so that the changes take effect // https://en.wikipedia.org/wiki/Image_file_formats defaultImageFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000 << QLatin1String("tif") << QLatin1String("tiff") // TIFF << QLatin1String("png") // PNG << QLatin1String("gif") << QLatin1String("xpm") << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf") << QLatin1String("bmp") << QLatin1String("xcf") << QLatin1String("pcx") << QLatin1String("webp"); defaultImageFilter << DRawDecoder::rawFilesList(); // https://en.wikipedia.org/wiki/Video_file_format defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg") << QLatin1String("mpo") << QLatin1String("mpe") << QLatin1String("mts") << QLatin1String("vob") // MPEG << QLatin1String("avi") << QLatin1String("divx") // RIFF << QLatin1String("wmv") << QLatin1String("wmf") << QLatin1String("asf") // ASF << QLatin1String("mp4") << QLatin1String("3gp") << QLatin1String("mov") << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v") // QuickTime << QLatin1String("mkv") << QLatin1String("webm") // Matroska << QLatin1String("mng"); // Animated PNG image // https://en.wikipedia.org/wiki/Audio_file_format defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") << QLatin1String("wv") << QLatin1String("ape") // Linux audio << QLatin1String("mpc") << QLatin1String("au") // Linux audio << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa") // Book audio << QLatin1String("mp3") << QLatin1String("aac") // MPEG based audio << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") << QLatin1String("aiff") // Apple audio << QLatin1String("wma") << QLatin1String("wav"); // Windows audio } void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter) { // NOTE: when update this section, // just increment filterSettingsVersion() so that the changes take effect defaultIgnoreDirectoryFilter << QLatin1String("@eaDir"); } bool CoreDbSchemaUpdater::createFilterSettings() { QStringList defaultImageFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter; defaultFilterSettings(defaultImageFilter, defaultVideoFilter, defaultAudioFilter); defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setFilterSettings(defaultImageFilter, defaultVideoFilter, defaultAudioFilter); d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"), QString::number(filterSettingsVersion())); d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion())); return true; } bool CoreDbSchemaUpdater::updateFilterSettings() { QString filterVersion = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion")); QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion")); if (filterVersion.toInt() < filterSettingsVersion() || dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion()) { createFilterSettings(); } return true; } bool CoreDbSchemaUpdater::createDatabase() { if ( createTables() && createIndices() && createTriggers()) { setLegacySettingEntries(); d->currentVersion = schemaVersion(); // if we start with the V2 hash, version 6 is required d->albumDB->setUniqueHashVersion(uniqueHashVersion()); d->currentRequiredVersion = schemaVersion(); /* // Digikam for database version 5 can work with version 6, though not using the new features d->currentRequiredVersion = 5; */ return true; } else { return false; } } bool CoreDbSchemaUpdater::createTables() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB"))); } bool CoreDbSchemaUpdater::createIndices() { // TODO: see which more indices are needed // create indices return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices"))); } bool CoreDbSchemaUpdater::createTriggers() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers"))); } bool CoreDbSchemaUpdater::updateUniqueHash() { if (isUniqueHashUpToDate()) { return true; } readVersionSettings(); { CoreDbTransaction transaction; CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion()); CollectionScanner scanner; scanner.setNeedFileCount(true); scanner.setUpdateHashHint(); if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); // earlier digikam does not know about the hash if (d->currentRequiredVersion.toInt() < 6) { d->currentRequiredVersion = 6; setVersionSettings(); } } return true; } bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion) { if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(1); } DbEngineAction updateAction = d->backend->getDBAction(actionName); if (updateAction.name.isNull()) { QString errorMsg = i18n("The database update action cannot be found. Please ensure that " "the dbconfig.xml file of the current version of digiKam is installed " "at the correct place. "); } if (!d->backend->execDBAction(updateAction)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!"; // resort to default error message, set above return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion)); } d->currentVersion = newVersion; // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash d->currentRequiredVersion = newRequiredVersion; return true; } bool CoreDbSchemaUpdater::updateToVersion(int targetVersion) { if (d->currentVersion != targetVersion-1) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from" << d->currentVersion << "to" << targetVersion << ", aborting."; return false; } switch (targetVersion) { case 6: // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5); case 7: // Digikam for database version 5 and 6 can work with version 7, though not using the support for video files. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5); // NOTE: If you add a new update step, please check the d->currentVersion at the bottom of updateV4toV7 // If the update already comes with createTables, createTriggers, we don't need the extra update here case 8: // Digikam for database version 7 can work with version 8, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5); case 9: // Digikam for database version 8 can work with version 9, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5); + case 10: + // Digikam for database version 9 can work with version 10, remove ImageHaarMatrix table and add manualOrder column. + return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5); default: qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion; return false; } } bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath) { if (d->observer) { d->observer->moreSchemaUpdateSteps(2); } d->backend->close(); // We cannot use KIO here because KIO only works from the main thread QFile oldFile(digikam3DBPath); QFile newFile(currentDBPath); // QFile won't override. Remove the empty db file created when a non-existent file is opened newFile.remove(); if (!oldFile.copy(currentDBPath)) { QString errorMsg = i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Error message: \"%3\". " "Please make sure that the file can be copied, " "or delete it.", digikam3DBPath, currentDBPath, oldFile.errorString()); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Copied database file")); } if (!d->backend->open(d->parameters)) { QString errorMsg = i18n("The old database file (\"%1\") has been copied " "to the new location (\"%2\") but it cannot be opened. " "Please delete both files and try again, " "starting with an empty database. ", digikam3DBPath, currentDBPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Opened new database file")); } d->currentVersion = 4; return true; } static QStringList cleanUserFilterString(const QString& filterString) { // splits by either ; or space, removes "*.", trims QStringList filterList; QString wildcard(QLatin1String("*.")); QChar dot(QLatin1Char('.')); QChar sep(QLatin1Char(';')); int i = filterString.indexOf( sep ); if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 ) { sep = QChar(QLatin1Char(' ')); } QStringList sepList = filterString.split(sep, QString::SkipEmptyParts); foreach(const QString& f, sepList) { if (f.startsWith(wildcard)) { filterList << f.mid(2).trimmed().toLower(); } else { filterList << f.trimmed().toLower(); } } return filterList; } bool CoreDbSchemaUpdater::updateV4toV7() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7"; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(11); } // This update was introduced from digikam version 0.9 to digikam 0.10 // We operator on an SQLite3 database under a transaction (which will be rolled back on error) // --- Make space for new tables --- if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables"; // --- Drop some triggers and indices --- // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;")); d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Prepared table creation")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers"; // --- Create new tables --- if (!createTables() || !createIndices()) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Created tables")); } // --- Populate AlbumRoots (from config) --- KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Album Settings")); QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString()); if (albumLibraryPath.isEmpty()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update."; QString errorMsg = i18n("No album library path has been found in the configuration file. " "Giving up the schema updating process. " "Please try with an empty database, or repair your configuration."); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath)); CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName()); if (location.isNull()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update."; QString errorMsg = i18n("There was an error associating your albumLibraryPath (\"%1\") " "with a storage volume of your system. " "This problem may indicate that there is a problem with your installation. " "If you are working on Linux, check that HAL is installed and running. " "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). " "The database updating process will now be aborted because we do not want " "to create a new database based on false assumptions from a broken installation.", albumLibraryPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Configured one album root")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root"; // --- With the album root, populate albums --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Albums " " (id, albumRoot, relativePath, date, caption, collection, icon) " "SELECT id, ?, url, date, caption, collection, icon " " FROM AlbumsV3;" ), location.id()) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported albums")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums"; // --- Add images --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Images " " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) " "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL" " FROM ImagesV3;" ), DatabaseItem::Visible, DatabaseItem::UndefinedCategory) ) { return false; } if (!d->dbAccess->backend()->execSql(QString::fromUtf8( "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;")) ) { return false; } // remove orphan images that would not be removed by CollectionScanner d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported images information")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images"; // --- Port searches --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return false; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ImageQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); QString name = (*it).name; if (name == i18n("Last Search")) { name = i18n("Last Search (0.9)"); } if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query); } } // --- Create triggers --- if (!createTriggers()) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers"; // --- Populate name filters --- createFilterSettings(); // --- Set user settings from config --- QStringList defaultImageFilter, defaultVideoFilter, defaultAudioFilter; defaultFilterSettings(defaultImageFilter, defaultVideoFilter, defaultAudioFilter); QSet configImageFilter, configVideoFilter, configAudioFilter; configImageFilter = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"), QString())).toSet(); configImageFilter += cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"), QString())).toSet(); configVideoFilter = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())).toSet(); configAudioFilter = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())).toSet(); // remove those that are included in the default filter configImageFilter.subtract(defaultImageFilter.toSet()); configVideoFilter.subtract(defaultVideoFilter.toSet()); configAudioFilter.subtract(defaultAudioFilter.toSet()); d->albumDB->setUserFilterSettings(configImageFilter.toList(), configVideoFilter.toList(), configAudioFilter.toList()); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configImageFilter; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter")); } // --- do a full scan --- CollectionScanner scanner; if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Did the initial full scan")); } // --- Port date, comment and rating (_after_ the scan) --- // Port ImagesV3.date -> ImageInformation.creationDate if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) " "WHERE imageid IN (SELECT id FROM ImagesV3);") ) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported creation dates")); } // Port ImagesV3.comment to ImageComments // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around. d->backend->execSql(QString::fromUtf8( "DELETE FROM ImageComments WHERE " "type=? AND language=? AND author IS NULL " "AND imageid IN ( SELECT id FROM ImagesV3 ); "), (int)DatabaseComment::Comment, QLatin1String("x-default")); if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO ImageComments " " (imageid, type, language, comment) " "SELECT id, ?, ?, caption FROM ImagesV3;" ), (int)DatabaseComment::Comment, QLatin1String("x-default")) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported comments")); } // Port rating storage in ImageProperties to ImageInformation if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " rating=(SELECT value FROM ImageProperties " " WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) " "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);" ), QString::fromUtf8("Rating"), QString::fromUtf8("Rating")) ) { return false; } d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating")); d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported ratings")); } // --- Drop old tables --- d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); if (d->observer) { d->observer->schemaUpdateProgress(i18n("Dropped v3 tables")); } d->currentRequiredVersion = 5; d->currentVersion = 7; qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5"; return true; } void CoreDbSchemaUpdater::setLegacySettingEntries() { d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } // ---------- Legacy code ------------ void CoreDbSchemaUpdater::preAlpha010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return; } if ( !d->backend->execSql( QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches \n" " (id INTEGER PRIMARY KEY, \n" " type INTEGER, \n" " name TEXT NOT NULL, \n" " query TEXT NOT NULL);" ) )) { return; } if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ImageQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query); } } d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ImagePositionsTemp;"))) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;"))) { return; } d->backend->execSql( QString::fromUtf8("CREATE TABLE ImagePositions\n" " (imageid INTEGER PRIMARY KEY,\n" " latitude TEXT,\n" " latitudeNumber REAL,\n" " longitude TEXT,\n" " longitudeNumber REAL,\n" " altitude REAL,\n" " orientation REAL,\n" " tilt REAL,\n" " roll REAL,\n" " accuracy REAL,\n" " description TEXT);") ); d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, 0, description " " FROM ImagePositionsTemp;")); d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageMetadata\n" " (imageid INTEGER PRIMARY KEY,\n" " make TEXT,\n" " model TEXT,\n" " lens TEXT,\n" " aperture REAL,\n" " focalLength REAL,\n" " focalLength35 REAL,\n" " exposureTime REAL,\n" " exposureProgram INTEGER,\n" " exposureMode INTEGER,\n" " sensitivity INTEGER,\n" " flash INTEGER,\n" " whiteBalance INTEGER,\n" " whiteBalanceColorTemperature INTEGER,\n" " meteringMode INTEGER,\n" " subjectDistance REAL,\n" " subjectDistanceCategory INTEGER);") ); d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata " " (imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadataTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImagePositionsTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update3() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3")); if (!hasUpdate.isNull()) { return; } d->backend->execSql(QString::fromUtf8("DROP TABLE ImageCopyright;")); d->backend->execSql(QString::fromUtf8("CREATE TABLE ImageCopyright\n" " (imageid INTEGER,\n" " property TEXT,\n" " value TEXT,\n" " extraValue TEXT,\n" " UNIQUE(imageid, property, value, extraValue));") ); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1")); if (!hasUpdate.isNull()) { return; } // if Image has been deleted d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageHaarMatrix\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageInformation\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageMetadata\n " " WHERE imageid=OLD.id;\n" " DELETE From ImagePositions\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageComments\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageCopyright\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2")); if (!hasUpdate.isNull()) { return; } // force rescan and creation of ImageInformation entry for videos and audio d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } bool CoreDbSchemaUpdater::createTablesV3() { if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n" " (id INTEGER PRIMARY KEY,\n" " url TEXT NOT NULL UNIQUE,\n" " date DATE NOT NULL,\n" " caption TEXT,\n" " collection TEXT,\n" " icon INTEGER);") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n" " (id INTEGER PRIMARY KEY,\n" " pid INTEGER,\n" " name TEXT NOT NULL,\n" " icon INTEGER,\n" " iconkde TEXT,\n" " UNIQUE (name, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n" " (id INTEGER NOT NULL,\n" " pid INTEGER NOT NULL,\n" " UNIQUE (id, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n" " (id INTEGER PRIMARY KEY,\n" " name TEXT NOT NULL,\n" " dirid INTEGER NOT NULL,\n" " caption TEXT,\n" " datetime DATETIME,\n" " UNIQUE (name, dirid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n" " (imageid INTEGER NOT NULL,\n" " tagid INTEGER NOT NULL,\n" " UNIQUE (imageid, tagid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n" " (imageid INTEGER NOT NULL,\n" " property TEXT NOT NULL,\n" " value TEXT NOT NULL,\n" " UNIQUE (imageid, property));") )) { return false; } if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches \n" " (id INTEGER PRIMARY KEY, \n" " name TEXT NOT NULL UNIQUE, \n" " url TEXT NOT NULL);") ) ) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings \n" "(keyword TEXT NOT NULL UNIQUE,\n" " value TEXT);") )) { return false; } // TODO: see which more indices are needed // create indices d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images (dirid);")); d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);")); // create triggers // trigger: delete from Images/ImageTags/ImageProperties // if Album has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE From ImageProperties\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE FROM Images\n" " WHERE dirid = OLD.id;\n" "END;")); // trigger: delete from ImageTags/ImageProperties // if Image has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); // trigger: delete from ImageTags if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n" "BEGIN\n" " DELETE FROM ImageTags WHERE tagid=OLD.id;\n" "END;")); // trigger: insert into TagsTree if Tag has been added d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n" "BEGIN\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n" "BEGIN\n" " DELETE FROM Tags\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id=OLD.id;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n" "BEGIN\n" " DELETE FROM TagsTree\n" " WHERE\n" " ((id = OLD.id)\n" " OR\n" " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n" " AND\n" " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n" " UNION\n" " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n" " UNION\n" " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n" " WHERE\n" " A.pid = NEW.id AND B.id = NEW.pid;\n" "END;")); return true; } } // namespace Digikam diff --git a/core/libs/database/tags/facetags.cpp b/core/libs/database/tags/facetags.cpp index dc1f965ce2..2b2ed01c5a 100644 --- a/core/libs/database/tags/facetags.cpp +++ b/core/libs/database/tags/facetags.cpp @@ -1,449 +1,450 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2011-08-08 * Description : Accessing face tags * * Copyright (C) 2010-2011 by Aditya Bhatt * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "facetags.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "coredbconstants.h" #include "tagscache.h" #include "tagregion.h" #include "tagproperties.h" namespace Digikam { // --- FaceIfacePriv ---------------------------------------------------------------------------------------- class FaceTagsHelper { public: static QString tagPath(const QString& name, int parentId); static void makeFaceTag(int tagId, const QString& fullName); static int findFirstTagWithProperty(const QString& property, const QString& value = QString()); static int tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create); }; // --- Private methods --- int FaceTagsHelper::findFirstTagWithProperty(const QString& property, const QString& value) { QList candidates = TagsCache::instance()->tagsWithProperty(property, value); if (!candidates.isEmpty()) { return candidates.first(); } return 0; } QString FaceTagsHelper::tagPath(const QString& name, int parentId) { QString faceParentTagName = TagsCache::instance()->tagName(parentId); if ((faceParentTagName).contains(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) { return QLatin1Char('/') + name; } else { return faceParentTagName + QLatin1Char('/') + name; } } void FaceTagsHelper::makeFaceTag(int tagId, const QString& fullName) { QString faceEngineName = fullName; /* * // find a unique FacesEngineId * for (int i=0; d->findFirstTagWithProperty(TagPropertyName::FacesEngineId(), FacesEngineId); ++i) * FacesEngineId = fullName + QString::fromUtf8(" (%1)").arg(i); */ TagProperties props(tagId); props.setProperty(TagPropertyName::person(), fullName); props.setProperty(TagPropertyName::faceEngineName(), faceEngineName); } int FaceTagsHelper::tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create) { if (name.isEmpty() && givenFullName.isEmpty() && !tagId) { return FaceTags::unknownPersonTagId(); } QString fullName = givenFullName.isNull() ? name : givenFullName; if (tagId) { if (FaceTags::isPerson(tagId)) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Proposed tag is already a person"; return tagId; } else if (convert) { if (fullName.isNull()) { fullName = TagsCache::instance()->tagName(tagId); } qCDebug(DIGIKAM_DATABASE_LOG) << "Converting proposed tag to person, full name" << fullName; makeFaceTag(tagId, fullName); return tagId; } return 0; } // First attempt: Find by full name in "person" attribute QList candidates = TagsCache::instance()->tagsWithProperty(TagPropertyName::person(), fullName); foreach(int id, candidates) { qCDebug(DIGIKAM_DATABASE_LOG) << "Candidate with set full name:" << id << fullName; if (parentId == -1) { return id; } else if (TagsCache::instance()->parentTag(id) == parentId) { return id; } } // Second attempt: Find by tag name if (parentId == -1) { candidates = TagsCache::instance()->tagsForName(name); } else { tagId = TagsCache::instance()->tagForName(name, parentId); candidates.clear(); if (tagId) { candidates << tagId; } } foreach(int id, candidates) { // Is this tag already a person tag? if (FaceTags::isPerson(id)) { qCDebug(DIGIKAM_DATABASE_LOG) << "Found tag with name" << name << "is already a person." << id; return id; } else if (convert) { qCDebug(DIGIKAM_DATABASE_LOG) << "Converting tag with name" << name << "to a person." << id; makeFaceTag(id, fullName); return id; } } // Third: If desired, create a new tag if (create) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new tag for name" << name << "fullName" << fullName; if (parentId == -1) { parentId = FaceTags::personParentTag(); } tagId = TagsCache::instance()->getOrCreateTag(tagPath(name, parentId)); makeFaceTag(tagId, fullName); return tagId; } return 0; } // --- public methods --- QList FaceTags::allPersonNames() { return TagsCache::instance()->tagNames(allPersonTags()); } QList FaceTags::allPersonPaths() { return TagsCache::instance()->tagPaths(allPersonTags()); } int FaceTags::tagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, false, false); } int FaceTags::getOrCreateTagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, true, true); } void FaceTags::ensureIsPerson(int tagId, const QString& fullName) { FaceTagsHelper::tagForName(QString(), tagId, 0, fullName, true, false); } bool FaceTags::isPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::person()); } bool FaceTags::isTheUnknownPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unknownPerson()); } bool FaceTags::isTheUnconfirmedPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unconfirmedPerson()); } QList FaceTags::allPersonTags() { return TagsCache::instance()->tagsWithProperty(TagPropertyName::person()); } int FaceTags::scannedForFacesTagId() { return TagsCache::instance()->getOrCreateInternalTag(InternalTagName::scannedForFaces()); // no i18n } QMap FaceTags::identityAttributes(int tagId) { QMap attributes; QString uuid = TagsCache::instance()->propertyValue(tagId, TagPropertyName::faceEngineUuid()); if (!uuid.isEmpty()) { attributes[QLatin1String("uuid")] = uuid; } QString fullName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (!fullName.isEmpty()) { attributes[QLatin1String("fullName")] = fullName; } QString faceEngineName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); - QString tagName = TagsCache::instance()->tagName(tagId); + QString tagName = TagsCache::instance()->tagName(tagId); if (tagName != faceEngineName) { attributes.insertMulti(QLatin1String("name"), faceEngineName); attributes.insertMulti(QLatin1String("name"), tagName); } else { attributes[QLatin1String("name")] = tagName; } return attributes; } void FaceTags::applyTagIdentityMapping(int tagId, const QMap& attributes) { TagProperties props(tagId); if (attributes.contains(QLatin1String("fullName"))) { props.setProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName"))); } // we do not change the digikam tag name at this point, but we have this extra tag property if (attributes.contains(QLatin1String("name"))) { props.setProperty(TagPropertyName::faceEngineName(), attributes.value(QLatin1String("name"))); } props.setProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid"))); } int FaceTags::getOrCreateTagForIdentity(const QMap& attributes) { // Attributes from FacesEngine's Identity object. // The text constants are defines in FacesEngine's API docs if (attributes.isEmpty()) { return FaceTags::unknownPersonTagId(); } int tagId; // First, look for UUID if (!attributes.value(QLatin1String("uuid")).isEmpty()) { if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid")))) ) { return tagId; } } // Second, look for full name if (!attributes.value(QLatin1String("fullName")).isEmpty()) { if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName")))) ) { return tagId; } } // Third, look for either name or full name // TODO: better support for "fullName" QString name = attributes.value(QLatin1String("name")); if (name.isEmpty()) { name = attributes.value(QLatin1String("fullName")); } if (name.isEmpty()) { return FaceTags::unknownPersonTagId(); } if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineName(), name)) ) { return tagId; } if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), name)) ) { return tagId; } // identity is in FacesEngine's database, but not in ours, so create. tagId = FaceTagsHelper::tagForName(name, 0, -1, attributes.value(QLatin1String("fullName")), true, true); applyTagIdentityMapping(tagId, attributes); return tagId; } QString FaceTags::faceNameForTag(int tagId) { if (!TagsCache::instance()->hasTag(tagId)) { return QString(); } QString id = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (id.isNull()) { id = TagsCache::instance()->tagName(tagId); } return id; } int FaceTags::personParentTag() { // check default QString i18nName = i18nc("People on your photos", "People"); int tagId = TagsCache::instance()->tagForPath(i18nName); if (tagId) { return tagId; } // employ a heuristic QList personTags = allPersonTags(); if (!personTags.isEmpty()) { // we find the most toplevel parent tag of a person tag - QMultiMap tiers; + QMultiMap tiers; foreach(int tagId, personTags) { tiers.insert(TagsCache::instance()->parentTags(tagId).size(), tagId); } QList mosttoplevelTags = tiers.values(tiers.begin().key()); // as a pretty weak criterion, take the largest id which usually corresponds to the latest tag creation. std::sort(mosttoplevelTags.begin(), mosttoplevelTags.end()); + return TagsCache::instance()->parentTag(mosttoplevelTags.last()); } // create default return TagsCache::instance()->getOrCreateTag(i18nName); } int FaceTags::unknownPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unknownPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of detected faces from the collections but not recognized", "Unknown"), personParentTag())); TagProperties props(unknownPersonTagId); props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unknownPerson(), QString()); // special property return unknownPersonTagId; } int FaceTags::unconfirmedPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unconfirmedPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of recognized faces from the collections but not confirmed", "Unconfirmed"), personParentTag())); TagProperties props(unknownPersonTagId); - props.setProperty(TagPropertyName::person(), QString()); // no name associated + props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unconfirmedPerson(), QString()); // special property return unknownPersonTagId; } } // Namespace Digikam diff --git a/core/libs/dmetadata/dmetadata.cpp b/core/libs/dmetadata/dmetadata.cpp index d8e83bf9cb..11c481bc81 100644 --- a/core/libs/dmetadata/dmetadata.cpp +++ b/core/libs/dmetadata/dmetadata.cpp @@ -1,3443 +1,3443 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-02-23 * Description : image metadata interface * * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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, 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. * * ============================================================ */ #include "dmetadata.h" // C++ includes #include // Qt includes #include #include #include #include // Local includes #include "rawinfo.h" #include "drawdecoder.h" #include "filereadwritelock.h" #include "metadatasettings.h" #include "template.h" #include "dimg.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { DMetadata::DMetadata() : MetaEngine() { registerMetadataSettings(); } DMetadata::DMetadata(const QString& filePath) : MetaEngine() { registerMetadataSettings(); load(filePath); } DMetadata::DMetadata(const MetaEngineData& data) : MetaEngine(data) { registerMetadataSettings(); } DMetadata::~DMetadata() { } void DMetadata::registerMetadataSettings() { setSettings(MetadataSettings::instance()->settings()); } void DMetadata::setSettings(const MetadataSettingsContainer& settings) { setUseXMPSidecar4Reading(settings.useXMPSidecar4Reading); setWriteRawFiles(settings.writeRawFiles); setMetadataWritingMode(settings.metadataWritingMode); setUpdateFileTimeStamp(settings.updateFileTimeStamp); } bool DMetadata::load(const QString& filePath) { // In first, we trying to get metadata using Exiv2, // else we will use other engine to extract minimal information. FileReadLocker lock(filePath); if (!MetaEngine::load(filePath)) { if (!loadUsingRawEngine(filePath)) { if (!loadUsingFFmpeg(filePath)) { return false; } } } return true; } bool DMetadata::save(const QString& filePath, bool setVersion) const { FileWriteLocker lock(filePath); return MetaEngine::save(filePath, setVersion); } bool DMetadata::applyChanges() const { FileWriteLocker lock(getFilePath()); return MetaEngine::applyChanges(); } bool DMetadata::loadUsingRawEngine(const QString& filePath) { RawInfo identify; if (DRawDecoder::rawFileIdentify(identify, filePath)) { long int num=1, den=1; if (!identify.model.isNull()) { setExifTagString("Exif.Image.Model", identify.model); } if (!identify.make.isNull()) { setExifTagString("Exif.Image.Make", identify.make); } if (!identify.owner.isNull()) { setExifTagString("Exif.Image.Artist", identify.owner); } if (identify.sensitivity != -1) { setExifTagLong("Exif.Photo.ISOSpeedRatings", lroundf(identify.sensitivity)); } if (identify.dateTime.isValid()) { setImageDateTime(identify.dateTime, false); } if (identify.exposureTime != -1.0) { convertToRationalSmallDenominator(identify.exposureTime, &num, &den); setExifTagRational("Exif.Photo.ExposureTime", num, den); } if (identify.aperture != -1.0) { convertToRational(identify.aperture, &num, &den, 8); setExifTagRational("Exif.Photo.ApertureValue", num, den); } if (identify.focalLength != -1.0) { convertToRational(identify.focalLength, &num, &den, 8); setExifTagRational("Exif.Photo.FocalLength", num, den); } if (identify.imageSize.isValid()) { setImageDimensions(identify.imageSize); } // A RAW image is always uncalibrated. */ setImageColorWorkSpace(WORKSPACE_UNCALIBRATED); return true; } return false; } int DMetadata::getMSecsInfo() const { int ms = 0; bool ok = mSecTimeStamp("Exif.Photo.SubSecTime", ms); if (ok) return ms; ok = mSecTimeStamp("Exif.Photo.SubSecTimeOriginal", ms); if (ok) return ms; ok = mSecTimeStamp("Exif.Photo.SubSecTimeDigitized", ms); if (ok) return ms; return 0; } bool DMetadata::mSecTimeStamp(const char* const exifTagName, int& ms) const { bool ok = false; QString val = getExifTagString(exifTagName); if (!val.isEmpty()) { int sub = val.toUInt(&ok); if (ok) { int _ms = (int)(QString::fromLatin1("0.%1").arg(sub).toFloat(&ok) * 1000.0); if (ok) { ms = _ms; qCDebug(DIGIKAM_METAENGINE_LOG) << "msec timestamp: " << ms; } } } return ok; } CaptionsMap DMetadata::getImageComments(const DMetadataSettingsContainer& settings) const { if (getFilePath().isEmpty()) { return CaptionsMap(); } CaptionsMap captionsMap; MetaEngine::AltLangMap authorsMap; MetaEngine::AltLangMap datesMap; MetaEngine::AltLangMap commentsMap; QString commonAuthor; // In first try to get captions properties from digiKam XMP namespace if (supportXmp()) { authorsMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", false); datesMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", false); if (authorsMap.isEmpty() && commonAuthor.isEmpty()) { QString xmpAuthors = getXmpTagString("Xmp.acdsee.author", false); if (!xmpAuthors.isEmpty()) { authorsMap.insert(QLatin1String("x-default"), xmpAuthors); } } } // Get author name from IPTC DescriptionWriter. Private namespace above gets precedence. QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter); if (!descriptionWriter.isNull()) { commonAuthor = descriptionWriter.toString(); } // In first, we check XMP alternative language tags to create map of values. bool xmpSupported = hasXmp(); bool iptcSupported = hasIptc(); bool exivSupported = hasExif(); - for (NamespaceEntry entry : settings.getReadMapping(QLatin1String(DM_COMMENT_CONTAINER))) + for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER))) { if (entry.isDisabled) continue; QString commentString; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.subspace) { case NamespaceEntry::XMP: switch(entry.specialOpts) { case NamespaceEntry::COMMENT_ALTLANG: if (xmpSupported) commentString = getXmpTagStringLangAlt(nameSpace, QString(), false); break; case NamespaceEntry::COMMENT_ATLLANGLIST: if (xmpSupported) commentsMap = getXmpTagStringListLangAlt(nameSpace, false); break; case NamespaceEntry::COMMENT_XMP: if (xmpSupported) commentString = getXmpTagString("Xmp.acdsee.notes", false); break; case NamespaceEntry::COMMENT_JPEG: // Now, we trying to get image comments, outside of XMP. // For JPEG, string is extracted from JFIF Comments section. // For PNG, string is extracted from iTXt chunk. commentString = getCommentsDecoded(); default: break; } break; case NamespaceEntry::IPTC: if (iptcSupported) commentString = getIptcTagString(nameSpace, false); break; case NamespaceEntry::EXIF: if (exivSupported) commentString = getExifComment(); break; default: break; } if (!commentString.isEmpty() &&!commentString.trimmed().isEmpty()) { commentsMap.insert(QLatin1String("x-default"), commentString); captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap); return captionsMap; } if (!commentsMap.isEmpty()) { captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap); return captionsMap; } } return captionsMap; } bool DMetadata::setImageComments(const CaptionsMap& comments, const DMetadataSettingsContainer &settings) const { /* // See bug #139313: An empty string is also a valid value if (comments.isEmpty()) return false; */ qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Comment: " << comments; // In first, set captions properties to digiKam XMP namespace if (supportXmp()) { if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", comments.authorsList())) { return false; } QString defaultAuthor = comments.value(QLatin1String("x-default")).author; removeXmpTag("Xmp.acdsee.author"); if (!defaultAuthor.isNull()) { if (!setXmpTagString("Xmp.acdsee.author", defaultAuthor)) { return false; } } if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", comments.datesList())) { return false; } } QString defaultComment = comments.value(QLatin1String("x-default")).caption; - QList toWrite = settings.getReadMapping(QLatin1String(DM_COMMENT_CONTAINER)); + QList toWrite = settings.getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)); if (!settings.unifyReadWrite()) - toWrite = settings.getWriteMapping(QLatin1String(DM_COMMENT_CONTAINER)); + toWrite = settings.getWriteMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)); for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) continue; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.subspace) { case NamespaceEntry::XMP: if (entry.namespaceName.contains(QLatin1String("Xmp."))) removeXmpTag(nameSpace); switch(entry.specialOpts) { case NamespaceEntry::COMMENT_ALTLANG: if (!defaultComment.isNull()) { if (!setXmpTagStringLangAlt(nameSpace, defaultComment, QString())) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image comment failed" << nameSpace; return false; } } break; case NamespaceEntry::COMMENT_ATLLANGLIST: if (!setXmpTagStringListLangAlt(nameSpace, comments.toAltLangMap())) { return false; } break; case NamespaceEntry::COMMENT_XMP: if (!defaultComment.isNull()) { if (!setXmpTagString(nameSpace, defaultComment)) { return false; } } break; case NamespaceEntry::COMMENT_JPEG: // In first we set image comments, outside of Exif, XMP, and IPTC. if (!setComments(defaultComment.toUtf8())) { return false; } break; default: break; } break; case NamespaceEntry::IPTC: removeIptcTag(nameSpace); if (!defaultComment.isNull()) { defaultComment.truncate(2000); if (!setIptcTagString(nameSpace, defaultComment)) { return false; } } break; case NamespaceEntry::EXIF: if (!setExifComment(defaultComment)) { return false; } break; default: break; } } return true; } int DMetadata::getImagePickLabel() const { if (getFilePath().isEmpty()) { return -1; } if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.PickLabel", false); if (!value.isEmpty()) { bool ok = false; long pickId = value.toLong(&ok); if (ok && pickId >= NoPickLabel && pickId <= AcceptedLabel) { return pickId; } } } return -1; } int DMetadata::getImageColorLabel() const { if (getFilePath().isEmpty()) { return -1; } if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.ColorLabel", false); if (value.isEmpty()) { // Nikon NX use this XMP tags to store Color Labels value = getXmpTagString("Xmp.photoshop.Urgency", false); } if (!value.isEmpty()) { bool ok = false; long colorId = value.toLong(&ok); if (ok && colorId >= NoColorLabel && colorId <= WhiteLabel) { return colorId; } } // LightRoom use this tag to store color name as string. // Values are limited : see bug #358193. value = getXmpTagString("Xmp.xmp.Label", false); if (value == QLatin1String("Blue")) { return BlueLabel; } else if (value == QLatin1String("Green")) { return GreenLabel; } else if (value == QLatin1String("Red")) { return RedLabel; } else if (value == QLatin1String("Yellow")) { return YellowLabel; } else if (value == QLatin1String("Purple")) { return MagentaLabel; } } return -1; } CaptionsMap DMetadata::getImageTitles() const { if (getFilePath().isEmpty()) return CaptionsMap(); CaptionsMap captionsMap; MetaEngine::AltLangMap authorsMap; MetaEngine::AltLangMap datesMap; MetaEngine::AltLangMap titlesMap; QString commonAuthor; // Get author name from IPTC DescriptionWriter. Private namespace above gets precedence. QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter); if (!descriptionWriter.isNull()) commonAuthor = descriptionWriter.toString(); // In first, we check XMP alternative language tags to create map of values. if (hasXmp()) { titlesMap = getXmpTagStringListLangAlt("Xmp.dc.title", false); if (!titlesMap.isEmpty()) { captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); return captionsMap; } QString xmpTitle = getXmpTagString("Xmp.acdsee.caption" ,false); if (!xmpTitle.isEmpty() && !xmpTitle.trimmed().isEmpty()) { titlesMap.insert(QLatin1String("x-default"), xmpTitle); captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); return captionsMap; } } // We trying to get IPTC title if (hasIptc()) { QString iptcTitle = getIptcTagString("Iptc.Application2.ObjectName", false); if (!iptcTitle.isEmpty() && !iptcTitle.trimmed().isEmpty()) { titlesMap.insert(QLatin1String("x-default"), iptcTitle); captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); return captionsMap; } } return captionsMap; } bool DMetadata::setImageTitles(const CaptionsMap& titles) const { qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Title: " << titles; QString defaultTitle = titles[QLatin1String("x-default")].caption; // In First we write comments into XMP. Language Alternative rule is not yet used. if (supportXmp()) { // NOTE : setXmpTagStringListLangAlt remove xmp tag before to add new values if (!setXmpTagStringListLangAlt("Xmp.dc.title", titles.toAltLangMap())) { return false; } removeXmpTag("Xmp.acdsee.caption"); if (!defaultTitle.isEmpty()) { if (!setXmpTagString("Xmp.acdsee.caption", defaultTitle)) { return false; } } } // In Second we write comments into IPTC. // Note that Caption IPTC tag is limited to 64 char and ASCII charset. removeIptcTag("Iptc.Application2.ObjectName"); if (!defaultTitle.isNull()) { defaultTitle.truncate(64); // See if we have any non printable chars in there. If so, skip IPTC // to avoid confusing other apps and web services with invalid tags. bool hasInvalidChar = false; for (QString::const_iterator c = defaultTitle.constBegin(); c != defaultTitle.constEnd(); ++c) { if (!(*c).isPrint()) { hasInvalidChar = true; break; } } if (!hasInvalidChar) { if (!setIptcTagString("Iptc.Application2.ObjectName", defaultTitle)) return false; } } return true; } int DMetadata::getImageRating(const DMetadataSettingsContainer &settings) const { if (getFilePath().isEmpty()) { return -1; } long rating = -1; bool xmpSupported = hasXmp(); bool iptcSupported = hasIptc(); bool exivSupported = hasExif(); - for (NamespaceEntry entry : settings.getReadMapping(QLatin1String(DM_RATING_CONTAINER))) + for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER))) { if (entry.isDisabled) continue; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); QString value; switch(entry.subspace) { case NamespaceEntry::XMP: if (xmpSupported) value = getXmpTagString(nameSpace, false); break; case NamespaceEntry::IPTC: if (iptcSupported) value = QString::fromUtf8(getIptcTagData(nameSpace)); break; case NamespaceEntry::EXIF: if (exivSupported) getExifTagLong(nameSpace, rating); break; default: break; } if (!value.isEmpty()) { bool ok = false; rating = value.toLong(&ok); if (!ok) { return -1; } } int index = entry.convertRatio.indexOf(rating); // Exact value was not found,but rating is in range, // so we try to aproximate it if ((index == -1) && (rating > entry.convertRatio.first()) && (rating < entry.convertRatio.last())) { for (int i = 0 ; i < entry.convertRatio.size() ; i++) { if (rating > entry.convertRatio.at(i)) { index = i; } } } if (index != -1) { return index; } } return -1; } bool DMetadata::setImagePickLabel(int pickId) const { if (pickId < NoPickLabel || pickId > AcceptedLabel) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Pick Label value to write is out of range!"; return false; } qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Pick Label: " << pickId; if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.PickLabel", QString::number(pickId))) { return false; } } return true; } bool DMetadata::setImageColorLabel(int colorId) const { if (colorId < NoColorLabel || colorId > WhiteLabel) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Color Label value to write is out of range!"; return false; } qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Color Label: " << colorId; if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.ColorLabel", QString::number(colorId))) { return false; } // Nikon NX use this XMP tags to store Color Labels if (!setXmpTagString("Xmp.photoshop.Urgency", QString::number(colorId))) { return false; } // LightRoom use this XMP tags to store Color Labels name // Values are limited : see bug #358193. QString LRLabel; switch(colorId) { case BlueLabel: LRLabel = QLatin1String("Blue"); break; case GreenLabel: LRLabel = QLatin1String("Green"); break; case RedLabel: LRLabel = QLatin1String("Red"); break; case YellowLabel: LRLabel = QLatin1String("Yellow"); break; case MagentaLabel: LRLabel = QLatin1String("Purple"); break; } if (!LRLabel.isEmpty()) { if (!setXmpTagString("Xmp.xmp.Label", LRLabel)) { return false; } } } return true; } bool DMetadata::setImageRating(int rating, const DMetadataSettingsContainer &settings) const { // NOTE : with digiKam 0.9.x, we have used IPTC Urgency to store Rating. // Now this way is obsolete, and we use standard XMP rating tag instead. if (rating < RatingMin || rating > RatingMax) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Rating value to write is out of range!"; return false; } qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Rating:" << rating; - QList toWrite = settings.getReadMapping(QLatin1String(DM_RATING_CONTAINER)); + QList toWrite = settings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER)); if (!settings.unifyReadWrite()) - toWrite = settings.getWriteMapping(QLatin1String(DM_RATING_CONTAINER)); + toWrite = settings.getWriteMapping(QString::fromUtf8(DM_RATING_CONTAINER)); for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) continue; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.subspace) { case NamespaceEntry::XMP: if (!setXmpTagString(nameSpace, QString::number(entry.convertRatio.at(rating)))) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting rating failed" << nameSpace; return false; } break; case NamespaceEntry::EXIF: if (!setExifTagLong(nameSpace, rating)) { return false; } break; case NamespaceEntry::IPTC: // IPTC rating deprecated default: break; } } // Set Exif rating tag used by Windows Vista. if (!setExifTagLong("Exif.Image.0x4746", rating)) { return false; } // Wrapper around rating percents managed by Windows Vista. int ratePercents = 0; switch (rating) { case 0: ratePercents = 0; break; case 1: ratePercents = 1; break; case 2: ratePercents = 25; break; case 3: ratePercents = 50; break; case 4: ratePercents = 75; break; case 5: ratePercents = 99; break; } if (!setExifTagLong("Exif.Image.0x4749", ratePercents)) { return false; } return true; } bool DMetadata::setImageHistory(QString& imageHistoryXml) const { if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.ImageHistory", imageHistoryXml)) { return false; } else { return true; } } return false; } QString DMetadata::getImageHistory() const { if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.ImageHistory", false); qCDebug(DIGIKAM_METAENGINE_LOG) << "Loading image history " << value; return value; } return QString(); } bool DMetadata::hasImageHistoryTag() const { if (hasXmp()) { if (QString(getXmpTagString("Xmp.digiKam.ImageHistory", false)).length() > 0) { return true; } else { return false; } } return false; } QString DMetadata::getImageUniqueId() const { QString exifUid; if (hasXmp()) { QString uuid = getXmpTagString("Xmp.digiKam.ImageUniqueID"); if (!uuid.isEmpty()) { return uuid; } exifUid = getXmpTagString("Xmp.exif.ImageUniqueId"); } if (exifUid.isEmpty()) { exifUid = getExifTagString("Exif.Photo.ImageUniqueID"); } // same makers may choose to use a "click counter" to generate the id, // which is then weak and not a universally unique id // The Exif ImageUniqueID is 128bit, or 32 hex digits. // If the first 20 are zero, it's probably a counter, // the left 12 are sufficient for more then 10^14 clicks. if (!exifUid.isEmpty() && !exifUid.startsWith(QLatin1String("00000000000000000000"))) { if (getExifTagString("Exif.Image.Make").contains(QLatin1String("SAMSUNG"), Qt::CaseInsensitive)) { // Generate for Samsung a new random 32 hex digits unique ID. QString imageUniqueID(QUuid::createUuid().toString()); imageUniqueID.remove(QLatin1Char('-')); imageUniqueID.remove(0, 1).chop(1); return imageUniqueID; } return exifUid; } // Exif.Image.ImageID can also be a pathname, so it's not sufficiently unique QString dngUid = getExifTagString("Exif.Image.RawDataUniqueID"); if (!dngUid.isEmpty()) { return dngUid; } return QString(); } bool DMetadata::setImageUniqueId(const QString& uuid) const { if (supportXmp()) { return setXmpTagString("Xmp.digiKam.ImageUniqueID", uuid); } return false; } PhotoInfoContainer DMetadata::getPhotographInformation() const { PhotoInfoContainer photoInfo; if (hasExif() || hasXmp()) { photoInfo.dateTime = getImageDateTime(); // ----------------------------------------------------------------------------------- photoInfo.make = getExifTagString("Exif.Image.Make"); if (photoInfo.make.isEmpty()) { photoInfo.make = getXmpTagString("Xmp.tiff.Make"); } // ----------------------------------------------------------------------------------- photoInfo.model = getExifTagString("Exif.Image.Model"); if (photoInfo.model.isEmpty()) { photoInfo.model = getXmpTagString("Xmp.tiff.Model"); } // ----------------------------------------------------------------------------------- photoInfo.lens = getLensDescription(); // ----------------------------------------------------------------------------------- photoInfo.aperture = getExifTagString("Exif.Photo.FNumber"); if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue"); } if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getXmpTagString("Xmp.exif.FNumber"); } if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getXmpTagString("Xmp.exif.ApertureValue"); } // ----------------------------------------------------------------------------------- photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime"); if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue"); } if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getXmpTagString("Xmp.exif.ExposureTime"); } if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getXmpTagString("Xmp.exif.ShutterSpeedValue"); } // ----------------------------------------------------------------------------------- photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode"); if (photoInfo.exposureMode.isEmpty()) { photoInfo.exposureMode = getXmpTagString("Xmp.exif.ExposureMode"); } if (photoInfo.exposureMode.isEmpty()) { photoInfo.exposureMode = getExifTagString("Exif.CanonCs.MeteringMode"); } // ----------------------------------------------------------------------------------- photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram"); if (photoInfo.exposureProgram.isEmpty()) { photoInfo.exposureProgram = getXmpTagString("Xmp.exif.ExposureProgram"); } if (photoInfo.exposureProgram.isEmpty()) { photoInfo.exposureProgram = getExifTagString("Exif.CanonCs.ExposureProgram"); } // ----------------------------------------------------------------------------------- photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength"); if (photoInfo.focalLength.isEmpty()) { photoInfo.focalLength = getXmpTagString("Xmp.exif.FocalLength"); } if (photoInfo.focalLength.isEmpty()) { photoInfo.focalLength = getExifTagString("Exif.Canon.FocalLength"); } // ----------------------------------------------------------------------------------- photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm"); if (photoInfo.focalLength35mm.isEmpty()) { photoInfo.focalLength35mm = getXmpTagString("Xmp.exif.FocalLengthIn35mmFilm"); } // ----------------------------------------------------------------------------------- QStringList ISOSpeedTags; ISOSpeedTags << QLatin1String("Exif.Photo.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Exif.Photo.ExposureIndex"); ISOSpeedTags << QLatin1String("Exif.Image.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Xmp.exif.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Xmp.exif.ExposureIndex"); ISOSpeedTags << QLatin1String("Exif.CanonSi.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.CanonCs.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon1.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon2.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon3.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO"); ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO2"); ISOSpeedTags << QLatin1String("Exif.MinoltaCsNew.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.MinoltaCsOld.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.MinoltaCs5D.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.MinoltaCs7D.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Sony1Cs.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony2Cs.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony1Cs2.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony2Cs2.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony1MltCsA100.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Pentax.ISO"); ISOSpeedTags << QLatin1String("Exif.Olympus.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Samsung2.ISO"); photoInfo.sensitivity = getExifTagStringFromTagsList(ISOSpeedTags); // ----------------------------------------------------------------------------------- photoInfo.flash = getExifTagString("Exif.Photo.Flash"); if (photoInfo.flash.isEmpty()) { photoInfo.flash = getXmpTagString("Xmp.exif.Flash"); } if (photoInfo.flash.isEmpty()) { photoInfo.flash = getExifTagString("Exif.CanonCs.FlashActivity"); } // ----------------------------------------------------------------------------------- photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance"); if (photoInfo.whiteBalance.isEmpty()) { photoInfo.whiteBalance = getXmpTagString("Xmp.exif.WhiteBalance"); } // ----------------------------------------------------------------------------------- double l, L, a; photoInfo.hasCoordinates = getGPSInfo(a, l, L); } return photoInfo; } VideoInfoContainer DMetadata::getVideoInformation() const { VideoInfoContainer videoInfo; if (hasXmp()) { if (videoInfo.aspectRatio.isEmpty()) { videoInfo.aspectRatio = getMetadataField(MetadataInfo::AspectRatio).toString(); } if (videoInfo.audioBitRate.isEmpty()) { videoInfo.audioBitRate = getXmpTagString("Xmp.audio.SampleRate"); } if (videoInfo.audioChannelType.isEmpty()) { videoInfo.audioChannelType = getXmpTagString("Xmp.audio.ChannelType"); } if (videoInfo.audioCodec.isEmpty()) { videoInfo.audioCodec = getXmpTagString("Xmp.audio.Codec"); } if (videoInfo.duration.isEmpty()) { videoInfo.duration = getXmpTagString("Xmp.video.duration"); } if (videoInfo.frameRate.isEmpty()) { videoInfo.frameRate = getXmpTagString("Xmp.video.FrameRate"); } if (videoInfo.videoCodec.isEmpty()) { videoInfo.videoCodec = getXmpTagString("Xmp.video.Codec"); } } return videoInfo; } bool DMetadata::getImageTagsPath(QStringList& tagsPath, const DMetadataSettingsContainer& settings) const { - for (NamespaceEntry entry : settings.getReadMapping(QLatin1String(DM_TAG_CONTAINER))) + for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER))) { if (entry.isDisabled) continue; int index = 0; QString currentNamespace = entry.namespaceName; NamespaceEntry::SpecialOptions currentOpts = entry.specialOpts; // Some namespaces have altenative paths, we must search them both switch(entry.subspace) { case NamespaceEntry::XMP: while(index < 2) { const std::string myStr = currentNamespace.toStdString(); const char* nameSpace = myStr.data(); switch(currentOpts) { case NamespaceEntry::TAG_XMPBAG: tagsPath = getXmpTagStringBag(nameSpace, false); break; case NamespaceEntry::TAG_XMPSEQ: tagsPath = getXmpTagStringSeq(nameSpace, false); break; case NamespaceEntry::TAG_ACDSEE: getACDSeeTagsPath(tagsPath); break; // not used here, to suppress warnings case NamespaceEntry::COMMENT_XMP: case NamespaceEntry::COMMENT_ALTLANG: case NamespaceEntry::COMMENT_ATLLANGLIST: case NamespaceEntry::NO_OPTS: default: break; } if (!tagsPath.isEmpty()) { if (entry.separator != QLatin1String("/")) { tagsPath = tagsPath.replaceInStrings(entry.separator, QLatin1String("/")); } return true; } else if (!entry.alternativeName.isEmpty()) { currentNamespace = entry.alternativeName; currentOpts = entry.secondNameOpts; } else { break; // no alternative namespace, go to next one } index++; } break; case NamespaceEntry::IPTC: // Try to get Tags Path list from IPTC keywords. // digiKam 0.9.x has used IPTC keywords to store Tags Path list. // This way is obsolete now since digiKam support XMP because IPTC // do not support UTF-8 and have strings size limitation. But we will // let the capability to import it for interworking issues. tagsPath = getIptcKeywords(); if (!tagsPath.isEmpty()) { // Work around to Imach tags path list hosted in IPTC with '.' as separator. QStringList ntp = tagsPath.replaceInStrings(entry.separator, QLatin1String("/")); if (ntp != tagsPath) { tagsPath = ntp; qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from Imach: " << tagsPath; } return true; } break; case NamespaceEntry::EXIF: { // Try to get Tags Path list from Exif Windows keywords. QString keyWords = getExifTagString("Exif.Image.XPKeywords", false); if (!keyWords.isEmpty()) { tagsPath = keyWords.split(entry.separator); if (!tagsPath.isEmpty()) { return true; } } break; } default: break; } } return false; } bool DMetadata::setImageTagsPath(const QStringList& tagsPath, const DMetadataSettingsContainer& settings) const { // NOTE : with digiKam 0.9.x, we have used IPTC Keywords for that. // Now this way is obsolete, and we use XMP instead. // Set the new Tags path list. This is set, not add-to like setXmpKeywords. // Unlike the other keyword fields, we do not need to merge existing entries. - QList toWrite = settings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)); + QList toWrite = settings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)); if (!settings.unifyReadWrite()) - toWrite = settings.getWriteMapping(QLatin1String(DM_TAG_CONTAINER)); + toWrite = settings.getWriteMapping(QString::fromUtf8(DM_TAG_CONTAINER)); for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) continue; QStringList newList; // get keywords from tags path, for type tag for (QString tagPath : tagsPath) { newList.append(tagPath.split(QLatin1String("/")).last()); } switch(entry.subspace) { case NamespaceEntry::XMP: if (supportXmp()) { if (entry.tagPaths != NamespaceEntry::TAG) { newList = tagsPath; if (entry.separator.compare(QLatin1String("/")) != 0) { newList = newList.replaceInStrings(QLatin1String("/"), entry.separator); } } const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.specialOpts) { case NamespaceEntry::TAG_XMPSEQ: if (!setXmpTagStringSeq(nameSpace, newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } break; case NamespaceEntry::TAG_XMPBAG: if (!setXmpTagStringBag(nameSpace, newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } break; case NamespaceEntry::TAG_ACDSEE: if (!setACDSeeTagsPath(newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } default: break; } } break; case NamespaceEntry::IPTC: if (entry.namespaceName == QLatin1String("Iptc.Application2.Keywords")) { if (!setIptcKeywords(getIptcKeywords(), newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << entry.namespaceName; return false; } } default: break; } } return true; } bool DMetadata::getACDSeeTagsPath(QStringList &tagsPath) const { // Try to get Tags Path list from ACDSee 8 Pro categories. QString xmlACDSee = getXmpTagString("Xmp.acdsee.categories", false); if (!xmlACDSee.isEmpty()) { xmlACDSee.remove(QLatin1String("")); xmlACDSee.remove(QLatin1String("")); xmlACDSee.replace(QLatin1String("/"), QLatin1String("\\")); QStringList xmlTags = xmlACDSee.split(QLatin1String("")); int length = tags.length() - (11 * count) - 5; if (category == 0) { tagsPath << tags.mid(5, length); } else { tagsPath.last().append(QLatin1String("/") + tags.mid(5, length)); } category = category - count + 1; if (tags.left(5) == QLatin1String("=\"1\">") && category > 0) { tagsPath << tagsPath.last().section(QLatin1String("/"), 0, category - 1); } } } if (!tagsPath.isEmpty()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from ACDSee: " << tagsPath; return true; } } return false; } bool DMetadata::setACDSeeTagsPath(const QStringList &tagsPath) const { // Converting Tags path list to ACDSee 8 Pro categories. const QString category(QLatin1String("")); QStringList splitTags; QStringList xmlTags; foreach(const QString& tags, tagsPath) { splitTags = tags.split(QLatin1String("/")); int current = 0; for (int index = 0; index < splitTags.size(); index++) { int tagIndex = xmlTags.indexOf(category.arg(0) + splitTags[index]); if (tagIndex == -1) { tagIndex = xmlTags.indexOf(category.arg(1) + splitTags[index]); } splitTags[index].insert(0, category.arg(index == splitTags.size() - 1 ? 1 : 0)); if (tagIndex == -1) { if (index == 0) { xmlTags << splitTags[index]; xmlTags << QLatin1String(""); current = xmlTags.size() - 1; } else { xmlTags.insert(current, splitTags[index]); xmlTags.insert(current + 1, QLatin1String("")); current++; } } else { if (index == splitTags.size() - 1) { xmlTags[tagIndex] = splitTags[index]; } current = tagIndex + 1; } } } QString xmlACDSee = QLatin1String("") + xmlTags.join(QLatin1String("")) + QLatin1String(""); qCDebug(DIGIKAM_METAENGINE_LOG) << "xmlACDSee" << xmlACDSee; removeXmpTag("Xmp.acdsee.categories"); if (!xmlTags.isEmpty()) { if (!setXmpTagString("Xmp.acdsee.categories", xmlACDSee)) { return false; } } return true; } bool DMetadata::getImageFacesMap(QMultiMap& faces) const { faces.clear(); // The example code for Exiv2 says: // > There are no specialized values for structures, qualifiers and nested // > types. However, these can be added by using an XmpTextValue and a path as // > the key. // I think that means I have to iterate over the WLPG face tags in the clunky // way below (guess numbers and look them up as strings). (Leif) const QString personPathTemplate = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions[%1]/MPReg:PersonDisplayName"); const QString rectPathTemplate = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions[%1]/MPReg:Rectangle"); for (int i = 1; ; i++) { QString person = getXmpTagString(personPathTemplate.arg(i).toLatin1().constData(), false); if (person.isEmpty()) break; // The WLPG tags have the format X.XX, Y.YY, W.WW, H.HH // That is, four decimal numbers ranging from 0-1. // The top left position is indicated by X.XX, Y.YY (as a // percentage of the width/height of the entire image). // Similarly the width and height of the face's box are // indicated by W.WW and H.HH. QString rectString = getXmpTagString(rectPathTemplate.arg(i).toLatin1().constData(), false); QStringList list = rectString.split(QLatin1Char(',')); if (list.size() < 4) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Cannot parse WLPG rectangle string" << rectString; continue; } QRectF rect(list.at(0).toFloat(), list.at(1).toFloat(), list.at(2).toFloat(), list.at(3).toFloat()); faces.insertMulti(person, rect); } /** Read face tags only if libkexiv can write them, otherwise * garbage tags will be generated on image transformation */ // Read face tags as saved by Picasa // http://www.exiv2.org/tags-xmp-mwg-rs.html const QString mwg_personPathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Name"); const QString mwg_rect_x_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:x"); const QString mwg_rect_y_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:y"); const QString mwg_rect_w_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:w"); const QString mwg_rect_h_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:h"); for (int i = 1; ; i++) { QString person = getXmpTagString(mwg_personPathTemplate.arg(i).toLatin1().constData(), false); if (person.isEmpty()) break; // x and y is the center point float x = getXmpTagString(mwg_rect_x_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); float y = getXmpTagString(mwg_rect_y_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); float w = getXmpTagString(mwg_rect_w_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); float h = getXmpTagString(mwg_rect_h_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); QRectF rect(x - w/2, y - h/2, w, h); faces.insertMulti(person, rect); qCDebug(DIGIKAM_METAENGINE_LOG) << "Found new rect " << person << " "<< rect; } return !faces.isEmpty(); } bool DMetadata::setImageFacesMap(QMultiMap< QString, QVariant >& facesPath, bool write) const { QString qxmpTagName(QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList")); QString nameTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Name"); QString typeTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Type"); QString areaTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area"); QString areaxTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:x"); QString areayTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:y"); QString areawTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:w"); QString areahTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:h"); QString areanormTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:unit"); QString winQxmpTagName = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions"); QString winRectTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:Rectangle"); QString winNameTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:PersonDisplayName"); if (!write) { QString check = getXmpTagString(nameTagKey.arg(1).toLatin1().constData()); if (check.isEmpty()) return true; } setXmpTagString(qxmpTagName.toLatin1().constData(), QString(), MetaEngine::XmpTagType(1)); setXmpTagString(winQxmpTagName.toLatin1().constData(), QString(), MetaEngine::XmpTagType(1)); QMap::const_iterator it = facesPath.constBegin(); int i = 1; bool ok = true; while (it != facesPath.constEnd()) { qreal x, y, w, h; it.value().toRectF().getRect(&x, &y, &w, &h); qCDebug(DIGIKAM_METAENGINE_LOG) << "Set face region:" << x << y << w << h; /** Write face tags in Windows Live Photo format **/ QString rectString; rectString.append(QString::number(x) + QLatin1String(", ")); rectString.append(QString::number(y) + QLatin1String(", ")); rectString.append(QString::number(w) + QLatin1String(", ")); rectString.append(QString::number(h)); /** Set tag rect **/ setXmpTagString(winRectTagKey.arg(i).toLatin1().constData(), rectString, MetaEngine::XmpTagType(0)); /** Set tag name **/ setXmpTagString(winNameTagKey.arg(i).toLatin1().constData(),it.key(), MetaEngine::XmpTagType(0)); /** Writing rectangle in Metadata Group format **/ x += w/2; y += h/2; /** Set tag name **/ ok &= setXmpTagString(nameTagKey.arg(i).toLatin1().constData(), it.key(),MetaEngine::XmpTagType(0)); /** Set tag type as Face **/ ok &= setXmpTagString(typeTagKey.arg(i).toLatin1().constData(), QLatin1String("Face"), MetaEngine::XmpTagType(0)); /** Set tag Area, with xmp type struct **/ ok &= setXmpTagString(areaTagKey.arg(i).toLatin1().constData(), QString(), MetaEngine::XmpTagType(2)); /** Set stArea:x inside Area structure **/ ok &= setXmpTagString(areaxTagKey.arg(i).toLatin1().constData(), QString::number(x), MetaEngine::XmpTagType(0)); /** Set stArea:y inside Area structure **/ ok &= setXmpTagString(areayTagKey.arg(i).toLatin1().constData(), QString::number(y), MetaEngine::XmpTagType(0)); /** Set stArea:w inside Area structure **/ ok &= setXmpTagString(areawTagKey.arg(i).toLatin1().constData(), QString::number(w), MetaEngine::XmpTagType(0)); /** Set stArea:h inside Area structure **/ ok &= setXmpTagString(areahTagKey.arg(i).toLatin1().constData(), QString::number(h), MetaEngine::XmpTagType(0)); /** Set stArea:unit inside Area structure as normalized **/ ok &= setXmpTagString(areanormTagKey.arg(i).toLatin1().constData(), QLatin1String("normalized"), MetaEngine::XmpTagType(0)); ++it; ++i; } return ok; } bool DMetadata::setMetadataTemplate(const Template& t) const { if (t.isNull()) { return false; } QStringList authors = t.authors(); QString authorsPosition = t.authorsPosition(); QString credit = t.credit(); QString source = t.source(); MetaEngine::AltLangMap copyright = t.copyright(); MetaEngine::AltLangMap rightUsage = t.rightUsageTerms(); QString instructions = t.instructions(); qCDebug(DIGIKAM_METAENGINE_LOG) << "Applying Metadata Template: " << t.templateTitle() << " :: " << authors; // Set XMP tags. XMP<->IPTC Schema from Photoshop 7.0 if (supportXmp()) { if (!setXmpTagStringSeq("Xmp.dc.creator", authors)) { return false; } if (!setXmpTagStringSeq("Xmp.tiff.Artist", authors)) { return false; } if (!setXmpTagString("Xmp.photoshop.AuthorsPosition", authorsPosition)) { return false; } if (!setXmpTagString("Xmp.photoshop.Credit", credit)) { return false; } if (!setXmpTagString("Xmp.photoshop.Source", source)) { return false; } if (!setXmpTagString("Xmp.dc.source", source)) { return false; } if (!setXmpTagStringListLangAlt("Xmp.dc.rights", copyright)) { return false; } if (!setXmpTagStringListLangAlt("Xmp.tiff.Copyright", copyright)) { return false; } if (!setXmpTagStringListLangAlt("Xmp.xmpRights.UsageTerms", rightUsage)) { return false; } if (!setXmpTagString("Xmp.photoshop.Instructions", instructions)) { return false; } } // Set IPTC tags. if (!setIptcTagsStringList("Iptc.Application2.Byline", 32, getIptcTagsStringList("Iptc.Application2.Byline"), authors)) { return false; } if (!setIptcTag(authorsPosition, 32, "Authors Title", "Iptc.Application2.BylineTitle")) { return false; } if (!setIptcTag(credit, 32, "Credit", "Iptc.Application2.Credit")) { return false; } if (!setIptcTag(source, 32, "Source", "Iptc.Application2.Source")) { return false; } if (!setIptcTag(copyright[QLatin1String("x-default")], 128, "Copyright", "Iptc.Application2.Copyright")) { return false; } if (!setIptcTag(instructions, 256, "Instructions", "Iptc.Application2.SpecialInstructions")) { return false; } if (!setIptcCoreLocation(t.locationInfo())) { return false; } if (!setCreatorContactInfo(t.contactInfo())) { return false; } if (supportXmp()) { if (!setXmpSubjects(t.IptcSubjects())) { return false; } } // Synchronize Iptc subjects tags with Xmp subjects tags. QStringList list = t.IptcSubjects(); QStringList newList; foreach(QString str, list) // krazy:exclude=foreach { if (str.startsWith(QLatin1String("XMP"))) { str.replace(0, 3, QLatin1String("IPTC")); } newList.append(str); } if (!setIptcSubjects(getIptcSubjects(), newList)) { return false; } return true; } bool DMetadata::removeMetadataTemplate() const { // Remove Rights info. removeXmpTag("Xmp.dc.creator"); removeXmpTag("Xmp.tiff.Artist"); removeXmpTag("Xmp.photoshop.AuthorsPosition"); removeXmpTag("Xmp.photoshop.Credit"); removeXmpTag("Xmp.photoshop.Source"); removeXmpTag("Xmp.dc.source"); removeXmpTag("Xmp.dc.rights"); removeXmpTag("Xmp.tiff.Copyright"); removeXmpTag("Xmp.xmpRights.UsageTerms"); removeXmpTag("Xmp.photoshop.Instructions"); removeIptcTag("Iptc.Application2.Byline"); removeIptcTag("Iptc.Application2.BylineTitle"); removeIptcTag("Iptc.Application2.Credit"); removeIptcTag("Iptc.Application2.Source"); removeIptcTag("Iptc.Application2.Copyright"); removeIptcTag("Iptc.Application2.SpecialInstructions"); // Remove Location info. removeXmpTag("Xmp.photoshop.Country"); removeXmpTag("Xmp.iptc.CountryCode"); removeXmpTag("Xmp.photoshop.City"); removeXmpTag("Xmp.iptc.Location"); removeXmpTag("Xmp.photoshop.State"); removeIptcTag("Iptc.Application2.CountryName"); removeIptcTag("Iptc.Application2.CountryCode"); removeIptcTag("Iptc.Application2.City"); removeIptcTag("Iptc.Application2.SubLocation"); removeIptcTag("Iptc.Application2.ProvinceState"); // Remove Contact info. removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork"); removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork"); // Remove IPTC Subjects. removeXmpTag("Xmp.iptc.SubjectCode"); removeIptcTag("Iptc.Application2.Subject"); return true; } Template DMetadata::getMetadataTemplate() const { Template t; getCopyrightInformation(t); t.setLocationInfo(getIptcCoreLocation()); t.setIptcSubjects(getIptcCoreSubjects()); // get from XMP or Iptc return t; } static bool hasValidField(const QVariantList& list) { for (QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { if (!(*it).isNull()) { return true; } } return false; } bool DMetadata::getCopyrightInformation(Template& t) const { MetadataFields fields; fields << MetadataInfo::IptcCoreCopyrightNotice << MetadataInfo::IptcCoreCreator << MetadataInfo::IptcCoreProvider << MetadataInfo::IptcCoreRightsUsageTerms << MetadataInfo::IptcCoreSource << MetadataInfo::IptcCoreCreatorJobTitle << MetadataInfo::IptcCoreInstructions; QVariantList metadataInfos = getMetadataFields(fields); IptcCoreContactInfo contactInfo = getCreatorContactInfo(); if (!hasValidField(metadataInfos) && contactInfo.isNull()) { return false; } t.setCopyright(toAltLangMap(metadataInfos.at(0))); t.setAuthors(metadataInfos.at(1).toStringList()); t.setCredit(metadataInfos.at(2).toString()); t.setRightUsageTerms(toAltLangMap(metadataInfos.at(3))); t.setSource(metadataInfos.at(4).toString()); t.setAuthorsPosition(metadataInfos.at(5).toString()); t.setInstructions(metadataInfos.at(6).toString()); t.setContactInfo(contactInfo); return true; } IptcCoreContactInfo DMetadata::getCreatorContactInfo() const { MetadataFields fields; fields << MetadataInfo::IptcCoreContactInfoCity << MetadataInfo::IptcCoreContactInfoCountry << MetadataInfo::IptcCoreContactInfoAddress << MetadataInfo::IptcCoreContactInfoPostalCode << MetadataInfo::IptcCoreContactInfoProvinceState << MetadataInfo::IptcCoreContactInfoEmail << MetadataInfo::IptcCoreContactInfoPhone << MetadataInfo::IptcCoreContactInfoWebUrl; QVariantList metadataInfos = getMetadataFields(fields); IptcCoreContactInfo info; if (metadataInfos.size() == 8) { info.city = metadataInfos.at(0).toString(); info.country = metadataInfos.at(1).toString(); info.address = metadataInfos.at(2).toString(); info.postalCode = metadataInfos.at(3).toString(); info.provinceState = metadataInfos.at(4).toString(); info.email = metadataInfos.at(5).toString(); info.phone = metadataInfos.at(6).toString(); info.webUrl = metadataInfos.at(7).toString(); } return info; } bool DMetadata::setCreatorContactInfo(const IptcCoreContactInfo& info) const { if (!supportXmp()) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity", info.city)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry", info.country)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr", info.address)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode", info.postalCode)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion", info.provinceState)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork", info.email)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork", info.phone)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork", info.webUrl)) { return false; } return true; } IptcCoreLocationInfo DMetadata::getIptcCoreLocation() const { MetadataFields fields; fields << MetadataInfo::IptcCoreCountry << MetadataInfo::IptcCoreCountryCode << MetadataInfo::IptcCoreCity << MetadataInfo::IptcCoreLocation << MetadataInfo::IptcCoreProvinceState; QVariantList metadataInfos = getMetadataFields(fields); IptcCoreLocationInfo location; if (fields.size() == 5) { location.country = metadataInfos.at(0).toString(); location.countryCode = metadataInfos.at(1).toString(); location.city = metadataInfos.at(2).toString(); location.location = metadataInfos.at(3).toString(); location.provinceState = metadataInfos.at(4).toString(); } return location; } bool DMetadata::setIptcCoreLocation(const IptcCoreLocationInfo& location) const { if (supportXmp()) { if (!setXmpTagString("Xmp.photoshop.Country", location.country)) { return false; } if (!setXmpTagString("Xmp.iptc.CountryCode", location.countryCode)) { return false; } if (!setXmpTagString("Xmp.photoshop.City", location.city)) { return false; } if (!setXmpTagString("Xmp.iptc.Location", location.location)) { return false; } if (!setXmpTagString("Xmp.photoshop.State", location.provinceState)) { return false; } } if (!setIptcTag(location.country, 64, "Country", "Iptc.Application2.CountryName")) { return false; } if (!setIptcTag(location.countryCode, 3, "Country Code", "Iptc.Application2.CountryCode")) { return false; } if (!setIptcTag(location.city, 32, "City", "Iptc.Application2.City")) { return false; } if (!setIptcTag(location.location, 32, "SubLocation", "Iptc.Application2.SubLocation")) { return false; } if (!setIptcTag(location.provinceState, 32, "Province/State", "Iptc.Application2.ProvinceState")) { return false; } return true; } QStringList DMetadata::getIptcCoreSubjects() const { QStringList list = getXmpSubjects(); if (!list.isEmpty()) { return list; } return getIptcSubjects(); } QString DMetadata::getLensDescription() const { QString lens; QStringList lensExifTags; // In first, try to get Lens information from makernotes. lensExifTags.append(QLatin1String("Exif.CanonCs.LensType")); // Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.CanonCs.Lens")); // Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Canon.0x0095")); // Alternative Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd1.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd2.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd3.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Minolta.LensID")); // Minolta Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Photo.LensModel")); // Sony Cameras Makernote (and others?). lensExifTags.append(QLatin1String("Exif.Sony1.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Sony2.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.SonyMinolta.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Pentax.LensType")); // Pentax Cameras Makernote. lensExifTags.append(QLatin1String("Exif.PentaxDng.LensType")); // Pentax Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Panasonic.0x0051")); // Panasonic Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Panasonic.0x0310")); // Panasonic Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Sigma.LensRange")); // Sigma Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Samsung2.LensType")); // Samsung Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Photo.0xFDEA")); // Non-standard Exif tag set by Camera Raw. lensExifTags.append(QLatin1String("Exif.OlympusEq.LensModel")); // Olympus Cameras Makernote. // Olympus Cameras Makernote. FIXME is this necessary? exiv2 returns complete name, which doesn't match with lensfun information, see bug #311295 //lensExifTags.append("Exif.OlympusEq.LensType"); // TODO : add Fuji camera Makernotes. // ------------------------------------------------------------------- // Try to get Lens Data information from Exif. for (QStringList::const_iterator it = lensExifTags.constBegin(); it != lensExifTags.constEnd(); ++it) { lens = getExifTagString((*it).toLatin1().constData()); if ( !lens.isEmpty() && !(lens.startsWith(QLatin1Char('(')) && lens.endsWith(QLatin1Char(')')) ) ) // To prevent undecoded tag values from Exiv2 as "(65535)". { return lens; } } // ------------------------------------------------------------------- // Try to get Lens Data information from XMP. // XMP aux tags. lens = getXmpTagString("Xmp.aux.Lens"); if (lens.isEmpty()) { // XMP M$ tags (Lens Maker + Lens Model). lens = getXmpTagString("Xmp.MicrosoftPhoto.LensManufacturer"); if (!lens.isEmpty()) { lens.append(QLatin1String(" ")); } lens.append(getXmpTagString("Xmp.MicrosoftPhoto.LensModel")); } return lens; } IccProfile DMetadata::getIccProfile() const { // Check if Exif data contains an ICC color profile. QByteArray data = getExifTagData("Exif.Image.InterColorProfile"); if (!data.isNull()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Found an ICC profile in Exif metadata"; return IccProfile(data); } // Else check the Exif color-space tag and use default profiles that we ship switch (getImageColorWorkSpace()) { case DMetadata::WORKSPACE_SRGB: { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is sRGB. Using default sRGB ICC profile."; return IccProfile::sRGB(); } case DMetadata::WORKSPACE_ADOBERGB: { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile."; return IccProfile::adobeRGB(); } default: break; } return IccProfile(); } bool DMetadata::setIccProfile(const IccProfile& profile) { if (profile.isNull()) { removeExifTag("Exif.Image.InterColorProfile"); } else { QByteArray data = IccProfile(profile).data(); if (!setExifTagData("Exif.Image.InterColorProfile", data)) { return false; } } removeExifColorSpace(); return true; } bool DMetadata::setIptcTag(const QString& text, int maxLength, const char* const debugLabel, const char* const tagKey) const { QString truncatedText = text; truncatedText.truncate(maxLength); qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> " << debugLabel << ": " << truncatedText; return setIptcTagString(tagKey, truncatedText); // returns false if failed } inline QVariant DMetadata::fromExifOrXmp(const char* const exifTagName, const char* const xmpTagName) const { QVariant var; if (exifTagName) { var = getExifTagVariant(exifTagName, false); if (!var.isNull()) { return var; } } if (xmpTagName) { var = getXmpTagVariant(xmpTagName); if (!var.isNull()) { return var; } } return var; } inline QVariant DMetadata::fromIptcOrXmp(const char* const iptcTagName, const char* const xmpTagName) const { if (iptcTagName) { QString iptcValue = getIptcTagString(iptcTagName); if (!iptcValue.isNull()) { return iptcValue; } } if (xmpTagName) { QVariant var = getXmpTagVariant(xmpTagName); if (!var.isNull()) { return var; } } return QVariant(QVariant::String); } inline QVariant DMetadata::fromIptcEmulateList(const char* const iptcTagName) const { return toStringListVariant(getIptcTagsStringList(iptcTagName)); } inline QVariant DMetadata::fromXmpList(const char* const xmpTagName) const { QVariant var = getXmpTagVariant(xmpTagName); if (var.isNull()) { return QVariant(QVariant::StringList); } return var; } inline QVariant DMetadata::fromIptcEmulateLangAlt(const char* const iptcTagName) const { QString str = getIptcTagString(iptcTagName); if (str.isNull()) { return QVariant(QVariant::Map); } QMap map; map[QLatin1String("x-default")] = str; return map; } inline QVariant DMetadata::fromXmpLangAlt(const char* const xmpTagName) const { QVariant var = getXmpTagVariant(xmpTagName); if (var.isNull()) { return QVariant(QVariant::Map); } return var; } inline QVariant DMetadata::toStringListVariant(const QStringList& list) const { if (list.isEmpty()) { return QVariant(QVariant::StringList); } return list; } QVariant DMetadata::getMetadataField(MetadataInfo::Field field) const { switch (field) { case MetadataInfo::Comment: return getImageComments()[QLatin1String("x-default")].caption; case MetadataInfo::CommentJfif: return getCommentsDecoded(); case MetadataInfo::CommentExif: return getExifComment(); case MetadataInfo::CommentIptc: return fromIptcOrXmp("Iptc.Application2.Caption", 0); case MetadataInfo::Description: { QVariant var = fromXmpLangAlt("Xmp.dc.description"); if (!var.isNull()) { return var; } var = fromXmpLangAlt("Xmp.tiff.ImageDescription"); if (!var.isNull()) { return var; } return fromIptcEmulateLangAlt("Iptc.Application2.Caption"); } case MetadataInfo::Headline: return fromIptcOrXmp("Iptc.Application2.Headline", "Xmp.photoshop.Headline"); case MetadataInfo::Title: { QString str = getImageTitles()[QLatin1String("x-default")].caption; if (str.isEmpty()) { return QVariant(QVariant::Map); } QMap map; map[QLatin1String("x-default")] = str; return map; } case MetadataInfo::DescriptionWriter: return fromIptcOrXmp("Iptc.Application2.Writer", "Xmp.photoshop.CaptionWriter"); case MetadataInfo::Keywords: { QStringList list; getImageTagsPath(list); return toStringListVariant(list); } case MetadataInfo::Faces: { QMultiMap faceMap; getImageFacesMap(faceMap); QVariant var(faceMap); return var; } case MetadataInfo::Rating: return getImageRating(); case MetadataInfo::CreationDate: return getImageDateTime(); case MetadataInfo::DigitizationDate: return getDigitizationDateTime(true); case MetadataInfo::Orientation: return (int)getImageOrientation(); case MetadataInfo::Make: { QVariant var = fromExifOrXmp("Exif.Image.Make", "Xmp.tiff.Make"); return QVariant(var.toString().trimmed()); } case MetadataInfo::Model: { QVariant var = fromExifOrXmp("Exif.Image.Model", "Xmp.tiff.Model"); return QVariant(var.toString().trimmed()); } case MetadataInfo::Lens: return getLensDescription(); case MetadataInfo::Aperture: { QVariant var = fromExifOrXmp("Exif.Photo.FNumber", "Xmp.exif.FNumber"); if (var.isNull()) { var = fromExifOrXmp("Exif.Photo.ApertureValue", "Xmp.exif.ApertureValue"); if (!var.isNull()) { var = apexApertureToFNumber(var.toDouble()); } } return var; } case MetadataInfo::FocalLength: return fromExifOrXmp("Exif.Photo.FocalLength", "Xmp.exif.FocalLength"); case MetadataInfo::FocalLengthIn35mm: return fromExifOrXmp("Exif.Photo.FocalLengthIn35mmFilm", "Xmp.exif.FocalLengthIn35mmFilm"); case MetadataInfo::ExposureTime: { QVariant var = fromExifOrXmp("Exif.Photo.ExposureTime", "Xmp.exif.ExposureTime"); if (var.isNull()) { var = fromExifOrXmp("Exif.Photo.ShutterSpeedValue", "Xmp.exif.ShutterSpeedValue"); if (!var.isNull()) { var = apexShutterSpeedToExposureTime(var.toDouble()); } } return var; } case MetadataInfo::ExposureProgram: return fromExifOrXmp("Exif.Photo.ExposureProgram", "Xmp.exif.ExposureProgram"); case MetadataInfo::ExposureMode: return fromExifOrXmp("Exif.Photo.ExposureMode", "Xmp.exif.ExposureMode"); case MetadataInfo::Sensitivity: { QVariant var = fromExifOrXmp("Exif.Photo.ISOSpeedRatings", "Xmp.exif.ISOSpeedRatings"); //if (var.isNull()) // TODO: has this ISO format??? We must convert to the format of ISOSpeedRatings! // var = fromExifOrXmp("Exif.Photo.ExposureIndex", "Xmp.exif.ExposureIndex"); return var; } case MetadataInfo::FlashMode: return fromExifOrXmp("Exif.Photo.Flash", "Xmp.exif.Flash"); case MetadataInfo::WhiteBalance: return fromExifOrXmp("Exif.Photo.WhiteBalance", "Xmp.exif.WhiteBalance"); case MetadataInfo::MeteringMode: return fromExifOrXmp("Exif.Photo.MeteringMode", "Xmp.exif.MeteringMode"); case MetadataInfo::SubjectDistance: return fromExifOrXmp("Exif.Photo.SubjectDistance", "Xmp.exif.SubjectDistance"); case MetadataInfo::SubjectDistanceCategory: return fromExifOrXmp("Exif.Photo.SubjectDistanceRange", "Xmp.exif.SubjectDistanceRange"); case MetadataInfo::WhiteBalanceColorTemperature: //TODO: ?? return QVariant(QVariant::Int); case MetadataInfo::Longitude: return getGPSLongitudeString(); case MetadataInfo::LongitudeNumber: { double longitude; if (getGPSLongitudeNumber(&longitude)) { return longitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::Latitude: return getGPSLatitudeString(); case MetadataInfo::LatitudeNumber: { double latitude; if (getGPSLatitudeNumber(&latitude)) { return latitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::Altitude: { double altitude; if (getGPSAltitude(&altitude)) { return altitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::PositionOrientation: case MetadataInfo::PositionTilt: case MetadataInfo::PositionRoll: case MetadataInfo::PositionAccuracy: // TODO or unsupported? return QVariant(QVariant::Double); case MetadataInfo::PositionDescription: // TODO or unsupported? return QVariant(QVariant::String); case MetadataInfo::IptcCoreCopyrightNotice: { QVariant var = fromXmpLangAlt("Xmp.dc.rights"); if (!var.isNull()) { return var; } var = fromXmpLangAlt("Xmp.tiff.Copyright"); if (!var.isNull()) { return var; } return fromIptcEmulateLangAlt("Iptc.Application2.Copyright"); } case MetadataInfo::IptcCoreCreator: { QVariant var = fromXmpList("Xmp.dc.creator"); if (!var.isNull()) { return var; } QString artist = getXmpTagString("Xmp.tiff.Artist"); if (!artist.isNull()) { QStringList list; list << artist; return list; } return fromIptcEmulateList("Iptc.Application2.Byline"); } case MetadataInfo::IptcCoreProvider: return fromIptcOrXmp("Iptc.Application2.Credit", "Xmp.photoshop.Credit"); case MetadataInfo::IptcCoreRightsUsageTerms: return fromXmpLangAlt("Xmp.xmpRights.UsageTerms"); case MetadataInfo::IptcCoreSource: return fromIptcOrXmp("Iptc.Application2.Source", "Xmp.photoshop.Source"); case MetadataInfo::IptcCoreCreatorJobTitle: return fromIptcOrXmp("Iptc.Application2.BylineTitle", "Xmp.photoshop.AuthorsPosition"); case MetadataInfo::IptcCoreInstructions: return fromIptcOrXmp("Iptc.Application2.SpecialInstructions", "Xmp.photoshop.Instructions"); case MetadataInfo::IptcCoreLocationInfo: { IptcCoreLocationInfo location = getIptcCoreLocation(); if (location.isNull()) { return QVariant(); } return QVariant::fromValue(location); } case MetadataInfo::IptcCoreCountryCode: return fromIptcOrXmp("Iptc.Application2.CountryCode", "Xmp.iptc.CountryCode"); case MetadataInfo::IptcCoreCountry: return fromIptcOrXmp("Iptc.Application2.CountryName", "Xmp.photoshop.Country"); case MetadataInfo::IptcCoreCity: return fromIptcOrXmp("Iptc.Application2.City", "Xmp.photoshop.City"); case MetadataInfo::IptcCoreLocation: return fromIptcOrXmp("Iptc.Application2.SubLocation", "Xmp.iptc.Location"); case MetadataInfo::IptcCoreProvinceState: return fromIptcOrXmp("Iptc.Application2.ProvinceState", "Xmp.photoshop.State"); case MetadataInfo::IptcCoreIntellectualGenre: return fromIptcOrXmp("Iptc.Application2.ObjectAttribute", "Xmp.iptc.IntellectualGenre"); case MetadataInfo::IptcCoreJobID: return fromIptcOrXmp("Iptc.Application2.TransmissionReference", "Xmp.photoshop.TransmissionReference"); case MetadataInfo::IptcCoreScene: return fromXmpList("Xmp.iptc.Scene"); case MetadataInfo::IptcCoreSubjectCode: return toStringListVariant(getIptcCoreSubjects()); case MetadataInfo::IptcCoreContactInfo: { IptcCoreContactInfo info = getCreatorContactInfo(); if (info.isNull()) { return QVariant(); } return QVariant::fromValue(info); } case MetadataInfo::IptcCoreContactInfoCity: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity"); case MetadataInfo::IptcCoreContactInfoCountry: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry"); case MetadataInfo::IptcCoreContactInfoAddress: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr"); case MetadataInfo::IptcCoreContactInfoPostalCode: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode"); case MetadataInfo::IptcCoreContactInfoProvinceState: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion"); case MetadataInfo::IptcCoreContactInfoEmail: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork"); case MetadataInfo::IptcCoreContactInfoPhone: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork"); case MetadataInfo::IptcCoreContactInfoWebUrl: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork"); case MetadataInfo::AspectRatio: { long num = 0; long den = 1; // NOTE: there is a bug in Exiv2 xmp::video tag definition as "Rational" value is defined as "Ratio"... //QList list = getXmpTagVariant("Xmp.video.AspectRatio").toList(); QString ar = getXmpTagString("Xmp.video.AspectRatio"); QStringList list = ar.split(QLatin1Char('/')); if (list.size() >= 1) num = list[0].toInt(); if (list.size() >= 2) den = list[1].toInt(); return QString::number((double)num / (double)den); } case MetadataInfo::AudioBitRate: return fromXmpLangAlt("Xmp.audio.SampleRate"); case MetadataInfo::AudioChannelType: return fromXmpLangAlt("Xmp.audio.ChannelType"); case MetadataInfo::AudioCodec: return fromXmpLangAlt("Xmp.audio.Codec"); case MetadataInfo::Duration: return fromXmpLangAlt("Xmp.video.duration"); // duration is in ms case MetadataInfo::FrameRate: return fromXmpLangAlt("Xmp.video.FrameRate"); case MetadataInfo::VideoCodec: return fromXmpLangAlt("Xmp.video.Codec"); case MetadataInfo::VideoBitDepth: return fromXmpLangAlt("Xmp.video.BitDepth"); case MetadataInfo::VideoHeight: return fromXmpLangAlt("Xmp.video.Height"); case MetadataInfo::VideoWidth: return fromXmpLangAlt("Xmp.video.Width"); case MetadataInfo::VideoColorSpace: { QString cs = getXmpTagString("Xmp.video.ColorSpace"); if (cs == QLatin1String("sRGB")) return QString::number(VIDEOCOLORMODEL_SRGB); else if (cs == QLatin1String("CCIR-601")) return QString::number(VIDEOCOLORMODEL_BT601); else if (cs == QLatin1String("CCIR-709")) return QString::number(VIDEOCOLORMODEL_BT709); else if (cs == QLatin1String("Other")) return QString::number(VIDEOCOLORMODEL_OTHER); else return QVariant(QVariant::Int); } default: return QVariant(); } } QVariantList DMetadata::getMetadataFields(const MetadataFields& fields) const { QVariantList list; foreach(MetadataInfo::Field field, fields) // krazy:exclude=foreach { list << getMetadataField(field); } return list; } QString DMetadata::valueToString(const QVariant& value, MetadataInfo::Field field) { MetaEngine exiv2Iface; switch (field) { case MetadataInfo::Rating: return value.toString(); case MetadataInfo::CreationDate: case MetadataInfo::DigitizationDate: return value.toDateTime().toString(Qt::LocaleDate); case MetadataInfo::Orientation: { switch (value.toInt()) { // Example why the English text differs from the enum names: ORIENTATION_ROT_90. // Rotation by 90 degrees is right (clockwise) rotation. // But: The enum names describe what needs to be done to get the image right again. // And an image that needs to be rotated 90 degrees is currently rotated 270 degrees = left. case ORIENTATION_UNSPECIFIED: return i18n("Unspecified"); case ORIENTATION_NORMAL: return i18nc("Rotation of an unrotated image", "Normal"); case ORIENTATION_HFLIP: return i18n("Flipped Horizontally"); case ORIENTATION_ROT_180: return i18n("Rotated by 180 Degrees"); case ORIENTATION_VFLIP: return i18n("Flipped Vertically"); case ORIENTATION_ROT_90_HFLIP: return i18n("Flipped Horizontally and Rotated Left"); case ORIENTATION_ROT_90: return i18n("Rotated Left"); case ORIENTATION_ROT_90_VFLIP: return i18n("Flipped Vertically and Rotated Left"); case ORIENTATION_ROT_270: return i18n("Rotated Right"); default: return i18n("Unknown"); } break; } case MetadataInfo::Make: return exiv2Iface.createExifUserStringFromValue("Exif.Image.Make", value); case MetadataInfo::Model: return exiv2Iface.createExifUserStringFromValue("Exif.Image.Model", value); case MetadataInfo::Lens: // heterogeneous source, non-standardized string return value.toString(); case MetadataInfo::Aperture: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FNumber", value); case MetadataInfo::FocalLength: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLength", value); case MetadataInfo::FocalLengthIn35mm: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLengthIn35mmFilm", value); case MetadataInfo::ExposureTime: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureTime", value); case MetadataInfo::ExposureProgram: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureProgram", value); case MetadataInfo::ExposureMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureMode", value); case MetadataInfo::Sensitivity: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ISOSpeedRatings", value); case MetadataInfo::FlashMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.Flash", value); case MetadataInfo::WhiteBalance: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.WhiteBalance", value); case MetadataInfo::MeteringMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.MeteringMode", value); case MetadataInfo::SubjectDistance: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistance", value); case MetadataInfo::SubjectDistanceCategory: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistanceRange", value); case MetadataInfo::WhiteBalanceColorTemperature: return i18nc("Temperature in Kelvin", "%1 K", value.toInt()); case MetadataInfo::AspectRatio: case MetadataInfo::AudioBitRate: case MetadataInfo::AudioChannelType: case MetadataInfo::AudioCodec: case MetadataInfo::Duration: case MetadataInfo::FrameRate: case MetadataInfo::VideoCodec: return value.toString(); case MetadataInfo::Longitude: { int degrees, minutes; double seconds; char directionRef; if (!convertToUserPresentableNumbers(value.toString(), °rees, &minutes, &seconds, &directionRef)) { return QString(); } QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ? i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::LongitudeNumber: { int degrees, minutes; double seconds; char directionRef; convertToUserPresentableNumbers(false, value.toDouble(), °rees, &minutes, &seconds, &directionRef); QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ? i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::Latitude: { int degrees, minutes; double seconds; char directionRef; if (!convertToUserPresentableNumbers(value.toString(), °rees, &minutes, &seconds, &directionRef)) { return QString(); } QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ? i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::LatitudeNumber: { int degrees, minutes; double seconds; char directionRef; convertToUserPresentableNumbers(false, value.toDouble(), °rees, &minutes, &seconds, &directionRef); QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ? i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::Altitude: { QString meters = QString::fromLatin1("%L1").arg(value.toDouble(), 0, 'f', 2); // xgettext: no-c-format return i18nc("Height in meters", "%1m", meters); } case MetadataInfo::PositionOrientation: case MetadataInfo::PositionTilt: case MetadataInfo::PositionRoll: case MetadataInfo::PositionAccuracy: //TODO return value.toString(); case MetadataInfo::PositionDescription: return value.toString(); // Lang Alt case MetadataInfo::IptcCoreCopyrightNotice: case MetadataInfo::IptcCoreRightsUsageTerms: case MetadataInfo::Description: case MetadataInfo::Title: { QMap map = value.toMap(); // the most common cases if (map.isEmpty()) { return QString(); } else if (map.size() == 1) { return map.begin().value().toString(); } // Try "en-us" QString spec = QLocale().name().toLower().replace(QLatin1Char('_'), QLatin1Char('-')); if (map.contains(spec)) { return map[spec].toString(); } // Try "en-" QStringList keys = map.keys(); QString spec2 = QLocale().name().toLower(); QRegExp exp(spec2.left(spec2.indexOf(QLatin1Char('_'))) + QLatin1Char('-')); QStringList matches = keys.filter(exp); if (!matches.isEmpty()) { return map[matches.first()].toString(); } // return default if (map.contains(QLatin1String("x-default"))) { return map[QLatin1String("x-default")].toString(); } // return first entry return map.begin().value().toString(); } // List case MetadataInfo::IptcCoreCreator: case MetadataInfo::IptcCoreScene: case MetadataInfo::IptcCoreSubjectCode: return value.toStringList().join(QLatin1String(" ")); // Text case MetadataInfo::Comment: case MetadataInfo::CommentJfif: case MetadataInfo::CommentExif: case MetadataInfo::CommentIptc: case MetadataInfo::Headline: case MetadataInfo::DescriptionWriter: case MetadataInfo::IptcCoreProvider: case MetadataInfo::IptcCoreSource: case MetadataInfo::IptcCoreCreatorJobTitle: case MetadataInfo::IptcCoreInstructions: case MetadataInfo::IptcCoreCountryCode: case MetadataInfo::IptcCoreCountry: case MetadataInfo::IptcCoreCity: case MetadataInfo::IptcCoreLocation: case MetadataInfo::IptcCoreProvinceState: case MetadataInfo::IptcCoreIntellectualGenre: case MetadataInfo::IptcCoreJobID: return value.toString(); default: break; } return QString(); } QStringList DMetadata::valuesToString(const QVariantList& values, const MetadataFields& fields) { int size = values.size(); Q_ASSERT(size == values.size()); QStringList list; for (int i = 0; i < size; ++i) { list << valueToString(values.at(i), fields.at(i)); } return list; } QMap DMetadata::possibleValuesForEnumField(MetadataInfo::Field field) { QMap map; int min, max; switch (field) { case MetadataInfo::Orientation: /// Int, enum from libMetaEngine min = ORIENTATION_UNSPECIFIED; max = ORIENTATION_ROT_270; break; case MetadataInfo::ExposureProgram: /// Int, enum from Exif min = 0; max = 8; break; case MetadataInfo::ExposureMode: /// Int, enum from Exif min = 0; max = 2; break; case MetadataInfo::WhiteBalance: /// Int, enum from Exif min = 0; max = 1; break; case MetadataInfo::MeteringMode: /// Int, enum from Exif min = 0; max = 6; map[255] = valueToString(255, field); break; case MetadataInfo::SubjectDistanceCategory: /// int, enum from Exif min = 0; max = 3; break; case MetadataInfo::FlashMode: /// Int, bit mask from Exif // This one is a bit special. // We return a bit mask for binary AND searching. map[0x1] = i18n("Flash has been fired"); map[0x40] = i18n("Flash with red-eye reduction mode"); //more: TODO? return map; default: qCWarning(DIGIKAM_METAENGINE_LOG) << "Unsupported field " << field << " in DMetadata::possibleValuesForEnumField"; return map; } for (int i = min; i <= max; ++i) { map[i] = valueToString(i, field); } return map; } double DMetadata::apexApertureToFNumber(double aperture) { // convert from APEX. See Exif spec, Annex C. if (aperture == 0.0) { return 1; } else if (aperture == 1.0) { return 1.4; } else if (aperture == 2.0) { return 2; } else if (aperture == 3.0) { return 2.8; } else if (aperture == 4.0) { return 4; } else if (aperture == 5.0) { return 5.6; } else if (aperture == 6.0) { return 8; } else if (aperture == 7.0) { return 11; } else if (aperture == 8.0) { return 16; } else if (aperture == 9.0) { return 22; } else if (aperture == 10.0) { return 32; } return exp(log(2) * aperture / 2.0); } double DMetadata::apexShutterSpeedToExposureTime(double shutterSpeed) { // convert from APEX. See Exif spec, Annex C. if (shutterSpeed == -5.0) { return 30; } else if (shutterSpeed == -4.0) { return 15; } else if (shutterSpeed == -3.0) { return 8; } else if (shutterSpeed == -2.0) { return 4; } else if (shutterSpeed == -1.0) { return 2; } else if (shutterSpeed == 0.0) { return 1; } else if (shutterSpeed == 1.0) { return 0.5; } else if (shutterSpeed == 2.0) { return 0.25; } else if (shutterSpeed == 3.0) { return 0.125; } else if (shutterSpeed == 4.0) { return 1.0 / 15.0; } else if (shutterSpeed == 5.0) { return 1.0 / 30.0; } else if (shutterSpeed == 6.0) { return 1.0 / 60.0; } else if (shutterSpeed == 7.0) { return 0.008; // 1/125 } else if (shutterSpeed == 8.0) { return 0.004; // 1/250 } else if (shutterSpeed == 9.0) { return 0.002; // 1/500 } else if (shutterSpeed == 10.0) { return 0.001; // 1/1000 } else if (shutterSpeed == 11.0) { return 0.0005; // 1/2000 } // additions by me else if (shutterSpeed == 12.0) { return 0.00025; // 1/4000 } else if (shutterSpeed == 13.0) { return 0.000125; // 1/8000 } return exp( - log(2) * shutterSpeed); } MetaEngine::AltLangMap DMetadata::toAltLangMap(const QVariant& var) { MetaEngine::AltLangMap map; if (var.isNull()) { return map; } switch (var.type()) { case QVariant::String: map.insert(QLatin1String("x-default"), var.toString()); break; case QVariant::Map: { QMap varMap = var.toMap(); for (QMap::const_iterator it = varMap.constBegin(); it != varMap.constEnd(); ++it) { map.insert(it.key(), it.value().toString()); } break; } default: break; } return map; } bool DMetadata::addToXmpTagStringBag(const char* const xmpTagName, const QStringList& entriesToAdd) const { //#ifdef _XMP_SUPPORT_ QStringList oldEntries = getXmpTagStringBag(xmpTagName, false); QStringList newEntries = entriesToAdd; // Create a list of keywords including old one which already exists. for (QStringList::const_iterator it = oldEntries.constBegin(); it != oldEntries.constEnd(); ++it ) { if (!newEntries.contains(*it)) { newEntries.append(*it); } } if (setXmpTagStringBag(xmpTagName, newEntries)) { return true; } //#endif // _XMP_SUPPORT_ return false; } bool DMetadata::removeFromXmpTagStringBag(const char* const xmpTagName, const QStringList& entriesToRemove) const { //#ifdef _XMP_SUPPORT_ QStringList currentEntries = getXmpTagStringBag(xmpTagName, false); QStringList newEntries; // Create a list of current keywords except those that shall be removed for (QStringList::const_iterator it = currentEntries.constBegin(); it != currentEntries.constEnd(); ++it ) { if (!entriesToRemove.contains(*it)) { newEntries.append(*it); } } if (setXmpTagStringBag(xmpTagName, newEntries)) { return true; } //#endif // _XMP_SUPPORT_ return false; } QStringList DMetadata::getXmpKeywords() const { return (getXmpTagStringBag("Xmp.dc.subject", false)); } bool DMetadata::setXmpKeywords(const QStringList& newKeywords) const { return setXmpTagStringBag("Xmp.dc.subject", newKeywords); } bool DMetadata::removeXmpKeywords(const QStringList& keywordsToRemove) { return removeFromXmpTagStringBag("Xmp.dc.subject", keywordsToRemove); } QStringList DMetadata::getXmpSubCategories() const { return (getXmpTagStringBag("Xmp.photoshop.SupplementalCategories", false)); } bool DMetadata::setXmpSubCategories(const QStringList& newSubCategories) const { return addToXmpTagStringBag("Xmp.photoshop.SupplementalCategories", newSubCategories); } bool DMetadata::removeXmpSubCategories(const QStringList& subCategoriesToRemove) { return removeFromXmpTagStringBag("Xmp.photoshop.SupplementalCategories", subCategoriesToRemove); } QStringList DMetadata::getXmpSubjects() const { return (getXmpTagStringBag("Xmp.iptc.SubjectCode", false)); } bool DMetadata::setXmpSubjects(const QStringList& newSubjects) const { return addToXmpTagStringBag("Xmp.iptc.SubjectCode", newSubjects); } bool DMetadata::removeXmpSubjects(const QStringList& subjectsToRemove) { return removeFromXmpTagStringBag("Xmp.iptc.SubjectCode", subjectsToRemove); } bool DMetadata::removeExifColorSpace() const { bool ret = true; ret &= removeExifTag("Exif.Photo.ColorSpace"); ret &= removeXmpTag("Xmp.exif.ColorSpace"); return ret; } QString DMetadata::getExifTagStringFromTagsList(const QStringList& tagsList) const { QString val; foreach(const QString& tag, tagsList) { val = getExifTagString(tag.toLatin1().constData()); if (!val.isEmpty()) return val; } return QString(); } bool DMetadata::removeExifTags(const QStringList& tagFilters) { MetaDataMap m = getExifTagsDataList(tagFilters); if (m.isEmpty()) return false; for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it) { removeExifTag(it.key().toLatin1().constData()); } return true; } bool DMetadata::removeIptcTags(const QStringList& tagFilters) { MetaDataMap m = getIptcTagsDataList(tagFilters); if (m.isEmpty()) return false; for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it) { removeIptcTag(it.key().toLatin1().constData()); } return true; } bool DMetadata::removeXmpTags(const QStringList& tagFilters) { MetaDataMap m = getXmpTagsDataList(tagFilters); if (m.isEmpty()) return false; for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it) { removeXmpTag(it.key().toLatin1().constData()); } return true; } } // namespace Digikam diff --git a/core/libs/dmetadata/dmetadatasettingscontainer.cpp b/core/libs/dmetadata/dmetadatasettingscontainer.cpp index 48b0efc4be..485c9c56f7 100644 --- a/core/libs/dmetadata/dmetadatasettingscontainer.cpp +++ b/core/libs/dmetadata/dmetadatasettingscontainer.cpp @@ -1,467 +1,467 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-08-12 * Description : DMetadata Settings Container. * * Copyright (C) 2015 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "metadatasettingscontainer.h" // KDE includes #include // Local includes #include "dmetadatasettings.h" #include "digikam_debug.h" namespace Digikam { bool dmcompare(NamespaceEntry& e1, NamespaceEntry e2) { return e1.index < e2.index; } // ------------------------------------------------------------------------------------------------- class DMetadataSettingsContainer::Private { public: explicit Private() { unifyReadWrite = false; } public: - QMap > readMappings; - QMap > writeMappings; - bool unifyReadWrite; + QMap > readMappings; + QMap > writeMappings; + bool unifyReadWrite; }; DMetadataSettingsContainer::DMetadataSettingsContainer() : d(new Private) { - addMapping(QLatin1String(DM_TAG_CONTAINER)); - addMapping(QLatin1String(DM_RATING_CONTAINER)); - addMapping(QLatin1String(DM_COMMENT_CONTAINER)); + addMapping(QString::fromUtf8(DM_TAG_CONTAINER)); + addMapping(QString::fromUtf8(DM_RATING_CONTAINER)); + addMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)); } DMetadataSettingsContainer::DMetadataSettingsContainer(const DMetadataSettingsContainer& other) : d(new Private) { *d = *other.d; } DMetadataSettingsContainer& DMetadataSettingsContainer::operator=(const DMetadataSettingsContainer& other) { *d = *other.d; return *this; } DMetadataSettingsContainer::~DMetadataSettingsContainer() { delete d; } bool DMetadataSettingsContainer::unifyReadWrite() const { return d->unifyReadWrite; } void DMetadataSettingsContainer::setUnifyReadWrite(bool b) { d->unifyReadWrite = b; } void DMetadataSettingsContainer::readFromConfig(KConfigGroup& group) { bool valid = true; - foreach(const QLatin1String& str, mappingKeys()) + foreach(const QString& str, mappingKeys()) { if (!group.hasGroup(QLatin1String("read") + str + QLatin1String("Namespaces"))) { valid = false; qCDebug(DIGIKAM_GENERAL_LOG) << "Does not contain " << str << " Namespace"; break; } if (!group.hasGroup(QLatin1String("write") + str + QLatin1String("Namespaces"))) { valid = false; qCDebug(DIGIKAM_GENERAL_LOG) << "Does not contain " << str << " Namespace"; break; } } if (valid) { - foreach(const QLatin1String& str, mappingKeys()) + foreach(const QString& str, mappingKeys()) { readOneGroup(group, QLatin1String("read") + str + QLatin1String("Namespaces"), getReadMapping(str)); readOneGroup(group, QLatin1String("write") + str + QLatin1String("Namespaces"), getWriteMapping(str)); } } else { defaultValues(); } // if (group.hasGroup("readTagNamespaces") && // group.hasGroup("readRatingNamespaces") && // group.hasGroup("readCommentNamespaces") && // group.hasGroup("writeTagNamespaces") && // group.hasGroup("writeRatingNamespaces") && // group.hasGroup("writeCommentNamespaces")) // { // readOneGroup(group, QLatin1String("readTagNamespaces"), readTagNamespaces); // readOneGroup(group, QLatin1String("readRatingNamespaces"), readRatingNamespaces); // readOneGroup(group, QLatin1String("readCommentNamespaces"), readCommentNamespaces); // readOneGroup(group, QLatin1String("writeTagNamespaces"), writeTagNamespaces); // readOneGroup(group, QLatin1String("writeRatingNamespaces"), writeRatingNamespaces); // readOneGroup(group, QLatin1String("writeCommentNamespaces"), writeCommentNamespaces); // } // else // { // defaultValues(); // } } void DMetadataSettingsContainer::writeToConfig(KConfigGroup& group) const { - foreach(const QLatin1String& str, mappingKeys()) + foreach(const QString& str, mappingKeys()) { writeOneGroup(group, QLatin1String("read") + str + QLatin1String("Namespaces"), getReadMapping(str)); writeOneGroup(group, QLatin1String("write") + str + QLatin1String("Namespaces"), getWriteMapping(str)); } // writeOneGroup(group, QLatin1String("readTagNamespaces"), readTagNamespaces); // writeOneGroup(group, QLatin1String("readRatingNamespaces"), readRatingNamespaces); // writeOneGroup(group, QLatin1String("readCommentNamespaces"), readCommentNamespaces); // writeOneGroup(group, QLatin1String("writeTagNamespaces"), writeTagNamespaces); // writeOneGroup(group, QLatin1String("writeRatingNamespaces"), writeRatingNamespaces); // writeOneGroup(group, QLatin1String("writeCommentNamespaces"), writeCommentNamespaces); group.sync(); } void DMetadataSettingsContainer::defaultValues() { qCDebug(DIGIKAM_METAENGINE_LOG) << "Loading default values ++++++++++++++++"; d->unifyReadWrite = true; d->writeMappings.clear(); d->readMappings.clear(); defaultTagValues(); defaultRatingValues(); defaultCommentValues(); } -void DMetadataSettingsContainer::addMapping(const QLatin1String& key) +void DMetadataSettingsContainer::addMapping(const QString& key) { d->readMappings[key] = QList(); d->writeMappings[key] = QList(); } -QList &DMetadataSettingsContainer::getReadMapping(const QLatin1String& key) const +QList &DMetadataSettingsContainer::getReadMapping(const QString& key) const { return d->readMappings[key]; } -QList &DMetadataSettingsContainer::getWriteMapping(const QLatin1String& key) const +QList &DMetadataSettingsContainer::getWriteMapping(const QString& key) const { return d->writeMappings[key]; } -QList DMetadataSettingsContainer::mappingKeys() const +QList DMetadataSettingsContainer::mappingKeys() const { return d->readMappings.keys(); } void DMetadataSettingsContainer::defaultTagValues() { // Default tag namespaces NamespaceEntry tagNs1; tagNs1.namespaceName = QLatin1String("Xmp.digiKam.TagsList"); tagNs1.tagPaths = NamespaceEntry::TAGPATH; tagNs1.separator = QLatin1String("/"); tagNs1.nsType = NamespaceEntry::TAGS; tagNs1.index = 0; tagNs1.specialOpts = NamespaceEntry::TAG_XMPSEQ; tagNs1.subspace = NamespaceEntry::XMP; NamespaceEntry tagNs2; tagNs2.namespaceName = QLatin1String("Xmp.MicrosoftPhoto.LastKeywordXMP"); tagNs2.tagPaths = NamespaceEntry::TAGPATH; tagNs2.separator = QLatin1String("/"); tagNs2.nsType = NamespaceEntry::TAGS; tagNs2.index = 1; tagNs2.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs2.subspace = NamespaceEntry::XMP; NamespaceEntry tagNs3; tagNs3.namespaceName = QLatin1String("Xmp.lr.hierarchicalSubject"); tagNs3.tagPaths = NamespaceEntry::TAGPATH; tagNs3.separator = QLatin1String("|"); tagNs3.nsType = NamespaceEntry::TAGS; tagNs3.index = 2; tagNs3.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs3.subspace = NamespaceEntry::XMP; tagNs3.alternativeName = QLatin1String("Xmp.lr.HierarchicalSubject"); tagNs3.secondNameOpts = NamespaceEntry::TAG_XMPSEQ; NamespaceEntry tagNs4; tagNs4.namespaceName = QLatin1String("Xmp.mediapro.CatalogSets"); tagNs4.tagPaths = NamespaceEntry::TAGPATH; tagNs4.separator = QLatin1String("|"); tagNs4.nsType = NamespaceEntry::TAGS; tagNs4.index = 3; tagNs4.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs4.subspace = NamespaceEntry::XMP; NamespaceEntry tagNs5; tagNs5.namespaceName = QLatin1String("Xmp.acdsee.categories"); tagNs5.tagPaths = NamespaceEntry::TAGPATH; tagNs5.separator = QLatin1String("/"); tagNs5.nsType = NamespaceEntry::TAGS; tagNs5.index = 4; tagNs5.specialOpts = NamespaceEntry::TAG_ACDSEE; tagNs5.subspace = NamespaceEntry::XMP; NamespaceEntry tagNs6; tagNs6.namespaceName = QLatin1String("Xmp.dc.subject"); tagNs6.tagPaths = NamespaceEntry::TAG; tagNs6.separator = QLatin1String("/"); tagNs6.nsType = NamespaceEntry::TAGS; tagNs6.index = 5; tagNs6.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs6.subspace = NamespaceEntry::XMP; NamespaceEntry tagNs7; tagNs7.namespaceName = QLatin1String("Iptc.Application2.Keywords"); tagNs7.tagPaths = NamespaceEntry::TAGPATH; tagNs7.separator = QLatin1String("."); tagNs7.nsType = NamespaceEntry::TAGS; tagNs7.index = 6; tagNs7.subspace = NamespaceEntry::IPTC; NamespaceEntry tagNs8; tagNs8.namespaceName = QLatin1String("Exif.Image.XPKeywords"); tagNs8.tagPaths = NamespaceEntry::TAGPATH; tagNs8.separator = QLatin1String(";"); tagNs8.nsType = NamespaceEntry::TAGS; tagNs8.index = 7; tagNs8.subspace = NamespaceEntry::EXIF; - getReadMapping(QLatin1String(DM_TAG_CONTAINER)) << tagNs1 - << tagNs2 - << tagNs3 - << tagNs4 - << tagNs5 - << tagNs6 - << tagNs7 - << tagNs8; + getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)) << tagNs1 + << tagNs2 + << tagNs3 + << tagNs4 + << tagNs5 + << tagNs6 + << tagNs7 + << tagNs8; - d->writeMappings[QLatin1String(DM_TAG_CONTAINER)] = QList(getReadMapping(QLatin1String(DM_TAG_CONTAINER))); + d->writeMappings[QString::fromUtf8(DM_TAG_CONTAINER)] = QList(getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER))); } void DMetadataSettingsContainer::defaultRatingValues() { QList defaultVal; QList microsoftMappings; QList iptcMappings; defaultVal << 0 << 1 << 2 << 3 << 4 << 5; microsoftMappings << 0 << 1 << 25 << 50 << 75 << 99; iptcMappings << 8 << 6 << 5 << 4 << 2 << 1; NamespaceEntry ratingNs1; ratingNs1.namespaceName = QLatin1String("Xmp.xmp.Rating"); ratingNs1.convertRatio = defaultVal; ratingNs1.nsType = NamespaceEntry::RATING; ratingNs1.index = 0; ratingNs1.subspace = NamespaceEntry::XMP; NamespaceEntry ratingNs2; ratingNs2.namespaceName = QLatin1String("Xmp.acdsee.rating"); ratingNs2.convertRatio = defaultVal; ratingNs2.nsType = NamespaceEntry::RATING; ratingNs2.index = 1; ratingNs2.subspace = NamespaceEntry::XMP; NamespaceEntry ratingNs3; ratingNs3.namespaceName = QLatin1String("Xmp.MicrosoftPhoto.Rating"); ratingNs3.convertRatio = microsoftMappings; ratingNs3.nsType = NamespaceEntry::RATING; ratingNs3.index = 2; ratingNs3.subspace = NamespaceEntry::XMP; NamespaceEntry ratingNs4; ratingNs4.namespaceName = QLatin1String("Exif.Image.0x4746"); ratingNs4.convertRatio = defaultVal; ratingNs4.nsType = NamespaceEntry::RATING; ratingNs4.index = 3; ratingNs4.subspace = NamespaceEntry::EXIF; NamespaceEntry ratingNs5; ratingNs5.namespaceName = QLatin1String("Exif.Image.0x4749"); ratingNs5.convertRatio = microsoftMappings; ratingNs5.nsType = NamespaceEntry::RATING; ratingNs5.index = 4; ratingNs5.subspace = NamespaceEntry::EXIF; NamespaceEntry ratingNs6; ratingNs6.namespaceName = QLatin1String("Iptc.Application2.Urgency"); ratingNs6.convertRatio = iptcMappings; ratingNs6.nsType = NamespaceEntry::RATING; ratingNs6.index = 5; ratingNs6.subspace = NamespaceEntry::IPTC; - getReadMapping(QLatin1String(DM_RATING_CONTAINER)) << ratingNs1 - << ratingNs2 - << ratingNs3 - << ratingNs4 - << ratingNs5 - << ratingNs6; + getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER)) << ratingNs1 + << ratingNs2 + << ratingNs3 + << ratingNs4 + << ratingNs5 + << ratingNs6; - d->writeMappings[QLatin1String(DM_RATING_CONTAINER)] = QList(getReadMapping(QLatin1String(DM_RATING_CONTAINER))); + d->writeMappings[QString::fromUtf8(DM_RATING_CONTAINER)] = QList(getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER))); } void DMetadataSettingsContainer::defaultCommentValues() { NamespaceEntry commNs1; commNs1.namespaceName = QLatin1String("Xmp.dc.description"); commNs1.nsType = NamespaceEntry::COMMENT; commNs1.specialOpts = NamespaceEntry::COMMENT_ATLLANGLIST; commNs1.index = 0; commNs1.subspace = NamespaceEntry::XMP; NamespaceEntry commNs2; commNs2.namespaceName = QLatin1String("Xmp.exif.UserComment"); commNs2.nsType = NamespaceEntry::COMMENT; commNs2.specialOpts = NamespaceEntry::COMMENT_ALTLANG; commNs2.index = 1; commNs2.subspace = NamespaceEntry::XMP; NamespaceEntry commNs3; commNs3.namespaceName = QLatin1String("Xmp.tiff.ImageDescription"); commNs3.nsType = NamespaceEntry::COMMENT; commNs3.specialOpts = NamespaceEntry::COMMENT_ALTLANG; commNs3.index = 2; commNs3.subspace = NamespaceEntry::XMP; NamespaceEntry commNs4; commNs4.namespaceName = QLatin1String("Xmp.acdsee.notes"); commNs4.nsType = NamespaceEntry::COMMENT; commNs4.specialOpts = NamespaceEntry::COMMENT_XMP; commNs4.index = 3; commNs4.subspace = NamespaceEntry::XMP; NamespaceEntry commNs5; commNs5.namespaceName = QLatin1String("JPEG/TIFF Comments"); commNs5.nsType = NamespaceEntry::COMMENT; commNs5.specialOpts = NamespaceEntry::COMMENT_JPEG; commNs5.index = 4; commNs5.subspace = NamespaceEntry::XMP; NamespaceEntry commNs6; commNs6.namespaceName = QLatin1String("Exif.Image.ImageDescription"); commNs6.nsType = NamespaceEntry::COMMENT; commNs6.specialOpts = NamespaceEntry::NO_OPTS; commNs6.index = 5; commNs6.alternativeName = QLatin1String("Exif.Photo.UserComment"); commNs6.subspace = NamespaceEntry::EXIF; NamespaceEntry commNs7; commNs7.namespaceName = QLatin1String("Iptc.Application2.Caption"); commNs7.nsType = NamespaceEntry::COMMENT; commNs7.specialOpts = NamespaceEntry::NO_OPTS; commNs7.index = 6; commNs7.subspace = NamespaceEntry::IPTC; - getReadMapping(QLatin1String(DM_COMMENT_CONTAINER)) << commNs1 - << commNs2 - << commNs3 - << commNs4 - << commNs5 - << commNs6 - << commNs7; + getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)) << commNs1 + << commNs2 + << commNs3 + << commNs4 + << commNs5 + << commNs6 + << commNs7; - d->writeMappings[QLatin1String(DM_COMMENT_CONTAINER)] = QList(getReadMapping(QLatin1String(DM_COMMENT_CONTAINER))); + d->writeMappings[QString::fromUtf8(DM_COMMENT_CONTAINER)] = QList(getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER))); } void DMetadataSettingsContainer::readOneGroup(KConfigGroup& group, const QString& name, QList& container) { KConfigGroup myItems = group.group(name); for (QString element : myItems.groupList()) { KConfigGroup gr = myItems.group(element); NamespaceEntry ns; ns.namespaceName = element; ns.tagPaths = (NamespaceEntry::TagType)gr.readEntry("tagPaths").toInt(); ns.separator = gr.readEntry("separator"); ns.nsType = (NamespaceEntry::NamespaceType)gr.readEntry("nsType").toInt(); ns.index = gr.readEntry("index").toInt(); ns.subspace = (NamespaceEntry::NsSubspace)gr.readEntry("subspace").toInt(); ns.alternativeName = gr.readEntry("alternativeName"); ns.specialOpts = (NamespaceEntry::SpecialOptions)gr.readEntry("specialOpts").toInt(); ns.secondNameOpts = (NamespaceEntry::SpecialOptions)gr.readEntry("secondNameOpts").toInt(); ns.isDefault = gr.readEntry(QLatin1String("isDefault"), QVariant(true)).toBool(); ns.isDisabled = gr.readEntry(QLatin1String("isDisabled"), QVariant(false)).toBool(); QString conversion = gr.readEntry("convertRatio"); for (QString str : conversion.split(QLatin1String(","))) { ns.convertRatio.append(str.toInt()); } container.append(ns); } std::sort(container.begin(), container.end(), Digikam::dmcompare); } void DMetadataSettingsContainer::writeOneGroup(KConfigGroup& group, const QString& name, QList& container) const { KConfigGroup namespacesGroup = group.group(name); for (NamespaceEntry e : container) { KConfigGroup tmp = namespacesGroup.group(e.namespaceName); tmp.writeEntry("alternativeName", e.alternativeName); tmp.writeEntry("subspace", (int)e.subspace); tmp.writeEntry("tagPaths", (int)e.tagPaths); tmp.writeEntry("separator", e.separator); tmp.writeEntry("nsType", (int)e.nsType); tmp.writeEntry("convertRatio", e.convertRatio); tmp.writeEntry("specialOpts", (int)e.specialOpts); tmp.writeEntry("secondNameOpts", (int)e.secondNameOpts); tmp.writeEntry("index", e.index); tmp.writeEntry("isDisabled", e.isDisabled); tmp.writeEntry("isDefault", e.isDefault); } } } // namespace Digikam diff --git a/core/libs/dmetadata/dmetadatasettingscontainer.h b/core/libs/dmetadata/dmetadatasettingscontainer.h index b35c10df55..71f52ad87f 100644 --- a/core/libs/dmetadata/dmetadatasettingscontainer.h +++ b/core/libs/dmetadata/dmetadatasettingscontainer.h @@ -1,200 +1,200 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-08-20 * Description : DMetadata Settings Container. * * Copyright (C) 2015 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_DMETADATA_SETTINGS_CONTAINER_H #define DIGIKAM_DMETADATA_SETTINGS_CONTAINER_H // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_export.h" class KConfigGroup; namespace Digikam { /** * @brief The NamespaceEntry class provide a simple container * for dmetadata namespaces variables, such * as names, what types of data expects and extra * xml tags */ const char* const DM_TAG_CONTAINER = I18N_NOOP("Tags"); const char* const DM_RATING_CONTAINER = I18N_NOOP("Rating"); const char* const DM_COMMENT_CONTAINER = I18N_NOOP("Comment"); class NamespaceEntry { public: enum NsSubspace { EXIF = 0, IPTC = 1, XMP = 2 }; enum TagType { TAG = 0, TAGPATH = 1 }; enum SpecialOptions { NO_OPTS = 0, COMMENT_ALTLANG = 1, COMMENT_ATLLANGLIST = 2, COMMENT_XMP = 3, COMMENT_JPEG = 4, TAG_XMPBAG = 5, TAG_XMPSEQ = 6, TAG_ACDSEE = 7 }; enum NamespaceType { TAGS = 0, RATING = 1, COMMENT = 2 }; public: explicit NamespaceEntry() { specialOpts = NO_OPTS; secondNameOpts = NO_OPTS; isDefault = true; isDisabled = false; nsType = TAGS; subspace = XMP; index = -1; tagPaths = TAGPATH; } NamespaceEntry(const NamespaceEntry& copy) { this->namespaceName = copy.namespaceName; this->alternativeName = copy.alternativeName; this->tagPaths = copy.tagPaths; this->separator = copy.separator; this->nsType = copy.nsType; this->convertRatio = QList(copy.convertRatio); this->specialOpts = copy.specialOpts; this->secondNameOpts = copy.secondNameOpts; this->index = copy.index; this->subspace = copy.subspace; this->isDefault = copy.isDefault; this->isDisabled = copy.isDisabled; } ~NamespaceEntry() { } public: NamespaceType nsType; NsSubspace subspace; bool isDefault; bool isDisabled; int index; /** * Tag Options */ QString namespaceName; QString alternativeName; TagType tagPaths; QString separator; /** * Rating Options */ QList convertRatio; SpecialOptions specialOpts; SpecialOptions secondNameOpts; }; /** * The class DMetadataSettingsContainer is designed to dynamically add namespaces. */ class DIGIKAM_EXPORT DMetadataSettingsContainer { public: explicit DMetadataSettingsContainer(); DMetadataSettingsContainer(const DMetadataSettingsContainer& other); ~DMetadataSettingsContainer(); DMetadataSettingsContainer& operator=(const DMetadataSettingsContainer& other); public: void readFromConfig(KConfigGroup& group); void writeToConfig(KConfigGroup& group) const; /** * @brief defaultValues - default namespaces used by digiKam */ void defaultValues(); bool unifyReadWrite() const; void setUnifyReadWrite(bool b); - void addMapping(const QLatin1String& key); + void addMapping(const QString& key); - QList& getReadMapping(const QLatin1String& key) const; + QList& getReadMapping(const QString& key) const; - QList& getWriteMapping(const QLatin1String& key) const; + QList& getWriteMapping(const QString& key) const; - QList mappingKeys() const; + QList mappingKeys() const; private: void defaultTagValues(); void defaultRatingValues(); void defaultCommentValues(); void readOneGroup(KConfigGroup& group, const QString& name, QList& container); void writeOneGroup(KConfigGroup& group, const QString& name, QList& container) const; class Private; Private* d; }; } // namespace Digikam #endif // DIGIKAM_DMETADATA_SETTINGS_CONTAINER_H diff --git a/core/libs/dtrash/dtrash.cpp b/core/libs/dtrash/dtrash.cpp index 6b66a31655..cf2e3f5a29 100644 --- a/core/libs/dtrash/dtrash.cpp +++ b/core/libs/dtrash/dtrash.cpp @@ -1,240 +1,240 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-07-27 * Description : Special digiKam trash implementation * * Copyright (C) 2015 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #include "dtrash.h" // Qt includes #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "collectionmanager.h" #include "albummanager.h" namespace Digikam { const QString DTrash::TRASH_FOLDER = QLatin1String(".dtrash"); const QString DTrash::FILES_FOLDER = QLatin1String("files"); const QString DTrash::INFO_FOLDER = QLatin1String("info"); const QString DTrash::INFO_FILE_EXTENSION = QLatin1String(".dtrashinfo"); const QString DTrash::PATH_JSON_KEY = QLatin1String("path"); const QString DTrash::DELETIONTIMESTAMP_JSON_KEY = QLatin1String("deletiontimestamp"); const QString DTrash::IMAGEID_JSON_KEY = QLatin1String("imageid"); // ---------------------------------------------- DTrash::DTrash() { } bool DTrash::deleteImage(const QString& imageToDelete) { QString collection = CollectionManager::instance()->albumRootPath(imageToDelete); qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: Image album root path:" << collection; if (!prepareCollectionTrash(collection)) { return false; } QFileInfo imageFileInfo(imageToDelete); QFile imageFile(imageToDelete); QString fileName = imageFileInfo.fileName(); // Get the album path, i.e. collection + album. For this, // get the n leftmost characters where n is the complete path without the size of the filename QString completePath = imageFileInfo.path(); qlonglong imageId = -1; // Get the album and with this the image id of the image to trash. PAlbum* pAlbum = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(completePath)); if (pAlbum) { imageId = AlbumManager::instance()->getItemFromAlbum(pAlbum, fileName); } QString baseNameForMovingIntoTrash = createJsonRecordForFile(collection, imageToDelete, imageId); QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + FILES_FOLDER + QLatin1Char('/') + - baseNameForMovingIntoTrash + QLatin1String(".") + + baseNameForMovingIntoTrash + QLatin1Char('.') + imageFileInfo.completeSuffix(); if (!imageFile.rename(destinationInTrash)) { return false; } return true; } bool DTrash::deleteDirRecursivley(const QString& dirToDelete) { QDir srcDir(dirToDelete); foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Files)) { if (!deleteImage(fileInfo.filePath())) { return false; } } foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { deleteDirRecursivley(fileInfo.filePath()); } return srcDir.removeRecursively(); } void DTrash::extractJsonForItem(const QString& collPath, const QString& baseName, DTrashItemInfo& itemInfo) { QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + INFO_FILE_EXTENSION; QFile jsonFile(jsonFilePath); if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) return; QJsonDocument doc = QJsonDocument::fromJson(jsonFile.readAll()); jsonFile.close(); QJsonObject fileInfoObj = doc.object(); itemInfo.jsonFilePath = jsonFilePath; itemInfo.collectionPath = fileInfoObj.value(PATH_JSON_KEY).toString(); itemInfo.collectionRelativePath = fileInfoObj.value(PATH_JSON_KEY).toString() .replace(collPath, QLatin1String("")); itemInfo.deletionTimestamp = QDateTime::fromString( fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString()); QJsonValue imageIdValue = fileInfoObj.value(IMAGEID_JSON_KEY); if (!imageIdValue.isUndefined()) { itemInfo.imageId = imageIdValue.toString().toLongLong(); } else { itemInfo.imageId = -1; } } bool DTrash::prepareCollectionTrash(const QString& collectionPath) { QString trashFolder = collectionPath + QLatin1Char('/') + TRASH_FOLDER; QDir trashDir(trashFolder); if (!trashDir.exists()) { bool isCreated = true; isCreated &= trashDir.mkpath(trashFolder); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + FILES_FOLDER); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + INFO_FOLDER); if (!isCreated) { qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: could not create trash folder for collection"; return false; } } qCDebug(DIGIKAM_IOJOB_LOG) << "Trash folder for collection: " << trashFolder; return true; } QString DTrash::createJsonRecordForFile(const QString& collectionPath, const QString& imagePath, qlonglong imageId) { QJsonObject jsonObjForImg; QJsonValue pathJsonVal(imagePath); QJsonValue timestampJsonVal(QDateTime::currentDateTime().toString()); QJsonValue imageIdJsonVal(QString::number(imageId)); jsonObjForImg.insert(PATH_JSON_KEY, pathJsonVal); jsonObjForImg.insert(DELETIONTIMESTAMP_JSON_KEY, timestampJsonVal); jsonObjForImg.insert(IMAGEID_JSON_KEY, imageIdJsonVal); QJsonDocument jsonDocForImg(jsonObjForImg); QFileInfo imgFileInfo(imagePath); QString jsonFileName = getAvialableJsonFilePathInTrash(collectionPath, imgFileInfo.baseName()); QFile jsonFileForImg(jsonFileName); QFileInfo jsonFileInfo(jsonFileName); if (!jsonFileForImg.open(QFile::WriteOnly)) return jsonFileInfo.baseName(); jsonFileForImg.write(jsonDocForImg.toJson()); jsonFileForImg.close(); return jsonFileInfo.baseName(); } QString DTrash::getAvialableJsonFilePathInTrash(const QString& collectionPath, const QString& baseName, int version) { QString pathToCreateJsonFile = collectionPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + QLatin1Char('-') + QUuid::createUuid().toString().mid(1, 8) + (version ? QString::number(version) : QLatin1String("")) + INFO_FILE_EXTENSION; QFileInfo jsonFileInfo(pathToCreateJsonFile); if (jsonFileInfo.exists()) { return getAvialableJsonFilePathInTrash(collectionPath, baseName, ++version); } else { return pathToCreateJsonFile; } } } // namespace Digikam diff --git a/core/libs/tags/tagfolderview.cpp b/core/libs/tags/tagfolderview.cpp index 059fb323b9..7c83f67382 100644 --- a/core/libs/tags/tagfolderview.cpp +++ b/core/libs/tags/tagfolderview.cpp @@ -1,384 +1,386 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-03-22 * Description : tags folder view. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2009-2011 by Andi Clemens * Copyright (C) 2009-2011 by Johannes Wienke * * 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, 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. * * ============================================================ */ #include "tagfolderview.h" // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "albummanager.h" #include "contextmenuhelper.h" #include "tagmodificationhelper.h" #include "facetags.h" namespace Digikam { class TagFolderView::Private { public: explicit Private() : showFindDuplicateAction(true), showDeleteFaceTagsAction(false), resetIconAction(0), findDuplAction(0) { } bool showFindDuplicateAction; bool showDeleteFaceTagsAction; QAction* resetIconAction; QAction* findDuplAction; }; TagFolderView::TagFolderView(QWidget* const parent, TagModel* const model) : TagTreeView(parent), d(new Private) { setAlbumModel(model); d->resetIconAction = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Tag Icon"), this); d->findDuplAction = new QAction(QIcon::fromTheme(QLatin1String("tools-wizard")), i18n("Find Duplicates..."), this); setSortingEnabled(true); setSelectAlbumOnClick(true); setEnableContextMenu(true); } TagFolderView::~TagFolderView() { delete d; } void TagFolderView::setShowFindDuplicateAction(bool show) { d->showFindDuplicateAction = show; } void TagFolderView::setShowDeleteFaceTagsAction(bool show) { d->showDeleteFaceTagsAction = show; } QString TagFolderView::contextMenuTitle() const { return i18n("Tags"); } void TagFolderView::addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album) { TAlbum* const tag = dynamic_cast (album); if (!tag) { return; } cmh.addActionNewTag(tagModificationHelper(), tag); #ifdef HAVE_AKONADICONTACT cmh.addCreateTagFromAddressbookMenu(); #endif cmh.addAction(d->resetIconAction); cmh.addSeparator(); QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Nodes"), this); cmh.addAction(expandSel, this, SLOT(slotExpandNode()), false); QAction* const collapseSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Collapse Selected Recursively"), this); cmh.addAction(collapseSel, this, SLOT(slotCollapseNode()), false); cmh.addSeparator(); if (d->showFindDuplicateAction) { cmh.addAction(d->findDuplAction); } cmh.addExportMenu(); cmh.addSeparator(); + if (d->showDeleteFaceTagsAction) { cmh.addActionDeleteFaceTag(tagModificationHelper(), tag); cmh.addSeparator(); } else { cmh.addActionDeleteTag(tagModificationHelper(), tag); cmh.addSeparator(); // If the tag is no face tag, add the option to set it as face tag. if (!FaceTags::isPerson(tag->id()) && !tag->isRoot()) { cmh.addActionTagToFaceTag(tagModificationHelper(), tag); } } + cmh.addActionEditTag(tagModificationHelper(), tag); connect(&cmh, SIGNAL(signalAddNewTagFromABCMenu(QString)), this, SLOT(slotTagNewFromABCMenu(QString))); d->resetIconAction->setEnabled(!tag->isRoot()); } void TagFolderView::slotTagNewFromABCMenu(const QString& personName) { TAlbum* const parent = currentAlbum(); if (!parent) { return; } tagModificationHelper()->slotTagNew(parent, personName, QLatin1String("tag-people")); } void TagFolderView::slotExpandNode() { //QModelIndex root = this->model()->index(0,0); QItemSelectionModel* const model = this->selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; foreach(const QModelIndex& index, selected) { greyNodes.append(index); expand(index); } - while(!greyNodes.isEmpty()) + while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); - if(!(current.isValid())) + if (!current.isValid()) { continue; } int it = 0; QModelIndex child = current.child(it++, 0); - while(child.isValid()) + while (child.isValid()) { expand(child); greyNodes.enqueue(child); child = current.child(it++, 0); } } } void TagFolderView::slotCollapseNode() { //QModelIndex root = this->model()->index(0,0); QItemSelectionModel* const model = this->selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; foreach(const QModelIndex& index, selected) { greyNodes.append(index); collapse(index); } - while(!greyNodes.isEmpty()) + while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); - if(!(current.isValid())) + if (!current.isValid()) { continue; } int it = 0; QModelIndex child = current.child(it++, 0); - while(child.isValid()) + while (child.isValid()) { collapse(child); greyNodes.enqueue(child); child = current.child(it++, 0); } } } void TagFolderView::handleCustomContextMenuAction(QAction* action, AlbumPointer album) { Album* const a = album; TAlbum* const tag = dynamic_cast(a); if (!tag) { return; } if (!tag || !action) { return; } if (action == d->resetIconAction) { QString errMsg; AlbumManager::instance()->updateTAlbumIcon(tag, QLatin1String("tag"), 0, errMsg); } else if (action == d->findDuplAction) { QList selected = selectedTagAlbums(); emit signalFindDuplicates(selected); } } -void TagFolderView::setContexMenuItems(ContextMenuHelper& cmh, QList< TAlbum* > albums) +void TagFolderView::setContexMenuItems(ContextMenuHelper& cmh, const QList& albums) { - - if(albums.size() == 1) + if (albums.size() == 1) { addCustomContextMenuActions(cmh, albums.first()); return; } if (d->showFindDuplicateAction) { cmh.addAction(d->findDuplAction); } QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Recursively"), this); cmh.addAction(expandSel, this, SLOT(slotExpandNode()), false); QAction* const collapseSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Collapse Selected Recursively"), this); cmh.addAction(collapseSel, this, SLOT(slotCollapseNode()), false); cmh.addSeparator(); cmh.addExportMenu(); cmh.addSeparator(); + if (d->showDeleteFaceTagsAction) { cmh.addActionDeleteFaceTags(tagModificationHelper(), albums); } else { cmh.addActionDeleteTags(tagModificationHelper(), albums); // If one of the selected tags is no face tag, add the action to mark them as face tags. - foreach (TAlbum* const tag, albums) + foreach(TAlbum* const tag, albums) { if (!FaceTags::isPerson(tag->id())) { cmh.addSeparator(); cmh.addActionTagToFaceTag(tagModificationHelper(), tag); break; } } } cmh.addSeparator(); } void TagFolderView::contextMenuEvent(QContextMenuEvent* event) { /* if (!d->enableContextMenu) { return; } */ Album* const album = albumFilterModel()->albumForIndex(indexAt(event->pos())); if (!showContextMenuAt(event, album)) { return; } // switch to the selected album if need /* if (d->selectOnContextMenu && album) { setCurrentAlbum(album); } */ // -------------------------------------------------------- QModelIndexList selectedItems = selectionModel()->selectedIndexes(); std::sort(selectedItems.begin(), selectedItems.end()); QList items; foreach(const QModelIndex& mIndex, selectedItems) { TAlbum* const temp = static_cast(albumForIndex(mIndex)); items.append(temp); } /** * If no item is selected append root tag */ if (items.isEmpty()) { QModelIndex root = this->model()->index(0, 0); items.append(static_cast(albumForIndex(root))); } QMenu popmenu(this); popmenu.addSection(contextMenuIcon(), contextMenuTitle()); ContextMenuHelper cmhelper(&popmenu); setContexMenuItems(cmhelper, items); /* foreach(ContextMenuElement* const element, d->contextMenuElements) { element->addActions(this, cmhelper, album); } */ AlbumPointer albumPointer(album); QAction* const choice = cmhelper.exec(QCursor::pos()); handleCustomContextMenuAction(choice, albumPointer); } } // namespace Digikam diff --git a/core/libs/tags/tagfolderview.h b/core/libs/tags/tagfolderview.h index 5f6f4e333b..055ba371af 100644 --- a/core/libs/tags/tagfolderview.h +++ b/core/libs/tags/tagfolderview.h @@ -1,151 +1,151 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-03-22 * Description : tags folder view. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_TAG_FOLDER_VIEW_H #define DIGIKAM_TAG_FOLDER_VIEW_H // Qt includes #include // Local includes #include "albumtreeview.h" namespace Digikam { class ContextMenuHelper; class TagFolderView: public TagTreeView { Q_OBJECT public: /** * Constructor. * * @param parent parent for Qt's parent child mechanism * @param model tag model to display */ TagFolderView(QWidget* const parent, Digikam::TagModel* const model); /** * Destructor. */ virtual ~TagFolderView(); /** * Define whether to show the "find duplicate" action in context menus * or not. * * @param show if true the action to find duplicate images in * the tag album is displayed */ void setShowFindDuplicateAction(bool show); /** * Define whether to show the "Delete People Tags" action in context menus * or not. * * @param show if true the action to delete people tags in * the tag album is displayed */ void setShowDeleteFaceTagsAction(bool show); Q_SIGNALS: - void signalFindDuplicates(QList); + void signalFindDuplicates(const QList& albums); protected: QString contextMenuTitle() const; /** * Hook method to add custom actions to the generated context menu. * * The default implementation adds actions to reset the tag icon and to * find duplicates in a tag album. If you want to use these actions, * remember to call this class' implementation of this method and * the handleCustomContextMenuAction in your derived class. * * @param cmh helper object to create the context menu * @param album tag on which the context menu will be created. May be null if * it is requested on no tag entry */ virtual void addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album); /** * Hook method to handle the custom context menu actions that were added * with addCustomContextMenuActions. * * @param action the action that was chosen by the user, may be null if none * of the custom actions were selected * @param album the tag on which the context menu was requested. May be null * if there was no */ virtual void handleCustomContextMenuAction(QAction* action, AlbumPointer album); /** * Reimplement contextMenuEvent from AbstractAlbumTree to support multiple * selection * * @param event context menu event triggered by right click */ void contextMenuEvent(QContextMenuEvent* event); /** * Implementation of AddCustomContextMenuActions(see above) that handle * multiple selection. If only one element is selected, only * AddCustomContextMenuActions is called * * @param cmh - helper object to create context menu * @param albums - vector of selected albums to be used on menu actions */ - virtual void setContexMenuItems(ContextMenuHelper& cmh, QList< TAlbum* > albums); + virtual void setContexMenuItems(ContextMenuHelper& cmh, const QList& albums); private Q_SLOTS: void slotTagNewFromABCMenu(const QString& personName); /** * @brief slotExpandNode - expands recursively selected nodes */ void slotExpandNode(); /** * @brief slotCollapseNode - collapse recursively selected nodes */ void slotCollapseNode(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_TAG_FOLDER_VIEW_H diff --git a/core/libs/tags/tagmodificationhelper.cpp b/core/libs/tags/tagmodificationhelper.cpp index 222e323c5c..a5b2881370 100644 --- a/core/libs/tags/tagmodificationhelper.cpp +++ b/core/libs/tags/tagmodificationhelper.cpp @@ -1,710 +1,710 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify tag albums in views * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "tagmodificationhelper.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "coredbtransaction.h" #include "imageinfo.h" #include "imagetagpair.h" #include "metadatahub.h" #include "scancontroller.h" #include "statusprogressbar.h" #include "tagsactionmngr.h" #include "tagproperties.h" #include "tageditdlg.h" #include "facetags.h" #include "facedbaccess.h" #include "facedb.h" namespace Digikam { class TagModificationHelper::Private { public: explicit Private() { parentTag = 0; dialogParent = 0; } AlbumPointer parentTag; QWidget* dialogParent; }; TagModificationHelper::TagModificationHelper(QObject* const parent, QWidget* const dialogParent) : QObject(parent), d(new Private) { d->dialogParent = dialogParent; } TagModificationHelper::~TagModificationHelper() { delete d; } void TagModificationHelper::bindTag(QAction* action, TAlbum* album) const { action->setData(QVariant::fromValue(AlbumPointer(album))); } TAlbum* TagModificationHelper::boundTag(QObject* sender) const { QAction* action = 0; if ( (action = qobject_cast(sender)) ) { return action->data().value >(); } return 0; } void TagModificationHelper::bindMultipleTags(QAction* action, QList tags) { action->setData(QVariant::fromValue(tags)); } QList TagModificationHelper::boundMultipleTags(QObject* sender) { QAction* action = 0; if ((action = qobject_cast(sender))) { return (action->data().value >()); } return QList(); } TAlbum* TagModificationHelper::slotTagNew(TAlbum* parent, const QString& title, const QString& iconName) { // ensure that there is a parent AlbumPointer p(parent); if (!p) { p = AlbumManager::instance()->findTAlbum(0); if (!p) { qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find root tag album"; return 0; } } QString editTitle = title; QString editIconName = iconName; QKeySequence ks; if (title.isEmpty()) { bool doCreate = TagEditDlg::tagCreate(d->dialogParent, p, editTitle, editIconName, ks); if (!doCreate || !p) { return 0; } } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(p, editTitle, editIconName, ks, errMap); TagEditDlg::showtagsListCreationError(d->dialogParent, errMap); if (errMap.isEmpty() && !tList.isEmpty()) { TAlbum* const tag = static_cast(tList.last()); emit tagCreated(tag); return tag; } else { return 0; } } TAlbum* TagModificationHelper::slotTagNew() { return slotTagNew(boundTag(sender())); } void TagModificationHelper::slotTagEdit(TAlbum* t) { if (!t) { return; } AlbumPointer tag(t); QString title, icon; QKeySequence ks; bool doEdit = TagEditDlg::tagEdit(d->dialogParent, tag, title, icon, ks); if (!doEdit || !tag) { return; } if (tag && tag->title() != title) { QString errMsg; if (AlbumManager::instance()->renameTAlbum(tag, title, errMsg)) { // TODO: make an option to edit the full name of a face tag if (FaceTags::isPerson(tag->id())) { TagProperties props(tag->id()); props.setProperty(TagPropertyName::person(), title); props.setProperty(TagPropertyName::faceEngineName(), title); } } else { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->icon() != icon) { QString errMsg; if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->property(TagPropertyName::tagKeyboardShortcut()) != ks.toString()) { TagsActionMngr::defaultManager()->updateTagShortcut(tag->id(), ks); } emit tagEdited(tag); } void TagModificationHelper::slotTagEdit() { slotTagEdit(boundTag(sender())); } void TagModificationHelper::slotTagDelete(TAlbum* t) { if (!t || t->isRoot()) { return; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } // ask for deletion of children if (children) { int result = QMessageBox::warning(d->dialogParent, qApp->applicationName(), i18np("Tag '%2' has one subtag. " "Deleting this will also delete " "the subtag.\n" "Do you want to continue?", "Tag '%2' has %1 subtags. " "Deleting this will also delete " "the subtags.\n" "Do you want to continue?", children, tag->title()), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes || !tag) { return; } } QString message; QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { message = i18np("Tag '%2' is assigned to one item. " "Do you want to continue?", "Tag '%2' is assigned to %1 items. " "Do you want to continue?", assignedItems.count(), tag->title()); } else { message = i18n("Delete '%1' tag?", tag->title()); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes && tag) { emit aboutToDeleteTag(tag); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(tag, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } void TagModificationHelper::slotTagDelete() { slotTagDelete(boundTag(sender())); } -void TagModificationHelper::slotMultipleTagDel(QList& tags) +void TagModificationHelper::slotMultipleTagDel(QList& tags) { QString tagWithChildrens; QString tagWithoutImages; QString tagWithImages; QMultiMap sortedTags; foreach(TAlbum* const t, tags) { if (!t || t->isRoot()) { continue; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } if (children) - tagWithChildrens.append(tag->title() + QLatin1String(" ")); + tagWithChildrens.append(tag->title() + QLatin1Char(' ')); QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { - tagWithImages.append(tag->title() + QLatin1String(" ")); + tagWithImages.append(tag->title() + QLatin1Char(' ')); } else { - tagWithoutImages.append(tag->title() + QLatin1String(" ")); + tagWithoutImages.append(tag->title() + QLatin1Char(' ')); } /** * Tags must be deleted from children to parents, if we don't want * to step on invalid index. Use QMultiMap to order them by distance * to root tag */ Album* parent = t; int depth = 0; while (!parent->isRoot()) { parent = parent->parent(); depth++; } sortedTags.insert(depth, tag); } // ask for deletion of children if (!tagWithChildrens.isEmpty()) { int result = QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), i18n("Tags '%1' have one or more subtags. " "Deleting them will also delete " "the subtags.\n" "Do you want to continue?", tagWithChildrens), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } } QString message; if (!tagWithImages.isEmpty()) { message = i18n("Tags '%1' are assigned to one or more items. " "Do you want to continue?", tagWithImages); } else { message = i18n("Delete '%1' tag(s)?", tagWithoutImages); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes) { QMultiMap::iterator it; /** * QMultimap doesn't provide reverse iterator, -1 is required * because end() points after the last element */ for (it = sortedTags.end()-1 ; it != sortedTags.begin()-1 ; --it) { emit aboutToDeleteTag(it.value()); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagModificationHelper::slotMultipleTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagDel(lst); } void TagModificationHelper::slotFaceTagDelete(TAlbum* t) { QList tag; tag.append(t); slotMultipleFaceTagDel(tag); } void TagModificationHelper::slotFaceTagDelete() { slotFaceTagDelete(boundTag(sender())); } void TagModificationHelper::slotMultipleFaceTagDel(QList& tags) { QString tagsWithChildren; QString tagsWithImages; // We use a set here since else one tag could occur more than once // which could lead to undefined behaviour. QSet allPersonTagsToDelete; int tagsWithChildrenCount = 0; QSet allAssignedItems; int tagsWithImagesCount = 0; foreach(TAlbum* const selectedTag, tags) { if (!selectedTag || selectedTag->isRoot()) { continue; } // find tags and subtags with person property QSet personTagsToDelete = getFaceTags(selectedTag).toSet(); // If there is more than one person tag in the list, // the tag to remove has at least one sub tag that is a face tag. // Thus, we have to warn. // // If there is only one face tag, it is either the original tag, // or it is the only sub tag of the original tag. // Behave, like the face tag itself was selected. if (personTagsToDelete.size() > 1) { if (tagsWithChildrenCount > 0) { tagsWithChildren.append(QLatin1String(",")); } tagsWithChildren.append(selectedTag->title()); ++tagsWithChildrenCount; } // Get the assigned faces for all person tags to delete foreach(TAlbum* const tAlbum, personTagsToDelete) { // If the global set does not yet contain the tag if (!allPersonTagsToDelete.contains(tAlbum)) { QSet assignedItems = CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::tagRegion()).toSet(); assignedItems.unite(CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::autodetectedFace()).toSet()); if (!assignedItems.isEmpty()) { // Add the items to the global set for potential untagging allAssignedItems.unite(assignedItems); if (tagsWithImagesCount > 0) { tagsWithImages.append(QLatin1String(",")); } tagsWithImages.append(tAlbum->title()); ++tagsWithImagesCount; } } } // Add the found tags to the global set. allPersonTagsToDelete.unite(personTagsToDelete); } // ask for deletion of children if (tagsWithChildrenCount) { QString message = i18np("Face tag '%2' has at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", "Face tags '%2' have at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", tagsWithChildrenCount, tagsWithChildren); bool removeChildren = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (!removeChildren) { return; } } QString message; if (!allAssignedItems.isEmpty()) { message = i18np("Face tag '%2' is assigned to at least one item. " "Do you want to continue?", "Face tags '%2' are assigned to at least one item. " "Do you want to continue?", tagsWithImagesCount, tagsWithImages); } else { message = i18np("Remove face tag?", "Remove face tags?", tags.size()); } bool removeFaceTag = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (removeFaceTag) { // Now we ask the user if we should also remove the tags from the images. QString msg = i18np("Remove the tag corresponding to this face tag from the images?", "Remove the %1 tags corresponding to this face tags from the images?", allPersonTagsToDelete.size()); bool removeTagFromImages = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::No)); MetadataHub metadataHub; // remove the face region from images and unassign the tag if wished foreach(const qlonglong& imageId, allAssignedItems) { foreach (TAlbum* const tagToRemove, allPersonTagsToDelete) { ImageTagPair imageTagAssociation(imageId,tagToRemove->id()); if (imageTagAssociation.isAssigned()) { imageTagAssociation.removeProperties(ImageTagPropertyName::autodetectedFace()); imageTagAssociation.removeProperties(ImageTagPropertyName::tagRegion()); if (removeTagFromImages) { imageTagAssociation.unAssignTag(); // Load the current metadata and sync the tags ImageInfo info(imageId); if (!info.isNull()) { metadataHub.load(info); if (!metadataHub.writeToMetadata(info)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed writing tags to image " << info.filePath(); } } } } } } foreach(TAlbum* const tAlbum, allPersonTagsToDelete) { TagProperties props(tAlbum->id()); // Delete TagPropertyName::person() and TagPropertyName::faceEngineName() // fetch the UUID to delete the identity from facesdb props.removeProperties(TagPropertyName::person()); props.removeProperties(TagPropertyName::faceEngineName()); QString uuid = props.value(TagPropertyName::faceEngineUuid()); qCDebug(DIGIKAM_GENERAL_LOG) << "Deleting person tag properties for tag " << tAlbum->title() << " with uuid " << uuid; if (!uuid.isEmpty()) { // Delete the UUID props.removeProperties(TagPropertyName::faceEngineUuid()); // delete the faces db identity with this uuid. FaceDbAccess access; access.db()->deleteIdentity(uuid); } } } } void TagModificationHelper::slotMultipleFaceTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleFaceTagDel(lst); } void TagModificationHelper::slotTagToFaceTag(TAlbum* tAlbum) { if (!tAlbum) { return; } if (!FaceTags::isPerson(tAlbum->id())) { FaceTags::ensureIsPerson(tAlbum->id()); } } void TagModificationHelper::slotTagToFaceTag() { slotTagToFaceTag(boundTag(sender())); } void TagModificationHelper::slotMultipleTagsToFaceTags(QList& tags) { foreach(TAlbum* const selectedTag, tags) { slotTagToFaceTag(selectedTag); } } void TagModificationHelper::slotMultipleTagsToFaceTags() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagsToFaceTags(lst); } QList TagModificationHelper::getFaceTags(TAlbum* rootTag) { if (!rootTag) { return QList(); } QList tags; tags.append(rootTag); return getFaceTags(tags).toList(); } QSet TagModificationHelper::getFaceTags(QList tags) { QSet faceTags; foreach(TAlbum* const tAlbum, tags) { if (FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } AlbumPointer tag(tAlbum); AlbumIterator iter(tag); // Get all shild tags which have the person property. while (iter.current()) { Album* const album = iter.current(); // Make sure that no nullp pointer dereference is done. // though while(iter.current()) already tests for the current // album being true, i.e. > 0 , i.e. non-null if (album) { TAlbum* const tAlbum = dynamic_cast(album); if (tAlbum && FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } ++iter; } } } return faceTags; } } // namespace Digikam diff --git a/core/libs/tags/tagmodificationhelper.h b/core/libs/tags/tagmodificationhelper.h index 2aff6c3e85..d19886268b 100644 --- a/core/libs/tags/tagmodificationhelper.h +++ b/core/libs/tags/tagmodificationhelper.h @@ -1,237 +1,237 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify tag albums in views * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_TAG_MODIFICATION_HELPER_H #define DIGIKAM_TAG_MODIFICATION_HELPER_H // Qt includes #include #include // Local includes #include "album.h" class QAction; namespace Digikam { /** * Utility class providing methods to modify tag albums (TAlbum) in a way * useful to implement views. * * This class can do background processing for batch tag operations. So be sure * that the signals indicating the progress of these operations are used. * * @author jwienke */ class TagModificationHelper: public QObject { Q_OBJECT public: /** * Constructor. * * @param parent parent for qt parent child mechanism * @param dialogParent paret widget for dialogs displayed by this object */ explicit TagModificationHelper(QObject* const parent, QWidget* const dialogParent); /** * Destructor. */ virtual ~TagModificationHelper(); public Q_SLOTS: /** * Creates one ore more new tags under the given parent. If only the parent * is given, then a dialog is shown to create new tags. Else, if also a * title and optionally an icon are given, then these values will be used * directly to create the tag. * * @param parent parent tag album under which to create the new tags. May be * 0 to use the root album * @param title if this isn't an empty string, then this tag name is * suggested * @param iconName an optional name for the icon to suggest for the new tag * @return new tag album or 0 if not created */ TAlbum* slotTagNew(TAlbum* parent, const QString& title = QString(), const QString& iconName = QString()); /** * Same as above, but this slot can be triggered from a QAction * if a parent tag is bound to this action, see below. * Without this mechanism, will add a toplevel tag. * * @return new tag created or 0 if no tag was created */ TAlbum* slotTagNew(); /** * Edits the given tag via a user dialog. * * @param tag the tag to change */ void slotTagEdit(TAlbum* tag); void slotTagEdit(); /// must use bindTag and a QAction /** * Deletes the given tag and after prompting the user for this. * * @param tag the tag to delete, must not be the root tag album */ void slotTagDelete(TAlbum* tag); /** * must use bindTag and a QAction */ void slotTagDelete(); /// must use bindTag and a QAction /** * Delete multiple tags and prompt user only once for all * * @param tags tags to be deleted, without root tag */ - void slotMultipleTagDel(QList& tags); + void slotMultipleTagDel(QList& tags); /** * must use bindMultipleTags and a QAction */ void slotMultipleTagDel(); /** * Deletes the given face tag and after prompting the user for this. * The tag itself is not deleted. Only its property as face tag. * * @param tag the face tag to delete */ void slotFaceTagDelete(TAlbum* tag); /** * must use bindTag and a QAction */ void slotFaceTagDelete(); /// must use bindTag and a QAction /** * Delete multiple face tags and prompt user only once for all * The tags itself are not deleted. Only their properties as face tags. * * @param tags face tags to be deleted. */ - void slotMultipleFaceTagDel(QList& tags); + void slotMultipleFaceTagDel(QList& tags); /** * must use bindMultipleTags and a QAction */ void slotMultipleFaceTagDel(); /** * Marks the tag as face tag if it is not already. * * @param tag the tag to mark */ void slotTagToFaceTag(TAlbum* tag); /** * must use bindTag and a QAction */ void slotTagToFaceTag(); /// must use bindTag and a QAction /** * Marks the tags as face tags if they are not already. * * @param tags the tags to mark. */ - void slotMultipleTagsToFaceTags(QList& tags); + void slotMultipleTagsToFaceTags(QList& tags); /** * must use bindMultipleTags and a QAction */ void slotMultipleTagsToFaceTags(); /** * Sets the tag that the given action operates on. * You must call bindTag and then connect the action's triggered * to the desired slot, slotTagNew(), slotTagEdit() or slotTagDelete(). * Note: Changes the Action's user data. */ void bindTag(QAction* action, TAlbum* parent) const; /** * Returns the tag bound with bindTag. The given QObject shall be * a QAction, but for convenience the given object * will be checked with qobject_cast first, so you can pass QObject::sender(). */ TAlbum* boundTag(QObject* action) const; /** * Set QVector's pointer into action's data. Make sure that QVector is not * a local object and it's not destroyed before boundMultipleTags are called * * @param action - action to store pointer * @param tags - QVector pointer to be stored */ void bindMultipleTags(QAction* action, QList tags); /** * Return QVector pointer bound with bindMultipleTags. Use when context menu * should delete more than one item: multiple-selection. */ QList< TAlbum* > boundMultipleTags(QObject* sender); Q_SIGNALS: void tagCreated(TAlbum* tag); void tagEdited(TAlbum* tag); void aboutToDeleteTag(TAlbum* tag); private: /** * Returns all sub-tags of the given one which have the person property. * This includes the root tag, if it has the property, too. */ QList getFaceTags(TAlbum* rootTags = 0); /** * Returns all sub-tags of the given ones which have the person property. * This includes the root tags, if they have the property, too. */ QSet getFaceTags(QList tags); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_TAG_MODIFICATION_HELPER_H diff --git a/core/libs/tags/tagsmanager/taglist.cpp b/core/libs/tags/tagsmanager/taglist.cpp index 1118a6a617..e18c6750ab 100644 --- a/core/libs/tags/tagsmanager/taglist.cpp +++ b/core/libs/tags/tagsmanager/taglist.cpp @@ -1,289 +1,289 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 20013-07-31 * Description : Tag List implementation as Quick Access for various * subtrees in Tag Manager * * Copyright (C) 2013 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "taglist.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "albumtreeview.h" #include "tagmngrtreeview.h" #include "tagmngrlistmodel.h" #include "tagmngrlistview.h" #include "tagmngrlistitem.h" namespace Digikam { class TagList::Private { public: explicit Private() { addButton = 0; tagList = 0; tagListModel = 0; treeView = 0; } QPushButton* addButton; TagMngrListView* tagList; TagMngrListModel* tagListModel; TagMngrTreeView* treeView; QMap > tagMap; }; TagList::TagList(TagMngrTreeView* const treeView, QWidget* const parent) : QWidget(parent), d(new Private()) { d->treeView = treeView; QVBoxLayout* const layout = new QVBoxLayout(); d->addButton = new QPushButton(i18n("Add to List")); d->addButton->setToolTip(i18n("Add selected tags to Quick Access List")); d->tagList = new TagMngrListView(this); d->tagListModel = new TagMngrListModel(this); d->tagList->setModel(d->tagListModel); d->tagList->setSelectionMode(QAbstractItemView::ExtendedSelection); d->tagList->setDragEnabled(true); d->tagList->setAcceptDrops(true); d->tagList->setDropIndicatorShown(true); layout->addWidget(d->addButton); layout->addWidget(d->tagList); connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddPressed())); connect(d->tagList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotTagDeleted(Album*))); restoreSettings(); this->setLayout(layout); } TagList::~TagList() { delete d->tagList; delete d->tagListModel; delete d; } void TagList::saveSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); conf.deleteGroup(QLatin1String("List Content")); KConfigGroup group = conf.group(QLatin1String("List Content")); QList currentItems = d->tagListModel->allItems(); group.writeEntry(QLatin1String("Size"), currentItems.count()-1); - for (int it = 1; it < currentItems.size(); it++) + for (int it = 1 ; it < currentItems.size() ; ++it) { QList ids = currentItems.at(it)->getTagIds(); QString saveData; - for (int jt = 0; jt < ids.size(); jt++) + for (int jt = 0 ; jt < ids.size() ; ++jt) { - saveData.append(QString::number(ids.at(jt)) + QLatin1String(" ")); + saveData.append(QString::number(ids.at(jt)) + QLatin1Char(' ')); } group.writeEntry(QString::fromUtf8("item%1").arg(it-1), saveData); } } void TagList::restoreSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); KConfigGroup group = conf.group(QLatin1String("List Content")); QStringList items; int size = group.readEntry(QLatin1String("Size"), -1); /** * If config is empty add generic All Tags */ d->tagListModel->addItem(QList() << QBrush(Qt::cyan, Qt::Dense2Pattern)); if (size == 0 || size < 0) { return; } - for(int it = 0; it < size; it++) + for (int it = 0 ; it < size ; ++it) { QString data = group.readEntry(QString::fromUtf8("item%1").arg(it), ""); - if(data.isEmpty()) + if (data.isEmpty()) continue; QStringList ids = data.split(QLatin1String(" "), QString::SkipEmptyParts); QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach(const QString& tagId, ids) { TAlbum* const item = AlbumManager::instance()->findTAlbum(tagId.toInt()); - if(item) + if (item) { itemData << item->id(); } } ListItem* const listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach(int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } /** "All Tags" item should be selected **/ QModelIndex rootIndex = d->tagList->model()->index(0,0); d->tagList->setCurrentIndex(rootIndex); } void TagList::slotAddPressed() { QModelIndexList selected = d->treeView->selectionModel()->selectedIndexes(); if (selected.isEmpty()) { return; } QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach(const QModelIndex& index, selected) { TAlbum* const album = static_cast(d->treeView->albumForIndex(index)); itemData << album->id(); } ListItem* listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach(int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } void TagList::slotSelectionChanged() { QModelIndexList indexList = d->tagList->mySelectedIndexes(); QSet mySet; foreach(const QModelIndex& index, indexList) { ListItem* const item = static_cast(index.internalPointer()); if (item->getTagIds().isEmpty()) { mySet.clear(); break; } foreach(int tagId, item->getTagIds()) { mySet.insert(tagId); } } TagsManagerFilterModel* const filterModel = d->treeView->getFilterModel(); filterModel->setQuickListTags(QList::fromSet(mySet)); } void TagList::slotTagDeleted(Album* album) { TAlbum* const talbum = dynamic_cast(album); if (!talbum) { return; } int delId = talbum->id(); QList items = d->tagMap[delId]; foreach(ListItem* const item, items) { item->removeTagId(delId); if (item->getTagIds().isEmpty()) { d->tagListModel->deleteItem(item); d->tagMap[delId].removeOne(item); d->treeView->getFilterModel()->setQuickListTags(QList()); } } } void TagList::slotDeleteSelected() { QModelIndexList sel = d->tagList->selectionModel()->selectedIndexes(); if (sel.isEmpty()) { return; } foreach(const QModelIndex& index, sel) { ListItem* const item = static_cast(index.internalPointer()); d->tagListModel->deleteItem(item); } d->tagList->selectionModel()->select(d->tagList->model()->index(0,0), QItemSelectionModel::SelectCurrent); } void TagList::enableAddButton(bool value) { d->addButton->setEnabled(value); } } // namespace Digikam diff --git a/core/libs/tags/tagsmanager/tagmngrtreeview.cpp b/core/libs/tags/tagsmanager/tagmngrtreeview.cpp index dab7769daa..8c963aa8c9 100644 --- a/core/libs/tags/tagsmanager/tagmngrtreeview.cpp +++ b/core/libs/tags/tagsmanager/tagmngrtreeview.cpp @@ -1,240 +1,241 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 20013-08-05 * Description : Tag Manager Tree View derived from TagsFolderView to implement * a custom context menu and some batch view options, such as * expanding multiple items * * Copyright (C) 2013 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "tagmngrtreeview.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "tagsmanager.h" namespace Digikam { class TagMngrTreeView::Private { public: + explicit Private() { tagMngr = 0; } TagsManager* tagMngr; }; TagMngrTreeView::TagMngrTreeView(TagsManager* const parent, TagModel* const model) : TagFolderView(parent, model), d(new Private()) { d->tagMngr = parent; setAlbumFilterModel(new TagsManagerFilterModel(this), albumFilterModel()); setSelectAlbumOnClick(false); expand(albumFilterModel()->rootAlbumIndex()); } TagMngrTreeView::~TagMngrTreeView() { delete d; } void TagMngrTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndexList selectedItems = selectionModel()->selectedIndexes(); std::sort(selectedItems.begin(), selectedItems.end()); QList items; foreach(const QModelIndex& mIndex, selectedItems) { TAlbum* const temp = static_cast(albumForIndex(mIndex)); items.append(temp); } /** * Append root tag if no nodes are selected */ if (items.isEmpty()) { - QModelIndex root = this->model()->index(0, 0); + QModelIndex root = model()->index(0, 0); items.append(static_cast(albumForIndex(root))); } QMenu popmenu(this); popmenu.addSection(contextMenuIcon(), contextMenuTitle()); ContextMenuHelper cmhelper(&popmenu); setContexMenuItems(cmhelper, items); QAction* const choice = cmhelper.exec(QCursor::pos()); Q_UNUSED(choice); Q_UNUSED(event); } void TagMngrTreeView::setAlbumFilterModel(TagsManagerFilterModel* const filteredModel, CheckableAlbumFilterModel* const filterModel) { Q_UNUSED(filterModel); m_tfilteredModel = filteredModel; albumFilterModel()->setSourceFilterModel(m_tfilteredModel); } -void TagMngrTreeView::setContexMenuItems(ContextMenuHelper& cmh, QList albums) +void TagMngrTreeView::setContexMenuItems(ContextMenuHelper& cmh, const QList& albums) { bool isRoot = false; if (albums.size() == 1) { TAlbum* const tag = dynamic_cast (albums.first()); if (!tag) { return; } if (tag->isRoot()) { isRoot = true; } cmh.addActionNewTag(tagModificationHelper(), tag); } if (!isRoot) { cmh.addActionDeleteTags(tagModificationHelper(), albums); } else { /** This is a dummy action, delete is disable for root tag **/ QAction* deleteTagsAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18n("Delete Tags"), this); cmh.addAction(deleteTagsAction); deleteTagsAction->setEnabled(false); } cmh.addSeparator(); QAction* const titleEdit = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit Tag Title"), this); titleEdit->setShortcut(QKeySequence(Qt::Key_F2)); QAction* const resetIcon = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Tag Icon"), this); QAction* const invSel = new QAction(QIcon::fromTheme(QLatin1String("tag-reset")), i18n("Invert Selection"), this); QAction* const expandTree = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Tag Tree"), this); QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Nodes"), this); QAction* const delTagFromImg = new QAction(QIcon::fromTheme(QLatin1String("tag-delete")), i18n("Remove Tag from Images"), this); cmh.addAction(titleEdit, d->tagMngr, SLOT(slotEditTagTitle()), false); cmh.addAction(resetIcon, d->tagMngr, SLOT(slotResetTagIcon()), false); cmh.addAction(invSel, d->tagMngr, SLOT(slotInvertSel()), false); cmh.addAction(expandTree, this, SLOT(slotExpandTree()), false); cmh.addAction(expandSel, this , SLOT(slotExpandSelected()), false); cmh.addAction(delTagFromImg, d->tagMngr, SLOT(slotRemoveTagsFromImgs()), false); if (isRoot) { titleEdit->setEnabled(false); resetIcon->setEnabled(false); delTagFromImg->setEnabled(false); } if (albums.size() != 1) { titleEdit->setEnabled(false); } } void TagMngrTreeView::slotExpandSelected() { QModelIndexList list = selectionModel()->selectedIndexes(); foreach(const QModelIndex& index, list) { expand(index); } } void TagMngrTreeView::slotExpandTree() { - QModelIndex root = this->model()->index(0, 0); - QItemSelectionModel* const model = this->selectionModel(); + QModelIndex root = model()->index(0, 0); + QItemSelectionModel* const model = selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; greyNodes.append(root); while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); - if (!(current.isValid())) + if (!current.isValid()) { continue; } - if (this->isExpanded(current)) + if (isExpanded(current)) { int it = 0; QModelIndex child = current.child(it++, 0); while (child.isValid()) { - if (this->isExpanded(child)) + if (isExpanded(child)) { greyNodes.enqueue(child); } else { expand(child); } child = current.child(it++, 0); } } else { expand(current); } } } } // namespace Digikam diff --git a/core/libs/tags/tagsmanager/tagmngrtreeview.h b/core/libs/tags/tagsmanager/tagmngrtreeview.h index fb4f3ea6dd..028e1c3131 100644 --- a/core/libs/tags/tagsmanager/tagmngrtreeview.h +++ b/core/libs/tags/tagsmanager/tagmngrtreeview.h @@ -1,105 +1,105 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 20013-08-05 * Description : Tag Manager Tree View derived from TagsFolderView to implement * a custom context menu and some batch view options, such as * expanding multiple items * * Copyright (C) 2013 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_TAG_MNGR_TREE_VIEW_H #define DIGIKAM_TAG_MNGR_TREE_VIEW_H // Local includes #include "tagfolderview.h" namespace Digikam { class TagsManager; class TagMngrTreeView : public TagFolderView { Q_OBJECT public: explicit TagMngrTreeView(TagsManager* const parent, TagModel* const model); virtual ~TagMngrTreeView(); /** * @brief setAlbumFilterModel - reimplement from AbstractAlbumTree */ void setAlbumFilterModel(TagsManagerFilterModel* const filteredModel, CheckableAlbumFilterModel* const filterModel); TagsManagerFilterModel* getFilterModel() const { return m_tfilteredModel; } protected: /** * @brief setContexMenuItems - Reimplemented method from TagsFolderView. * Will set custom actions for Tags Manager. * Some actions are also available in toolbar * * @param chm - ContextMenuHelper class to help setting some * basic actions * @param albums - List of currently selected albums */ - virtual void setContexMenuItems(ContextMenuHelper& cmh, QList albums); + virtual void setContexMenuItems(ContextMenuHelper& cmh, const QList& albums); /** * @brief contextMenuEvent - Reimplement contextMenuEvent from AbstractAlbumTree * to support multiple selection * * @param event context menu event triggered by right click */ void contextMenuEvent(QContextMenuEvent* event); protected: TagsManagerFilterModel* m_tfilteredModel; public Q_SLOTS: /** * @brief slotExpandTree - connected to expandTree action and will * expand tree by one level */ void slotExpandTree(); /** * @brief slotExpandSelected - connected to expandSel action and will * expand selected nodes by one level */ void slotExpandSelected(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_TAG_MNGR_TREE_VIEW_H diff --git a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp index 50bd528b9d..004f4e5acd 100644 --- a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp +++ b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp @@ -1,371 +1,371 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-07-22 * Description : Qt item model for Showfoto thumbnails entries * * Copyright (C) 2013 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #include "showfotothumbnailmodel.h" // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "imagescanner.h" #include "thumbnailsize.h" #include "thumbnailloadthread.h" #include "loadingdescription.h" using namespace Digikam; namespace ShowFoto { class ShowfotoThumbnailModel::Private { public: explicit Private() : thread(0), preloadThread(0), thumbSize(0), lastGlobalThumbSize(0), preloadThumbSize(0), emitDataChanged(true) { maxThumbSize = ThumbnailSize::Huge; } int preloadThumbnailSize() const { if (preloadThumbSize.size()) { return preloadThumbSize.size(); } return thumbSize.size(); } public: ThumbnailLoadThread* thread; ThumbnailLoadThread* preloadThread; ThumbnailSize thumbSize; ThumbnailSize lastGlobalThumbSize; ThumbnailSize preloadThumbSize; QRect detailRect; int maxThumbSize; bool emitDataChanged; }; ShowfotoThumbnailModel::ShowfotoThumbnailModel(QObject* const parent) : ShowfotoImageModel(parent), d(new Private) { connect(this, &ShowfotoThumbnailModel::signalThumbInfo, this, &ShowfotoThumbnailModel::slotThumbInfoLoaded); } ShowfotoThumbnailModel::~ShowfotoThumbnailModel() { delete d->preloadThread; delete d; } void ShowfotoThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) { d->thread = thread; connect(d->thread, &ThumbnailLoadThread::signalThumbnailLoaded, this, &ShowfotoThumbnailModel::slotThumbnailLoaded); } ThumbnailLoadThread* ShowfotoThumbnailModel::thumbnailLoadThread() const { return d->thread; } ThumbnailSize ShowfotoThumbnailModel::thumbnailSize() const { return d->thumbSize; } void ShowfotoThumbnailModel::setThumbnailSize(const ThumbnailSize& size) { d->lastGlobalThumbSize = size; d->thumbSize = size; } void ShowfotoThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) { d->preloadThumbSize = size; } void ShowfotoThumbnailModel::setEmitDataChanged(bool emitSignal) { d->emitDataChanged = emitSignal; } void ShowfotoThumbnailModel::showfotoItemInfosCleared() { if (d->preloadThread) { d->preloadThread->stopAllTasks(); } } QVariant ShowfotoThumbnailModel::data(const QModelIndex& index, int role) const { if (role == ThumbnailRole && d->thread && index.isValid()) { QImage thumbnailImage; QPixmap pixmap; ShowfotoItemInfo info = showfotoItemInfo(index); QString url = info.url.toDisplayString(); - QString path = info.folder + QLatin1String("/") + info.name; + QString path = info.folder + QLatin1Char('/') + info.name; if (info.isNull() || url.isEmpty()) { return QVariant(QVariant::Pixmap); } if(pixmapForItem(path,pixmap)) { return pixmap; } //if pixmapForItem Failed if(getThumbnail(info,thumbnailImage)) { thumbnailImage = thumbnailImage.scaled(d->thumbSize.size(),d->thumbSize.size(),Qt::KeepAspectRatio); emit signalThumbInfo(info,thumbnailImage); return thumbnailImage; } return QVariant(QVariant::Pixmap); } return ShowfotoImageModel::data(index, role); } bool ShowfotoThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == ThumbnailRole) { switch (value.type()) { case QVariant::Invalid: d->thumbSize = d->lastGlobalThumbSize; d->detailRect = QRect(); break; case QVariant::Int: if (value.isNull()) { d->thumbSize = ThumbnailSize(d->lastGlobalThumbSize); } else { d->thumbSize = ThumbnailSize(value.toInt()); } break; case QVariant::Rect: if (value.isNull()) { d->detailRect = QRect(); } else { d->detailRect = value.toRect(); } break; default: break; } } return ShowfotoImageModel::setData(index, value, role); } void ShowfotoThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) { if (thumb.isNull()) { return; } // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. foreach(const QModelIndex& index, indexesForUrl(QUrl::fromLocalFile(loadingDescription.filePath))) { if (thumb.isNull()) { emit thumbnailFailed(index, loadingDescription.previewParameters.size); } else { emit thumbnailAvailable(index, loadingDescription.previewParameters.size); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } bool ShowfotoThumbnailModel::getThumbnail(const ShowfotoItemInfo& itemInfo, QImage& thumbnail) const { - QString path = itemInfo.folder + QLatin1String("/") + itemInfo.name; + QString path = itemInfo.folder + QLatin1Char('/') + itemInfo.name; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getImagePreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); - if (thumbnail.load(itemInfo.folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm"))) // Lowercase + if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } - else if (thumbnail.load(itemInfo.folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM"))) // Uppercase + else if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). // qCDebug(DIGIKAM_SHOWFOTO_LOG) << "Use DImg loader to get thumbnail from : " << path; // DImg dimgThumb(path); // if (!dimgThumb.isNull()) // { // thumbnail = dimgThumb.copyQImage(); // return true; // } return false; } bool ShowfotoThumbnailModel::pixmapForItem(QString url, QPixmap& pix) const { if (d->thumbSize.size() > d->maxThumbSize) { //TODO: Install a widget maximum size to prevent this situation bool hasPixmap = d->thread->find(ThumbnailIdentifier(url), pix, d->maxThumbSize); if (hasPixmap) { qCWarning(DIGIKAM_GENERAL_LOG) << "Thumbbar: Requested thumbnail size" << d->thumbSize.size() << "is larger than the maximum thumbnail size" << d->maxThumbSize << ". Returning a scaled-up image."; pix = pix.scaled(d->thumbSize.size(), d->thumbSize.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); return true; } else { return false; } } else { return d->thread->find(ThumbnailIdentifier(url), pix, d->thumbSize.size()); } } void ShowfotoThumbnailModel::slotThumbInfoLoaded(const ShowfotoItemInfo& info, const QImage& thumbnailImage) { QImage thumbnail = thumbnailImage; if (thumbnail.isNull()) { thumbnail = QImage(); } foreach(const QModelIndex& index, indexesForUrl(info.url)) { if (thumbnail.isNull()) { emit thumbnailFailed(index, d->thumbSize.size()); } else { emit thumbnailAvailable(index, d->thumbSize.size()); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } } // namespace ShowFoto diff --git a/core/tests/albummodel/albummodeltest.cpp b/core/tests/albummodel/albummodeltest.cpp index 7fe9cfa43f..ae82257b71 100644 --- a/core/tests/albummodel/albummodeltest.cpp +++ b/core/tests/albummodel/albummodeltest.cpp @@ -1,811 +1,811 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-12-11 * Description : test cases for the various album models * * Copyright (C) 2009 by Johannes Wienke * * 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, 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. * * ============================================================ */ #include "albummodeltest.h" // Qt includes #include #include #include #include // Local includes #include "albumfiltermodel.h" #include "albummanager.h" #include "albummodel.h" #include "applicationsettings.h" #include "albumthumbnailloader.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "loadingcacheinterface.h" #include "scancontroller.h" #include "thumbnailloadthread.h" #include "../modeltest/modeltest.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/")); QTEST_MAIN(AlbumModelTest) AlbumModelTest::AlbumModelTest() : albumCategory(QLatin1String("DummyCategory")), palbumRoot0(0), palbumRoot1(0), palbumRoot2(0), palbumChild0Root0(0), palbumChild1Root0(0), palbumChild2Root0(0), palbumChild0Root1(0), rootTag(0), talbumRoot0(0), talbumRoot1(0), talbumChild0Root0(0), talbumChild1Root0(0), talbumChild0Child1Root0(0), talbumChild0Root1(0), startModel(0) { } AlbumModelTest::~AlbumModelTest() { } /** * TODO: this test case needs to be removed, since it depends on database, * threading etc, therefore it is not predictable and very hard to fix */ void AlbumModelTest::initTestCase() { tempSuffix = QLatin1String("albummodeltest-") + QTime::currentTime().toString(); - dbPath = QDir::temp().absolutePath() + QLatin1String("/") + tempSuffix; + dbPath = QDir::temp().absolutePath() + QLatin1Char('/') + tempSuffix; if (QDir::temp().exists(tempSuffix)) { QString msg = QLatin1String("Error creating temp path") + dbPath; QVERIFY2(false, msg.toLatin1().constData()); } QDir::temp().mkdir(tempSuffix); qDebug() << "Using database path for test: " << dbPath; ApplicationSettings::instance()->setShowFolderTreeViewItemsCount(true); // use a testing database AlbumManager::instance(); // catch palbum counts for waiting connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), this, SLOT(setLastPAlbumCountMap(QMap))); AlbumManager::checkDatabaseDirsAfterFirstRun(QDir::temp().absoluteFilePath( tempSuffix), QDir::temp().absoluteFilePath(tempSuffix)); DbEngineParameters params(QLatin1String("QSQLITE"), QDir::temp().absoluteFilePath(tempSuffix + QLatin1String("/digikam4.db")), QString(), QString(), -1, false, QString(), QString()); bool dbChangeGood = AlbumManager::instance()->setDatabase(params, false, QDir::temp().absoluteFilePath(tempSuffix)); QVERIFY2(dbChangeGood, "Could not set temp album db"); QList locs = CollectionManager::instance()->allAvailableLocations(); QVERIFY2(locs.size(), "Failed to auto-create one collection in setDatabase"); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->startScan(); AlbumList all = AlbumManager::instance()->allPAlbums(); qDebug() << "PAlbum registered : " << all.size(); foreach(Album* const a, all) { if (a) { qDebug() << " ==> Id : " << a->id() << " , is root : " << a->isRoot() << " , title : " << a->title(); } } QVERIFY2( all.size() == 3, "Failed to scan empty directory. We must have one root album, one album, and one trash."); } void AlbumModelTest::cleanupTestCase() { qDebug() << "Start AlbumModelTest::cleanupTestCase()"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); QDir dir(dbPath); dir.removeRecursively(); qDebug() << "deleted test folder " << dbPath; } // Qt test doesn't use exceptions, so using assertion macros in methods called // from a test slot doesn't stop the test method and may result in inconsistent // data or segfaults. Therefore use macros for these functions. #define safeCreatePAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createPAlbum(parent, name, name, \ QDate::currentDate(), albumCategory, error); \ QVERIFY2(result, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); \ } #define safeCreateTAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createTAlbum(parent, name, QLatin1String(""), error); \ QVERIFY2(result, QString::fromUtf8("Error creating TAlbum for test: %1").arg(error).toLatin1().constData()); \ } void AlbumModelTest::init() { qDebug() << "Start AlbumModelTest::init()"; palbumCountMap.clear(); // create a model to check that model work is done correctly while scanning addedIds.clear(); startModel = new AlbumModel; startModel->setShowCount(true); connect(startModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotStartModelRowsInserted(QModelIndex,int,int))); connect(startModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotStartModelDataChanged(QModelIndex,QModelIndex))); qDebug() << "Created startModel" << startModel; // ensure that this model is empty in the beginning except for the root // album and the collection that include trash QCOMPARE(startModel->rowCount(), 1); QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 1); // insert some test data // physical albums // create two of them by creating directories and scanning QDir dir(dbPath); dir.mkdir(QLatin1String("root0")); dir.mkdir(QLatin1String("root1")); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->refresh(); QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 5); QString error; palbumRoot0 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root0"))); QVERIFY2(palbumRoot0, "Error having PAlbum root0 in AlbumManager"); palbumRoot1 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root1"))); QVERIFY2(palbumRoot1, "Error having PAlbum root1 in AlbumManager"); // Create some more through AlbumManager palbumRoot2 = AlbumManager::instance()->createPAlbum(dbPath, QLatin1String("root2"), QLatin1String("root album 2"), QDate::currentDate(), albumCategory, error); QVERIFY2(palbumRoot2, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child0"), palbumChild0Root0); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child1"), palbumChild1Root0); const QString sameName = QLatin1String("sameName Album"); safeCreatePAlbum(palbumRoot0, sameName, palbumChild2Root0); safeCreatePAlbum(palbumRoot1, sameName, palbumChild0Root1); qDebug() << "AlbumManager now knows these PAlbums:"; foreach(Album* const a, AlbumManager::instance()->allPAlbums()) { qDebug() << "\t" << a->title(); } // tags rootTag = AlbumManager::instance()->findTAlbum(0); QVERIFY(rootTag); safeCreateTAlbum(rootTag, QLatin1String("root0"), talbumRoot0); safeCreateTAlbum(rootTag, QLatin1String("root1"), talbumRoot1); safeCreateTAlbum(talbumRoot0, QLatin1String("child0 root 0"), talbumChild0Root0); safeCreateTAlbum(talbumRoot0, QLatin1String("child1 root 0"), talbumChild1Root0); safeCreateTAlbum(talbumChild1Root0, sameName, talbumChild0Child1Root0); safeCreateTAlbum(talbumRoot1, sameName, talbumChild0Root1); qDebug() << "created tags"; // add some images for having date albums QDir imageDir(IMAGE_PATH); imageDir.setNameFilters(QStringList() << QLatin1String("*.jpg")); QStringList imageFiles = imageDir.entryList(); qDebug() << "copying images " << imageFiles << " to " << palbumChild0Root0->fileUrl(); foreach(const QString& imageFile, imageFiles) { QString src = IMAGE_PATH + QLatin1Char('/') + imageFile; QString dst = palbumChild0Root0->fileUrl().toLocalFile() + QLatin1Char('/') + imageFile; bool copied = QFile::copy(src, dst); QVERIFY2(copied, "Test images must be copied"); } ScanController::instance()->completeCollectionScan(); if (AlbumManager::instance()->allDAlbums().count() <= 1) { ensureItemCounts(); } qDebug() << "date albums: " << AlbumManager::instance()->allDAlbums(); // root + 2 years + 2 and 3 months per year + (1997 as test year for date ordering with 12 months) = 21 QCOMPARE(AlbumManager::instance()->allDAlbums().size(), 21); // ensure that there is a root date album DAlbum* const rootFromAlbumManager = AlbumManager::instance()->findDAlbum(0); QVERIFY(rootFromAlbumManager); DAlbum* rootFromList = 0; foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); if (dAlbum->isRoot()) { rootFromList = dAlbum; } } QVERIFY(rootFromList); QVERIFY(rootFromList == rootFromAlbumManager); } void AlbumModelTest::testStartAlbumModel() { qDebug() << "Start AlbumModelTest::testStartAlbumModel()"; // verify that the start album model got all these changes // one root QCOMPARE(startModel->rowCount(), 1); // one collection QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); // two albums in the collection QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 3); // this is should be enough for now // We must have received an added notation for everything except album root // and collection QCOMPARE(addedIds.size(), 7); } void AlbumModelTest::ensureItemCounts() { // trigger listing job QEventLoop dAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalAllDAlbumsLoaded()), &dAlbumLoop, SLOT(quit())); AlbumManager::instance()->prepareItemCounts(); qDebug() << "Waiting for AlbumManager and the IOSlave to create DAlbums..."; dAlbumLoop.exec(); qDebug() << "DAlbums were created"; while (palbumCountMap.size() < 8) { QEventLoop pAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), &pAlbumLoop, SLOT(quit())); qDebug() << "Waiting for first PAlbum count map"; pAlbumLoop.exec(); qDebug() << "Got new PAlbum count map"; } } void AlbumModelTest::slotStartModelRowsInserted(const QModelIndex& parent, int start, int end) { qDebug() << "called, parent:" << parent << ", start:" << start << ", end:" << end; for (int row = start; row <= end; ++row) { QModelIndex child = startModel->index(row, 0, parent); QVERIFY(child.isValid()); Album* album = startModel->albumForIndex(child); const int id = child.data(AbstractAlbumModel::AlbumIdRole).toInt(); QVERIFY(album); qDebug() << "added album with id" << id << "and name" << album->title(); addedIds << id; } } void AlbumModelTest::slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = startModel->index(row, topLeft.column(), topLeft.parent()); if (!index.isValid()) { qDebug() << "Illegal index received"; continue; } int albumId = index.data(AbstractAlbumModel::AlbumIdRole).toInt(); if (!addedIds.contains(albumId)) { QString message = QLatin1String("Album id ") + QString::number(albumId) + QLatin1String(" was changed before adding signal was received"); QFAIL(message.toLatin1().constData()); qDebug() << message; } } } void AlbumModelTest::deletePAlbum(PAlbum* album) { QDir dir(album->folderPath()); dir.removeRecursively(); } void AlbumModelTest::setLastPAlbumCountMap(const QMap &map) { qDebug() << "Receiving new count map "<< map; palbumCountMap = map; } void AlbumModelTest::cleanup() { if (startModel) { disconnect(startModel); } delete startModel; addedIds.clear(); // remove all test data AlbumManager::instance()->refresh(); // remove all palbums' directories deletePAlbum(palbumRoot0); deletePAlbum(palbumRoot1); deletePAlbum(palbumRoot2); // take over changes to database ScanController::instance()->completeCollectionScan(); // reread from database AlbumManager::instance()->refresh(); // root + one collection QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 2); // remove all tags QString error; bool removed = AlbumManager::instance()->deleteTAlbum(talbumRoot0, error, false); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); removed = AlbumManager::instance()->deleteTAlbum(talbumRoot1, error, false); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); QCOMPARE(AlbumManager::instance()->allTAlbums().size(), 1); } void AlbumModelTest::testPAlbumModel() { qDebug() << "Start AlbumModelTest::testPAlbumModel()"; AlbumModel* albumModel = new AlbumModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new AlbumModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testDisablePAlbumCount() { qDebug() << "Start AlbumModelTest::testDisablePAlbumCount()"; AlbumModel albumModel; albumModel.setCountMap(palbumCountMap); albumModel.setShowCount(true); QRegExp countRegEx(QLatin1String(".+ \\(\\d+\\)")); countRegEx.setMinimal(true); QVERIFY(countRegEx.exactMatch(QLatin1String("test (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (0)"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st ()"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st (10) bla"))); // ensure that all albums except the root album have a count attached QModelIndex rootIndex = albumModel.index(0, 0, QModelIndex()); QString rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } // now disable showing the count albumModel.setShowCount(false); // ensure that no album has a count attached rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } } void AlbumModelTest::testDAlbumModel() { qDebug() << "Start AlbumModelTest::testDAlbumModel()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testDAlbumContainsAlbums() { qDebug() << "Start AlbumModelTest::testDAlbumContainsAlbums()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); QVERIFY(albumModel->rootAlbum()); foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); qDebug() << "checking album for date " << dAlbum->date() << ", range = " << dAlbum->range(); QModelIndex index = albumModel->indexForAlbum(dAlbum); if (!dAlbum->isRoot()) { QVERIFY(index.isValid()); } if (dAlbum->isRoot()) { // root album QVERIFY(dAlbum->isRoot()); QCOMPARE(albumModel->rowCount(index), 3); QCOMPARE(index, albumModel->rootAlbumIndex()); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2007) { QCOMPARE(albumModel->rowCount(index), 2); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2009) { QCOMPARE(albumModel->rowCount(index), 3); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 5) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->date().year() == 1997) { // Ignore these albums for order testing } else { qDebug() << "Unexpected album: " << dAlbum->title(); QFAIL("Unexpected album returned from model"); } } delete albumModel; } void AlbumModelTest::testDAlbumSorting() { qDebug() << "Start AlbumModelTest::testDAlbumSorting()"; DateAlbumModel dateAlbumModel; AlbumFilterModel albumModel; albumModel.setSourceAlbumModel(&dateAlbumModel); // first check ascending order albumModel.sort(0, Qt::AscendingOrder); int previousYear = 0; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() > previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 0; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() > previousMonth); previousMonth = monthAlbum->date().month(); } } // then check descending order albumModel.sort(0, Qt::DescendingOrder); previousYear = 1000000; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() < previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 13; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() < previousMonth); previousMonth = monthAlbum->date().month(); } } } void AlbumModelTest::testDAlbumCount() { qDebug() << "Start AlbumModelTest::testDAlbumCount()"; DateAlbumModel* const albumModel = new DateAlbumModel(); albumModel->setShowCount(true); ensureItemCounts(); qDebug() << "iterating over root indices"; // check year albums for (int yearRow = 0; yearRow < albumModel->rowCount(albumModel->rootAlbumIndex()); ++yearRow) { QModelIndex yearIndex = albumModel->index(yearRow, 0); DAlbum* const yearDAlbum = albumModel->albumForIndex(yearIndex); QVERIFY(yearDAlbum); QVERIFY(yearDAlbum->range() == DAlbum::Year); if (yearDAlbum->date().year() == 2007) { const int imagesInYear = 7; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2007); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 3; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 4; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2007"); } } } else if (yearDAlbum->date().year() == 2009) { const int imagesInYear = 5; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2009); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 5) { const int imagesInMonth = 1; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2009"); } } } else if (yearDAlbum->date().year() == 1997) { // Nothing to do here, ignore the albums for ordering tests } else { QFAIL("Received unexpected album from model"); } } delete albumModel; } void AlbumModelTest::testTAlbumModel() { qDebug() << "Start AlbumModelTest::testTAlbumModel()"; TagModel* albumModel = new TagModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testSAlbumModel() { qDebug() << "Start AlbumModelTest::testSAlbumModel()"; SearchModel* const albumModel = new SearchModel(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } diff --git a/core/tests/database/databasetagstest.cpp b/core/tests/database/databasetagstest.cpp index 37e5d99991..0aeb0f40aa 100644 --- a/core/tests/database/databasetagstest.cpp +++ b/core/tests/database/databasetagstest.cpp @@ -1,805 +1,805 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-12-13 * Description : test cases for tags tree manipulation in database * * Copyright (C) 2015-2018 by Gilles Caulier, * * 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, 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. * * ============================================================ */ #include "databasetagstest.h" // Qt includes #include #include #include #include // Local includes #include "albummanager.h" #include "albummodel.h" #include "albumthumbnailloader.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "loadingcacheinterface.h" #include "scancontroller.h" #include "thumbnailloadthread.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/")); QTEST_MAIN(DatabaseTagsTest) DatabaseTagsTest::DatabaseTagsTest() : palbumRoot0(0), palbumRoot1(0), palbumRoot2(0), palbumChild0Root0(0), palbumChild1Root0(0), palbumChild2Root0(0), palbumChild0Root1(0), rootTag(0), talbumRoot0(0), talbumRoot1(0), talbumChild0Root0(0), talbumChild1Root0(0), talbumChild0Child1Root0(0), talbumChild0Root1(0) { } DatabaseTagsTest::~DatabaseTagsTest() { } void DatabaseTagsTest::initTestCase() { tempSuffix = QLatin1String("databasetagstest-") + QTime::currentTime().toString(); - dbPath = QDir::temp().absolutePath() + QLatin1String("/") + tempSuffix; + dbPath = QDir::temp().absolutePath() + QLatin1Char('/') + tempSuffix; if (QDir::temp().exists(tempSuffix)) { QString msg = QLatin1String("Error creating temp path") + dbPath; QVERIFY2(false, msg.toLatin1().constData()); } QDir::temp().mkdir(tempSuffix); qDebug() << "Using database path for test: " << dbPath; // use a testing database AlbumManager::instance(); AlbumManager::checkDatabaseDirsAfterFirstRun(QDir::temp().absoluteFilePath( tempSuffix), QDir::temp().absoluteFilePath(tempSuffix)); DbEngineParameters params = DbEngineParameters::defaultParameters(QLatin1String("QMYSQL")); params.setInternalServerPath(dbPath); DatabaseServerStarter::startServerManagerProcess(); bool dbChangeGood = AlbumManager::instance()->setDatabase(params, false, QDir::temp().absoluteFilePath(tempSuffix)); QVERIFY2(dbChangeGood, "Could not set temp album db"); QList locs = CollectionManager::instance()->allAvailableLocations(); QVERIFY2(locs.size(), "Failed to auto-create one collection in setDatabase"); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->startScan(); AlbumList all = AlbumManager::instance()->allPAlbums(); qDebug() << "PAlbum registered : " << all.size(); foreach(Album* const a, all) { if (a) { qDebug() << " ==> Id : " << a->id() << " , is root : " << a->isRoot() << " , title : " << a->title(); } } QVERIFY2( all.size() == 3, "Failed to scan empty directory. We must have one root album, one album, and one trash."); } void DatabaseTagsTest::cleanupTestCase() { qDebug() << "Start DatabaseTagsTest::cleanupTestCase()"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); QDir dir(dbPath); dir.removeRecursively(); qDebug() << "deleted test folder " << dbPath; } // Qt test doesn't use exceptions, so using assertion macros in methods called // from a test slot doesn't stop the test method and may result in inconsistent // data or segfaults. Therefore use macros for these functions. #define safeCreatePAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createPAlbum(parent, name, name, \ QDate::currentDate(), albumCategory, error); \ QVERIFY2(result, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); \ } #define safeCreateTAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createTAlbum(parent, name, QLatin1String(""), error); \ QVERIFY2(result, QString::fromUtf8("Error creating TAlbum for test: %1").arg(error).toLatin1().constData()); \ } void DatabaseTagsTest::init() { qDebug() << "Start DatabaseTagsTest::init()"; /* palbumCountMap.clear(); // create a model to check that model work is done correctly while scanning addedIds.clear(); startModel = new AlbumModel; startModel->setShowCount(true); connect(startModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotStartModelRowsInserted(QModelIndex,int,int))); connect(startModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotStartModelDataChanged(QModelIndex,QModelIndex))); qDebug() << "Created startModel" << startModel; // ensure that this model is empty in the beginning except for the root // album and the collection that include trash QCOMPARE(startModel->rowCount(), 1); QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 1); // insert some test data // physical albums // create two of them by creating directories and scanning QDir dir(dbPath); dir.mkdir(QLatin1String("root0")); dir.mkdir(QLatin1String("root1")); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->refresh(); QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 5); QString error; palbumRoot0 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root0"))); QVERIFY2(palbumRoot0, "Error having PAlbum root0 in AlbumManager"); palbumRoot1 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root1"))); QVERIFY2(palbumRoot1, "Error having PAlbum root1 in AlbumManager"); // Create some more through AlbumManager palbumRoot2 = AlbumManager::instance()->createPAlbum(dbPath, QLatin1String("root2"), QLatin1String("root album 2"), QDate::currentDate(), albumCategory, error); QVERIFY2(palbumRoot2, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child0"), palbumChild0Root0); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child1"), palbumChild1Root0); const QString sameName = QLatin1String("sameName Album"); safeCreatePAlbum(palbumRoot0, sameName, palbumChild2Root0); safeCreatePAlbum(palbumRoot1, sameName, palbumChild0Root1); qDebug() << "AlbumManager now knows these PAlbums:"; foreach(Album* const a, AlbumManager::instance()->allPAlbums()) { qDebug() << "\t" << a->title(); } // tags rootTag = AlbumManager::instance()->findTAlbum(0); QVERIFY(rootTag); safeCreateTAlbum(rootTag, QLatin1String("root0"), talbumRoot0); safeCreateTAlbum(rootTag, QLatin1String("root1"), talbumRoot1); safeCreateTAlbum(talbumRoot0, QLatin1String("child0 root 0"), talbumChild0Root0); safeCreateTAlbum(talbumRoot0, QLatin1String("child1 root 0"), talbumChild1Root0); safeCreateTAlbum(talbumChild1Root0, sameName, talbumChild0Child1Root0); safeCreateTAlbum(talbumRoot1, sameName, talbumChild0Root1); qDebug() << "created tags"; // add some images for having date albums QDir imageDir(IMAGE_PATH); imageDir.setNameFilters(QStringList() << QLatin1String("*.jpg")); QStringList imageFiles = imageDir.entryList(); qDebug() << "copying images " << imageFiles << " to " << palbumChild0Root0->fileUrl(); foreach(const QString& imageFile, imageFiles) { QString src = IMAGE_PATH + QLatin1Char('/') + imageFile; QString dst = palbumChild0Root0->fileUrl().toLocalFile() + QLatin1Char('/') + imageFile; bool copied = QFile::copy(src, dst); QVERIFY2(copied, "Test images must be copied"); } ScanController::instance()->completeCollectionScan(); if (AlbumManager::instance()->allDAlbums().count() <= 1) { ensureItemCounts(); } qDebug() << "date albums: " << AlbumManager::instance()->allDAlbums(); // root + 2 years + 2 and 3 months per year + (1997 as test year for date ordering with 12 months) = 21 QCOMPARE(AlbumManager::instance()->allDAlbums().size(), 21); // ensure that there is a root date album DAlbum* const rootFromAlbumManager = AlbumManager::instance()->findDAlbum(0); QVERIFY(rootFromAlbumManager); DAlbum* rootFromList = 0; foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); if (dAlbum->isRoot()) { rootFromList = dAlbum; } } QVERIFY(rootFromList); QVERIFY(rootFromList == rootFromAlbumManager); */ } void DatabaseTagsTest::cleanup() { /* if (startModel) { disconnect(startModel); } delete startModel; addedIds.clear(); // remove all test data AlbumManager::instance()->refresh(); // remove all palbums' directories deletePAlbum(palbumRoot0); deletePAlbum(palbumRoot1); deletePAlbum(palbumRoot2); // take over changes to database ScanController::instance()->completeCollectionScan(); // reread from database AlbumManager::instance()->refresh(); // root + one collection QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 2); // remove all tags QString error; bool removed = AlbumManager::instance()->deleteTAlbum(talbumRoot0, error); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); removed = AlbumManager::instance()->deleteTAlbum(talbumRoot1, error); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); QCOMPARE(AlbumManager::instance()->allTAlbums().size(), 1); */ } /* void DatabaseTagsTest::testStartAlbumModel() { qDebug() << "Start DatabaseTagsTest::testStartAlbumModel()"; // verify that the start album model got all these changes // one root QCOMPARE(startModel->rowCount(), 1); // one collection QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); // two albums in the collection QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 3); // this is should be enough for now // We must have received an added notation for everything except album root // and collection QCOMPARE(addedIds.size(), 7); } void DatabaseTagsTest::ensureItemCounts() { // trigger listing job QEventLoop dAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalAllDAlbumsLoaded()), &dAlbumLoop, SLOT(quit())); AlbumManager::instance()->prepareItemCounts(); qDebug() << "Waiting for AlbumManager and the IOSlave to create DAlbums..."; dAlbumLoop.exec(); qDebug() << "DAlbums were created"; while (palbumCountMap.size() < 8) { QEventLoop pAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), &pAlbumLoop, SLOT(quit())); qDebug() << "Waiting for first PAlbum count map"; pAlbumLoop.exec(); qDebug() << "Got new PAlbum count map"; } } void DatabaseTagsTest::slotStartModelRowsInserted(const QModelIndex& parent, int start, int end) { qDebug() << "called, parent:" << parent << ", start:" << start << ", end:" << end; for (int row = start; row <= end; ++row) { QModelIndex child = startModel->index(row, 0, parent); QVERIFY(child.isValid()); Album* album = startModel->albumForIndex(child); const int id = child.data(AbstractAlbumModel::AlbumIdRole).toInt(); QVERIFY(album); qDebug() << "added album with id" << id << "and name" << album->title(); addedIds << id; } } void DatabaseTagsTest::slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = startModel->index(row, topLeft.column(), topLeft.parent()); if (!index.isValid()) { qDebug() << "Illegal index received"; continue; } int albumId = index.data(AbstractAlbumModel::AlbumIdRole).toInt(); if (!addedIds.contains(albumId)) { QString message = QLatin1String("Album id ") + QString::number(albumId) + QLatin1String(" was changed before adding signal was received"); QFAIL(message.toLatin1().constData()); qDebug() << message; } } } void DatabaseTagsTest::deletePAlbum(PAlbum* album) { QDir dir(album->folderPath()); dir.removeRecursively(); } void DatabaseTagsTest::setLastPAlbumCountMap(const QMap &map) { qDebug() << "Receiving new count map "<< map; palbumCountMap = map; } void DatabaseTagsTest::testPAlbumModel() { qDebug() << "Start DatabaseTagsTest::testPAlbumModel()"; AlbumModel* albumModel = new AlbumModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new AlbumModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testDisablePAlbumCount() { qDebug() << "Start DatabaseTagsTest::testDisablePAlbumCount()"; AlbumModel albumModel; albumModel.setCountMap(palbumCountMap); albumModel.setShowCount(true); QRegExp countRegEx(QLatin1String(".+ \\(\\d+\\)")); countRegEx.setMinimal(true); QVERIFY(countRegEx.exactMatch(QLatin1String("test (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (0)"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st ()"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st (10) bla"))); // ensure that all albums except the root album have a count attached QModelIndex rootIndex = albumModel.index(0, 0, QModelIndex()); QString rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } // now disable showing the count albumModel.setShowCount(false); // ensure that no album has a count attached rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } } void DatabaseTagsTest::testDAlbumModel() { qDebug() << "Start DatabaseTagsTest::testDAlbumModel()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testDAlbumContainsAlbums() { qDebug() << "Start DatabaseTagsTest::testDAlbumContainsAlbums()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); QVERIFY(albumModel->rootAlbum()); foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); qDebug() << "checking album for date " << dAlbum->date() << ", range = " << dAlbum->range(); QModelIndex index = albumModel->indexForAlbum(dAlbum); if (!dAlbum->isRoot()) { QVERIFY(index.isValid()); } if (dAlbum->isRoot()) { // root album QVERIFY(dAlbum->isRoot()); QCOMPARE(albumModel->rowCount(index), 3); QCOMPARE(index, albumModel->rootAlbumIndex()); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2007) { QCOMPARE(albumModel->rowCount(index), 2); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2009) { QCOMPARE(albumModel->rowCount(index), 3); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 5) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->date().year() == 1997) { // Ignore these albums for order testing } else { qDebug() << "Unexpected album: " << dAlbum->title(); QFAIL("Unexpected album returned from model"); } } delete albumModel; } void DatabaseTagsTest::testDAlbumSorting() { qDebug() << "Start DatabaseTagsTest::testDAlbumSorting()"; DateAlbumModel dateAlbumModel; AlbumFilterModel albumModel; albumModel.setSourceAlbumModel(&dateAlbumModel); // first check ascending order albumModel.sort(0, Qt::AscendingOrder); int previousYear = 0; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() > previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 0; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() > previousMonth); previousMonth = monthAlbum->date().month(); } } // then check descending order albumModel.sort(0, Qt::DescendingOrder); previousYear = 1000000; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() < previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 13; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() < previousMonth); previousMonth = monthAlbum->date().month(); } } } void DatabaseTagsTest::testDAlbumCount() { qDebug() << "Start DatabaseTagsTest::testDAlbumCount()"; DateAlbumModel* const albumModel = new DateAlbumModel(); albumModel->setShowCount(true); ensureItemCounts(); qDebug() << "iterating over root indices"; // check year albums for (int yearRow = 0; yearRow < albumModel->rowCount(albumModel->rootAlbumIndex()); ++yearRow) { QModelIndex yearIndex = albumModel->index(yearRow, 0); DAlbum* const yearDAlbum = albumModel->albumForIndex(yearIndex); QVERIFY(yearDAlbum); QVERIFY(yearDAlbum->range() == DAlbum::Year); if (yearDAlbum->date().year() == 2007) { const int imagesInYear = 7; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2007); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 3; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 4; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2007"); } } } else if (yearDAlbum->date().year() == 2009) { const int imagesInYear = 5; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2009); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 5) { const int imagesInMonth = 1; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2009"); } } } else if (yearDAlbum->date().year() == 1997) { // Nothing to do here, ignore the albums for ordering tests } else { QFAIL("Received unexpected album from model"); } } delete albumModel; } void DatabaseTagsTest::testTAlbumModel() { qDebug() << "Start DatabaseTagsTest::testTAlbumModel()"; TagModel* albumModel = new TagModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testSAlbumModel() { qDebug() << "Start DatabaseTagsTest::testSAlbumModel()"; SearchModel* const albumModel = new SearchModel(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } */ diff --git a/core/tests/dmetadata/commentreadwritetest.cpp b/core/tests/dmetadata/commentreadwritetest.cpp index 0dd21cbd4a..b5738f719e 100644 --- a/core/tests/dmetadata/commentreadwritetest.cpp +++ b/core/tests/dmetadata/commentreadwritetest.cpp @@ -1,159 +1,159 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-08-12 * Description : DMetadata Settings Tests for getImageComment and setImageComment. * * Copyright (C) 2015 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "commentreadwritetest.h" // Qt includes #include #include #include // Local includes #include "dmetadata.h" QTEST_GUILESS_MAIN(CommentReadWriteTest) using namespace Digikam; void CommentReadWriteTest::initTestCase() { MetaEngine::AltLangMap authorsMap, authorsMap2; MetaEngine::AltLangMap datesMap, datesMap2; MetaEngine::AltLangMap commentsMap, commentsMap2; QString commonAuthor, commonAuthor2; authorsMap.insert(QLatin1String("x-default"), QLatin1String("Veaceslav")); commentsMap.insert(QLatin1String("x-default"), QLatin1String("Veaceslav's comment")); commonAuthor = QLatin1String("Veaceslav"); commentSet1.setData(commentsMap, authorsMap, commonAuthor, datesMap); authorsMap2.insert(QLatin1String("x-default"), QLatin1String("Munteanu")); commentsMap2.insert(QLatin1String("x-default"), QLatin1String("Munteanu's comment")); commonAuthor2 = QLatin1String("Munteanu"); commentSet2.setData(commentsMap2, authorsMap2, commonAuthor2, datesMap2); } void CommentReadWriteTest::testSimpleReadAfterWrite() { DMetadata dmeta; CaptionsMap result; // Trick dmetadata, so it will think that we have a file path dmeta.setFilePath(QLatin1String("random.org")); dmeta.setImageComments(commentSet1); result = dmeta.getImageComments(); QString rezAuthor = result.value(QLatin1String("x-default")).author; QString rezComment = result.value(QLatin1String("x-default")).caption; QCOMPARE(rezAuthor, commentSet1.value(QLatin1String("x-default")).author); QCOMPARE(rezComment, commentSet1.value(QLatin1String("x-default")).caption); } void CommentReadWriteTest::testWriteToDisabledNamespaces() { DMetadata dmeta; dmeta.setFilePath(QLatin1String("random.org")); MetaEngine::AltLangMap commentsMap; QString commentString; DMetadataSettingsContainer dmsettings; NamespaceEntry commNs1; commNs1.namespaceName = QLatin1String("Xmp.dc.description"); commNs1.nsType = NamespaceEntry::COMMENT; commNs1.specialOpts = NamespaceEntry::COMMENT_ATLLANGLIST; commNs1.index = 0; commNs1.subspace = NamespaceEntry::XMP; commNs1.isDisabled = true; NamespaceEntry commNs2; commNs2.namespaceName = QLatin1String("Xmp.exif.UserComment"); commNs2.nsType = NamespaceEntry::COMMENT; commNs2.specialOpts = NamespaceEntry::COMMENT_ALTLANG; commNs2.index = 1; commNs2.subspace = NamespaceEntry::XMP; dmsettings.setUnifyReadWrite(false); - dmsettings.getWriteMapping(QLatin1String(DM_COMMENT_CONTAINER)).clear(); - dmsettings.getWriteMapping(QLatin1String(DM_COMMENT_CONTAINER)) + dmsettings.getWriteMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)).clear(); + dmsettings.getWriteMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)) << commNs1 << commNs2; bool rez = dmeta.setImageComments(commentSet1, dmsettings); QVERIFY(rez); commentsMap = dmeta.getXmpTagStringListLangAlt("Xmp.dc.description", false); QCOMPARE(commentsMap.value(QLatin1String("x-default")), QString()); commentString = dmeta.getXmpTagStringLangAlt("Xmp.exif.UserComment", QString(), false); QCOMPARE(commentString, commentSet1.value(QLatin1String("x-default")).caption); } void CommentReadWriteTest::testReadFromDisabledNamespaces() { DMetadata dmeta; dmeta.setFilePath(QLatin1String("random.org")); CaptionsMap rez; DMetadataSettingsContainer dmsettings; NamespaceEntry commNs1; commNs1.namespaceName = QLatin1String("Xmp.dc.description"); commNs1.nsType = NamespaceEntry::COMMENT; commNs1.specialOpts = NamespaceEntry::COMMENT_ATLLANGLIST; commNs1.index = 0; commNs1.subspace = NamespaceEntry::XMP; commNs1.isDisabled = true; NamespaceEntry commNs2; commNs2.namespaceName = QLatin1String("Xmp.exif.UserComment"); commNs2.nsType = NamespaceEntry::COMMENT; commNs2.specialOpts = NamespaceEntry::COMMENT_ALTLANG; commNs2.index = 1; commNs2.subspace = NamespaceEntry::XMP; dmsettings.setUnifyReadWrite(false); - dmsettings.getReadMapping(QLatin1String(DM_COMMENT_CONTAINER)).clear(); - dmsettings.getReadMapping(QLatin1String(DM_COMMENT_CONTAINER)) + dmsettings.getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)).clear(); + dmsettings.getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)) << commNs1 << commNs2; dmeta.setXmpTagStringListLangAlt("Xmp.dc.description", commentSet1.toAltLangMap()); dmeta.setXmpTagStringLangAlt("Xmp.exif.UserComment", commentSet2.value(QLatin1String("x-default")).caption, QString()); rez = dmeta.getImageComments(dmsettings); QCOMPARE(rez.value(QLatin1String("x-default")).caption, commentSet2.value(QLatin1String("x-default")).caption); } diff --git a/core/tests/dmetadata/ratingreadwritetest.cpp b/core/tests/dmetadata/ratingreadwritetest.cpp index 381f05ced0..cd882afb64 100644 --- a/core/tests/dmetadata/ratingreadwritetest.cpp +++ b/core/tests/dmetadata/ratingreadwritetest.cpp @@ -1,157 +1,157 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-08-12 * Description : DMetadata Settings Tests for getImageRating and setImageRating. * * Copyright (C) 2015 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "ratingreadwritetest.h" // Qt includes #include #include #include #include // Local includes #include "dmetadata.h" using namespace Digikam; QTEST_GUILESS_MAIN(RatingReadWriteTest) void RatingReadWriteTest::initTestCase() { } void RatingReadWriteTest::testSimpleReadAfterWrite() { DMetadata dmeta; // Trick dmetadata, so it will think that we have a file path dmeta.setFilePath(QLatin1String("random.org")); int rez = -1; qDebug() << dmeta.supportXmp(); for (int i = 0; i < 6; i++) { dmeta.setImageRating(i); rez = dmeta.getImageRating(); QCOMPARE(rez, i); } } void RatingReadWriteTest::testWriteToDisabledNamespaces() { DMetadata dmeta; dmeta.setFilePath(QLatin1String("random.org")); DMetadataSettingsContainer dmsettings; QList defaultVal, microsoftMappings, iptcMappings; defaultVal << 0 << 1 << 2 << 3 << 4 << 5; microsoftMappings << 0 << 1 << 25 << 50 << 75 << 99; iptcMappings << 8 << 6 << 5 << 4 << 2 << 1; NamespaceEntry ratingNs2; ratingNs2.namespaceName = QLatin1String("Xmp.acdsee.rating"); ratingNs2.convertRatio = defaultVal; ratingNs2.nsType = NamespaceEntry::RATING; ratingNs2.index = 1; ratingNs2.subspace = NamespaceEntry::XMP; ratingNs2.isDisabled = true; NamespaceEntry ratingNs3; ratingNs3.namespaceName = QLatin1String("Xmp.MicrosoftPhoto.Rating"); ratingNs3.convertRatio = microsoftMappings; ratingNs3.nsType = NamespaceEntry::RATING; ratingNs3.index = 2; ratingNs3.subspace = NamespaceEntry::XMP; - dmsettings.getWriteMapping(QLatin1String(DM_RATING_CONTAINER)).clear(); - dmsettings.getWriteMapping(QLatin1String(DM_RATING_CONTAINER)) + dmsettings.getWriteMapping(QString::fromUtf8(DM_RATING_CONTAINER)).clear(); + dmsettings.getWriteMapping(QString::fromUtf8(DM_RATING_CONTAINER)) << ratingNs2 << ratingNs3; for (int i = 0; i < 6; i++) { dmeta.setImageRating(i, dmsettings); QString data; bool ok; data = dmeta.getXmpTagString("Xmp.acdsee.rating", false); QVERIFY(data.isEmpty()); data = dmeta.getXmpTagString("Xmp.MicrosoftPhoto.Rating", false); int rez = data.toInt(&ok); QCOMPARE(ok, true); QCOMPARE(rez, microsoftMappings.at(i)); } } void RatingReadWriteTest::testReadFromDisabledNamespaces() { DMetadata dmeta; dmeta.setFilePath(QLatin1String("random.org")); DMetadataSettingsContainer dmsettings; QList defaultVal, microsoftMappings, iptcMappings; defaultVal << 0 << 1 << 2 << 3 << 4 << 5; microsoftMappings << 0 << 1 << 25 << 50 << 75 << 99; iptcMappings << 8 << 6 << 5 << 4 << 2 << 1; NamespaceEntry ratingNs2; ratingNs2.namespaceName = QLatin1String("Xmp.acdsee.rating"); ratingNs2.convertRatio = defaultVal; ratingNs2.nsType = NamespaceEntry::RATING; ratingNs2.index = 1; ratingNs2.subspace = NamespaceEntry::XMP; ratingNs2.isDisabled = true; NamespaceEntry ratingNs3; ratingNs3.namespaceName = QLatin1String("Xmp.MicrosoftPhoto.Rating"); ratingNs3.convertRatio = microsoftMappings; ratingNs3.nsType = NamespaceEntry::RATING; ratingNs3.index = 2; ratingNs3.subspace = NamespaceEntry::XMP; - dmsettings.getReadMapping(QLatin1String(DM_RATING_CONTAINER)).clear(); - dmsettings.getReadMapping(QLatin1String(DM_RATING_CONTAINER)) + dmsettings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER)).clear(); + dmsettings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER)) << ratingNs2 << ratingNs3; for (int i = 0; i < 6; i++) { dmeta.setXmpTagString("Xmp.acdsee.rating", QString::number(5-i)); dmeta.setXmpTagString("Xmp.MicrosoftPhoto.Rating", QString::number(microsoftMappings.at(i))); int rez = dmeta.getImageRating(dmsettings); QCOMPARE(rez, i); } } diff --git a/core/tests/dmetadata/tagsreadwritetest.cpp b/core/tests/dmetadata/tagsreadwritetest.cpp index c78af7f107..c48a8eb9e2 100644 --- a/core/tests/dmetadata/tagsreadwritetest.cpp +++ b/core/tests/dmetadata/tagsreadwritetest.cpp @@ -1,263 +1,263 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-08-12 * Description : DMetadata Settings Tests for getImageTagPaths and setImageTagPaths. * * Copyright (C) 2015 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "tagsreadwritetest.h" // Qt includes #include #include #include // Local includes #include "dmetadata.h" using namespace Digikam; QTEST_GUILESS_MAIN(TagsReadWriteTest) void TagsReadWriteTest::initTestCase() { this->tagSet1 << QLatin1String("/root/child1/child2") << QLatin1String("/root/extra/child2/triple") << QLatin1String("/root/extra/ch223/triple"); this->tagSet2 << QLatin1String("/root/child1/chilasdfasdf") << QLatin1String("/root/exrebhtra/chsdrild2/asdfad") << QLatin1String("/root/exfgvdtra/ch2gfg23/tridfgvle"); this->tagSet3 << QLatin1String("/rowet/child1/crehild2") << QLatin1String("/rsdfsoot/extsdera/chihgld2/triple") << QLatin1String("/roosdst/extfamnbra/ch2hg23/triple"); } void TagsReadWriteTest::testSimpleReadAfterWrite() { DMetadata dmeta; QStringList tagPaths2; dmeta.setImageTagsPath(this->tagSet1); dmeta.getImageTagsPath(tagPaths2); QCOMPARE(tagSet1, tagPaths2); } void TagsReadWriteTest::testWriteToDisabledNamespaces() { DMetadata dmeta; DMetadataSettingsContainer dmsettings; QStringList empty; QStringList secondNamespace; NamespaceEntry tagNs2; tagNs2.namespaceName = QLatin1String("Xmp.MicrosoftPhoto.LastKeywordXMP"); tagNs2.tagPaths = NamespaceEntry::TAGPATH; tagNs2.separator = QLatin1String("/"); tagNs2.nsType = NamespaceEntry::TAGS; tagNs2.index = 1; tagNs2.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs2.subspace = NamespaceEntry::XMP; tagNs2.isDisabled = true; NamespaceEntry tagNs3; tagNs3.namespaceName = QLatin1String("Xmp.lr.hierarchicalSubject"); tagNs3.tagPaths = NamespaceEntry::TAGPATH; tagNs3.separator = QLatin1String("|"); tagNs3.nsType = NamespaceEntry::TAGS; tagNs3.index = 2; tagNs3.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs3.subspace = NamespaceEntry::XMP; tagNs3.alternativeName = QLatin1String("Xmp.lr.HierarchicalSubject"); tagNs3.secondNameOpts = NamespaceEntry::TAG_XMPSEQ; - dmsettings.getWriteMapping(QLatin1String(DM_TAG_CONTAINER)).clear(); - dmsettings.getWriteMapping(QLatin1String(DM_TAG_CONTAINER)) + dmsettings.getWriteMapping(QString::fromUtf8(DM_TAG_CONTAINER)).clear(); + dmsettings.getWriteMapping(QString::fromUtf8(DM_TAG_CONTAINER)) << tagNs2 << tagNs3; dmeta.setImageTagsPath(tagSet1, dmsettings); empty = dmeta.getXmpTagStringBag("Xmp.MicrosoftPhoto.LastKeywordXMP", false); QCOMPARE(empty, QStringList()); secondNamespace = dmeta.getXmpTagStringBag("Xmp.lr.hierarchicalSubject", false); secondNamespace = secondNamespace.replaceInStrings(QLatin1String("|"),QLatin1String("/")); QCOMPARE(secondNamespace, tagSet1); } void TagsReadWriteTest::testReadFromDisabledNamespaces() { DMetadata dmeta; DMetadataSettingsContainer dmsettings; QStringList actual; NamespaceEntry tagNs2; tagNs2.namespaceName = QLatin1String("Xmp.MicrosoftPhoto.LastKeywordXMP"); tagNs2.tagPaths = NamespaceEntry::TAGPATH; tagNs2.separator = QLatin1String("/"); tagNs2.nsType = NamespaceEntry::TAGS; tagNs2.index = 1; tagNs2.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs2.subspace = NamespaceEntry::XMP; tagNs2.isDisabled = true; NamespaceEntry tagNs3; tagNs3.namespaceName = QLatin1String("Xmp.lr.hierarchicalSubject"); tagNs3.tagPaths = NamespaceEntry::TAGPATH; tagNs3.separator = QLatin1String("|"); tagNs3.nsType = NamespaceEntry::TAGS; tagNs3.index = 2; tagNs3.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs3.subspace = NamespaceEntry::XMP; tagNs3.alternativeName = QLatin1String("Xmp.lr.HierarchicalSubject"); tagNs3.secondNameOpts = NamespaceEntry::TAG_XMPSEQ; - dmsettings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)).clear(); - dmsettings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)) + dmsettings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)).clear(); + dmsettings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)) << tagNs2 << tagNs3; dmeta.setXmpTagStringBag("Xmp.MicrosoftPhoto.LastKeywordXMP", tagSet1); dmeta.setXmpTagStringBag("Xmp.lr.hierarchicalSubject", tagSet2); dmeta.getImageTagsPath(actual, dmsettings); QCOMPARE(actual, tagSet2); } void TagsReadWriteTest::testTagSeparatorWrite() { DMetadata dmeta; DMetadataSettingsContainer dmsettings; QStringList readResult; QStringList expected; NamespaceEntry tagNs3; tagNs3.namespaceName = QLatin1String("Xmp.lr.hierarchicalSubject"); tagNs3.tagPaths = NamespaceEntry::TAGPATH; tagNs3.separator = QLatin1String("|"); tagNs3.nsType = NamespaceEntry::TAGS; tagNs3.index = 2; tagNs3.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs3.subspace = NamespaceEntry::XMP; tagNs3.alternativeName = QLatin1String("Xmp.lr.HierarchicalSubject"); tagNs3.secondNameOpts = NamespaceEntry::TAG_XMPSEQ; - dmsettings.getWriteMapping(QLatin1String(DM_TAG_CONTAINER)).clear(); - dmsettings.getWriteMapping(QLatin1String(DM_TAG_CONTAINER)) + dmsettings.getWriteMapping(QString::fromUtf8(DM_TAG_CONTAINER)).clear(); + dmsettings.getWriteMapping(QString::fromUtf8(DM_TAG_CONTAINER)) << tagNs3; dmeta.setImageTagsPath(tagSet1, dmsettings); readResult = dmeta.getXmpTagStringBag("Xmp.lr.hierarchicalSubject", false); expected = tagSet1; expected = expected.replaceInStrings(QLatin1String("/"),QLatin1String("|")); QCOMPARE(readResult, expected); } void TagsReadWriteTest::testTagSeparatorRead() { DMetadata dmeta; DMetadataSettingsContainer dmsettings; QStringList toWrite; QStringList actual; QStringList reference; NamespaceEntry tagNs3; tagNs3.namespaceName = QLatin1String("Xmp.lr.hierarchicalSubject"); tagNs3.tagPaths = NamespaceEntry::TAGPATH; tagNs3.separator = QLatin1String("|"); tagNs3.nsType = NamespaceEntry::TAGS; tagNs3.index = 2; tagNs3.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs3.subspace = NamespaceEntry::XMP; tagNs3.alternativeName = QLatin1String("Xmp.lr.HierarchicalSubject"); tagNs3.secondNameOpts = NamespaceEntry::TAG_XMPSEQ; - dmsettings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)).clear(); - dmsettings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)) + dmsettings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)).clear(); + dmsettings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)) << tagNs3; toWrite = tagSet1; toWrite = toWrite.replaceInStrings(QLatin1String("/"),QLatin1String("|")); dmeta.setXmpTagStringBag("Xmp.lr.hierarchicalSubject", toWrite); reference = dmeta.getXmpTagStringBag("Xmp.lr.hierarchicalSubject", false); QCOMPARE(reference, toWrite); dmeta.getImageTagsPath(actual, dmsettings); QCOMPARE(actual, tagSet1); } void TagsReadWriteTest::testTagReadAlternativeNameSpace() { DMetadata dmeta; DMetadataSettingsContainer dmsettings; QStringList toWrite; QStringList actual; QStringList reference; NamespaceEntry tagNs3; tagNs3.namespaceName = QLatin1String("Xmp.lr.hierarchicalSubject"); tagNs3.tagPaths = NamespaceEntry::TAGPATH; tagNs3.separator = QLatin1String("|"); tagNs3.nsType = NamespaceEntry::TAGS; tagNs3.index = 2; tagNs3.specialOpts = NamespaceEntry::TAG_XMPBAG; tagNs3.subspace = NamespaceEntry::XMP; tagNs3.alternativeName = QLatin1String("Xmp.lr.HierarchicalSubject"); tagNs3.secondNameOpts = NamespaceEntry::TAG_XMPSEQ; - dmsettings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)).clear(); - dmsettings.getReadMapping(QLatin1String(DM_TAG_CONTAINER)) + dmsettings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)).clear(); + dmsettings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)) << tagNs3; toWrite = tagSet1; toWrite = toWrite.replaceInStrings(QLatin1String("/"),QLatin1String("|")); dmeta.setXmpTagStringSeq("Xmp.lr.HierarchicalSubject", toWrite); // We write some data to alternative namespace reference = dmeta.getXmpTagStringSeq("Xmp.lr.HierarchicalSubject", false); QCOMPARE(reference, toWrite); dmeta.getImageTagsPath(actual, dmsettings); QCOMPARE(actual, tagSet1); } diff --git a/core/utilities/assistants/panorama/tasks/panotask.cpp b/core/utilities/assistants/panorama/tasks/panotask.cpp index 7009882594..780090cc9b 100644 --- a/core/utilities/assistants/panorama/tasks/panotask.cpp +++ b/core/utilities/assistants/panorama/tasks/panotask.cpp @@ -1,54 +1,54 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-03-15 * Description : a tool to create panorama by fusion of several images. * * Copyright (C) 2012-2016 by Benjamin Girault * * 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, 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. * * ============================================================ */ #include "panotask.h" // Qt includes #include namespace Digikam { PanoTask::PanoTask(PanoAction action, const QString& workDirPath) : action(action), isAbortedFlag(false), successFlag(false), - tmpDir(QUrl::fromLocalFile(workDirPath + QLatin1String("/"))) + tmpDir(QUrl::fromLocalFile(workDirPath + QLatin1Char('/'))) { } PanoTask::~PanoTask() { } bool PanoTask::success() const { return successFlag; } void PanoTask::requestAbort() { isAbortedFlag = true; } } // namespace Digikam diff --git a/core/utilities/assistants/printcreator/manager/advprinttask.cpp b/core/utilities/assistants/printcreator/manager/advprinttask.cpp index 552bd704fc..0d9024df32 100644 --- a/core/utilities/assistants/printcreator/manager/advprinttask.cpp +++ b/core/utilities/assistants/printcreator/manager/advprinttask.cpp @@ -1,705 +1,705 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-11-07 * Description : a tool to print images * * Copyright (C) 2017-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "advprinttask.h" // C++ includes #include // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "advprintwizard.h" #include "advprintphoto.h" #include "advprintcaptionpage.h" #include "dmetadata.h" #include "dfileoperations.h" #include "dimg.h" #include "digikam_debug.h" #include "digikam_config.h" namespace Digikam { class AdvPrintTask::Private { public: explicit Private() : settings(0), mode(AdvPrintTask::PRINT), sizeIndex(0) { } public: AdvPrintSettings* settings; PrintMode mode; QSize size; int sizeIndex; }; // ------------------------------------------------------- AdvPrintTask::AdvPrintTask(AdvPrintSettings* const settings, PrintMode mode, const QSize& size, int sizeIndex) : ActionJob(), d(new Private) { d->settings = settings; d->mode = mode; d->size = size; d->sizeIndex = sizeIndex; } AdvPrintTask::~AdvPrintTask() { cancel(); delete d; } void AdvPrintTask::run() { switch (d->mode) { case PREPAREPRINT: qCDebug(DIGIKAM_GENERAL_LOG) << "Start prepare to print"; preparePrint(); emit signalDone(!m_cancel); qCDebug(DIGIKAM_GENERAL_LOG) << "Prepare to print is done"; break; case PRINT: qCDebug(DIGIKAM_GENERAL_LOG) << "Start to print"; if (d->settings->printerName != d->settings->outputName(AdvPrintSettings::FILES) && d->settings->printerName != d->settings->outputName(AdvPrintSettings::GIMP)) { printPhotos(); emit signalDone(!m_cancel); } else { QStringList files = printPhotosToFile(); if (d->settings->printerName == d->settings->outputName(AdvPrintSettings::GIMP)) { d->settings->gimpFiles << files; } emit signalDone(!m_cancel && !files.isEmpty()); } qCDebug(DIGIKAM_GENERAL_LOG) << "Print is done"; break; default: // PREVIEW qCDebug(DIGIKAM_GENERAL_LOG) << "Start to compute preview"; QImage img(d->size, QImage::Format_ARGB32_Premultiplied); QPainter p(&img); p.setCompositionMode(QPainter::CompositionMode_Clear); p.fillRect(img.rect(), Qt::color0); p.setCompositionMode(QPainter::CompositionMode_SourceOver); paintOnePage(p, d->settings->photos, d->settings->outputLayouts->m_layouts, d->settings->currentPreviewPage, d->settings->disableCrop, true); p.end(); if (!m_cancel) emit signalPreview(img); qCDebug(DIGIKAM_GENERAL_LOG) << "Preview computation is done"; break; } } void AdvPrintTask::preparePrint() { int photoIndex = 0; for (QList::iterator it = d->settings->photos.begin() ; it != d->settings->photos.end() ; ++it) { AdvPrintPhoto* const photo = static_cast(*it); if (photo && photo->m_cropRegion == QRect(-1, -1, -1, -1)) { QRect* const curr = d->settings->getLayout(photoIndex, d->sizeIndex); photo->updateCropRegion(curr->width(), curr->height(), d->settings->outputLayouts->m_autoRotate); } photoIndex++; emit signalProgress(photoIndex); if (m_cancel) { signalMessage(i18n("Printing canceled"), true); return; } } } void AdvPrintTask::printPhotos() { AdvPrintPhotoSize* const layouts = d->settings->outputLayouts; QPrinter* const printer = d->settings->outputPrinter; Q_ASSERT(layouts); Q_ASSERT(printer); Q_ASSERT(layouts->m_layouts.count() > 1); QList photos = d->settings->photos; QPainter p; p.begin(printer); int current = 0; int pageCount = 1; bool printing = true; while (printing) { signalMessage(i18n("Processing page %1", pageCount), false); printing = paintOnePage(p, photos, layouts->m_layouts, current, d->settings->disableCrop); if (printing) { printer->newPage(); } pageCount++; emit signalProgress(current); if (m_cancel) { printer->abort(); signalMessage(i18n("Printing canceled"), true); return; } } p.end(); } QStringList AdvPrintTask::printPhotosToFile() { AdvPrintPhotoSize* const layouts = d->settings->outputLayouts; QString dir = d->settings->outputPath; Q_ASSERT(layouts); Q_ASSERT(!dir.isEmpty()); Q_ASSERT(layouts->m_layouts.count() > 1); QList photos = d->settings->photos; QStringList files; int current = 0; int pageCount = 1; bool printing = true; QRect* const srcPage = layouts->m_layouts.at(0); while (printing) { // make a pixmap to save to file. Make it just big enough to show the // highest-dpi image on the page without losing data. double dpi = layouts->m_dpi; if (dpi == 0.0) { dpi = getMaxDPI(photos, layouts->m_layouts, current) * 1.1; } int w = AdvPrintWizard::normalizedInt(srcPage->width()); int h = AdvPrintWizard::normalizedInt(srcPage->height()); QImage image(w, h, QImage::Format_ARGB32_Premultiplied); QPainter painter; painter.begin(&image); QString ext = d->settings->format(); QString name = QLatin1String("output"); - QString filename = dir + QLatin1String("/") + - name + QLatin1String("_") + + QString filename = dir + QLatin1Char('/') + + name + QLatin1Char('_') + QString::number(pageCount) + - QLatin1String(".") + ext; + QLatin1Char('.') + ext; if (QFile::exists(filename) && d->settings->conflictRule != FileSaveConflictBox::OVERWRITE) { filename = DFileOperations::getUniqueFileUrl(QUrl::fromLocalFile(filename)).toLocalFile(); } signalMessage(i18n("Processing page %1", pageCount), false); printing = paintOnePage(painter, photos, layouts->m_layouts, current, d->settings->disableCrop); painter.end(); if (!image.save(filename, 0, 100)) { signalMessage(i18n("Could not save file %1", filename), true); break; } else { files.append(filename); signalMessage(i18n("Page %1 saved as %2", pageCount, filename), false); } pageCount++; emit signalProgress(current); if (m_cancel) { signalMessage(i18n("Printing canceled"), true); break; } } return files; } bool AdvPrintTask::paintOnePage(QPainter& p, const QList& photos, const QList& layouts, int& current, bool cropDisabled, bool useThumbnails) { if (layouts.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid layout content"; return true; } if (photos.count() == 0) { qCWarning(DIGIKAM_GENERAL_LOG) << "no photo to print"; // no photos => last photo return true; } QList::const_iterator it = layouts.begin(); QRect* const srcPage = static_cast(*it); ++it; QRect* layout = static_cast(*it); // scale the page size to best fit the painter // size the rectangle based on the minimum image dimension int destW = p.window().width(); int destH = p.window().height(); int srcW = srcPage->width(); int srcH = srcPage->height(); if (destW < destH) { destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW)); if (destH > p.window().height()) { destH = p.window().height(); destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH)); } } else { destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH)); if (destW > p.window().width()) { destW = p.window().width(); destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW)); } } double xRatio = (double) destW / (double) srcPage->width(); double yRatio = (double) destH / (double) srcPage->height(); int left = (p.window().width() - destW) / 2; int top = (p.window().height() - destH) / 2; // FIXME: may not want to erase the background page p.eraseRect(left, top, AdvPrintWizard::normalizedInt((double) srcPage->width() * xRatio), AdvPrintWizard::normalizedInt((double) srcPage->height() * yRatio)); for (; (current < photos.count()) && !m_cancel ; ++current) { AdvPrintPhoto* const photo = photos.at(current); // crop QImage img; if (useThumbnails) { img = photo->thumbnail().copyQImage(); } else { img = photo->loadPhoto().copyQImage(); } // next, do we rotate? if (photo->m_rotation != 0) { // rotate QMatrix matrix; matrix.rotate(photo->m_rotation); img = img.transformed(matrix); } if (useThumbnails) { // scale the crop region to thumbnail coords double xRatio = 0.0; double yRatio = 0.0; if (photo->thumbnail().width() != 0) { xRatio = (double)photo->thumbnail().width() / (double)photo->width(); } if (photo->thumbnail().height() != 0) { yRatio = (double)photo->thumbnail().height() / (double)photo->height(); } int x1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.left() * xRatio); int y1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.top() * yRatio); int w = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.width() * xRatio); int h = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.height() * yRatio); img = img.copy(QRect(x1, y1, w, h)); } else if (!cropDisabled) { img = img.copy(photo->m_cropRegion); } int x1 = AdvPrintWizard::normalizedInt((double) layout->left() * xRatio); int y1 = AdvPrintWizard::normalizedInt((double) layout->top() * yRatio); int w = AdvPrintWizard::normalizedInt((double) layout->width() * xRatio); int h = AdvPrintWizard::normalizedInt((double) layout->height() * yRatio); QRect rectViewPort = p.viewport(); QRect newRectViewPort = QRect(x1 + left, y1 + top, w, h); QSize imageSize = img.size(); /* qCDebug(DIGIKAM_GENERAL_LOG) << "Image " << photo->filename << " size " << imageSize; qCDebug(DIGIKAM_GENERAL_LOG) << "viewport size " << newRectViewPort.size(); */ QPoint point; if (cropDisabled) { imageSize.scale(newRectViewPort.size(), Qt::KeepAspectRatio); int spaceLeft = (newRectViewPort.width() - imageSize.width()) / 2; int spaceTop = (newRectViewPort.height() - imageSize.height()) / 2; p.setViewport(spaceLeft + newRectViewPort.x(), spaceTop + newRectViewPort.y(), imageSize.width(), imageSize.height()); point = QPoint(newRectViewPort.x() + spaceLeft + imageSize.width(), newRectViewPort.y() + spaceTop + imageSize.height()); } else { p.setViewport(newRectViewPort); point = QPoint(x1 + left + w, y1 + top + w); } QRect rectWindow = p.window(); p.setWindow(img.rect()); p.drawImage(0, 0, img); p.setViewport(rectViewPort); p.setWindow(rectWindow); p.setBrushOrigin(point); if (photo->m_pAdvPrintCaptionInfo && photo->m_pAdvPrintCaptionInfo->m_captionType != AdvPrintSettings::NONE) { p.save(); QString caption = AdvPrintCaptionPage::captionFormatter(photo); qCDebug(DIGIKAM_GENERAL_LOG) << "Caption for" << photo->m_url << ":" << caption; // draw the text at (0,0), but we will translate and rotate the world // before drawing so the text will be in the correct location // next, do we rotate? int captionW = w - 2; double ratio = photo->m_pAdvPrintCaptionInfo->m_captionSize * 0.01; int captionH = (int)(qMin(w, h) * ratio); int exifOrientation = DMetadata::ORIENTATION_NORMAL; int orientatation = photo->m_rotation; if (photo->m_iface) { DItemInfo info(photo->m_iface->itemInfo(photo->m_url)); exifOrientation = info.orientation(); } else { DMetadata meta(photo->m_url.toLocalFile()); exifOrientation = meta.getImageOrientation(); } // ROT_90_HFLIP .. ROT_270 if (exifOrientation == DMetadata::ORIENTATION_ROT_90_HFLIP || exifOrientation == DMetadata::ORIENTATION_ROT_90 || exifOrientation == DMetadata::ORIENTATION_ROT_90_VFLIP || exifOrientation == DMetadata::ORIENTATION_ROT_270) { orientatation = (photo->m_rotation + 270) % 360; // -90 degrees } if (orientatation == 90 || orientatation == 270) { captionW = h; } p.rotate(orientatation); qCDebug(DIGIKAM_GENERAL_LOG) << "rotation " << photo->m_rotation << " orientation " << orientatation; int tx = left; int ty = top; switch (orientatation) { case 0 : { tx += x1 + 1; ty += y1 + (h - captionH - 1); break; } case 90 : { tx = top + y1 + 1; ty = -left - x1 - captionH - 1; break; } case 180 : { tx = -left - x1 - w + 1; ty = -top - y1 - (captionH + 1); break; } case 270 : { tx = -top - y1 - h + 1; ty = left + x1 + (w - captionH) - 1; break; } } p.translate(tx, ty); printCaption(p, photo, captionW, captionH, caption); p.restore(); } // iterate to the next position ++it; layout = (it == layouts.end()) ? 0 : static_cast(*it); if (layout == 0) { current++; break; } } // did we print the last photo? return (current < photos.count()); } double AdvPrintTask::getMaxDPI(const QList& photos, const QList& layouts, int current) { Q_ASSERT(layouts.count() > 1); QList::const_iterator it = layouts.begin(); QRect* layout = static_cast(*it); double maxDPI = 0.0; for (; current < photos.count(); ++current) { AdvPrintPhoto* const photo = photos.at(current); double dpi = ((double) photo->m_cropRegion.width() + (double) photo->m_cropRegion.height()) / (((double) layout->width() / 1000.0) + ((double) layout->height() / 1000.0)); if (dpi > maxDPI) maxDPI = dpi; // iterate to the next position ++it; layout = (it == layouts.end()) ? 0 : static_cast(*it); if (layout == 0) { break; } } return maxDPI; } void AdvPrintTask::printCaption(QPainter& p, AdvPrintPhoto* const photo, int captionW, int captionH, const QString& caption) { QStringList captionByLines; int captionIndex = 0; while (captionIndex < caption.length()) { QString newLine; bool breakLine = false; // End Of Line found int currIndex; // Caption QString current index // Check minimal lines dimension // TODO: fix length, maybe useless int captionLineLocalLength = 40; for (currIndex = captionIndex ; currIndex < caption.length() && !breakLine ; ++currIndex) { if (caption[currIndex] == QLatin1Char('\n') || caption[currIndex].isSpace()) { breakLine = true; } } if (captionLineLocalLength <= (currIndex - captionIndex)) { captionLineLocalLength = (currIndex - captionIndex); } breakLine = false; for (currIndex = captionIndex; (currIndex <= captionIndex + captionLineLocalLength) && (currIndex < caption.length()) && !breakLine; ++currIndex) { breakLine = (caption[currIndex] == QLatin1Char('\n')) ? true : false; if (breakLine) newLine.append(QLatin1Char(' ')); else newLine.append(caption[currIndex]); } captionIndex = currIndex; // The line is ended if (captionIndex != caption.length()) { while (!newLine.endsWith(QLatin1Char(' '))) { newLine.truncate(newLine.length() - 1); captionIndex--; } } captionByLines.prepend(newLine.trimmed()); } QFont font(photo->m_pAdvPrintCaptionInfo->m_captionFont); font.setStyleHint(QFont::SansSerif); font.setPixelSize((int)(captionH * 0.8F)); // Font height ratio font.setWeight(QFont::Normal); QFontMetrics fm(font); int pixelsHigh = fm.height(); p.setFont(font); p.setPen(photo->m_pAdvPrintCaptionInfo->m_captionColor); qCDebug(DIGIKAM_GENERAL_LOG) << "Number of lines " << (int) captionByLines.count() ; // Now draw the caption // TODO allow printing captions per photo and on top, bottom and vertically for (int lineNumber = 0 ; lineNumber < (int) captionByLines.count() ; ++lineNumber) { if (lineNumber > 0) { p.translate(0, - (int)(pixelsHigh)); } QRect r(0, 0, captionW, captionH); p.drawText(r, Qt::AlignLeft, captionByLines[lineNumber], &r); } } } // namespace Digikam diff --git a/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp b/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp index 0d310b0994..4041ef1b22 100644 --- a/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp +++ b/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp @@ -1,1516 +1,1516 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-05-25 * Description : a tool to print images * * Copyright (C) 2017-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "advprintphotopage.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "advprintwizard.h" #include "advprintcustomdlg.h" #include "templateicon.h" namespace Digikam { static const char* const CUSTOM_PAGE_LAYOUT_NAME = I18N_NOOP("Custom"); class AdvPrintPhotoPage::Private { public: template class WizardUI : public QWidget, public Ui_Class { public: explicit WizardUI(QWidget* const parent) : QWidget(parent) { this->setupUi(this); } }; typedef WizardUI PhotoUI; public: explicit Private(QWizard* const dialog) : pageSetupDlg(0), printer(0), wizard(0), settings(0), iface(0) { photoUi = new PhotoUI(dialog); wizard = dynamic_cast(dialog); if (wizard) { settings = wizard->settings(); iface = wizard->iface(); } } PhotoUI* photoUi; QPageSetupDialog* pageSetupDlg; QPrinter* printer; QList printerList; AdvPrintWizard* wizard; AdvPrintSettings* settings; DInfoInterface* iface; }; AdvPrintPhotoPage::AdvPrintPhotoPage(QWizard* const wizard, const QString& title) : DWizardPage(wizard, title), d(new Private(wizard)) { d->photoUi->BtnPreviewPageUp->setIcon(QIcon::fromTheme(QLatin1String("go-next")) .pixmap(16, 16)); d->photoUi->BtnPreviewPageDown->setIcon(QIcon::fromTheme(QLatin1String("go-previous")) .pixmap(16, 16)); // ---------------------- d->photoUi->m_printer_choice->setEditable(false); d->photoUi->m_printer_choice->setWhatsThis(i18n("Select your preferred print output.")); // Populate hardcoded printers QMap map2 = AdvPrintSettings::outputNames(); QMap::const_iterator it2 = map2.constBegin(); while (it2 != map2.constEnd()) { d->photoUi->m_printer_choice->addSqueezedItem(it2.value(), (int)it2.key()); ++it2; } // Populate real printers d->printerList = QPrinterInfo::availablePrinters(); for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { d->photoUi->m_printer_choice->addSqueezedItem(it->printerName()); } connect(d->photoUi->m_printer_choice, SIGNAL(activated(QString)), this, SLOT(slotOutputChanged(QString))); connect(d->photoUi->BtnPreviewPageUp, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageUpClicked())); connect(d->photoUi->BtnPreviewPageDown, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageDownClicked())); connect(d->photoUi->ListPhotoSizes, SIGNAL(currentRowChanged(int)), this, SLOT(slotListPhotoSizesSelected())); connect(d->photoUi->m_pagesetup, SIGNAL(clicked()), this, SLOT(slotPageSetup())); if (d->photoUi->mPrintList->layout()) { delete d->photoUi->mPrintList->layout(); } // ----------------------------------- d->photoUi->mPrintList->setIface(d->iface); d->photoUi->mPrintList->setAllowDuplicate(true); d->photoUi->mPrintList->setControlButtons(DImagesList::Add | DImagesList::Remove | DImagesList::MoveUp | DImagesList::MoveDown | DImagesList::Clear | DImagesList::Save | DImagesList::Load); d->photoUi->mPrintList->setControlButtonsPlacement(DImagesList::ControlButtonsAbove); d->photoUi->mPrintList->enableDragAndDrop(false); d->photoUi->BmpFirstPagePreview->setAlignment(Qt::AlignHCenter); connect(d->photoUi->mPrintList, SIGNAL(signalMoveDownItem()), this, SLOT(slotBtnPrintOrderDownClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalMoveUpItem()), this, SLOT(slotBtnPrintOrderUpClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalAddItems(QList)), this, SLOT(slotAddItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalRemovedItems(QList)), this, SLOT(slotRemovingItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalContextMenuRequested()), this, SLOT(slotContextMenuRequested())); connect(d->photoUi->mPrintList, SIGNAL(signalXMLSaveItem(QXmlStreamWriter&,int)), this, SLOT(slotXMLSaveItem(QXmlStreamWriter&,int))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamWriter&)), this, SLOT(slotXMLCustomElement(QXmlStreamWriter&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLLoadImageElement(QXmlStreamReader&)), this, SLOT(slotXMLLoadElement(QXmlStreamReader&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamReader&)), this, SLOT(slotXMLCustomElement(QXmlStreamReader&))); // ----------------------------------- setPageWidget(d->photoUi); setLeftBottomPix(QIcon::fromTheme(QLatin1String("image-stack"))); slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } AdvPrintPhotoPage::~AdvPrintPhotoPage() { delete d->printer; delete d->pageSetupDlg; delete d; } void AdvPrintPhotoPage::initializePage() { d->photoUi->mPrintList->listView()->clear(); if (d->settings->selMode == AdvPrintSettings::IMAGES) { d->photoUi->mPrintList->loadImagesFromCurrentSelection(); } else { d->wizard->setItemsList(d->settings->inputImages); } initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // reset preview page number d->settings->currentPreviewPage = 0; // create our photo sizes list d->wizard->previewPhotos(); int gid = d->photoUi->m_printer_choice->findText(d->settings->outputName(AdvPrintSettings::GIMP)); if (d->settings->gimpPath.isEmpty()) { // Gimp is not available : we disable the option. d->photoUi->m_printer_choice->setItemData(gid, false, Qt::UserRole-1); } int index = d->photoUi->m_printer_choice->findText(d->settings->printerName); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); d->photoUi->ListPhotoSizes->setIconSize(QSize(32, 32)); initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); } bool AdvPrintPhotoPage::validatePage() { d->settings->inputImages = d->photoUi->mPrintList->imageUrls(); d->settings->printerName = d->photoUi->m_printer_choice->itemHighlighted(); if (d->photoUi->ListPhotoSizes->currentItem()) { d->settings->savedPhotoSize = d->photoUi->ListPhotoSizes->currentItem()->text(); } return true; } bool AdvPrintPhotoPage::isComplete() const { return (!d->photoUi->mPrintList->imageUrls().isEmpty() || !d->wizard->itemsList().isEmpty()); } QPrinter* AdvPrintPhotoPage::printer() const { return d->printer; } DImagesList* AdvPrintPhotoPage::imagesList() const { return d->photoUi->mPrintList; } Ui_AdvPrintPhotoPage* AdvPrintPhotoPage::ui() const { return d->photoUi; } void AdvPrintPhotoPage::slotOutputChanged(const QString& text) { if (AdvPrintSettings::outputNames().values().contains(text)) { delete d->printer; d->printer = new QPrinter(); d->printer->setOutputFormat(QPrinter::PdfFormat); } else // real printer { for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { if (it->printerName() == text) { qCDebug(DIGIKAM_GENERAL_LOG) << "Chosen printer: " << it->printerName(); delete d->printer; d->printer = new QPrinter(*it); } } d->printer->setOutputFormat(QPrinter::NativeFormat); } // default no margins d->printer->setFullPage(true); d->printer->setPageMargins(0, 0, 0, 0, QPrinter::Millimeter); } void AdvPrintPhotoPage::slotXMLLoadElement(QXmlStreamReader& xmlReader) { if (d->settings->photos.size()) { // read image is the last. AdvPrintPhoto* const pPhoto = d->settings->photos[d->settings->photos.size()-1]; qCDebug(DIGIKAM_GENERAL_LOG) << " invoked " << xmlReader.name(); while (xmlReader.readNextStartElement()) { qCDebug(DIGIKAM_GENERAL_LOG) << pPhoto->m_url << " " << xmlReader.name(); if (xmlReader.name() == QLatin1String("pa_caption")) { //useless this item has been added now if (pPhoto->m_pAdvPrintCaptionInfo) delete pPhoto->m_pAdvPrintCaptionInfo; pPhoto->m_pAdvPrintCaptionInfo = new AdvPrintCaptionInfo(); // get all attributes and its value of a tag in attrs variable. QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("type")); bool ok; if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionType = (AdvPrintSettings::CaptionType)attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("font")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.fromString(attr.toString()); } attr = attrs.value(QLatin1String("color")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.setNamedColor(attr.toString()); } attr = attrs.value(QLatin1String("size")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionSize = attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("text")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionText = attr.toString(); } } } } } void AdvPrintPhotoPage::slotXMLSaveItem(QXmlStreamWriter& xmlWriter, int itemIndex) { if (d->settings->photos.size()) { AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; // TODO: first and copies could be removed since they are not useful any more xmlWriter.writeAttribute(QLatin1String("first"), QString::fromUtf8("%1") .arg(pPhoto->m_first)); xmlWriter.writeAttribute(QLatin1String("copies"), QString::fromUtf8("%1") .arg(pPhoto->m_first ? pPhoto->m_copies : 0)); // additional info (caption... etc) if (pPhoto->m_pAdvPrintCaptionInfo) { xmlWriter.writeStartElement(QLatin1String("pa_caption")); xmlWriter.writeAttribute(QLatin1String("type"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionType)); xmlWriter.writeAttribute(QLatin1String("font"), pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.toString()); xmlWriter.writeAttribute(QLatin1String("size"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionSize)); xmlWriter.writeAttribute(QLatin1String("color"), pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.name()); xmlWriter.writeAttribute(QLatin1String("text"), pPhoto->m_pAdvPrintCaptionInfo->m_captionText); xmlWriter.writeEndElement(); // pa_caption } } } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamWriter& xmlWriter) { xmlWriter.writeStartElement(QLatin1String("pa_layout")); xmlWriter.writeAttribute(QLatin1String("Printer"), d->photoUi->m_printer_choice->itemHighlighted()); xmlWriter.writeAttribute(QLatin1String("PageSize"), QString::fromUtf8("%1").arg(d->printer->paperSize())); xmlWriter.writeAttribute(QLatin1String("PhotoSize"), d->photoUi->ListPhotoSizes->currentItem()->text()); xmlWriter.writeEndElement(); // pa_layout } void AdvPrintPhotoPage::slotContextMenuRequested() { if (d->settings->photos.size()) { int itemIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); d->photoUi->mPrintList->listView()->blockSignals(true); QMenu menu(d->photoUi->mPrintList->listView()); QAction* const action = menu.addAction(i18n("Add again")); connect(action, SIGNAL(triggered()), this , SLOT(slotIncreaseCopies())); AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; qCDebug(DIGIKAM_GENERAL_LOG) << " copies " << pPhoto->m_copies << " first " << pPhoto->m_first; if (pPhoto->m_copies > 1 || !pPhoto->m_first) { QAction* const actionr = menu.addAction(i18n("Remove")); connect(actionr, SIGNAL(triggered()), this, SLOT(slotDecreaseCopies())); } menu.exec(QCursor::pos()); d->photoUi->mPrintList->listView()->blockSignals(false); } } void AdvPrintPhotoPage::slotIncreaseCopies() { if (d->settings->photos.size()) { QList list; DImagesListViewItem* const item = dynamic_cast(d->photoUi->mPrintList->listView()->currentItem()); if (!item) return; list.append(item->url()); qCDebug(DIGIKAM_GENERAL_LOG) << " Adding a copy of " << item->url(); d->photoUi->mPrintList->slotAddImages(list); } } void AdvPrintPhotoPage::slotDecreaseCopies() { if (d->settings->photos.size()) { DImagesListViewItem* const item = dynamic_cast (d->photoUi->mPrintList->listView()->currentItem()); if (!item) return; qCDebug(DIGIKAM_GENERAL_LOG) << " Removing a copy of " << item->url(); d->photoUi->mPrintList->slotRemoveItems(); } } void AdvPrintPhotoPage::slotAddItems(const QList& list) { if (list.count() == 0) { return; } QList urls; d->photoUi->mPrintList->blockSignals(true); for (QList::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it) { QUrl imageUrl = *it; // Check if the new item already exist in the list. bool found = false; for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && (pCurrentPhoto->m_url == imageUrl) && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies++; found = true; AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(*pCurrentPhoto); pPhoto->m_first = false; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_GENERAL_LOG) << "Added fileName: " << pPhoto->m_url.fileName() << " copy number " << pCurrentPhoto->m_copies; } } if (!found) { AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(150, d->iface); pPhoto->m_url = *it; pPhoto->m_first = true; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_GENERAL_LOG) << "Added new fileName: " << pPhoto->m_url.fileName(); } } d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.size()) { setComplete(true); } } void AdvPrintPhotoPage::slotRemovingItems(const QList& list) { if (list.count() == 0) { return; } d->photoUi->mPrintList->blockSignals(true); foreach (int itemIndex, list) { if (d->settings->photos.size() && itemIndex >= 0) { /// Debug data: found and copies bool found = false; int copies = 0; AdvPrintPhoto* const pPhotoToRemove = d->settings->photos.at(itemIndex); // photo to be removed could be: // 1) unique => just remove it // 2) first of n, => // search another with the same url // and set it a first and with a count to n-1 then remove it // 3) one of n, search the first one and set count to n-1 then remove it if (pPhotoToRemove && pPhotoToRemove->m_first) { if (pPhotoToRemove->m_copies > 0) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url) { pCurrentPhoto->m_copies = pPhotoToRemove->m_copies - 1; copies = pCurrentPhoto->m_copies; pCurrentPhoto->m_first = true; found = true; } } } // otherwise it's unique } else if (pPhotoToRemove) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies--; copies = pCurrentPhoto->m_copies; found = true; } } } else { qCDebug(DIGIKAM_GENERAL_LOG) << " NULL AdvPrintPhoto object "; return; } if (pPhotoToRemove) { qCDebug(DIGIKAM_GENERAL_LOG) << "Removed fileName: " << pPhotoToRemove->m_url.fileName() << " copy number " << copies; } d->settings->photos.removeAt(itemIndex); delete pPhotoToRemove; } } d->wizard->previewPhotos(); d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.isEmpty()) { // No photos => disabling next button (e.g. crop page) setComplete(false); } } void AdvPrintPhotoPage::slotBtnPrintOrderDownClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_GENERAL_LOG) << "Moved photo " << currentIndex - 1 << " to " << currentIndex; d->settings->photos.swap(currentIndex, currentIndex - 1); d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPrintOrderUpClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_GENERAL_LOG) << "Moved photo " << currentIndex << " to " << currentIndex + 1; d->settings->photos.swap(currentIndex, currentIndex + 1); d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamReader& xmlReader) { qCDebug(DIGIKAM_GENERAL_LOG) << " invoked " << xmlReader.name(); while (!xmlReader.atEnd()) { if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String("pa_layout")) { bool ok; QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("Printer")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); int index = d->photoUi->m_printer_choice->findText(attr.toString()); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } attr = attrs.value(QLatin1String("PageSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); QPrinter::PaperSize paperSize = (QPrinter::PaperSize)attr.toString().toInt(&ok); d->printer->setPaperSize(paperSize); } attr = attrs.value(QLatin1String("PhotoSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); d->settings->savedPhotoSize = attr.toString(); } } xmlReader.readNext(); } // reset preview page number d->settings->currentPreviewPage = 0; initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) { qCDebug(DIGIKAM_GENERAL_LOG) << " PhotoSize " << list[0]->text(); d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); } else { d->photoUi->ListPhotoSizes->setCurrentRow(0); } d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageDownClicked() { if (d->settings->currentPreviewPage == 0) return; d->settings->currentPreviewPage--; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageUpClicked() { if (d->settings->currentPreviewPage == getPageCount() - 1) return; d->settings->currentPreviewPage++; d->wizard->previewPhotos(); } int AdvPrintPhotoPage::getPageCount() const { int pageCount = 0; int photoCount = d->settings->photos.count(); if (photoCount > 0) { // get the selected layout AdvPrintPhotoSize* const s = d->settings->photosizes.at(d->photoUi->ListPhotoSizes->currentRow()); // how many pages? Recall that the first layout item is the paper size int photosPerPage = s->m_layouts.count() - 1; int remainder = photoCount % photosPerPage; int emptySlots = 0; if (remainder > 0) emptySlots = photosPerPage - remainder; pageCount = photoCount / photosPerPage; if (emptySlots > 0) pageCount++; } return pageCount; } void AdvPrintPhotoPage::createPhotoGrid(AdvPrintPhotoSize* const p, int pageWidth, int pageHeight, int rows, int columns, TemplateIcon* const iconpreview) { int MARGIN = (int)(((double)pageWidth + (double)pageHeight) / 2.0 * 0.04 + 0.5); int GAP = MARGIN / 4; int photoWidth = (pageWidth - (MARGIN * 2) - ((columns - 1) * GAP)) / columns; int photoHeight = (pageHeight - (MARGIN * 2) - ((rows - 1) * GAP)) / rows; int row = 0; for (int y = MARGIN ; (row < rows) && (y < (pageHeight - MARGIN)) ; y += photoHeight + GAP) { int col = 0; for (int x = MARGIN ; (col < columns) && (x < (pageWidth - MARGIN)) ; x += photoWidth + GAP) { p->m_layouts.append(new QRect(x, y, photoWidth, photoHeight)); iconpreview->fillRect(x, y, photoWidth, photoHeight, Qt::color1); col++; } row++; } } void AdvPrintPhotoPage::slotListPhotoSizesSelected() { AdvPrintPhotoSize* s = 0; QSizeF size, sizeManaged; // TODO FREE STYLE // check if layout is managed by templates or free one // get the selected layout int curr = d->photoUi->ListPhotoSizes->currentRow(); QListWidgetItem* item = d->photoUi->ListPhotoSizes->item(curr); // if custom page layout we launch a dialog to choose what kind if (item->text() == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { // check if a custom layout has already been added if (curr >= 0 && curr < d->settings->photosizes.size()) { s = d->settings->photosizes.at(curr); d->settings->photosizes.removeAt(curr); delete s; s = NULL; } QPointer custDlg = new AdvPrintCustomLayoutDlg(this); custDlg->readSettings(); custDlg->exec(); custDlg->saveSettings(); // get parameters from dialog size = d->settings->pageSize; int scaleValue = 10; // 0.1 mm // convert to mm if (custDlg->m_photoUnits->currentText() == i18n("inches")) { size /= 25.4; scaleValue = 1000; } else if (custDlg->m_photoUnits->currentText() == i18n("cm")) { size /= 10; scaleValue = 100; } sizeManaged = size * scaleValue; s = new AdvPrintPhotoSize; TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); if (custDlg->m_photoGridCheck->isChecked()) { // custom photo grid int rows = custDlg->m_gridRows->value(); int columns = custDlg->m_gridColumns->value(); s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int pageWidth = (int)(size.width()) * scaleValue; int pageHeight = (int)(size.height()) * scaleValue; createPhotoGrid(s, pageWidth, pageHeight, rows, columns, &iconpreview); } else if (custDlg->m_fitAsManyCheck->isChecked()) { int width = custDlg->m_photoWidth->value(); int height = custDlg->m_photoHeight->value(); //photo size must be less than page size static const float round_value = 0.01F; if ((height > (size.height() + round_value) || width > (size.width() + round_value))) { qCDebug(DIGIKAM_GENERAL_LOG) << "photo size " << QSize(width, height) << "> page size " << size; delete s; s = NULL; } else { // fit as many photos of given size as possible s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int nColumns = int(size.width() / width); int nRows = int(size.height() / height); int spareWidth = int(size.width()) % width; // check if there's no room left to separate photos if (nColumns > 1 && spareWidth == 0) { nColumns -= 1; spareWidth = width; } int spareHeight = int(size.height()) % height; // check if there's no room left to separate photos if (nRows > 1 && spareHeight == 0) { nRows -= 1; spareHeight = height; } if (nRows > 0 && nColumns > 0) { // n photos => dx1, photo1, dx2, photo2,... photoN, dxN+1 int dx = spareWidth * scaleValue / (nColumns + 1); int dy = spareHeight * scaleValue / (nRows + 1); int photoX = 0; int photoY = 0; width *= scaleValue; height *= scaleValue; for (int row = 0 ; row < nRows ; ++row) { photoY = dy * (row + 1) + (row * height); for (int col = 0 ; col < nColumns ; ++col) { photoX = dx * (col + 1) + (col * width); qCDebug(DIGIKAM_GENERAL_LOG) << "photo at P(" << photoX << ", " << photoY << ") size(" << width << ", " << height; s->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "I cannot go on, rows " << nRows << "> columns " << nColumns; delete s; s = NULL; } } } else { // Atckin's layout } // TODO not for Atckin's layout iconpreview.end(); if (s) { s->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(s); } delete custDlg; } else { s = d->settings->photosizes.at(curr); } if (!s) { // change position to top d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); d->photoUi->ListPhotoSizes->blockSignals(false); } // reset preview page number d->settings->currentPreviewPage = 0; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotPageSetup() { delete d->pageSetupDlg; QString lastSize = d->photoUi->ListPhotoSizes->currentItem()->text(); d->pageSetupDlg = new QPageSetupDialog(d->printer, this); int ret = d->pageSetupDlg->exec(); if (ret == QDialog::Accepted) { QPrinter* const printer = d->pageSetupDlg->printer(); qCDebug(DIGIKAM_GENERAL_LOG) << "Dialog exit, new size " << printer->paperSize(QPrinter::Millimeter) << " internal size " << d->printer->paperSize(QPrinter::Millimeter); qreal left, top, right, bottom; d->printer->getPageMargins(&left, &top, &right, &bottom, QPrinter::Millimeter); qCDebug(DIGIKAM_GENERAL_LOG) << "Dialog exit, new margins: left " << left << " right " << right << " top " << top << " bottom " << bottom; // next should be useless invoke once changing wizard page //d->wizard->initPhotoSizes(d->printer.paperSize(QPrinter::Millimeter)); //d->settings->pageSize = d->printer.paperSize(QPrinter::Millimeter); #ifdef DEBUG qCDebug(DIGIKAM_GENERAL_LOG) << " dialog exited num of copies: " << printer->numCopies() << " inside: " << d->printer->numCopies(); qCDebug(DIGIKAM_GENERAL_LOG) << " dialog exited from : " << printer->fromPage() << " to: " << d->printer->toPage(); #endif } // Fix the page size dialog and preview PhotoPage initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(lastSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // create our photo sizes list d->wizard->previewPhotos(); } void AdvPrintPhotoPage::manageBtnPreviewPage() { if (d->settings->photos.isEmpty()) { d->photoUi->BtnPreviewPageDown->setEnabled(false); d->photoUi->BtnPreviewPageUp->setEnabled(false); } else { d->photoUi->BtnPreviewPageDown->setEnabled(true); d->photoUi->BtnPreviewPageUp->setEnabled(true); if (d->settings->currentPreviewPage == 0) { d->photoUi->BtnPreviewPageDown->setEnabled(false); } if ((d->settings->currentPreviewPage + 1) == getPageCount()) { d->photoUi->BtnPreviewPageUp->setEnabled(false); } } } void AdvPrintPhotoPage::initPhotoSizes(const QSizeF& pageSize) { qCDebug(DIGIKAM_GENERAL_LOG) << "New page size " << pageSize << ", old page size " << d->settings->pageSize; // don't refresh anything if we haven't changed page sizes. if (pageSize == d->settings->pageSize) return; d->settings->pageSize = pageSize; // cleaning pageSize memory before invoking clear() for (int i = 0 ; i < d->settings->photosizes.count() ; ++i) delete d->settings->photosizes.at(i); d->settings->photosizes.clear(); // get template-files and parse them QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << QLatin1String("*.xml")); qCDebug(DIGIKAM_GENERAL_LOG) << "Template XML files list: " << list; foreach(const QString& fn, list) { - parseTemplateFile(dir.absolutePath() + QLatin1String("/") + fn, pageSize); + parseTemplateFile(dir.absolutePath() + QLatin1Char('/') + fn, pageSize); } qCDebug(DIGIKAM_GENERAL_LOG) << "photosizes count() =" << d->settings->photosizes.count(); qCDebug(DIGIKAM_GENERAL_LOG) << "photosizes isEmpty() =" << d->settings->photosizes.isEmpty(); if (d->settings->photosizes.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Empty photoSize-list, create default size"; // There is no valid page size yet. Create a default page (B10) to prevent crashes. AdvPrintPhotoSize* const p = new AdvPrintPhotoSize; // page size: B10 (32 x 45 mm) p->m_layouts.append(new QRect(0, 0, 3200, 4500)); p->m_layouts.append(new QRect(0, 0, 3200, 4500)); // add to the list d->settings->photosizes.append(p); } // load the photo sizes into the listbox d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->clear(); QList::iterator it; for (it = d->settings->photosizes.begin() ; it != d->settings->photosizes.end() ; ++it) { AdvPrintPhotoSize* const s = static_cast(*it); if (s) { QListWidgetItem* const pWItem = new QListWidgetItem(s->m_label); pWItem->setIcon(s->m_icon); d->photoUi->ListPhotoSizes->addItem(pWItem); } } // Adding custom choice QListWidgetItem* const pWItem = new QListWidgetItem(i18n(CUSTOM_PAGE_LAYOUT_NAME)); TemplateIcon ti(80, pageSize.toSize()); ti.begin(); QPainter& painter = ti.getPainter(); painter.setPen(Qt::color1); painter.drawText(painter.viewport(), Qt::AlignCenter, i18n("Custom\nlayout")); ti.end(); pWItem->setIcon(ti.getIcon()); d->photoUi->ListPhotoSizes->addItem(pWItem); d->photoUi->ListPhotoSizes->blockSignals(false); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); } void AdvPrintPhotoPage::parseTemplateFile(const QString& fn, const QSizeF& pageSize) { QDomDocument doc(QLatin1String("mydocument")); qCDebug(DIGIKAM_GENERAL_LOG) << " XXX: " << fn; if (fn.isEmpty()) { return; } QFile file(fn); if (!file.open(QIODevice::ReadOnly)) return; if (!doc.setContent(&file)) { file.close(); return; } file.close(); AdvPrintPhotoSize* p = 0; // print out the element names of all elements that are direct children // of the outermost element. QDomElement docElem = doc.documentElement(); qCDebug(DIGIKAM_GENERAL_LOG) << docElem.tagName(); // the node really is an element. QSizeF size; QString unit; int scaleValue; QDomNode n = docElem.firstChild(); while (!n.isNull()) { size = QSizeF(0, 0); scaleValue = 10; // 0.1 mm QDomElement e = n.toElement(); // try to convert the node to an element. if (!e.isNull()) { if (e.tagName() == QLatin1String("paper")) { size = QSizeF(e.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(), e.attribute(QLatin1String("height"), QLatin1String("0")).toFloat()); unit = e.attribute(QLatin1String("unit"), QLatin1String("mm")); qCDebug(DIGIKAM_GENERAL_LOG) << e.tagName() << QLatin1String(" name=") << e.attribute(QLatin1String("name"), QLatin1String("??")) << " size= " << size << " unit= " << unit; if (size == QSizeF(0.0, 0.0) && size == pageSize) { // skipping templates without page size since pageSize is not set n = n.nextSibling(); continue; } else if (unit != QLatin1String("mm") && size != QSizeF(0.0, 0.0)) // "cm", "inches" or "inch" { // convert to mm if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { size *= 25.4; scaleValue = 1000; qCDebug(DIGIKAM_GENERAL_LOG) << "template size " << size << " page size " << pageSize; } else if (unit == QLatin1String("cm")) { size *= 10; scaleValue = 100; qCDebug(DIGIKAM_GENERAL_LOG) << "template size " << size << " page size " << pageSize; } else { qCWarning(DIGIKAM_GENERAL_LOG) << "Wrong unit " << unit << " skipping layout"; n = n.nextSibling(); continue; } } static const float round_value = 0.01F; if (size == QSizeF(0, 0)) { size = pageSize; unit = QLatin1String("mm"); } else if (pageSize != QSizeF(0, 0) && (size.height() > (pageSize.height() + round_value) || size.width() > (pageSize.width() + round_value))) { qCDebug(DIGIKAM_GENERAL_LOG) << "skipping size " << size << " page size " << pageSize; // skipping layout it can't fit n = n.nextSibling(); continue; } // Next templates are good qCDebug(DIGIKAM_GENERAL_LOG) << "layout size " << size << " page size " << pageSize; QDomNode np = e.firstChild(); while (!np.isNull()) { QDomElement ep = np.toElement(); // try to convert the node to an element. if (!ep.isNull()) { if (ep.tagName() == QLatin1String("template")) { p = new AdvPrintPhotoSize; QSizeF sizeManaged; // set page size if (pageSize == QSizeF(0, 0)) { sizeManaged = size * scaleValue; } else if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { sizeManaged = pageSize * scaleValue / 25.4; } else { sizeManaged = pageSize * 10; } p->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); // create a small preview of the template TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); QString desktopFileName = ep.attribute(QLatin1String("name"), QLatin1String("XXX")) + QLatin1String(".desktop"); QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << desktopFileName); qCDebug(DIGIKAM_GENERAL_LOG) << "Template desktop files list: " << list; QStringList::ConstIterator it = list.constBegin(); QStringList::ConstIterator end = list.constEnd(); if (it != end) { p->m_label = KDesktopFile(dir.absolutePath() + QLatin1String("/") + *it).readName(); } else { p->m_label = ep.attribute(QLatin1String("name"), QLatin1String("XXX")); qCWarning(DIGIKAM_GENERAL_LOG) << "missed template translation " << desktopFileName; } p->m_dpi = ep.attribute(QLatin1String("dpi"), QLatin1String("0")).toInt(); p->m_autoRotate = (ep.attribute(QLatin1String("autorotate"), QLatin1String("false")) == QLatin1String("true")) ? true : false; QDomNode nt = ep.firstChild(); while (!nt.isNull()) { QDomElement et = nt.toElement(); // try to convert the node to an element. if (!et.isNull()) { if (et.tagName() == QLatin1String("photo")) { float value = et.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(); int width = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("height"), QLatin1String("0")).toFloat(); int height = (int)((value == 0 ? size.height() : value) * scaleValue); int photoX = (int)((et.attribute(QLatin1String("x"), QLatin1String("0")).toFloat() * scaleValue)); int photoY = (int)((et.attribute(QLatin1String("y"), QLatin1String("0")).toFloat() * scaleValue)); p->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } else if (et.tagName() == QLatin1String("photogrid")) { float value = et.attribute(QLatin1String("pageWidth"), QLatin1String("0")).toFloat(); int pageWidth = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("pageHeight"), QLatin1String("0")).toFloat(); int pageHeight = (int)((value == 0 ? size.height() : value) * scaleValue); int rows = et.attribute(QLatin1String("rows"), QLatin1String("0")).toInt(); int columns = et.attribute(QLatin1String("columns"), QLatin1String("0")).toInt(); if (rows > 0 && columns > 0) { createPhotoGrid(p, pageWidth, pageHeight, rows, columns, &iconpreview); } else { qCWarning(DIGIKAM_GENERAL_LOG) << " Wrong grid configuration, rows " << rows << ", columns " << columns; } } else { qCDebug(DIGIKAM_GENERAL_LOG) << " " << et.tagName(); } } nt = nt.nextSibling(); } iconpreview.end(); p->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(p); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "? " << ep.tagName() << " attr=" << ep.attribute(QLatin1String("name"), QLatin1String("??")); } } np = np.nextSibling(); } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "??" << e.tagName() << " name=" << e.attribute(QLatin1String("name"), QLatin1String("??")); } } n = n.nextSibling(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp index 77fe3b130b..50d8dca9fd 100644 --- a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp @@ -1,463 +1,463 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export images to Dropbox web service * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2013-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "dbwindow.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimageslist.h" #include "digikam_version.h" #include "dbtalker.h" #include "dbitem.h" #include "dbnewalbumdlg.h" #include "dbwidget.h" namespace Digikam { class DBWindow::Private { public: explicit Private() { imagesCount = 0; imagesTotal = 0; widget = 0; albumDlg = 0; talker = 0; } unsigned int imagesCount; unsigned int imagesTotal; DBWidget* widget; DBNewAlbumDlg* albumDlg; DBTalker* talker; QString currentAlbumName; QList transferQueue; }; DBWindow::DBWindow(DInfoInterface* const iface, QWidget* const /*parent*/) : WSToolDialog(0), d(new Private) { d->widget = new DBWidget(this, iface, QLatin1String("Dropbox")); d->widget->imagesList()->setIface(iface); setMainWidget(d->widget); setModal(false); setWindowTitle(i18n("Export to Dropbox")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to Dropbox")); d->widget->setMinimumSize(700, 500); connect(d->widget->imagesList(), SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); connect(d->widget->getChangeUserBtn(), SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->widget->getNewAlbmBtn(), SIGNAL(clicked()), this, SLOT(slotNewAlbumRequest())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest())); connect(startButton(), SIGNAL(clicked()), this, SLOT(slotStartTransfer())); d->albumDlg = new DBNewAlbumDlg(this, QLatin1String("Dropbox")); d->talker = new DBTalker(this); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalLinkingFailed()), this, SLOT(slotSignalLinkingFailed())); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotSignalLinkingSucceeded())); connect(d->talker, SIGNAL(signalSetUserName(QString)), this, SLOT(slotSetUserName(QString))); connect(d->talker, SIGNAL(signalListAlbumsFailed(QString)), this, SLOT(slotListAlbumsFailed(QString))); connect(d->talker, SIGNAL(signalListAlbumsDone(QList >)), // krazy:exclude=normalize this, SLOT(slotListAlbumsDone(QList >))); // krazy:exclude=normalize connect(d->talker, SIGNAL(signalCreateFolderFailed(QString)), this, SLOT(slotCreateFolderFailed(QString))); connect(d->talker, SIGNAL(signalCreateFolderSucceeded()), this, SLOT(slotCreateFolderSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished())); readSettings(); buttonStateChange(false); d->talker->link(); } DBWindow::~DBWindow() { delete d->widget; delete d->albumDlg; delete d->talker; delete d; } void DBWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void DBWindow::reactivate() { d->widget->imagesList()->loadImagesFromCurrentSelection(); d->widget->progressBar()->hide(); show(); } void DBWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); d->currentAlbumName = grp.readEntry("Current Album",QString()); if (grp.readEntry("Resize", false)) { d->widget->getResizeCheckBox()->setChecked(true); d->widget->getDimensionSpB()->setEnabled(true); d->widget->getImgQualitySpB()->setEnabled(true); } else { d->widget->getResizeCheckBox()->setChecked(false); d->widget->getDimensionSpB()->setEnabled(false); d->widget->getImgQualitySpB()->setEnabled(false); } d->widget->getDimensionSpB()->setValue(grp.readEntry("Maximum Width", 1600)); d->widget->getImgQualitySpB()->setValue(grp.readEntry("Image Quality", 90)); winId(); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void DBWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); grp.writeEntry("Current Album", d->currentAlbumName); grp.writeEntry("Resize", d->widget->getResizeCheckBox()->isChecked()); grp.writeEntry("Maximum Width", d->widget->getDimensionSpB()->value()); grp.writeEntry("Image Quality", d->widget->getImgQualitySpB()->value()); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void DBWindow::slotSetUserName(const QString& msg) { d->widget->updateLabels(msg, QLatin1String("")); } void DBWindow::slotListAlbumsDone(const QList >& list) { d->widget->getAlbumsCoB()->clear(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotListAlbumsDone:" << list.size(); for (int i = 0 ; i < list.size() ; i++) { d->widget->getAlbumsCoB()->addItem( QIcon::fromTheme(QLatin1String("system-users")), list.value(i).second, list.value(i).first); if (d->currentAlbumName == list.value(i).first) { d->widget->getAlbumsCoB()->setCurrentIndex(i); } } buttonStateChange(true); d->talker->getUserName(); } void DBWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); d->widget->getChangeUserBtn()->setEnabled(false); buttonStateChange(false); } else { setCursor(Qt::ArrowCursor); d->widget->getChangeUserBtn()->setEnabled(true); buttonStateChange(true); } } void DBWindow::slotStartTransfer() { d->widget->imagesList()->clearProcessedStatus(); if (d->widget->imagesList()->imageUrls().isEmpty()) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("No image selected. Please select which images should be uploaded.")); return; } if (!(d->talker->authenticated())) { if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); return; } else { return; } } d->transferQueue = d->widget->imagesList()->imageUrls(); if (d->transferQueue.isEmpty()) { return; } d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(0); d->widget->progressBar()->show(); d->widget->progressBar()->progressScheduled(i18n("Dropbox export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon(QLatin1String("dropbox")).pixmap(22, 22)); uploadNextPhoto(); } void DBWindow::uploadNextPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "uploadNextPhoto:" << d->transferQueue.count(); if (d->transferQueue.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "empty"; d->widget->progressBar()->progressCompleted(); return; } QString imgPath = d->transferQueue.first().toLocalFile(); - QString temp = d->currentAlbumName + QLatin1String("/"); + QString temp = d->currentAlbumName + QLatin1Char('/'); bool res = d->talker->addPhoto(imgPath, temp, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); if (!res) { slotAddPhotoFailed(QLatin1String("")); return; } } void DBWindow::slotAddPhotoFailed(const QString& msg) { if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload photo to Dropbox." "\n%1\n" "Do you want to continue?", msg)) != QMessageBox::Yes) { d->transferQueue.clear(); d->widget->progressBar()->hide(); } else { d->transferQueue.removeFirst(); d->imagesTotal--; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } } void DBWindow::slotAddPhotoSucceeded() { // Remove photo uploaded from the list d->widget->imagesList()->removeItemByUrl(d->transferQueue.first()); d->transferQueue.removeFirst(); d->imagesCount++; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } void DBWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->imagesList()->imageUrls().isEmpty())); } void DBWindow::slotNewAlbumRequest() { if (d->albumDlg->exec() == QDialog::Accepted) { DBFolder newFolder; d->albumDlg->getFolderTitle(newFolder); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotNewAlbumRequest:" << newFolder.title; d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); QString temp = d->currentAlbumName + newFolder.title; d->talker->createFolder(temp); } } void DBWindow::slotReloadAlbumsRequest() { d->talker->listFolders(); } void DBWindow::slotSignalLinkingFailed() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); } } void DBWindow::slotSignalLinkingSucceeded() { d->talker->listFolders(); } void DBWindow::slotListAlbumsFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderSucceeded() { d->talker->listFolders(); } void DBWindow::slotTransferCancel() { d->transferQueue.clear(); d->widget->progressBar()->hide(); d->talker->cancel(); } void DBWindow::slotUserChangeRequest() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); d->talker->unLink(); d->talker->link(); } void DBWindow::buttonStateChange(bool state) { d->widget->getNewAlbmBtn()->setEnabled(state); d->widget->getReloadBtn()->setEnabled(state); startButton()->setEnabled(state); } void DBWindow::slotFinished() { writeSettings(); d->widget->imagesList()->listView()->clear(); } void DBWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/flickr/flickrwindow.cpp b/core/utilities/assistants/webservices/flickr/flickrwindow.cpp index 9578531ca2..180b666dcd 100644 --- a/core/utilities/assistants/webservices/flickr/flickrwindow.cpp +++ b/core/utilities/assistants/webservices/flickr/flickrwindow.cpp @@ -1,869 +1,875 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-17-06 * Description : a tool to export images to Flickr web service * * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2009 by Luka Renko * * 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, 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. * * ============================================================ */ #include "flickrwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dprogresswdg.h" #include "flickrtalker.h" #include "flickritem.h" #include "flickrlist.h" #include "wsselectuserdlg.h" #include "digikam_debug.h" #include "flickrnewalbumdlg.h" #include "previewloadthread.h" #include "flickrwidget_p.h" namespace Digikam { class FlickrWindow::Private { public: explicit Private() { uploadCount = 0; uploadTotal = 0; newAlbumBtn = 0; changeUserButton = 0; removeAccount = 0; albumsListComboBox = 0; publicCheckBox = 0; familyCheckBox = 0; friendsCheckBox = 0; exportHostTagsCheckBox = 0; stripSpaceTagsCheckBox = 0; addExtraTagsCheckBox = 0; originalCheckBox = 0; resizeCheckBox = 0; dimensionSpinBox = 0; imageQualitySpinBox = 0; extendedPublicationButton = 0; extendedTagsButton = 0; contentTypeComboBox = 0; safetyLevelComboBox = 0; userNameDisplayLabel = 0; authProgressDlg = 0; tagsLineEdit = 0; widget = 0; talker = 0; imglst = 0; select = 0; albumDlg = 0; iface = 0; } unsigned int uploadCount; unsigned int uploadTotal; QString serviceName; QPushButton* newAlbumBtn; QPushButton* changeUserButton; QPushButton* removeAccount; QComboBox* albumsListComboBox; QCheckBox* publicCheckBox; QCheckBox* familyCheckBox; QCheckBox* friendsCheckBox; QCheckBox* exportHostTagsCheckBox; QCheckBox* stripSpaceTagsCheckBox; QCheckBox* addExtraTagsCheckBox; QCheckBox* originalCheckBox; QCheckBox* resizeCheckBox; QSpinBox* dimensionSpinBox; QSpinBox* imageQualitySpinBox; QPushButton* extendedPublicationButton; QPushButton* extendedTagsButton; WSComboBoxIntermediate* contentTypeComboBox; WSComboBoxIntermediate* safetyLevelComboBox; QString username; QString userId; QString lastSelectedAlbum; QLabel* userNameDisplayLabel; QProgressDialog* authProgressDlg; QList< QPair > uploadQueue; QLineEdit* tagsLineEdit; FlickrWidget* widget; FlickrTalker* talker; FlickrList* imglst; WSSelectUserDlg* select; FlickrNewAlbumDlg* albumDlg; DInfoInterface* iface; }; FlickrWindow::FlickrWindow(DInfoInterface* const iface, QWidget* const /*parent*/, const QString& serviceName) : WSToolDialog(0), d(new Private) { d->iface = iface; d->serviceName = serviceName; setWindowTitle(i18n("Export to %1 Web Service", d->serviceName)); setModal(false); KConfig config; KConfigGroup grp = config.group(QString::fromLatin1("%1Export Settings").arg(d->serviceName)); if (grp.exists()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString::fromLatin1("%1Export Settings").arg(d->serviceName) << " exists, deleting it"; grp.deleteGroup(); } d->select = new WSSelectUserDlg(0, serviceName); d->uploadCount = 0; d->uploadTotal = 0; d->widget = new FlickrWidget(this, iface, serviceName); d->albumDlg = new FlickrNewAlbumDlg(this, QString::fromLatin1("Flickr")); d->albumsListComboBox = d->widget->getAlbumsCoB(); d->newAlbumBtn = d->widget->getNewAlbmBtn(); d->originalCheckBox = d->widget->getOriginalCheckBox(); d->resizeCheckBox = d->widget->getResizeCheckBox(); d->publicCheckBox = d->widget->d->publicCheckBox; d->familyCheckBox = d->widget->d->familyCheckBox; d->friendsCheckBox = d->widget->d->friendsCheckBox; d->dimensionSpinBox = d->widget->getDimensionSpB(); d->imageQualitySpinBox = d->widget->getImgQualitySpB(); d->extendedTagsButton = d->widget->d->extendedTagsButton; d->addExtraTagsCheckBox = d->widget->d->addExtraTagsCheckBox; d->extendedPublicationButton = d->widget->d->extendedPublicationButton; d->safetyLevelComboBox = d->widget->d->safetyLevelComboBox; d->contentTypeComboBox = d->widget->d->contentTypeComboBox; d->tagsLineEdit = d->widget->d->tagsLineEdit; d->exportHostTagsCheckBox = d->widget->d->exportHostTagsCheckBox; d->stripSpaceTagsCheckBox = d->widget->d->stripSpaceTagsCheckBox; d->changeUserButton = d->widget->getChangeUserBtn(); d->removeAccount = d->widget->d->removeAccount; d->userNameDisplayLabel = d->widget->getUserNameLabel(); d->imglst = d->widget->d->imglst; startButton()->setText(i18n("Start Uploading")); startButton()->setToolTip(QString()); setMainWidget(d->widget); d->widget->setMinimumSize(800, 600); connect(d->imglst, SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); // -------------------------------------------------------------------------- d->talker = new FlickrTalker(this, serviceName, d->iface); connect(d->talker, SIGNAL(signalError(QString)), d->talker, SLOT(slotError(QString))); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSetSucceeded()), this, SLOT(slotAddPhotoSetSucceeded())); connect(d->talker, SIGNAL(signalListPhotoSetsSucceeded()), this, SLOT(slotPopulatePhotoSetComboBox())); connect(d->talker, SIGNAL(signalListPhotoSetsFailed(QString)), this, SLOT(slotListPhotoSetsFailed(QString))); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->widget->progressBar(), SIGNAL(signalProgressCanceled()), this, SLOT(slotAddPhotoCancelAndClose())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadPhotoSetRequest())); // -------------------------------------------------------------------------- connect(d->changeUserButton, SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->removeAccount, SIGNAL(clicked()), this, SLOT(slotRemoveAccount())); connect(d->newAlbumBtn, SIGNAL(clicked()), this, SLOT(slotCreateNewPhotoSet())); // -------------------------------------------------------------------------- d->authProgressDlg = new QProgressDialog(this); d->authProgressDlg->setModal(true); d->authProgressDlg->setAutoReset(true); d->authProgressDlg->setAutoClose(true); d->authProgressDlg->setMaximum(0); d->authProgressDlg->reset(); connect(d->authProgressDlg, SIGNAL(canceled()), this, SLOT(slotAuthCancel())); d->talker->m_authProgressDlg = d->authProgressDlg; // -------------------------------------------------------------------------- connect(this, &QDialog::finished, this, &FlickrWindow::slotFinished); connect(this, SIGNAL(cancelClicked()), this, SLOT(slotCancelClicked())); connect(startButton(), &QPushButton::clicked, this, &FlickrWindow::slotUser1); d->select->reactivate(); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); } FlickrWindow::~FlickrWindow() { delete d->select; delete d->authProgressDlg; delete d->talker; delete d->widget; delete d; } void FlickrWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void FlickrWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } void FlickrWindow::slotFinished() { writeSettings(); d->imglst->listView()->clear(); } void FlickrWindow::setUiInProgressState(bool inProgress) { setRejectButtonMode(inProgress ? QDialogButtonBox::Cancel : QDialogButtonBox::Close); if (inProgress) { d->widget->progressBar()->show(); } else { d->widget->progressBar()->hide(); d->widget->progressBar()->progressCompleted(); } } void FlickrWindow::slotCancelClicked() { d->talker->cancel(); d->uploadQueue.clear(); setUiInProgressState(false); } void FlickrWindow::slotAddPhotoCancelAndClose() { writeSettings(); d->imglst->listView()->clear(); d->uploadQueue.clear(); d->widget->progressBar()->reset(); setUiInProgressState(false); d->talker->cancel(); reject(); } void FlickrWindow::reactivate() { d->userNameDisplayLabel->setText(QString()); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); d->widget->d->imglst->loadImagesFromCurrentSelection(); show(); } void FlickrWindow::readSettings(QString uname) { KConfig config; - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is : "<serviceName, uname); - KConfigGroup grp = config.group(QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, uname)); - d->exportHostTagsCheckBox->setChecked(grp.readEntry("Export Host Tags", false)); - d->extendedTagsButton->setChecked(grp.readEntry("Show Extended Tag Options", false)); - d->addExtraTagsCheckBox->setChecked(grp.readEntry("Add Extra Tags", false)); - d->stripSpaceTagsCheckBox->setChecked(grp.readEntry("Strip Space From Tags", false)); - d->stripSpaceTagsCheckBox->setEnabled(d->exportHostTagsCheckBox->isChecked()); - d->exportHostTagsCheckBox->setEnabled(false); - d->stripSpaceTagsCheckBox->setEnabled(false); + QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, uname); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is:" << groupName; + KConfigGroup grp = config.group(groupName); + + d->exportHostTagsCheckBox->setChecked(grp.readEntry("Export Host Tags", false)); + d->extendedTagsButton->setChecked(grp.readEntry("Show Extended Tag Options", false)); + d->addExtraTagsCheckBox->setChecked(grp.readEntry("Add Extra Tags", false)); + d->stripSpaceTagsCheckBox->setChecked(grp.readEntry("Strip Space From Tags", false)); d->publicCheckBox->setChecked(grp.readEntry("Public Sharing", false)); d->familyCheckBox->setChecked(grp.readEntry("Family Sharing", false)); d->friendsCheckBox->setChecked(grp.readEntry("Friends Sharing", false)); d->extendedPublicationButton->setChecked(grp.readEntry("Show Extended Publication Options", false)); int safetyLevel = d->safetyLevelComboBox->findData(QVariant(grp.readEntry("Safety Level", 0))); if (safetyLevel == -1) { safetyLevel = 0; } d->safetyLevelComboBox->setCurrentIndex(safetyLevel); int contentType = d->contentTypeComboBox->findData(QVariant(grp.readEntry("Content Type", 0))); if (contentType == -1) { contentType = 0; } d->contentTypeComboBox->setCurrentIndex(contentType); d->originalCheckBox->setChecked(grp.readEntry("Upload Original", false)); d->resizeCheckBox->setChecked(grp.readEntry("Resize", false)); d->dimensionSpinBox->setValue(grp.readEntry("Maximum Width", 1600)); d->imageQualitySpinBox->setValue(grp.readEntry("Image Quality", 85)); winId(); KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void FlickrWindow::writeSettings() { KConfig config; - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is : "<serviceName,d->username); + QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is:" << groupName; - if (QString::compare(QString::fromLatin1("%1Export Settings").arg(d->serviceName), - QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username), Qt::CaseInsensitive) == 0) + if (QString::compare(QString::fromLatin1("%1Export Settings").arg(d->serviceName), groupName) == 0) { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Not writing entry of group " << QString::fromLatin1("%1%2Export Settings").arg(d->serviceName,d->username); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Not writing entry of group" << groupName; return; } - KConfigGroup grp = config.group(QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username)); + KConfigGroup grp = config.group(groupName); + grp.writeEntry("username", d->username); grp.writeEntry("Export Host Tags", d->exportHostTagsCheckBox->isChecked()); grp.writeEntry("Show Extended Tag Options", d->extendedTagsButton->isChecked()); grp.writeEntry("Add Extra Tags", d->addExtraTagsCheckBox->isChecked()); grp.writeEntry("Strip Space From Tags", d->stripSpaceTagsCheckBox->isChecked()); grp.writeEntry("Public Sharing", d->publicCheckBox->isChecked()); grp.writeEntry("Family Sharing", d->familyCheckBox->isChecked()); grp.writeEntry("Friends Sharing", d->friendsCheckBox->isChecked()); grp.writeEntry("Show Extended Publication Options", d->extendedPublicationButton->isChecked()); int safetyLevel = d->safetyLevelComboBox->itemData(d->safetyLevelComboBox->currentIndex()).toInt(); grp.writeEntry("Safety Level", safetyLevel); int contentType = d->contentTypeComboBox->itemData(d->contentTypeComboBox->currentIndex()).toInt(); grp.writeEntry("Content Type", contentType); grp.writeEntry("Resize", d->resizeCheckBox->isChecked()); grp.writeEntry("Upload Original", d->originalCheckBox->isChecked()); grp.writeEntry("Maximum Width", d->dimensionSpinBox->value()); grp.writeEntry("Image Quality", d->imageQualitySpinBox->value()); KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void FlickrWindow::slotLinkingSucceeded() { d->username = d->talker->getUserName(); d->userId = d->talker->getUserId(); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotLinkingSucceeded invoked setting user Display name to " << d->username; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotLinkingSucceeded invoked setting user Display name to" << d->username; d->userNameDisplayLabel->setText(QString::fromLatin1("%1").arg(d->username)); KConfig config; foreach(const QString& group, config.groupList()) { if (!(group.contains(d->serviceName))) continue; KConfigGroup grp = config.group(group); if (group.contains(d->username)) { readSettings(d->username); break; } } writeSettings(); d->talker->listPhotoSets(); } void FlickrWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); } else { setCursor(Qt::ArrowCursor); } } void FlickrWindow::slotError(const QString& msg) { QMessageBox::critical(this, i18n("Error"), msg); } void FlickrWindow::slotUserChangeRequest() { writeSettings(); d->userNameDisplayLabel->setText(QString()); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request "; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request"; d->select->reactivate(); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); } void FlickrWindow::slotRemoveAccount() { KConfig config; - KConfigGroup grp = config.group(QString::fromLatin1("%1%2Export Settings").arg(d->serviceName).arg(d->username)); + QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username); + KConfigGroup grp = config.group(groupName); if (grp.exists()) { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Removing Account having group"<serviceName); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Removing Account having group" << groupName; grp.deleteGroup(); } d->talker->unLink(); d->talker->removeUserName(d->serviceName + d->username); d->userNameDisplayLabel->setText(QString()); d->username = QString(); } /** * Try to guess a sensible set name from the urls given. * Currently, it extracs the last path name component, and returns the most * frequently seen. The function could be expanded to, for example, only * accept the path if it occurs at least 50% of the time. It could also look * further up in the path name. */ QString FlickrWindow::guessSensibleSetName(const QList& urlList) const { - QMap nrFolderOccurences; + QMap nrFolderOccurences; // Extract last component of directory foreach(const QUrl& url, urlList) { QString dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); QStringList list = dir.split(QLatin1Char('/')); if (list.isEmpty()) continue; nrFolderOccurences[list.last()]++; } int maxCount = 0; int totalCount = 0; QString name; - for (QMap::const_iterator it = nrFolderOccurences.constBegin(); + for (QMap::const_iterator it = nrFolderOccurences.constBegin(); it != nrFolderOccurences.constEnd() ; ++it) { totalCount += it.value(); if (it.value() > maxCount) { maxCount = it.value(); name = it.key(); } } // If there is only one entry or one name appears at least twice, return the suggestion if (totalCount == 1 || maxCount > 1) return name; return QString(); } /** This method is called when the photo set creation button is pressed. It * summons a creation dialog for user input. When that is closed, it * creates a new photo set in the local list. The id gets the form of * UNDEFINED_ followed by a number, to indicate that it doesn't exist on * Flickr yet. */ void FlickrWindow::slotCreateNewPhotoSet() { if (d->albumDlg->exec() == QDialog::Accepted) { FPhotoSet fps; d->albumDlg->getFolderProperties(fps); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in slotCreateNewPhotoSet() " << fps.title; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in slotCreateNewPhotoSet()" << fps.title; // Lets find an UNDEFINED_ style id that isn't taken yet.s QString id; int i = 0; id = QString::fromLatin1("UNDEFINED_") + QString::number(i); QLinkedList::iterator it = d->talker->m_photoSetsList->begin(); while (it != d->talker->m_photoSetsList->end()) { FPhotoSet fps = *it; if (fps.id == id) { id = QString::fromLatin1("UNDEFINED_") + QString::number(++i); it = d->talker->m_photoSetsList->begin(); } ++it; } fps.id = id; - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Created new photoset with temporary id " << id; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Created new photoset with temporary id" << id; // Append the new photoset to the list. d->talker->m_photoSetsList->prepend(fps); d->talker->m_selectedPhotoSet = fps; // Re-populate the photo sets combo box. slotPopulatePhotoSetComboBox(); } else { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "New Photoset creation aborted "; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "New Photoset creation aborted"; } } void FlickrWindow::slotAuthCancel() { d->talker->cancel(); d->authProgressDlg->hide(); } void FlickrWindow::slotPopulatePhotoSetComboBox() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotPopulatePhotoSetComboBox invoked"; if (d->talker && d->talker->m_photoSetsList) { QLinkedList * const list = d->talker->m_photoSetsList; d->albumsListComboBox->clear(); d->albumsListComboBox->insertItem(0, i18n("Photostream Only")); d->albumsListComboBox->insertSeparator(1); QLinkedList::iterator it = list->begin(); int index = 2; int curr_index = 0; while (it != list->end()) { FPhotoSet photoSet = *it; QString name = photoSet.title; // Store the id as user data, because the title is not unique. QVariant id = QVariant(photoSet.id); if (id == d->talker->m_selectedPhotoSet.id) { curr_index = index; } d->albumsListComboBox->insertItem(index++, name, id); ++it; } d->albumsListComboBox->setCurrentIndex(curr_index); } } /** This slot is call when 'Start Uploading' button is pressed. */ void FlickrWindow::slotUser1() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotUploadImages invoked"; //d->widget->d->tab->setCurrentIndex(FlickrWidget::FILELIST); if (d->imglst->imageUrls().isEmpty()) { return; } typedef QPair Pair; d->uploadQueue.clear(); - for (int i = 0; i < d->imglst->listView()->topLevelItemCount(); ++i) + for (int i = 0 ; i < d->imglst->listView()->topLevelItemCount() ; ++i) { FlickrListViewItem* const lvItem = dynamic_cast(d->imglst->listView()->topLevelItem(i)); if (lvItem) { DItemInfo info(d->iface->itemInfo(lvItem->url())); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Adding images"<< lvItem->url() << " to the list"; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Adding images" << lvItem->url() << " to the list"; FPhotoInfo temp; temp.title = info.title(); temp.description = info.comment(); temp.size = info.fileSize(); temp.is_public = lvItem->isPublic() ? 1 : 0; temp.is_family = lvItem->isFamily() ? 1 : 0; temp.is_friend = lvItem->isFriends() ? 1 : 0; temp.safety_level = lvItem->safetyLevel(); temp.content_type = lvItem->contentType(); QStringList tagsFromDialog = d->tagsLineEdit->text().split(QLatin1Char(','), QString::SkipEmptyParts); QStringList tagsFromList = lvItem->extraTags(); QStringList allTags; QStringList::Iterator itTags; // Tags from the dialog itTags = tagsFromDialog.begin(); while (itTags != tagsFromDialog.end()) { allTags.append(*itTags); ++itTags; } // Tags from the database if (d->exportHostTagsCheckBox->isChecked()) { QStringList tagsFromDatabase; tagsFromDatabase = info.keywords(); itTags = tagsFromDatabase.begin(); while (itTags != tagsFromDatabase.end()) { allTags.append(*itTags); ++itTags; } } // Tags from the list view. itTags = tagsFromList.begin(); while (itTags != tagsFromList.end()) { allTags.append(*itTags); ++itTags; } // Remove spaces if the user doesn't like them. if (d->stripSpaceTagsCheckBox->isChecked()) { for (QStringList::iterator it = allTags.begin(); it != allTags.end(); ++it) { *it = (*it).trimmed().remove(QLatin1Char(' ')); } } // Debug the tag list. itTags = allTags.begin(); while (itTags != allTags.end()) { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags list: " << (*itTags); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags list:" << (*itTags); ++itTags; } temp.tags = allTags; d->uploadQueue.append(Pair(lvItem->url(), temp)); } } d->uploadTotal = d->uploadQueue.count(); d->uploadCount = 0; d->widget->progressBar()->reset(); slotAddPhotoNext(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotUploadImages done"; } void FlickrWindow::slotAddPhotoNext() { if (d->uploadQueue.isEmpty()) { d->widget->progressBar()->reset(); setUiInProgressState(false); return; } typedef QPair Pair; Pair pathComments = d->uploadQueue.first(); FPhotoInfo info = pathComments.second; QString selectedPhotoSetId = d->albumsListComboBox->itemData(d->albumsListComboBox->currentIndex()).toString(); if (selectedPhotoSetId.isEmpty()) { d->talker->m_selectedPhotoSet = FPhotoSet(); } else { QLinkedList::iterator it = d->talker->m_photoSetsList->begin(); while (it != d->talker->m_photoSetsList->end()) { if (it->id == selectedPhotoSetId) { d->talker->m_selectedPhotoSet = *it; break; } ++it; } } - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Max allowed file size is : "<<((d->talker->getMaxAllowedFileSize()).toLongLong())<<"File Size is "<talker->getMaxAllowedFileSize().toLongLong() + << "File Size is" << info.size; bool res = d->talker->addPhoto(pathComments.first.toLocalFile(), //the file path - info, - d->originalCheckBox->isChecked(), - d->resizeCheckBox->isChecked(), - d->dimensionSpinBox->value(), - d->imageQualitySpinBox->value()); + info, + d->originalCheckBox->isChecked(), + d->resizeCheckBox->isChecked(), + d->dimensionSpinBox->value(), + d->imageQualitySpinBox->value()); if (!res) { slotAddPhotoFailed(QString::fromLatin1("")); return; } if (d->widget->progressBar()->isHidden()) { setUiInProgressState(true); d->widget->progressBar()->progressScheduled(i18n("Flickr Export"), true, true); d->widget->progressBar()->progressThumbnailChanged(QIcon(QLatin1String("flickr")).pixmap(22, 22)); } } void FlickrWindow::slotAddPhotoSucceeded() { // Remove photo uploaded from the list d->imglst->removeItemByUrl(d->uploadQueue.first().first); d->uploadQueue.removeFirst(); d->uploadCount++; d->widget->progressBar()->setMaximum(d->uploadTotal); d->widget->progressBar()->setValue(d->uploadCount); slotAddPhotoNext(); } void FlickrWindow::slotListPhotoSetsFailed(const QString& msg) { - QMessageBox::critical(this, QString::fromLatin1("Error"), i18n("Failed to Fetch Photoset information from %1. %2\n", d->serviceName, msg)); + QMessageBox::critical(this, QString::fromLatin1("Error"), + i18n("Failed to Fetch Photoset information from %1. %2\n", + d->serviceName, msg)); } void FlickrWindow::slotAddPhotoFailed(const QString& msg) { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), - i18n("Failed to upload photo into %1. %2\nDo you want to continue?", d->serviceName, msg), + i18n("Failed to upload photo into %1. %2\nDo you want to continue?", + d->serviceName, msg), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() != QMessageBox::Yes) { d->uploadQueue.clear(); d->widget->progressBar()->reset(); setUiInProgressState(false); } else { d->uploadQueue.removeFirst(); d->uploadTotal--; d->widget->progressBar()->setMaximum(d->uploadTotal); d->widget->progressBar()->setValue(d->uploadCount); slotAddPhotoNext(); } delete warn; } /* Method called when a photo set has been successfully created on Flickr. * It functions to restart the normal flow after a photo set has been created * on Flickr. */ void FlickrWindow::slotAddPhotoSetSucceeded() { slotPopulatePhotoSetComboBox(); slotAddPhotoSucceeded(); } void FlickrWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->d->imglst->imageUrls().isEmpty())); } void FlickrWindow::slotReloadPhotoSetRequest() { d->talker->listPhotoSets(); } } // namespace Digikam diff --git a/core/utilities/fuzzysearch/findduplicatesview.cpp b/core/utilities/fuzzysearch/findduplicatesview.cpp index d8dc571d5a..f8ba866787 100644 --- a/core/utilities/fuzzysearch/findduplicatesview.cpp +++ b/core/utilities/fuzzysearch/findduplicatesview.cpp @@ -1,555 +1,555 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-05-19 * Description : Find Duplicates View. * * Copyright (C) 2016-2017 by Mario Frank * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2008-2012 by Marcel Wiesweg * Copyright (C) 2009-2012 by Andi Clemens * * 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, 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. * * ============================================================ */ #include "findduplicatesview.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "albummanager.h" #include "albumselectors.h" #include "coredbaccess.h" #include "coredbbackend.h" #include "findduplicatesalbum.h" #include "findduplicatesalbumitem.h" #include "duplicatesfinder.h" #include "fingerprintsgenerator.h" #include "applicationsettings.h" #include "haariface.h" #include "squeezedcombobox.h" #include "drangebox.h" namespace Digikam { class FindDuplicatesView::Private { public: explicit Private() { includeAlbumsLabel = 0; listView = 0; scanDuplicatesBtn = 0; updateFingerPrtBtn = 0; progressItem = 0; similarityLabel = 0; similarityRange = 0; restrictResultsLabel = 0; albumTagRelationLabel = 0; searchResultRestriction = 0; albumTagRelation = 0; albumSelectors = 0; settings = 0; active = false; } QLabel* includeAlbumsLabel; QLabel* similarityLabel; QLabel* restrictResultsLabel; QLabel* albumTagRelationLabel; DIntRangeBox* similarityRange; SqueezedComboBox* searchResultRestriction; SqueezedComboBox* albumTagRelation; QPushButton* scanDuplicatesBtn; QPushButton* updateFingerPrtBtn; FindDuplicatesAlbum* listView; ProgressItem* progressItem; AlbumSelectors* albumSelectors; ApplicationSettings* settings; bool active; }; FindDuplicatesView::FindDuplicatesView(QWidget* const parent) : QWidget(parent), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); d->settings = ApplicationSettings::instance(); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); // --------------------------------------------------------------- d->listView = new FindDuplicatesAlbum(); d->listView->setSortingEnabled(false); d->updateFingerPrtBtn = new QPushButton(i18n("Update fingerprints")); d->updateFingerPrtBtn->setIcon(QIcon::fromTheme(QLatin1String("run-build"))); d->updateFingerPrtBtn->setWhatsThis(i18n("Use this button to update all image fingerprints.")); d->scanDuplicatesBtn = new QPushButton(i18n("Find duplicates")); d->scanDuplicatesBtn->setIcon(QIcon::fromTheme(QLatin1String("edit-find"))); d->scanDuplicatesBtn->setWhatsThis(i18n("Use this button to scan the selected albums for " "duplicate items.")); // --------------------------------------------------------------- d->albumSelectors = new AlbumSelectors(i18nc("@label", "Search in:"), QLatin1String("Find Duplicates View")); // --------------------------------------------------------------- d->similarityRange = new DIntRangeBox(); d->similarityRange->setSuffix(QLatin1String("%")); if (d->settings) { d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(), 100); d->similarityRange->setInterval(d->settings->getDuplicatesSearchLastMinSimilarity(), d->settings->getDuplicatesSearchLastMaxSimilarity()); } else { d->similarityRange->setRange(40, 100); d->similarityRange->setInterval(40, 100); } d->similarityLabel = new QLabel(i18n("Similarity range:")); d->similarityLabel->setBuddy(d->similarityRange); d->restrictResultsLabel = new QLabel(i18n("Restriction:")); d->restrictResultsLabel->setBuddy(d->searchResultRestriction); d->searchResultRestriction = new SqueezedComboBox(); d->searchResultRestriction->addSqueezedItem(i18nc("@label:listbox", "None"), HaarIface::DuplicatesSearchRestrictions::None); d->searchResultRestriction->addSqueezedItem(i18nc("@label:listbox", "Restrict to reference album"), HaarIface::DuplicatesSearchRestrictions::SameAlbum); d->searchResultRestriction->addSqueezedItem(i18nc("@label:listbox", "Exclude reference album"), HaarIface::DuplicatesSearchRestrictions::DifferentAlbum); d->searchResultRestriction->setToolTip(i18n("Use this option to restrict the duplicate search " "with some criteria, as to limit search to the album " "of reference image, or to exclude the album of " "reference image of the search.")); // Load the last choice from application settings. HaarIface::DuplicatesSearchRestrictions restrictions = d->settings ? (HaarIface::DuplicatesSearchRestrictions) d->settings->getDuplicatesSearchRestrictions() : HaarIface::DuplicatesSearchRestrictions::None; d->searchResultRestriction->setCurrentIndex(d->searchResultRestriction->findData(restrictions)); d->albumTagRelationLabel = new QLabel(i18n("Restrict to:")); d->albumTagRelationLabel->setBuddy(d->albumTagRelation); d->albumTagRelation = new SqueezedComboBox(); d->albumTagRelation->addSqueezedItem(i18nc("@label:listbox", "Only selected tab") , HaarIface::AlbumTagRelation::NoMix); d->albumTagRelation->addSqueezedItem(i18nc("@label:listbox", "One of"), HaarIface::AlbumTagRelation::Union); d->albumTagRelation->addSqueezedItem(i18nc("@label:listbox", "Both"), HaarIface::AlbumTagRelation::Intersection); d->albumTagRelation->addSqueezedItem(i18nc("@label:listbox", "Albums but not tags"), HaarIface::AlbumTagRelation::AlbumExclusive); d->albumTagRelation->addSqueezedItem(i18nc("@label:listbox", "Tags but not albums"), HaarIface::AlbumTagRelation::TagExclusive); d->albumTagRelation->setCurrentIndex(ApplicationSettings::instance()->getDuplicatesAlbumTagRelation()); d->albumTagRelation->setToolTip(i18n("Use this option to decide about the relation of the selected albums and tags.
" "One of means that the images are either in the selected albums or tags.
" "Both means that the images are both in the selected albums and tags.
" "Albums but not tags means that images must be in the selected albums but not tags.
" "Tags but not albums means that images must be in the selected tags but not albums.
" "Only selected tab means that only the selected tab is used.")); // Load the last choice from application settings. HaarIface::AlbumTagRelation relation = d->settings ? (HaarIface::AlbumTagRelation) d->settings->getDuplicatesAlbumTagRelation() : HaarIface::AlbumTagRelation::NoMix; d->albumTagRelation->setCurrentIndex(d->albumTagRelation->findData(relation)); // --------------------------------------------------------------- QGridLayout* const mainLayout = new QGridLayout(); mainLayout->addWidget(d->listView, 0, 0, 1, -1); mainLayout->addWidget(d->albumTagRelationLabel, 1, 0, 1, 2); mainLayout->addWidget(d->albumTagRelation, 1, 2, 1, -1); mainLayout->addWidget(d->albumSelectors, 2, 0, 1, -1); mainLayout->addWidget(d->similarityLabel, 3, 0, 1, 1); mainLayout->addWidget(d->similarityRange, 3, 2, 1, 1); mainLayout->addWidget(d->restrictResultsLabel, 4, 0, 1, 2); mainLayout->addWidget(d->searchResultRestriction, 4, 2, 1, -1); mainLayout->addWidget(d->updateFingerPrtBtn, 5, 0, 1, -1); mainLayout->addWidget(d->scanDuplicatesBtn, 6, 0, 1, -1); mainLayout->setRowStretch(0, 10); mainLayout->setColumnStretch(2, 10); mainLayout->setContentsMargins(spacing, spacing, spacing, spacing); mainLayout->setSpacing(spacing); setLayout(mainLayout); // --------------------------------------------------------------- connect(d->updateFingerPrtBtn, SIGNAL(clicked()), this, SLOT(slotUpdateFingerPrints())); connect(d->scanDuplicatesBtn, SIGNAL(clicked()), this, SLOT(slotFindDuplicates())); connect(d->listView, SIGNAL(itemSelectionChanged()), this, SLOT(slotDuplicatesAlbumActived())); connect(d->albumSelectors, SIGNAL(signalSelectionChanged()), this, SLOT(slotCheckForValidSettings())); connect(AlbumManager::instance(), SIGNAL(signalAllAlbumsLoaded()), this, SLOT(populateTreeView())); connect(AlbumManager::instance(), SIGNAL(signalAllAlbumsLoaded()), this, SLOT(initAlbumUpdateConnections())); connect(d->settings, SIGNAL(setupChanged()), this, SLOT(slotApplicationSettingsChanged())); } FindDuplicatesView::~FindDuplicatesView() { d->albumSelectors->saveState(); delete d; } void FindDuplicatesView::initAlbumUpdateConnections() { connect(AlbumManager::instance(), SIGNAL(signalAlbumAdded(Album*)), this, SLOT(slotAlbumAdded(Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotAlbumDeleted(Album*))); connect(AlbumManager::instance(), SIGNAL(signalSearchUpdated(SAlbum*)), this, SLOT(slotSearchUpdated(SAlbum*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumsCleared()), this, SLOT(slotClear())); connect(AlbumManager::instance(),SIGNAL(signalUpdateDuplicatesAlbums(QList,QList)), this,SLOT(slotUpdateDuplicates(QList,QList))); } void FindDuplicatesView::setActive(bool val) { d->active = val; } void FindDuplicatesView::populateTreeView() { const AlbumList& aList = AlbumManager::instance()->allSAlbums(); for (AlbumList::const_iterator it = aList.constBegin() ; it != aList.constEnd() ; ++it) { SAlbum* const salbum = dynamic_cast(*it); if (salbum && salbum->isDuplicatesSearch() && !salbum->extraData(this)) { FindDuplicatesAlbumItem* const item = new FindDuplicatesAlbumItem(d->listView, salbum); salbum->setExtraData(this, item); } } d->listView->setSortingEnabled(true); d->listView->sortByColumn(1, Qt::DescendingOrder); d->listView->resizeColumnToContents(0); d->albumSelectors->loadState(); d->listView->selectFirstItem(); } QList FindDuplicatesView::currentFindDuplicatesAlbums() const { QList selectedItems = d->listView->selectedItems(); if (selectedItems.isEmpty()) { QTreeWidgetItem* const item = d->listView->firstItem(); if (item) { selectedItems << item; } } QList albumList; foreach(QTreeWidgetItem* const item, selectedItems) { FindDuplicatesAlbumItem* const albumItem = dynamic_cast(item); if (albumItem) { albumList << albumItem->album(); } } return albumList; } void FindDuplicatesView::slotAlbumAdded(Album* a) { if (!a || a->type() != Album::SEARCH) { return; } SAlbum* const salbum = static_cast(a); if (!salbum->isDuplicatesSearch()) { return; } if (!d->active) { qCDebug(DIGIKAM_GENERAL_LOG) << "Duplicates view is not active, returning"; return; } if (!salbum->extraData(this)) { FindDuplicatesAlbumItem* const item = new FindDuplicatesAlbumItem(d->listView, salbum); salbum->setExtraData(this, item); } d->similarityRange->setInterval(d->settings->getDuplicatesSearchLastMinSimilarity(), d->settings->getDuplicatesSearchLastMaxSimilarity()); } void FindDuplicatesView::slotAlbumDeleted(Album* a) { if (!a || a->type() != Album::SEARCH) { return; } SAlbum* const album = static_cast(a); FindDuplicatesAlbumItem* const item = static_cast(album->extraData(this)); if (item) { a->removeExtraData(this); delete item; } d->similarityRange->setInterval(d->settings->getDuplicatesSearchLastMinSimilarity(), d->settings->getDuplicatesSearchLastMaxSimilarity()); } void FindDuplicatesView::slotSearchUpdated(SAlbum* a) { if (!a->isDuplicatesSearch()) { return; } slotAlbumDeleted(a); slotAlbumAdded(a); } void FindDuplicatesView::slotClear() { for (QTreeWidgetItemIterator it(d->listView) ; *it ; ++it) { SAlbum* const salbum = static_cast(*it)->album(); if (salbum) { salbum->removeExtraData(this); } } d->listView->clear(); } void FindDuplicatesView::enableControlWidgets(bool val) { d->searchResultRestriction->setEnabled(val); d->updateFingerPrtBtn->setEnabled(val); d->scanDuplicatesBtn->setEnabled(val); d->albumTagRelation->setEnabled(val); d->similarityRange->setEnabled(val); d->albumSelectors->setEnabled(val); d->albumTagRelationLabel->setEnabled(val); d->restrictResultsLabel->setEnabled(val); d->similarityLabel->setEnabled(val); } void FindDuplicatesView::slotFindDuplicates() { d->albumSelectors->saveState(); slotClear(); enableControlWidgets(false); AlbumList albums; AlbumList tags; if (d->albumTagRelation->itemData(d->albumTagRelation->currentIndex()).toInt() == HaarIface::AlbumTagRelation::NoMix) { if (d->albumSelectors->typeSelection() == AlbumSelectors::AlbumType::PhysAlbum) { albums = d->albumSelectors->selectedAlbums(); } else if (d->albumSelectors->typeSelection() == AlbumSelectors::AlbumType::TagsAlbum) { tags = d->albumSelectors->selectedTags(); } } else { albums = d->albumSelectors->selectedAlbums(); tags = d->albumSelectors->selectedTags(); } DuplicatesFinder* const finder = new DuplicatesFinder(albums, tags, d->albumTagRelation->itemData(d->albumTagRelation->currentIndex()).toInt(), d->similarityRange->minValue(), d->similarityRange->maxValue(), d->searchResultRestriction->itemData(d->searchResultRestriction->currentIndex()).toInt()); connect(finder, SIGNAL(signalComplete()), this, SLOT(slotComplete())); finder->start(); } void FindDuplicatesView::slotUpdateDuplicates(const QList& sAlbumsToRebuild, const QList& deletedImages) { d->listView->updateDuplicatesAlbumItems(sAlbumsToRebuild, deletedImages); } void FindDuplicatesView::slotApplicationSettingsChanged() { d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(), 100); } void FindDuplicatesView::slotComplete() { enableControlWidgets(true); slotCheckForValidSettings(); populateTreeView(); } void FindDuplicatesView::slotDuplicatesAlbumActived() { QList albums; foreach(QTreeWidgetItem* const item, d->listView->selectedItems()) { FindDuplicatesAlbumItem* const albumItem = dynamic_cast(item); if (albumItem) { albums << albumItem->album(); } } if (!albums.isEmpty()) { AlbumManager::instance()->setCurrentAlbums(QList() << albums); } } void FindDuplicatesView::slotCheckForValidSettings() { bool valid = d->albumSelectors->selectedAlbums().count() || d->albumSelectors->selectedTags().count(); d->scanDuplicatesBtn->setEnabled(valid); } void FindDuplicatesView::slotUpdateFingerPrints() { FingerPrintsGenerator* const tool = new FingerPrintsGenerator(false); tool->start(); } void FindDuplicatesView::slotSetSelectedAlbum(PAlbum* album) { if (!album) { return; } resetAlbumsAndTags(); // @ODD : Why is singleton set to true? resetAlbumsAndTags already clears the selection. d->albumSelectors->setAlbumSelected(album, true); d->albumSelectors->setTypeSelection(AlbumSelectors::AlbumType::PhysAlbum); slotCheckForValidSettings(); } -void FindDuplicatesView::slotSetSelectedAlbums(QList albums) +void FindDuplicatesView::slotSetSelectedAlbums(const QList& albums) { // @ODD : Why is singleton set to true? resetAlbumsAndTags already clears the selection. resetAlbumsAndTags(); foreach(PAlbum* const album, albums) { d->albumSelectors->setAlbumSelected(album, false); } d->albumSelectors->setTypeSelection(AlbumSelectors::AlbumType::PhysAlbum); slotCheckForValidSettings(); } -void FindDuplicatesView::slotSetSelectedAlbums(QList albums) +void FindDuplicatesView::slotSetSelectedAlbums(const QList& albums) { resetAlbumsAndTags(); foreach(TAlbum* const album, albums) { d->albumSelectors->setTagSelected(album, false); } d->albumSelectors->setTypeSelection(AlbumSelectors::AlbumType::TagsAlbum); slotCheckForValidSettings(); } void FindDuplicatesView::resetAlbumsAndTags() { d->albumSelectors->resetSelection(); slotCheckForValidSettings(); } } // namespace Digikam diff --git a/core/utilities/fuzzysearch/findduplicatesview.h b/core/utilities/fuzzysearch/findduplicatesview.h index d89118e8d8..23b6ddfbd7 100644 --- a/core/utilities/fuzzysearch/findduplicatesview.h +++ b/core/utilities/fuzzysearch/findduplicatesview.h @@ -1,95 +1,95 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-05-19 * Description : Find Duplicates View. * * Copyright (C) 2016-2017 by Mario Frank * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2008-2012 by Marcel Wiesweg * Copyright (C) 2009 by Andi Clemens * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_FIND_DUPLICATES_VIEW_H #define DIGIKAM_FIND_DUPLICATES_VIEW_H // Qt includes #include class QTreeWidgetItem; namespace Digikam { class Album; class SAlbum; class PAlbum; class TAlbum; class FindDuplicatesView : public QWidget { Q_OBJECT public: explicit FindDuplicatesView(QWidget* const parent = 0); virtual ~FindDuplicatesView(); QList currentFindDuplicatesAlbums() const; void setActive(bool val); public Q_SLOTS: void slotSetSelectedAlbum(PAlbum* album); - void slotSetSelectedAlbums(QList albums); - void slotSetSelectedAlbums(QList albums); + void slotSetSelectedAlbums(const QList& albums); + void slotSetSelectedAlbums(const QList& albums); private Q_SLOTS: void initAlbumUpdateConnections(); void populateTreeView(); void slotAlbumAdded(Album* a); void slotAlbumDeleted(Album* a); void slotSearchUpdated(SAlbum* a); void slotClear(); void slotFindDuplicates(); void slotUpdateDuplicates(const QList& sAlbumsToRebuild, const QList& deletedImages); void slotDuplicatesAlbumActived(); void slotComplete(); void slotUpdateFingerPrints(); void slotCheckForValidSettings(); void slotApplicationSettingsChanged(); private: void enableControlWidgets(bool); void updateAlbumsBox(); void updateTagsBox(); void resetAlbumsAndTags(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FIND_DUPLICATES_VIEW_H diff --git a/core/utilities/fuzzysearch/fuzzysearchview.cpp b/core/utilities/fuzzysearch/fuzzysearchview.cpp index b82a9020b3..777f45b1b3 100644 --- a/core/utilities/fuzzysearch/fuzzysearchview.cpp +++ b/core/utilities/fuzzysearch/fuzzysearchview.cpp @@ -1,1138 +1,1138 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-05-19 * Description : Fuzzy search sidebar tab contents. * * Copyright (C) 2016-2018 by Mario Frank * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2008-2012 by Marcel Wiesweg * Copyright (C) 2012 by Andi Clemens * * 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, 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. * * ============================================================ */ #include "fuzzysearchview.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dlayoutbox.h" #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "coredbalbuminfo.h" #include "albummanager.h" #include "albummodel.h" #include "albumselectors.h" #include "coredbaccess.h" #include "ddragobjects.h" #include "editablesearchtreeview.h" #include "findduplicatesview.h" #include "haariface.h" #include "imageinfo.h" #include "imagelister.h" #include "searchmodificationhelper.h" #include "searchtextbar.h" #include "coredbsearchxml.h" #include "sketchwidget.h" #include "thumbnailloadthread.h" #include "thumbnailsize.h" #include "dhuesaturationselect.h" #include "dcolorvalueselector.h" #include "dexpanderbox.h" #include "applicationsettings.h" #include "drangebox.h" #include "similaritydbaccess.h" #include "similaritydb.h" namespace Digikam { class FuzzySearchView::Private { public: enum FuzzySearchTab { DUPLICATES = 0, SIMILARS, SKETCH }; public: explicit Private() : // initially be active to update sketch panel when the search list is restored active(false), fingerprintsChecked(false), resetButton(0), saveBtnSketch(0), undoBtnSketch(0), redoBtnSketch(0), saveBtnImage(0), penSize(0), resultsSketch(0), similarityRange(0), imageWidget(0), timerSketch(0), timerImage(0), folderView(0), nameEditSketch(0), nameEditImage(0), tabWidget(0), hsSelector(0), vSelector(0), labelFile(0), labelFolder(0), searchFuzzyBar(0), searchTreeView(0), sketchWidget(0), thumbLoadThread(0), findDuplicatesPanel(0), imageSAlbum(0), sketchSAlbum(0), fuzzySearchAlbumSelectors(0), sketchSearchAlbumSelectors(0), searchModel(0), searchModificationHelper(0), settings(0) { } static const QString configTabEntry; static const QString configPenSketchSizeEntry; static const QString configResultSketchItemsEntry; static const QString configPenSketchHueEntry; static const QString configPenSketchSaturationEntry; static const QString configPenSkethValueEntry; static const QString configSimilarsThresholdEntry; static const QString configSimilarsMaxThresholdEntry; bool active; bool fingerprintsChecked; QColor selColor; QToolButton* resetButton; QToolButton* saveBtnSketch; QToolButton* undoBtnSketch; QToolButton* redoBtnSketch; QToolButton* saveBtnImage; QSpinBox* penSize; QSpinBox* resultsSketch; DIntRangeBox* similarityRange; QLabel* imageWidget; QTimer* timerSketch; QTimer* timerImage; DVBox* folderView; QLineEdit* nameEditSketch; QLineEdit* nameEditImage; QTabWidget* tabWidget; DHueSaturationSelector* hsSelector; DColorValueSelector* vSelector; DAdjustableLabel* labelFile; DAdjustableLabel* labelFolder; ImageInfo imageInfo; QUrl imageUrl; SearchTextBar* searchFuzzyBar; EditableSearchTreeView* searchTreeView; SketchWidget* sketchWidget; ThumbnailLoadThread* thumbLoadThread; FindDuplicatesView* findDuplicatesPanel; AlbumPointer imageSAlbum; AlbumPointer sketchSAlbum; AlbumSelectors* fuzzySearchAlbumSelectors; AlbumSelectors* sketchSearchAlbumSelectors; SearchModel* searchModel; SearchModificationHelper* searchModificationHelper; ApplicationSettings* settings; }; const QString FuzzySearchView::Private::configTabEntry(QLatin1String("FuzzySearch Tab")); const QString FuzzySearchView::Private::configPenSketchSizeEntry(QLatin1String("Pen Sketch Size")); const QString FuzzySearchView::Private::configResultSketchItemsEntry(QLatin1String("Result Sketch items")); const QString FuzzySearchView::Private::configPenSketchHueEntry(QLatin1String("Pen Sketch Hue")); const QString FuzzySearchView::Private::configPenSketchSaturationEntry(QLatin1String("Pen Sketch Saturation")); const QString FuzzySearchView::Private::configPenSkethValueEntry(QLatin1String("Pen Sketch Value")); const QString FuzzySearchView::Private::configSimilarsThresholdEntry(QLatin1String("Similars Threshold")); const QString FuzzySearchView::Private::configSimilarsMaxThresholdEntry(QLatin1String("Similars Maximum Threshold")); // -------------------------------------------------------- FuzzySearchView::FuzzySearchView(SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper, QWidget* const parent) : QScrollArea(parent), StateSavingObject(this), d(new Private) { d->settings = ApplicationSettings::instance(); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->thumbLoadThread = ThumbnailLoadThread::defaultThread(); d->searchModel = searchModel; d->searchModificationHelper = searchModificationHelper; setWidgetResizable(true); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); viewport()->setAcceptDrops(true); // --------------------------------------------------------------- QWidget* const imagePanel = setupFindSimilarPanel(); QWidget* const sketchPanel = setupSketchPanel(); d->findDuplicatesPanel = new FindDuplicatesView(); d->tabWidget = new QTabWidget(); d->tabWidget->insertTab(Private::DUPLICATES, d->findDuplicatesPanel, i18n("Duplicates")); d->tabWidget->insertTab(Private::SIMILARS, imagePanel, i18n("Image")); d->tabWidget->insertTab(Private::SKETCH, sketchPanel, i18n("Sketch")); // --------------------------------------------------------------- d->folderView = new DVBox(); d->searchTreeView = new EditableSearchTreeView(d->folderView, searchModel, searchModificationHelper); d->searchTreeView->filteredModel()->listHaarSearches(); d->searchTreeView->filteredModel()->setListTemporarySearches(true); d->searchTreeView->setAlbumManagerCurrentAlbum(true); d->searchFuzzyBar = new SearchTextBar(d->folderView, QLatin1String("FuzzySearchViewSearchFuzzyBar")); d->searchFuzzyBar->setModel(d->searchTreeView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchFuzzyBar->setFilterModel(d->searchTreeView->albumFilterModel()); d->folderView->setContentsMargins(QMargins()); d->folderView->setSpacing(spacing); // --------------------------------------------------------------- QWidget* const mainWidget = new QWidget(this); QVBoxLayout* const mainLayout = new QVBoxLayout(); mainLayout->addWidget(d->tabWidget); mainLayout->addWidget(d->folderView); mainLayout->setContentsMargins(QMargins()); mainLayout->setSpacing(0); mainWidget->setLayout(mainLayout); setWidget(mainWidget); // --------------------------------------------------------------- d->timerSketch = new QTimer(this); d->timerSketch->setSingleShot(true); d->timerSketch->setInterval(500); d->timerImage = new QTimer(this); d->timerImage->setSingleShot(true); d->timerImage->setInterval(500); // --------------------------------------------------------------- setupConnections(); // --------------------------------------------------------------- slotCheckNameEditSketchConditions(); slotCheckNameEditImageConditions(); } QWidget* FuzzySearchView::setupFindSimilarPanel() const { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); DHBox* const imageBox = new DHBox(); d->imageWidget = new QLabel(imageBox); d->imageWidget->setFixedSize(256, 256); d->imageWidget->setText(i18n("

Drag & drop an image here
to perform similar
items search.

" "

You can also use the context menu
when browsing through your images.

")); d->imageWidget->setAlignment(Qt::AlignCenter); imageBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); imageBox->setLineWidth(1); // --------------------------------------------------------------- QLabel* const file = new QLabel(i18n("File:")); d->labelFile = new DAdjustableLabel(0); QLabel* const folder = new QLabel(i18n("Folder:")); d->labelFolder = new DAdjustableLabel(0); int hgt = fontMetrics().height() - 2; file->setMaximumHeight(hgt); folder->setMaximumHeight(hgt); d->labelFile->setMaximumHeight(hgt); d->labelFolder->setMaximumHeight(hgt); // --------------------------------------------------------------- d->fuzzySearchAlbumSelectors = new AlbumSelectors(i18nc("@label", "Search in albums:"), QLatin1String("Fuzzy Search View"), 0, AlbumSelectors::AlbumType::PhysAlbum); // --------------------------------------------------------------- QLabel* const resultsLabel = new QLabel(i18n("Similarity range:")); d->similarityRange = new DIntRangeBox(); d->similarityRange->setSuffix(QLatin1String("%")); if (d->settings) { d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(), 100); d->similarityRange->setInterval(d->settings->getDuplicatesSearchLastMinSimilarity(), d->settings->getDuplicatesSearchLastMaxSimilarity()); } else { d->similarityRange->setRange(40, 100); d->similarityRange->setInterval(90, 100); } d->similarityRange->setWhatsThis(i18n("Select here the approximate similarity interval " "as a percentage. ")); // --------------------------------------------------------------- DHBox* const saveBox = new DHBox(); saveBox->setContentsMargins(QMargins()); saveBox->setSpacing(spacing); d->nameEditImage = new QLineEdit(saveBox); d->nameEditImage->setClearButtonEnabled(true); d->nameEditImage->setWhatsThis(i18n("Enter the name of the current similar image search to save in the " "\"Similarity Searches\" view.")); d->saveBtnImage = new QToolButton(saveBox); d->saveBtnImage->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); d->saveBtnImage->setEnabled(false); d->saveBtnImage->setToolTip(i18n("Save current similar image search to a new virtual Album")); d->saveBtnImage->setWhatsThis(i18n("If you press this button, the current " "similar image search will be saved to a new search " "virtual album using name " "set on the left side.")); // --------------------------------------------------------------- QWidget* const mainWidget = new QWidget(); QGridLayout* const mainLayout = new QGridLayout(); mainLayout->addWidget(imageBox, 0, 0, 1, 6); mainLayout->addWidget(file, 1, 0, 1, 1); mainLayout->addWidget(d->labelFile, 1, 1, 1, 5); mainLayout->addWidget(folder, 2, 0, 1, 1); mainLayout->addWidget(d->labelFolder, 2, 1, 1, 5); mainLayout->addWidget(d->fuzzySearchAlbumSelectors, 3, 0, 1, -1); mainLayout->addWidget(resultsLabel, 4, 0, 1, 1); mainLayout->addWidget(d->similarityRange, 4, 2, 1, 1); mainLayout->addWidget(saveBox, 5, 0, 1, 6); mainLayout->setRowStretch(0, 10); mainLayout->setColumnStretch(1, 10); mainLayout->setContentsMargins(spacing, spacing, spacing, spacing); mainLayout->setSpacing(spacing); mainWidget->setLayout(mainLayout); return mainWidget; } QWidget* FuzzySearchView::setupSketchPanel() const { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); DHBox* const drawingBox = new DHBox(); d->sketchWidget = new SketchWidget(drawingBox); drawingBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); drawingBox->setLineWidth(1); // --------------------------------------------------------------- QString tooltip(i18n("Set here the brush color used to draw sketch.")); d->hsSelector = new DHueSaturationSelector(); d->hsSelector->setMinimumSize(200, 96); d->hsSelector->setChooserMode(ChooserValue); d->hsSelector->setColorValue(255); d->hsSelector->setWhatsThis(tooltip); d->vSelector = new DColorValueSelector(); d->vSelector->setMinimumSize(26, 96); d->vSelector->setChooserMode(ChooserValue); d->vSelector->setIndent(false); d->vSelector->setWhatsThis(tooltip); // --------------------------------------------------------------- d->undoBtnSketch = new QToolButton(); d->undoBtnSketch->setAutoRepeat(true); d->undoBtnSketch->setIcon(QIcon::fromTheme(QLatin1String("edit-undo"))); d->undoBtnSketch->setToolTip(i18n("Undo last draw on sketch")); d->undoBtnSketch->setWhatsThis(i18n("Use this button to undo last drawing action on sketch.")); d->undoBtnSketch->setEnabled(false); d->redoBtnSketch = new QToolButton(); d->redoBtnSketch->setAutoRepeat(true); d->redoBtnSketch->setIcon(QIcon::fromTheme(QLatin1String("edit-redo"))); d->redoBtnSketch->setToolTip(i18n("Redo last draw on sketch")); d->redoBtnSketch->setWhatsThis(i18n("Use this button to redo last drawing action on sketch.")); d->redoBtnSketch->setEnabled(false); QLabel* const brushLabel = new QLabel(i18n("Pen:")); d->penSize = new QSpinBox(); d->penSize->setRange(1, 64); d->penSize->setSingleStep(1); d->penSize->setValue(10); d->penSize->setWhatsThis(i18n("Set here the brush size in pixels used to draw sketch.")); QLabel* const resultsLabel = new QLabel(i18n("Items:")); d->resultsSketch = new QSpinBox(); d->resultsSketch->setRange(1, 50); d->resultsSketch->setSingleStep(1); d->resultsSketch->setValue(10); d->resultsSketch->setWhatsThis(i18n("Set here the number of items to find using sketch.")); QGridLayout* const settingsLayout = new QGridLayout(); settingsLayout->addWidget(d->undoBtnSketch, 0, 0); settingsLayout->addWidget(d->redoBtnSketch, 0, 1); settingsLayout->addWidget(brushLabel, 0, 2); settingsLayout->addWidget(d->penSize, 0, 3); settingsLayout->addWidget(resultsLabel, 0, 5); settingsLayout->addWidget(d->resultsSketch, 0, 6); settingsLayout->setColumnStretch(4, 10); settingsLayout->setContentsMargins(QMargins()); settingsLayout->setSpacing(spacing); // --------------------------------------------------------------- d->sketchSearchAlbumSelectors = new AlbumSelectors(i18nc("@label", "Search in albums:"), QLatin1String("Sketch Search View"), 0, AlbumSelectors::AlbumType::PhysAlbum); // --------------------------------------------------------------- DHBox* const saveBox = new DHBox(); saveBox->setContentsMargins(QMargins()); saveBox->setSpacing(spacing); d->resetButton = new QToolButton(saveBox); d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert"))); d->resetButton->setToolTip(i18n("Clear sketch")); d->resetButton->setWhatsThis(i18n("Use this button to clear sketch contents.")); d->nameEditSketch = new QLineEdit(saveBox); d->nameEditSketch->setClearButtonEnabled(true); d->nameEditSketch->setWhatsThis(i18n("Enter the name of the current sketch search to save in the " "\"Similarity Searches\" view.")); d->saveBtnSketch = new QToolButton(saveBox); d->saveBtnSketch->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); d->saveBtnSketch->setEnabled(false); d->saveBtnSketch->setToolTip(i18n("Save current sketch search to a new virtual Album")); d->saveBtnSketch->setWhatsThis(i18n("If you press this button, the current sketch " "fuzzy search will be saved to a new search " "virtual album using the name " "set on the left side.")); // --------------------------------------------------------------- QWidget* const mainWidget = new QWidget; QGridLayout* const mainLayout = new QGridLayout(); mainLayout->addWidget(drawingBox, 0, 0, 1, 3); mainLayout->addWidget(d->hsSelector, 1, 0, 1, 2); mainLayout->addWidget(d->vSelector, 1, 2, 1, 1); mainLayout->addLayout(settingsLayout, 2, 0, 1, 3); mainLayout->addWidget(d->sketchSearchAlbumSelectors, 3, 0, 1, 3); mainLayout->addWidget(saveBox, 4, 0, 1, 3); mainLayout->setRowStretch(0, 10); mainLayout->setColumnStretch(1, 10); mainLayout->setContentsMargins(spacing, spacing, spacing, spacing); mainLayout->setSpacing(spacing); mainWidget->setLayout(mainLayout); return mainWidget; } void FuzzySearchView::setupConnections() { connect(d->settings, SIGNAL(setupChanged()), this, SLOT(slotApplicationSettingsChanged())); connect(d->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotTabChanged(int))); connect(d->searchTreeView, SIGNAL(currentAlbumChanged(Album*)), this, SLOT(slotAlbumSelected(Album*))); connect(d->hsSelector, SIGNAL(valueChanged(int,int)), this, SLOT(slotHSChanged(int,int))); connect(d->vSelector, SIGNAL(valueChanged(int)), this, SLOT(slotVChanged(int))); connect(d->penSize, SIGNAL(valueChanged(int)), d->sketchWidget, SLOT(setPenWidth(int))); connect(d->resultsSketch, SIGNAL(valueChanged(int)), this, SLOT(slotDirtySketch())); connect(d->sketchSearchAlbumSelectors, SIGNAL(signalSelectionChanged()), this, SLOT(slotDirtySketch())); connect(d->fuzzySearchAlbumSelectors, SIGNAL(signalSelectionChanged()), this, SLOT(slotFuzzyAlbumsChanged())); connect(d->similarityRange, SIGNAL(minChanged(int)), this, SLOT(slotMinLevelImageChanged(int))); connect(d->similarityRange, SIGNAL(maxChanged(int)), this, SLOT(slotMaxLevelImageChanged(int))); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotClearSketch())); connect(d->sketchWidget, SIGNAL(signalPenSizeChanged(int)), d->penSize, SLOT(setValue(int))); connect(d->sketchWidget, SIGNAL(signalPenColorChanged(QColor)), this, SLOT(slotPenColorChanged(QColor))); connect(d->sketchWidget, SIGNAL(signalSketchChanged(QImage)), this, SLOT(slotDirtySketch())); connect(d->sketchWidget, SIGNAL(signalUndoRedoStateChanged(bool,bool)), this, SLOT(slotUndoRedoStateChanged(bool,bool))); connect(d->undoBtnSketch, SIGNAL(clicked()), d->sketchWidget, SLOT(slotUndo())); connect(d->redoBtnSketch, SIGNAL(clicked()), d->sketchWidget, SLOT(slotRedo())); connect(d->saveBtnSketch, SIGNAL(clicked()), this, SLOT(slotSaveSketchSAlbum())); connect(d->saveBtnImage, SIGNAL(clicked()), this, SLOT(slotSaveImageSAlbum())); connect(d->nameEditSketch, SIGNAL(textChanged(QString)), this, SLOT(slotCheckNameEditSketchConditions())); connect(d->nameEditSketch, SIGNAL(returnPressed()), d->saveBtnSketch, SLOT(animateClick())); connect(d->nameEditImage, SIGNAL(textChanged(QString)), this, SLOT(slotCheckNameEditImageConditions())); connect(d->nameEditImage, SIGNAL(returnPressed()), d->saveBtnImage, SLOT(animateClick())); connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); connect(d->timerSketch, SIGNAL(timeout()), this, SLOT(slotTimerSketchDone())); connect(d->timerImage, SIGNAL(timeout()), this, SLOT(slotTimerImageDone())); } FuzzySearchView::~FuzzySearchView() { delete d->timerSketch; delete d->timerImage; delete d; } SAlbum* FuzzySearchView::currentAlbum() const { return d->searchTreeView->currentAlbum(); } void FuzzySearchView::setCurrentAlbum(SAlbum* const album) { d->searchTreeView->setCurrentAlbums(QList() << album); } void FuzzySearchView::newDuplicatesSearch(PAlbum* const album) { if (album) { d->findDuplicatesPanel->slotSetSelectedAlbum(album); } d->tabWidget->setCurrentIndex(Private::DUPLICATES); } -void FuzzySearchView::newDuplicatesSearch(QList const albums) +void FuzzySearchView::newDuplicatesSearch(const QList& albums) { if (!albums.isEmpty()) { d->findDuplicatesPanel->slotSetSelectedAlbums(albums); } d->tabWidget->setCurrentIndex(Private::DUPLICATES); } -void FuzzySearchView::newDuplicatesSearch(QList const albums) +void FuzzySearchView::newDuplicatesSearch(const QList& albums) { if (!albums.isEmpty()) { d->findDuplicatesPanel->slotSetSelectedAlbums(albums); } d->tabWidget->setCurrentIndex(Private::DUPLICATES); } void FuzzySearchView::setConfigGroup(const KConfigGroup& group) { StateSavingObject::setConfigGroup(group); d->searchTreeView->setConfigGroup(group); } void FuzzySearchView::doLoadState() { KConfigGroup group = getConfigGroup(); d->tabWidget->setCurrentIndex(group.readEntry(entryName(d->configTabEntry), (int)Private::DUPLICATES)); d->penSize->setValue(group.readEntry(entryName(d->configPenSketchSizeEntry), 10)); d->resultsSketch->setValue(group.readEntry(entryName(d->configResultSketchItemsEntry), 10)); d->hsSelector->setHue(group.readEntry(entryName(d->configPenSketchHueEntry), 180)); d->hsSelector->setSaturation(group.readEntry(entryName(d->configPenSketchSaturationEntry), 128)); d->vSelector->setValue(group.readEntry(entryName(d->configPenSkethValueEntry), 255)); d->similarityRange->setInterval(group.readEntry(entryName(d->configSimilarsThresholdEntry), 90), group.readEntry(entryName(d->configSimilarsMaxThresholdEntry), 100)); d->hsSelector->updateContents(); QColor col; col.setHsv(d->hsSelector->hue(), d->hsSelector->saturation(), d->vSelector->value()); setColor(col); d->sketchWidget->setPenWidth(d->penSize->value()); d->searchTreeView->loadState(); d->fuzzySearchAlbumSelectors->loadState(); d->sketchSearchAlbumSelectors->loadState(); } void FuzzySearchView::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->configTabEntry), d->tabWidget->currentIndex()); group.writeEntry(entryName(d->configPenSketchSizeEntry), d->penSize->value()); group.writeEntry(entryName(d->configResultSketchItemsEntry), d->resultsSketch->value()); group.writeEntry(entryName(d->configPenSketchHueEntry), d->hsSelector->hue()); group.writeEntry(entryName(d->configPenSketchSaturationEntry), d->hsSelector->saturation()); group.writeEntry(entryName(d->configPenSkethValueEntry), d->vSelector->value()); group.writeEntry(entryName(d->configSimilarsThresholdEntry), d->similarityRange->minValue()); group.writeEntry(entryName(d->configSimilarsMaxThresholdEntry), d->similarityRange->maxValue()); d->searchTreeView->saveState(); group.sync(); d->fuzzySearchAlbumSelectors->saveState(); d->sketchSearchAlbumSelectors->saveState(); } void FuzzySearchView::setActive(bool val) { d->active = val; // at first occasion, warn if no fingerprints are available if (val && !d->fingerprintsChecked && isVisible()) { if (!SimilarityDbAccess().db()->hasFingerprints()) { QString msg = i18n("Image fingerprints have not yet been generated for your collection. " "The Similarity Search Tools will not be operational " "without pre-generated fingerprints. Please generate " "the fingerprints first."); QMessageBox::information(this, i18n("No Fingerprints"), msg); } d->fingerprintsChecked = true; } int tab = d->tabWidget->currentIndex(); if (val) { slotTabChanged(tab); } } void FuzzySearchView::slotTabChanged(int tab) { /** * Set a list with only one element, albummanager can set only multiple albums */ QList albums; switch (tab) { case Private::SIMILARS: { albums << d->imageSAlbum; AlbumManager::instance()->setCurrentAlbums(albums); d->folderView->setVisible(true); break; } case Private::SKETCH: { albums << d->sketchSAlbum; AlbumManager::instance()->setCurrentAlbums(albums); d->folderView->setVisible(true); break; } default: // DUPLICATES { d->findDuplicatesPanel->setActive(true); QList sAlbums = d->findDuplicatesPanel->currentFindDuplicatesAlbums(); foreach(SAlbum* const album, sAlbums) { albums << album; } AlbumManager::instance()->setCurrentAlbums(albums); d->folderView->setVisible(false); break; } } } void FuzzySearchView::slotAlbumSelected(Album* album) { qCDebug(DIGIKAM_GENERAL_LOG) << "Selected new album" << album; SAlbum* const salbum = dynamic_cast(album); if (!salbum || !salbum->isHaarSearch()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Not a haar search, returning"; return; } if (!d->active) { qCDebug(DIGIKAM_GENERAL_LOG) << "Not active, returning"; return; } SearchXmlReader reader(salbum->query()); reader.readToFirstField(); QStringRef type = reader.attributes().value(QLatin1String("type")); QStringRef numResultsString = reader.attributes().value(QLatin1String("numberofresults")); QStringRef thresholdString = reader.attributes().value(QLatin1String("threshold")); QStringRef maxThresholdString = reader.attributes().value(QLatin1String("maxthreshold")); QStringRef sketchTypeString = reader.attributes().value(QLatin1String("sketchtype")); if (type == QLatin1String("imageid")) { setCurrentImage(reader.valueToLongLong()); d->imageSAlbum = salbum; d->tabWidget->setCurrentIndex((int)Private::SIMILARS); } else if (type == QLatin1String("signature")) { d->sketchSAlbum = salbum; d->tabWidget->setCurrentIndex((int)Private::SKETCH); if (reader.readToStartOfElement(QLatin1String("SketchImage"))) { d->sketchWidget->setSketchImageFromXML(reader); } } } void FuzzySearchView::slotApplicationSettingsChanged() { d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(),100); } // Sketch Searches methods ----------------------------------------------------------------------- void FuzzySearchView::slotHSChanged(int h, int s) { QColor color; int val = d->selColor.value(); color.setHsv(h, s, val); setColor(color); } void FuzzySearchView::slotVChanged(int v) { QColor color; int hue = d->selColor.hue(); int sat = d->selColor.saturation(); color.setHsv(hue, sat, v); setColor(color); } void FuzzySearchView::slotPenColorChanged(const QColor& color) { slotHSChanged(color.hue(), color.saturation()); slotVChanged(color.value()); } void FuzzySearchView::setColor(QColor c) { if (c.isValid()) { d->selColor = c; // set values d->hsSelector->setValues(c.hue(), c.saturation()); d->vSelector->setValue(c.value()); // set colors d->hsSelector->blockSignals(true); d->hsSelector->setHue(c.hue()); d->hsSelector->setSaturation(c.saturation()); d->hsSelector->setColorValue(c.value()); d->hsSelector->updateContents(); d->hsSelector->blockSignals(false); d->hsSelector->repaint(); d->vSelector->blockSignals(true); d->vSelector->setHue(c.hue()); d->vSelector->setSaturation(c.saturation()); d->vSelector->setColorValue(c.value()); d->vSelector->updateContents(); d->vSelector->blockSignals(false); d->vSelector->repaint(); d->sketchWidget->setPenColor(c); } } void FuzzySearchView::slotUndoRedoStateChanged(bool hasUndo, bool hasRedo) { d->undoBtnSketch->setEnabled(hasUndo); d->redoBtnSketch->setEnabled(hasRedo); } void FuzzySearchView::slotSaveSketchSAlbum() { createNewFuzzySearchAlbumFromSketch(d->nameEditSketch->text()); } void FuzzySearchView::slotDirtySketch() { if (d->active) { d->timerSketch->start(); } } void FuzzySearchView::slotTimerSketchDone() { slotCheckNameEditSketchConditions(); createNewFuzzySearchAlbumFromSketch(SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch), true); } void FuzzySearchView::createNewFuzzySearchAlbumFromSketch(const QString& name, bool force) { AlbumManager::instance()->setCurrentAlbums(QList()); QList albums = d->sketchSearchAlbumSelectors->selectedAlbumIds(); d->sketchSAlbum = d->searchModificationHelper->createFuzzySearchFromSketch(name, d->sketchWidget, d->resultsSketch->value(), albums, force); d->searchTreeView->setCurrentAlbums(QList() << d->sketchSAlbum); } void FuzzySearchView::slotClearSketch() { d->sketchWidget->slotClear(); slotCheckNameEditSketchConditions(); AlbumManager::instance()->setCurrentAlbums(QList()); } void FuzzySearchView::slotCheckNameEditSketchConditions() { if (!d->sketchWidget->isClear()) { d->nameEditSketch->setEnabled(true); if (!d->nameEditSketch->text().isEmpty()) { d->saveBtnSketch->setEnabled(true); } } else { d->nameEditSketch->setEnabled(false); d->saveBtnSketch->setEnabled(false); } } // Similars Searches methods ---------------------------------------------------------------------- void FuzzySearchView::dragEnterEvent(QDragEnterEvent* e) { if (DItemDrag::canDecode(e->mimeData())) { e->acceptProposedAction(); } else if (e->mimeData()->hasUrls()) { QList urls = e->mimeData()->urls(); // If there is at least one URL and the URL is a local file. if (!urls.isEmpty()) { if (urls.first().isLocalFile()) { HaarIface haarIface; QString path = urls.first().toLocalFile(); const QImage image = haarIface.loadQImage(path); if (!image.isNull()) { e->acceptProposedAction(); } } } } } void FuzzySearchView::dropEvent(QDropEvent* e) { if (DItemDrag::canDecode(e->mimeData())) { QList urls; QList albumIDs; QList imageIDs; if (!DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { return; } if (imageIDs.isEmpty()) { return; } setImageInfo(ImageInfo(imageIDs.first())); e->acceptProposedAction(); } // Allow dropping urls and handle them as sketch search if the urls represent images. if (e->mimeData()->hasUrls()) { QList urls = e->mimeData()->urls(); // If there is at least one URL and the URL is a local file. if (!urls.isEmpty()) { if (urls.first().isLocalFile()) { HaarIface haarIface; QString path = urls.first().toLocalFile(); const QImage image = haarIface.loadQImage(path); if (!image.isNull()) { // Set a temporary image id d->imageInfo = ImageInfo(-1); d->imageUrl = urls.first(); d->imageWidget->setPixmap(QPixmap::fromImage(image).scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation)); AlbumManager::instance()->setCurrentAlbums(QList()); QString haarTitle = SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch); QList albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds(); d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromDropped(haarTitle, path, d->similarityRange->minValue() / 100.0, d->similarityRange->maxValue() / 100.0, albums, true); d->searchTreeView->setCurrentAlbums(QList() << d->imageSAlbum); d->labelFile->setAdjustedText(urls.first().fileName()); d->labelFolder->setAdjustedText(urls.first().adjusted(QUrl::RemoveFilename).toLocalFile()); e->acceptProposedAction(); } } } } } void FuzzySearchView::slotMaxLevelImageChanged(int /*newValue*/) { if (d->active) { d->timerImage->start(); } } void FuzzySearchView::slotMinLevelImageChanged(int /*newValue*/) { if (d->active) { d->timerImage->start(); } } void FuzzySearchView::slotFuzzyAlbumsChanged() { if (d->active) { d->timerImage->start(); } } void FuzzySearchView::slotTimerImageDone() { if (d->imageInfo.isNull() && d->imageInfo.id() == -1 && !d->imageUrl.isEmpty()) { AlbumManager::instance()->setCurrentAlbums(QList()); QString haarTitle = SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch); QList albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds(); d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromDropped(haarTitle, d->imageUrl.toLocalFile(), d->similarityRange->minValue() / 100.0, d->similarityRange->maxValue() / 100.0, albums, true); d->searchTreeView->setCurrentAlbums(QList() << d->imageSAlbum); return; } if (!d->imageInfo.isNull() && d->active) { setImageInfo(d->imageInfo); } } void FuzzySearchView::setCurrentImage(qlonglong imageid) { setCurrentImage(ImageInfo(imageid)); } void FuzzySearchView::setCurrentImage(const ImageInfo& info) { d->imageInfo = info; d->imageUrl = info.fileUrl(); d->labelFile->setAdjustedText(d->imageInfo.name()); d->labelFolder->setAdjustedText(d->imageInfo.fileUrl().adjusted(QUrl::RemoveFilename).toLocalFile()); d->thumbLoadThread->find(d->imageInfo.thumbnailIdentifier()); } void FuzzySearchView::setImageInfo(const ImageInfo& info) { setCurrentImage(info); slotCheckNameEditImageConditions(); createNewFuzzySearchAlbumFromImage(SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch), true); d->tabWidget->setCurrentIndex((int)Private::SIMILARS); } void FuzzySearchView::slotThumbnailLoaded(const LoadingDescription& desc, const QPixmap& pix) { if (!d->imageInfo.isNull() && QUrl::fromLocalFile(desc.filePath) == d->imageInfo.fileUrl()) { d->imageWidget->setPixmap(pix.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } } void FuzzySearchView::createNewFuzzySearchAlbumFromImage(const QString& name, bool force) { AlbumManager::instance()->setCurrentAlbums(QList()); QList albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds(); d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromImage(name, d->imageInfo, d->similarityRange->minValue() / 100.0, d->similarityRange->maxValue() / 100.0, albums, force); d->searchTreeView->setCurrentAlbums(QList() << d->imageSAlbum); } void FuzzySearchView::slotCheckNameEditImageConditions() { if (!d->imageInfo.isNull()) { d->nameEditImage->setEnabled(true); if (!d->nameEditImage->text().isEmpty()) { d->saveBtnImage->setEnabled(true); } } else { d->nameEditImage->setEnabled(false); d->saveBtnImage->setEnabled(false); } } void FuzzySearchView::slotSaveImageSAlbum() { createNewFuzzySearchAlbumFromImage(d->nameEditImage->text()); } } // namespace Digikam diff --git a/core/utilities/fuzzysearch/fuzzysearchview.h b/core/utilities/fuzzysearch/fuzzysearchview.h index 698d5bd5d3..fc4336496e 100644 --- a/core/utilities/fuzzysearch/fuzzysearchview.h +++ b/core/utilities/fuzzysearch/fuzzysearchview.h @@ -1,135 +1,135 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-05-19 * Description : Fuzzy search sidebar tab contents. * * Copyright (C) 2016-2018 by Mario Frank * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2008-2012 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_FUZZY_SEARCH_VIEW_H #define DIGIKAM_FUZZY_SEARCH_VIEW_H // Qt includes #include // Local includs #include "statesavingobject.h" class QDragEnterEvent; class QDropEvent; class QPixmap; namespace Digikam { class Album; class FuzzySearchFolderView; class ImageInfo; class LoadingDescription; class SAlbum; class PAlbum; class TAlbum; class SearchModel; class SearchModificationHelper; class SearchTextBar; class FuzzySearchView : public QScrollArea, public StateSavingObject { Q_OBJECT public: explicit FuzzySearchView(SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper, QWidget* const parent = 0); virtual ~FuzzySearchView(); SAlbum* currentAlbum() const; void setCurrentAlbum(SAlbum* const album); void setActive(bool val); void setImageInfo(const ImageInfo& info); - void newDuplicatesSearch(PAlbum* const); - void newDuplicatesSearch(QList const); - void newDuplicatesSearch(QList const); + void newDuplicatesSearch(PAlbum* const album); + void newDuplicatesSearch(const QList& albums); + void newDuplicatesSearch(const QList& albums); virtual void setConfigGroup(const KConfigGroup& group); void doLoadState(); void doSaveState(); protected: void dragEnterEvent(QDragEnterEvent* e); void dropEvent(QDropEvent* e); private Q_SLOTS: void slotTabChanged(int); void slotHSChanged(int h, int s); void slotVChanged(int v); void slotPenColorChanged(const QColor&); void slotClearSketch(); void slotSaveSketchSAlbum(); void slotCheckNameEditSketchConditions(); void slotAlbumSelected(Album* album); void slotSaveImageSAlbum(); void slotCheckNameEditImageConditions(); void slotThumbnailLoaded(const LoadingDescription&, const QPixmap&); void slotDirtySketch(); void slotTimerSketchDone(); void slotUndoRedoStateChanged(bool, bool); void slotMinLevelImageChanged(int); void slotMaxLevelImageChanged(int); void slotFuzzyAlbumsChanged(); void slotTimerImageDone(); void slotApplicationSettingsChanged(); private: void setCurrentImage(qlonglong imageid); void setCurrentImage(const ImageInfo& info); void createNewFuzzySearchAlbumFromSketch(const QString& name, bool force = false); void createNewFuzzySearchAlbumFromImage(const QString& name, bool force = false); void setColor(QColor c); QWidget* setupFindSimilarPanel() const; QWidget* setupSketchPanel() const; void setupConnections(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FUZZY_SEARCH_VIEW_H diff --git a/core/utilities/geolocation/editor/items/gpsimageitem.cpp b/core/utilities/geolocation/editor/items/gpsimageitem.cpp index d5694461cc..4277e9eb78 100644 --- a/core/utilities/geolocation/editor/items/gpsimageitem.cpp +++ b/core/utilities/geolocation/editor/items/gpsimageitem.cpp @@ -1,968 +1,969 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-03-21 * Description : An item to hold information about an image. * * Copyright (C) 2010-2018 by Gilles Caulier * Copyright (C) 2010-2014 by Michael G. Hansen * * 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, 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. * * ============================================================ */ #include "gpsimageitem.h" // Qt includes #include #include #include #include // KDE includes #include // local includes #include "gpsimagemodel.h" #include "metadatasettings.h" namespace Digikam { bool setExifXmpTagDataVariant(DMetadata* const meta, const char* const exifTagName, const char* const xmpTagName, const QVariant& value) { bool success = meta->setExifTagVariant(exifTagName, value); if (success) { /** @todo Here we save all data types as XMP Strings. Is that okay or do we have to store them as some other type? */ switch (value.type()) { case QVariant::Int: case QVariant::UInt: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: success = meta->setXmpTagString(xmpTagName, QString::number(value.toInt())); break; case QVariant::Double: { long num, den; meta->convertToRationalSmallDenominator(value.toDouble(), &num, &den); success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); break; } case QVariant::List: { long num = 0, den = 1; QList list = value.toList(); if (list.size() >= 1) num = list[0].toInt(); if (list.size() >= 2) den = list[1].toInt(); success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); break; } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = value.toDateTime(); if(!dateTime.isValid()) { success = false; break; } success = meta->setXmpTagString(xmpTagName, dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss"))); break; } case QVariant::String: case QVariant::Char: success = meta->setXmpTagString(xmpTagName, value.toString()); break; case QVariant::ByteArray: /// @todo I don't know a straightforward way to convert a byte array to XMP success = false; break; default: success = false; break; } } return success; } GPSImageItem::GPSImageItem(const QUrl& url) : m_model(0), m_url(url), m_dateTime(), m_dirty(false), m_gpsData(), m_savedState(), m_tagListDirty(false), m_tagList(), m_savedTagList(), m_writeXmpTags(true) { } GPSImageItem::~GPSImageItem() { } DMetadata* GPSImageItem::getMetadataForFile() const { QScopedPointer meta(new DMetadata); if (!meta->load(m_url.toLocalFile())) { // It is possible that no sidecar file has yet been created. // If writing to sidecar file is activated, we ignore the loading error of the metadata. if (MetadataSettings::instance()->settings().metadataWritingMode == DMetadata::WRITETOIMAGEONLY) { return 0; } } return meta.take(); } int getWarningLevelFromGPSDataContainer(const GPSDataContainer& data) { if (data.hasDop()) { const int dopValue = data.getDop(); if (dopValue < 2) return 1; if (dopValue < 4) return 2; if (dopValue < 10) return 3; return 4; } else if (data.hasFixType()) { if (data.getFixType() < 3) return 4; } else if (data.hasNSatellites()) { if (data.getNSatellites() < 4) return 4; } // no warning level return -1; } bool GPSImageItem::loadImageData() { QScopedPointer meta(getMetadataForFile()); if (meta && !m_dateTime.isValid()) { m_dateTime = meta->getImageDateTime(); } if (!m_dateTime.isValid()) { // Get date from filesystem. QFileInfo info(m_url.toLocalFile()); QDateTime ctime = info.created(); QDateTime mtime = info.lastModified(); if (ctime.isNull() || mtime.isNull()) { m_dateTime = qMax(ctime, mtime); } else { m_dateTime = qMin(ctime, mtime); } } if (!meta) return false; // The way we read the coordinates here is problematic // if the coordinates were in the file initially, but // the user deleted them in the database. Then we still load // them from the file. On the other hand, we can not clear // the coordinates, because then we would loose them if // they are only stored in the database. // m_gpsData.clear(); if (!m_gpsData.hasCoordinates()) { // could not load the coordinates from the interface, // read them directly from the file double lat, lng; bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng); if (haveCoordinates) { GeoCoordinates coordinates(lat, lng); double alt; if (meta->getGPSAltitude(&alt)) { coordinates.setAlt(alt); } m_gpsData.setCoordinates(coordinates); } } /** @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist, * therefore no need to read XMP as well? */ // read the remaining GPS information from the file: const QByteArray speedRef = meta->getExifTagData("Exif.GPSInfo.GPSSpeedRef"); bool success = !speedRef.isEmpty(); long num, den; success &= meta->getExifTagRational("Exif.GPSInfo.GPSSpeed", num, den); if (success) { // be relaxed about 0/0 if ((num == 0.0) && (den == 0.0)) den = 1.0; const qreal speedInRef = qreal(num)/qreal(den); qreal FactorToMetersPerSecond; if (speedRef.startsWith('K')) { // km/h = 1000 * 3600 FactorToMetersPerSecond = 1.0/3.6; } else if (speedRef.startsWith('M')) { // TODO: someone please check that this is the 'right' mile // miles/hour = 1609.344 meters / hour = 1609.344 meters / 3600 seconds FactorToMetersPerSecond = 1.0 / (1609.344 / 3600.0); } else if (speedRef.startsWith('N')) { // speed is in knots. // knot = one nautic mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds FactorToMetersPerSecond = 1.0 / (1852.0 / 3600.0); } else { success = false; } if (success) { const qreal speedInMetersPerSecond = speedInRef * FactorToMetersPerSecond; m_gpsData.setSpeed(speedInMetersPerSecond); } } // number of satellites const QString gpsSatellitesString = meta->getExifTagString("Exif.GPSInfo.GPSSatellites"); bool satellitesOkay = !gpsSatellitesString.isEmpty(); if (satellitesOkay) { /** * @todo Here we only accept a single integer denoting the number of satellites used * but not detailed information about all satellites. */ const int nSatellites = gpsSatellitesString.toInt(&satellitesOkay); if (satellitesOkay) { m_gpsData.setNSatellites(nSatellites); } } // fix type / measure mode const QByteArray gpsMeasureModeByteArray = meta->getExifTagData("Exif.GPSInfo.GPSMeasureMode"); bool measureModeOkay = !gpsMeasureModeByteArray.isEmpty(); if (measureModeOkay) { const int measureMode = gpsMeasureModeByteArray.toInt(&measureModeOkay); if (measureModeOkay) { if ((measureMode == 2) || (measureMode == 3)) { m_gpsData.setFixType(measureMode); } } } // read the DOP value: success = meta->getExifTagRational("Exif.GPSInfo.GPSDOP", num, den); if (success) { // be relaxed about 0/0 if ((num == 0.0) && (den == 0.0)) den = 1.0; const qreal dop = qreal(num)/qreal(den); m_gpsData.setDop(dop); } // mark us as not-dirty, because the data was just loaded: m_dirty = false; m_savedState = m_gpsData; emitDataChanged(); return true; } QVariant GPSImageItem::data(const int column, const int role) const { if ((column == ColumnFilename) && (role == Qt::DisplayRole)) { return m_url.fileName(); } else if ((column == ColumnDateTime) && (role == Qt::DisplayRole)) { if (m_dateTime.isValid()) { return QLocale().toString(m_dateTime, QLocale::ShortFormat); } return i18n("Not available"); } else if (role == RoleCoordinates) { return QVariant::fromValue(m_gpsData.getCoordinates()); } else if ((column == ColumnLatitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasLatitude()) return QString(); return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lat(), 7); } else if ((column == ColumnLongitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasLongitude()) return QString(); return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lon(), 7); } else if ((column == ColumnAltitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasAltitude()) return QString(); return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().alt(), 7); } else if (column == ColumnAccuracy) { if (role == Qt::DisplayRole) { if (m_gpsData.hasDop()) { return i18n("DOP: %1", m_gpsData.getDop()); } if (m_gpsData.hasFixType()) { return i18n("Fix: %1d", m_gpsData.getFixType()); } if (m_gpsData.hasNSatellites()) { return i18n("#Sat: %1", m_gpsData.getNSatellites()); } } else if (role == Qt::BackgroundRole) { const int warningLevel = getWarningLevelFromGPSDataContainer(m_gpsData); switch (warningLevel) { case 1: return QBrush(Qt::green); case 2: return QBrush(Qt::yellow); case 3: // orange return QBrush(QColor(0xff, 0x80, 0x00)); case 4: return QBrush(Qt::red); default: break; } } } else if ((column == ColumnDOP) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasDop()) return QString(); return QString::number(m_gpsData.getDop()); } else if ((column == ColumnFixType) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasFixType()) return QString(); return i18n("%1d", m_gpsData.getFixType()); } else if ((column == ColumnNSatellites) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasNSatellites()) return QString(); return QString::number(m_gpsData.getNSatellites()); } else if ((column == ColumnSpeed) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasSpeed()) return QString(); return QString::number(m_gpsData.getSpeed()); } else if ((column == ColumnStatus) && (role == Qt::DisplayRole)) { if (m_dirty || m_tagListDirty) { return i18n("Modified"); } return QString(); } else if ((column == ColumnTags) && (role == Qt::DisplayRole)) { if (!m_tagList.isEmpty()) { QString myTagsList; for (int i = 0 ; i < m_tagList.count() ; ++i) { QString myTag; for (int j = 0 ; j < m_tagList[i].count() ; ++j) { myTag.append(QString::fromLatin1("/") + m_tagList[i].at(j).tagName); if (j == 0) myTag.remove(0, 1); } if (!myTagsList.isEmpty()) myTagsList.append(QString::fromLatin1(", ")); myTagsList.append(myTag); } return myTagsList; } return QString(); } return QVariant(); } void GPSImageItem::setCoordinates(const GeoCoordinates& newCoordinates) { m_gpsData.setCoordinates(newCoordinates); m_dirty = true; emitDataChanged(); } void GPSImageItem::setModel(GPSImageModel* const model) { m_model = model; } void GPSImageItem::emitDataChanged() { if (m_model) { m_model->itemChanged(this); } } void GPSImageItem::setHeaderData(GPSImageModel* const model) { model->setColumnCount(ColumnGPSImageItemCount); model->setHeaderData(ColumnThumbnail, Qt::Horizontal, i18n("Thumbnail"), Qt::DisplayRole); model->setHeaderData(ColumnFilename, Qt::Horizontal, i18n("Filename"), Qt::DisplayRole); model->setHeaderData(ColumnDateTime, Qt::Horizontal, i18n("Date and time"), Qt::DisplayRole); model->setHeaderData(ColumnLatitude, Qt::Horizontal, i18n("Latitude"), Qt::DisplayRole); model->setHeaderData(ColumnLongitude, Qt::Horizontal, i18n("Longitude"), Qt::DisplayRole); model->setHeaderData(ColumnAltitude, Qt::Horizontal, i18n("Altitude"), Qt::DisplayRole); model->setHeaderData(ColumnAccuracy, Qt::Horizontal, i18n("Accuracy"), Qt::DisplayRole); model->setHeaderData(ColumnDOP, Qt::Horizontal, i18n("DOP"), Qt::DisplayRole); model->setHeaderData(ColumnFixType, Qt::Horizontal, i18n("Fix type"), Qt::DisplayRole); model->setHeaderData(ColumnNSatellites, Qt::Horizontal, i18n("# satellites"), Qt::DisplayRole); model->setHeaderData(ColumnSpeed, Qt::Horizontal, i18n("Speed"), Qt::DisplayRole); model->setHeaderData(ColumnStatus, Qt::Horizontal, i18n("Status"), Qt::DisplayRole); model->setHeaderData(ColumnTags, Qt::Horizontal, i18n("Tags"), Qt::DisplayRole); } bool GPSImageItem::lessThan(const GPSImageItem* const otherItem, const int column) const { switch (column) { case ColumnThumbnail: return false; case ColumnFilename: return m_url < otherItem->m_url; case ColumnDateTime: return m_dateTime < otherItem->m_dateTime; case ColumnAltitude: { if (!m_gpsData.hasAltitude()) return false; if (!otherItem->m_gpsData.hasAltitude()) return true; return m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt(); } case ColumnNSatellites: { if (!m_gpsData.hasNSatellites()) return false; if (!otherItem->m_gpsData.hasNSatellites()) return true; return m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites(); } case ColumnAccuracy: { const int myWarning = getWarningLevelFromGPSDataContainer(m_gpsData); const int otherWarning = getWarningLevelFromGPSDataContainer(otherItem->m_gpsData); if (myWarning < 0) return false; if (otherWarning < 0) return true; if (myWarning != otherWarning) return myWarning < otherWarning; // TODO: this may not be the best way to sort images with equal warning levels // but it works for now if (m_gpsData.hasDop() != otherItem->m_gpsData.hasDop()) return !m_gpsData.hasDop(); if (m_gpsData.hasDop() && otherItem->m_gpsData.hasDop()) { return m_gpsData.getDop() < otherItem->m_gpsData.getDop(); } if (m_gpsData.hasFixType() != otherItem->m_gpsData.hasFixType()) return m_gpsData.hasFixType(); if (m_gpsData.hasFixType() && otherItem->m_gpsData.hasFixType()) { return m_gpsData.getFixType() > otherItem->m_gpsData.getFixType(); } if (m_gpsData.hasNSatellites() != otherItem->m_gpsData.hasNSatellites()) return m_gpsData.hasNSatellites(); if (m_gpsData.hasNSatellites() && otherItem->m_gpsData.hasNSatellites()) { return m_gpsData.getNSatellites() > otherItem->m_gpsData.getNSatellites(); } return false; } case ColumnDOP: { if (!m_gpsData.hasDop()) return false; if (!otherItem->m_gpsData.hasDop()) return true; return m_gpsData.getDop() < otherItem->m_gpsData.getDop(); } case ColumnFixType: { if (!m_gpsData.hasFixType()) return false; if (!otherItem->m_gpsData.hasFixType()) return true; return m_gpsData.getFixType() < otherItem->m_gpsData.getFixType(); } case ColumnSpeed: { if (!m_gpsData.hasSpeed()) return false; if (!otherItem->m_gpsData.hasSpeed()) return true; return m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed(); } case ColumnLatitude: { if (!m_gpsData.hasCoordinates()) return false; if (!otherItem->m_gpsData.hasCoordinates()) return true; return m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat(); } case ColumnLongitude: { if (!m_gpsData.hasCoordinates()) return false; if (!otherItem->m_gpsData.hasCoordinates()) return true; return m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon(); } case ColumnStatus: { return m_dirty && !otherItem->m_dirty; } default: return false; } } SaveProperties GPSImageItem::saveProperties() const { SaveProperties p; // do we have gps information? if (m_gpsData.hasCoordinates()) { p.shouldWriteCoordinates = true; p.latitude = m_gpsData.getCoordinates().lat(); p.longitude = m_gpsData.getCoordinates().lon(); if (m_gpsData.hasAltitude()) { p.shouldWriteAltitude = true; p.altitude = m_gpsData.getCoordinates().alt(); } else { p.shouldRemoveAltitude = true; } } else { p.shouldRemoveCoordinates = true; } return p; } QString GPSImageItem::saveChanges() { SaveProperties p = saveProperties(); QString returnString; // first try to write the information to the image file bool success = false; QScopedPointer meta(getMetadataForFile()); if (!meta) { // TODO: more verbosity! returnString = i18n("Failed to open file."); } else { if (p.shouldWriteCoordinates) { if (p.shouldWriteAltitude) { success = meta->setGPSInfo(p.altitude, p.latitude, p.longitude); } else { - success = meta->setGPSInfo(static_cast(0), p.latitude, p.longitude); + success = meta->setGPSInfo(const_cast(static_cast(0)), + p.latitude, p.longitude); } // write all other GPS information here too if (success && m_gpsData.hasSpeed()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeedRef", "Xmp.exif.GPSSpeedRef", QVariant(QString::fromLatin1("K"))); if (success) { const qreal speedInMetersPerSecond = m_gpsData.getSpeed(); // km/h = 0.001 * m / ( s * 1/(60*60) ) = 3.6 * m/s const qreal speedInKilometersPerHour = 3.6 * speedInMetersPerSecond; success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeed", "Xmp.exif.GPSSpeed", QVariant(speedInKilometersPerHour)); } } if (success && m_gpsData.hasNSatellites()) { /** * @todo According to the EXIF 2.2 spec, GPSSatellites is a free form field which can either hold only the * number of satellites or more details about each satellite used. For now, we just write * the number of satellites. Are we using the correct format for the number of satellites here? */ success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites", QVariant(QString::number(m_gpsData.getNSatellites()))); } if (success && m_gpsData.hasFixType()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode", QVariant(QString::number(m_gpsData.getFixType()))); } // write DOP if (success && m_gpsData.hasDop()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSDOP", "Xmp.exif.GPSDOP", QVariant(m_gpsData.getDop())); } if (!success) { returnString = i18n("Failed to add GPS info to image."); } } if (p.shouldRemoveCoordinates) { // TODO: remove only the altitude if requested success = meta->removeGPSInfo(); if (!success) { returnString = i18n("Failed to remove GPS info from image"); } } if (!m_tagList.isEmpty() && m_writeXmpTags) { QStringList tagSeq; for (int i = 0 ; i < m_tagList.count() ; ++i) { QList currentTagList = m_tagList[i]; QString tag; for (int j = 0 ; j < currentTagList.count() ; ++j) { tag.append(QString::fromLatin1("/") + currentTagList[j].tagName); } tag.remove(0, 1); tagSeq.append(tag); } bool success = meta->setXmpTagStringSeq("Xmp.digiKam.TagsList", tagSeq); if (!success) { returnString = i18n("Failed to save tags to file."); } success = meta->setXmpTagStringSeq("Xmp.dc.subject", tagSeq); if (!success) { returnString = i18n("Failed to save tags to file."); } } } if (success) { success = meta->save(m_url.toLocalFile()); if (!success) { returnString = i18n("Unable to save changes to file"); } else { m_dirty = false; m_savedState = m_gpsData; m_tagListDirty = false; m_savedTagList = m_tagList; } } if (returnString.isEmpty()) { // mark all changes as not dirty and tell the model: emitDataChanged(); } return returnString; } /** * @brief Restore the gps data to @p container. Sets m_dirty to false if container equals savedState. */ void GPSImageItem::restoreGPSData(const GPSDataContainer& container) { m_dirty = !(container == m_savedState); m_gpsData = container; emitDataChanged(); } void GPSImageItem::restoreRGTagList(const QList >& tagList) { //TODO: override == operator if (tagList.count() != m_savedTagList.count()) { m_tagListDirty = true; } else { for (int i = 0 ; i < tagList.count() ; ++i) { bool foundNotEqual = false; if (tagList[i].count() != m_savedTagList[i].count()) { m_tagListDirty = true; break; } for (int j = 0 ; j < tagList[i].count() ; ++j) { if (tagList[i].at(j).tagName != m_savedTagList[i].at(j).tagName) { foundNotEqual = true; break; } } if (foundNotEqual) { m_tagListDirty = true; break; } } } m_tagList = tagList; emitDataChanged(); } bool GPSImageItem::isDirty() const { return m_dirty; } QUrl GPSImageItem::url() const { return m_url; } QDateTime GPSImageItem::dateTime() const { return m_dateTime; } GeoCoordinates GPSImageItem::coordinates() const { return m_gpsData.getCoordinates(); } GPSDataContainer GPSImageItem::gpsData() const { return m_gpsData; } void GPSImageItem::setGPSData(const GPSDataContainer& container) { m_gpsData = container; m_dirty = true; emitDataChanged(); } void GPSImageItem::setTagList(const QList >& externalTagList) { m_tagList = externalTagList; m_tagListDirty = true; emitDataChanged(); } bool GPSImageItem::isTagListDirty() const { return m_tagListDirty; } QList > GPSImageItem::getTagList() const { return m_tagList; } } // namespace Digikam diff --git a/core/utilities/import/backend/umscamera.cpp b/core/utilities/import/backend/umscamera.cpp index 9b9b7e28be..320f982658 100644 --- a/core/utilities/import/backend/umscamera.cpp +++ b/core/utilities/import/backend/umscamera.cpp @@ -1,693 +1,693 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-12-21 * Description : USB Mass Storage camera interface * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2005-2018 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "umscamera.h" // C ANSI includes extern "C" { #include #include #include #include } // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "digikam_config.h" #include "dimg.h" #include "dmetadata.h" #include "imagescanner.h" namespace Digikam { UMSCamera::UMSCamera(const QString& title, const QString& model, const QString& port, const QString& path) : DKCamera(title, model, port, path) { m_cancel = false; getUUIDFromSolid(); } UMSCamera::~UMSCamera() { } // Method not supported by UMS camera. bool UMSCamera::getPreview(QImage& /*preview*/) { return false; } // Method not supported by UMS camera. bool UMSCamera::capture(CamItemInfo& /*itemInfo*/) { return false; } DKCamera::CameraDriverType UMSCamera::cameraDriverType() { return DKCamera::UMSDriver; } QByteArray UMSCamera::cameraMD5ID() { QString camData; // Camera media UUID is used (if available) to improve fingerprint value // registration to database. We want to be sure that MD5 sum is really unique. // We don't use camera information here. media UUID is enough because it came be // mounted by a card reader or a camera. In this case, "already downloaded" flag will // be independent of the device used to mount memory card. camData.append(uuid()); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(camData.toUtf8()); return md5.result().toHex(); } bool UMSCamera::getFreeSpace(unsigned long& /*kBSize*/, unsigned long& /*kBAvail*/) { return false; // NOTE: implemented in gui, outside the camera thread. } bool UMSCamera::doConnect() { QFileInfo dir(m_path); if (!dir.exists() || !dir.isReadable() || !dir.isDir()) { return false; } if (dir.isWritable()) { m_deleteSupport = true; m_uploadSupport = true; m_mkDirSupport = true; m_delDirSupport = true; } else { m_deleteSupport = false; m_uploadSupport = false; m_mkDirSupport = false; m_delDirSupport = false; } m_thumbnailSupport = true; // UMS camera always support thumbnails. m_captureImageSupport = false; // UMS camera never support capture mode. return true; } void UMSCamera::cancel() { // set the cancel flag m_cancel = true; } bool UMSCamera::getFolders(const QString& folder) { if (m_cancel) { return false; } QDir dir(folder); dir.setFilter(QDir::Dirs | QDir::Executable); const QFileInfoList list = dir.entryInfoList(); if (list.isEmpty()) { return true; } QFileInfoList::const_iterator fi; QStringList subFolderList; for (fi = list.constBegin() ; !m_cancel && (fi != list.constEnd()) ; ++fi) { if (fi->fileName() == QLatin1String(".") || fi->fileName() == QLatin1String("..")) { continue; } QString subFolder = folder + QString(folder.endsWith(QLatin1Char('/')) ? QLatin1String("") : QLatin1String("/")) + fi->fileName(); subFolderList.append(subFolder); } if (subFolderList.isEmpty()) { return true; } emit signalFolderList(subFolderList); return true; } bool UMSCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& infoList) { m_cancel = false; infoList.clear(); QDir dir(folder); dir.setFilter(QDir::Files); if (!dir.exists()) { return false; } const QFileInfoList list = dir.entryInfoList(); if (list.isEmpty()) { return true; // Nothing to do. } for (QFileInfoList::const_iterator fi = list.constBegin() ; !m_cancel && (fi != list.constEnd()) ; ++fi) { CamItemInfo info; getItemInfo(folder, fi->fileName(), info, useMetadata); infoList.append(info); } return true; } void UMSCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata) { info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; info.name = itemName; QFileInfo fi(info.folder + info.name); info.size = fi.size(); info.readPermissions = fi.isReadable(); info.writePermissions = fi.isWritable(); info.mime = mimeType(fi.suffix().toLower()); if (!info.mime.isEmpty()) { if (useMetadata) { // Try to use file metadata DMetadata meta; getMetadata(folder, itemName, meta); fillItemInfoFromMetadata(info, meta); // Fall back to file system info if (info.ctime.isNull()) { info.ctime = ImageScanner::creationDateFromFilesystem(fi); } } else { // Only use file system date info.ctime = ImageScanner::creationDateFromFilesystem(fi); } } // if we have an image, allow previews // TODO allow video previews at some point? /* if (info.mime.startsWith(QLatin1String("image/")) || info.mime.startsWith(QLatin1String("video/"))) */ if (info.mime.startsWith(QLatin1String("image/"))) { info.previewPossible = true; } } bool UMSCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail) { m_cancel = false; - QString path = folder + QLatin1String("/") + itemName; + QString path = folder + QLatin1Char('/') + itemName; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getImagePreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); - if (thumbnail.load(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm"))) // Lowercase + if (thumbnail.load(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } - else if (thumbnail.load(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM"))) // Uppercase + else if (thumbnail.load(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). qCDebug(DIGIKAM_IMPORTUI_LOG) << "Use DImg loader to get thumbnail from : " << path; DImg dimgThumb; // skip loading the data we don't need to speed it up. dimgThumb.load(path, false /*loadMetadata*/, false /*loadICCData*/, false /*loadUniqueHash*/, false /*loadHistory*/); if (!dimgThumb.isNull()) { thumbnail = dimgThumb.copyQImage(); return true; } return false; } bool UMSCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta) { QFileInfo fi, thmlo, thmup; bool ret = false; - fi.setFile(folder + QLatin1String("/") + itemName); - thmlo.setFile(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm")); - thmup.setFile(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM")); + fi.setFile(folder + QLatin1Char('/') + itemName); + thmlo.setFile(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm")); + thmup.setFile(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM")); if (thmlo.exists()) { // Try thumbnail sidecar files with lowercase extension. ret = meta.load(thmlo.filePath()); } else if (thmup.exists()) { // Try thumbnail sidecar files with uppercase extension. ret = meta.load(thmup.filePath()); } else { // If no thumbnail sidecar file available, try to load image metadata for files. ret = meta.load(fi.filePath()); } return ret; } bool UMSCamera::downloadItem(const QString& folder, const QString& itemName, const QString& saveFile) { m_cancel = false; - QString src = folder + QLatin1String("/") + itemName; + QString src = folder + QLatin1Char('/') + itemName; QString dest = saveFile; QFile sFile(src); QFile dFile(dest); if (!sFile.open(QIODevice::ReadOnly)) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; return false; } if (!dFile.open(QIODevice::WriteOnly)) { sFile.close(); qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; return false; } const int MAX_IPC_SIZE = (1024 * 32); char buffer[MAX_IPC_SIZE]; qint64 len; while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) { if ((len == -1) || (dFile.write(buffer, (quint64)len) != len)) { sFile.close(); dFile.close(); return false; } } sFile.close(); dFile.close(); // Set the file modification time of the downloaded file to the original file. // NOTE: this behavior don't need to be managed through Setup/Metadata settings. QT_STATBUF st; if (QT_STAT(QFile::encodeName(src).constData(), &st) == 0) { struct utimbuf ut; ut.modtime = st.st_mtime; ut.actime = st.st_atime; ::utime(QFile::encodeName(dest).constData(), &ut); } return true; } bool UMSCamera::setLockItem(const QString& folder, const QString& itemName, bool lock) { - QString src = folder + QLatin1String("/") + itemName; + QString src = folder + QLatin1Char('/') + itemName; if (lock) { // Lock the file to set read only flag if (::chmod(QFile::encodeName(src).constData(), S_IREAD) == -1) { return false; } } else { // Unlock the file to set read/write flag if (::chmod(QFile::encodeName(src).constData(), S_IREAD | S_IWRITE) == -1) { return false; } } return true; } bool UMSCamera::deleteItem(const QString& folder, const QString& itemName) { m_cancel = false; // Any camera provide THM (thumbnail) file with real image. We need to remove it also. - QFileInfo fi(folder + QLatin1String("/") + itemName); + QFileInfo fi(folder + QLatin1Char('/') + itemName); - QFileInfo thmLo(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm")); // Lowercase + QFileInfo thmLo(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm")); // Lowercase if (thmLo.exists()) { ::unlink(QFile::encodeName(thmLo.filePath()).constData()); } - QFileInfo thmUp(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM")); // Uppercase + QFileInfo thmUp(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM")); // Uppercase if (thmUp.exists()) { ::unlink(QFile::encodeName(thmUp.filePath()).constData()); } // Remove the real image. - return (::unlink(QFile::encodeName(folder + QLatin1String("/") + itemName).constData()) == 0); + return (::unlink(QFile::encodeName(folder + QLatin1Char('/') + itemName).constData()) == 0); } bool UMSCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& info) { m_cancel = false; - QString dest = folder + QLatin1String("/") + itemName; + QString dest = folder + QLatin1Char('/') + itemName; QString src = localFile; QFile sFile(src); QFile dFile(dest); if (!sFile.open(QIODevice::ReadOnly)) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; return false; } if (!dFile.open(QIODevice::WriteOnly)) { sFile.close(); qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; return false; } const int MAX_IPC_SIZE = (1024 * 32); char buffer[MAX_IPC_SIZE]; qint64 len; while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) { if ((len == -1) || (dFile.write(buffer, (quint64)len) == -1)) { sFile.close(); dFile.close(); return false; } } sFile.close(); dFile.close(); // Set the file modification time of the uploaded file to original file. // NOTE: this behavior don't need to be managed through Setup/Metadata settings. QT_STATBUF st; if (QT_STAT(QFile::encodeName(src).constData(), &st) == 0) { struct utimbuf ut; ut.modtime = st.st_mtime; ut.actime = st.st_atime; ::utime(QFile::encodeName(dest).constData(), &ut); } // Get new camera item information. PhotoInfoContainer pInfo; DMetadata meta; QFileInfo fi(dest); QString mime = mimeType(fi.suffix().toLower()); if (!mime.isEmpty()) { QSize dims; QDateTime dt; // Try to load image metadata. meta.load(fi.filePath()); dt = meta.getImageDateTime(); dims = meta.getImageDimensions(); pInfo = meta.getPhotographInformation(); if (dt.isNull()) // fall back to file system info { dt = ImageScanner::creationDateFromFilesystem(fi); } info.name = fi.fileName(); - info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1String("/") : folder; + info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; info.mime = mime; info.ctime = dt; info.size = fi.size(); info.width = dims.width(); info.height = dims.height(); info.downloaded = CamItemInfo::DownloadUnknown; info.readPermissions = fi.isReadable(); info.writePermissions = fi.isWritable(); info.photoInfo = pInfo; } return true; } bool UMSCamera::cameraSummary(QString& summary) { summary = QString(i18n("Mounted Camera driver for USB/IEEE1394 mass storage cameras and " "Flash disk card readers.

")); // we do not expect title/model/etc. to contain newlines, // so we just escape HTML characters summary += i18nc("@info List of device properties", "Title: %1
" "Model: %2
" "Port: %3
" "Path: %4
" "UUID: %5

", title().toHtmlEscaped(), model().toHtmlEscaped(), port().toHtmlEscaped(), path().toHtmlEscaped(), uuid().toHtmlEscaped()); summary += i18nc("@info List of supported device operations", "Thumbnails: %1
" "Capture image: %2
" "Delete items: %3
" "Upload items: %4
" "Create directories: %5
" "Delete directories: %6

", thumbnailSupport() ? i18n("yes") : i18n("no"), captureImageSupport() ? i18n("yes") : i18n("no"), deleteSupport() ? i18n("yes") : i18n("no"), uploadSupport() ? i18n("yes") : i18n("no"), mkDirSupport() ? i18n("yes") : i18n("no"), delDirSupport() ? i18n("yes") : i18n("no")); return true; } bool UMSCamera::cameraManual(QString& manual) { manual = QString(i18n("For more information about the Mounted Camera driver, " "please read the Supported Digital Still " "Cameras section in the digiKam manual.")); return true; } bool UMSCamera::cameraAbout(QString& about) { about = QString(i18n("The Mounted Camera driver is a simple interface to a camera disk " "mounted locally on your system.

" "It does not use libgphoto2 drivers.

" "To report any problems with this driver, please contact the digiKam team at:

" "http://www.digikam.org/?q=contact")); return true; } void UMSCamera::getUUIDFromSolid() { QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); foreach(const Solid::Device& accessDevice, devices) { // check for StorageAccess if (!accessDevice.is()) { continue; } const Solid::StorageAccess* const access = accessDevice.as(); if (!access->isAccessible()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } if (!driveDevice.isValid()) { continue; } // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } Solid::StorageVolume* const volume = volumeDevice.as(); if (m_path.startsWith(QDir::fromNativeSeparators(access->filePath())) && QDir::fromNativeSeparators(access->filePath()) != QLatin1String("/")) { m_uuid = volume->uuid(); } } } } // namespace Digikam diff --git a/core/utilities/setup/metadata/advancedmetadatatab.cpp b/core/utilities/setup/metadata/advancedmetadatatab.cpp index b4dc0218cc..440bccdf1d 100644 --- a/core/utilities/setup/metadata/advancedmetadatatab.cpp +++ b/core/utilities/setup/metadata/advancedmetadatatab.cpp @@ -1,533 +1,533 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-06-16 * Description : Advanced Configuration tab for metadata. * * Copyright (C) 2015 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ #include "advancedmetadatatab.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dmetadatasettings.h" #include "namespacelistview.h" #include "namespaceeditdlg.h" #include "dmessagebox.h" #include "digikam_debug.h" namespace Digikam { class AdvancedMetadataTab::Private { public: explicit Private() { metadataType = 0; operationType = 0; addButton = 0; editButton = 0; deleteButton = 0; moveUpButton = 0; moveDownButton = 0; revertChanges = 0; resetButton = 0; unifyReadWrite = 0; namespaceView = 0; metadataTypeSize = 0; changed = false; } QComboBox* metadataType; QComboBox* operationType; QPushButton* addButton; QPushButton* editButton; QPushButton* deleteButton; QPushButton* moveUpButton; QPushButton* moveDownButton; QPushButton* revertChanges; QPushButton* resetButton; QCheckBox* unifyReadWrite; QList models; NamespaceListView* namespaceView; DMetadataSettingsContainer container; int metadataTypeSize; bool changed; }; AdvancedMetadataTab::AdvancedMetadataTab(QWidget* const parent) : QWidget(parent), d(new Private()) { // ---------- Advanced Configuration Panel ----------------------------- d->container = DMetadataSettings::instance()->settings(); setUi(); setModels(); connectButtons(); d->unifyReadWrite->setChecked(d->container.unifyReadWrite()); connect(d->unifyReadWrite, SIGNAL(toggled(bool)), this, SLOT(slotUnifyChecked(bool))); connect(d->metadataType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged())); connect(d->operationType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged())); /** * Connect all actions to slotRevertAvailable, which will enable revert to original * if an add, edit, delete, or reorder was made */ connect(d->namespaceView, SIGNAL(signalItemsChanged()), this, SLOT(slotRevertChangesAvailable())); if (d->unifyReadWrite->isChecked()) { d->operationType->setEnabled(false); } } AdvancedMetadataTab::~AdvancedMetadataTab() { delete d; } void AdvancedMetadataTab::slotResetToDefault() { const int result = DMessageBox::showContinueCancel(QMessageBox::Warning, this, i18n("Warning"), i18n("This option will reset configuration to default\n" "All your changes will be lost.\n " "Do you want to continue?")); if (result != QMessageBox::Yes) { return; } d->container.defaultValues(); d->models.at(getModelIndex())->clear(); setModelData(d->models.at(getModelIndex()), getCurrentContainer()); d->namespaceView->setModel(d->models.at(getModelIndex())); } void AdvancedMetadataTab::slotRevertChanges() { d->models.at(getModelIndex())->clear(); setModelData(d->models.at(getModelIndex()), getCurrentContainer()); d->namespaceView->setModel(d->models.at(getModelIndex())); d->changed = false; d->revertChanges->setEnabled(false); } void AdvancedMetadataTab::slotAddNewNamespace() { NamespaceEntry entry; // Setting some default parameters; - if (d->metadataType->currentData().toString() == QLatin1String(DM_TAG_CONTAINER)) + if (d->metadataType->currentData().toString() == QString::fromUtf8(DM_TAG_CONTAINER)) { entry.nsType = NamespaceEntry::TAGS; } - else if (d->metadataType->currentData().toString() == QLatin1String(DM_RATING_CONTAINER)) + else if (d->metadataType->currentData().toString() == QString::fromUtf8(DM_RATING_CONTAINER)) { entry.nsType = NamespaceEntry::RATING; } - else if (d->metadataType->currentData().toString() == QLatin1String(DM_COMMENT_CONTAINER)) + else if (d->metadataType->currentData().toString() == QString::fromUtf8(DM_COMMENT_CONTAINER)) { entry.nsType = NamespaceEntry::COMMENT; } entry.isDefault = false; entry.subspace = NamespaceEntry::XMP; if (!NamespaceEditDlg::create(qApp->activeWindow(), entry)) { return; } QStandardItem* const root = d->models.at(getModelIndex())->invisibleRootItem(); QStandardItem* const item = new QStandardItem(entry.namespaceName); setDataToItem(item, entry); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); root->appendRow(item); getCurrentContainer().append(entry); slotRevertChangesAvailable(); } void AdvancedMetadataTab::slotEditNamespace() { if (!d->namespaceView->currentIndex().isValid()) { return; } NamespaceEntry entry = getCurrentContainer().at(d->namespaceView->currentIndex().row()); if (!NamespaceEditDlg::edit(qApp->activeWindow(), entry)) { return; } QStandardItem* const root = d->models.at(getModelIndex())->invisibleRootItem(); QStandardItem* const item = root->child(d->namespaceView->currentIndex().row()); getCurrentContainer().replace(d->namespaceView->currentIndex().row(), entry); setDataToItem(item, entry); slotRevertChangesAvailable(); } void AdvancedMetadataTab::applySettings() { - QList keys = d->container.mappingKeys(); - int index = 0; + QList keys = d->container.mappingKeys(); + int index = 0; - foreach(const QLatin1String& str, keys) + foreach(const QString& str, keys) { d->container.getReadMapping(str).clear(); saveModelData(d->models.at(index++), d->container.getReadMapping(str)); } - foreach(const QLatin1String& str, keys) + foreach(const QString& str, keys) { d->container.getWriteMapping(str).clear(); saveModelData(d->models.at(index++), d->container.getWriteMapping(str)); } DMetadataSettings::instance()->setSettings(d->container); } void AdvancedMetadataTab::slotUnifyChecked(bool value) { d->operationType->setDisabled(value); d->container.setUnifyReadWrite(value); d->operationType->setCurrentIndex(0); slotIndexChanged(); } void AdvancedMetadataTab::slotIndexChanged() { d->namespaceView->setModel(d->models.at(getModelIndex())); } void AdvancedMetadataTab::slotRevertChangesAvailable() { if (!d->changed) { d->revertChanges->setEnabled(true); d->changed = true; } } void AdvancedMetadataTab::connectButtons() { connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddNewNamespace())); connect(d->editButton, SIGNAL(clicked()), this, SLOT(slotEditNamespace())); connect(d->deleteButton, SIGNAL(clicked()), d->namespaceView, SLOT(slotDeleteSelected())); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotResetToDefault())); connect(d->revertChanges, SIGNAL(clicked()), this, SLOT(slotRevertChanges())); connect(d->moveUpButton, SIGNAL(clicked()), d->namespaceView, SLOT(slotMoveItemUp())); connect(d->moveDownButton, SIGNAL(clicked()), d->namespaceView, SLOT(slotMoveItemDown())); } void AdvancedMetadataTab::setModelData(QStandardItemModel* model, const QList& container) { QStandardItem* const root = model->invisibleRootItem(); for (NamespaceEntry e : container) { QStandardItem* const item = new QStandardItem(e.namespaceName); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); setDataToItem(item, e); root->appendRow(item); } connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotRevertChangesAvailable())); } void AdvancedMetadataTab::setUi() { QVBoxLayout* const advancedConfLayout = new QVBoxLayout(this); QHBoxLayout* const topLayout = new QHBoxLayout(); QHBoxLayout* const bottomLayout = new QHBoxLayout(); QLabel* const tipLabel = new QLabel(this); tipLabel->setTextFormat(Qt::RichText); tipLabel->setWordWrap(true); tipLabel->setText(i18n("Advanced configuration menu allow you to manage metadata namespaces" " used by digiKam to store and retrieve tags, rating and comments.
" "Note: Order is important when reading metadata" )); //--- Top layout ---------------- d->metadataType = new QComboBox(this); d->operationType = new QComboBox(this); d->operationType->insertItems(0, QStringList() << i18n("Read Options") << i18n("Write Options")); d->unifyReadWrite = new QCheckBox(i18n("Unify read and write")); topLayout->addWidget(d->metadataType); topLayout->addWidget(d->operationType); topLayout->addWidget(d->unifyReadWrite); //------------ Bottom Layout------------- // View d->namespaceView = new NamespaceListView(this); // Buttons QVBoxLayout* const buttonsLayout = new QVBoxLayout(); buttonsLayout->setAlignment(Qt::AlignTop); d->addButton = new QPushButton(QIcon::fromTheme(QLatin1String("list-add")), i18n("Add")); d->editButton = new QPushButton(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit")); d->deleteButton = new QPushButton(QIcon::fromTheme(QLatin1String("window-close")), i18n("Delete")); d->moveUpButton = new QPushButton(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Up")); d->moveDownButton = new QPushButton(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Down")); d->revertChanges = new QPushButton(QIcon::fromTheme(QLatin1String("edit-undo")), i18n("Revert Changes")); // Revert changes is disabled, until a change is made d->revertChanges->setEnabled(false); d->resetButton = new QPushButton(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset to Default")); buttonsLayout->addWidget(d->addButton); buttonsLayout->addWidget(d->editButton); buttonsLayout->addWidget(d->deleteButton); buttonsLayout->addWidget(d->moveUpButton); buttonsLayout->addWidget(d->moveDownButton); buttonsLayout->addWidget(d->revertChanges); buttonsLayout->addWidget(d->resetButton); QVBoxLayout* const vbox = new QVBoxLayout(); vbox->addWidget(d->namespaceView); bottomLayout->addLayout(vbox); bottomLayout->addLayout(buttonsLayout); advancedConfLayout->addWidget(tipLabel); advancedConfLayout->addLayout(topLayout); advancedConfLayout->addLayout(bottomLayout); } void AdvancedMetadataTab::setDataToItem(QStandardItem* item, NamespaceEntry& entry) { item->setData(entry.namespaceName, Qt::DisplayRole); item->setData(entry.namespaceName, NAME_ROLE); item->setData((int)entry.tagPaths, ISTAG_ROLE); item->setData(entry.separator, SEPARATOR_ROLE); item->setData((int)entry.nsType, NSTYPE_ROLE); if (entry.nsType == NamespaceEntry::RATING) { item->setData(entry.convertRatio.at(0), ZEROSTAR_ROLE); item->setData(entry.convertRatio.at(1), ONESTAR_ROLE); item->setData(entry.convertRatio.at(2), TWOSTAR_ROLE); item->setData(entry.convertRatio.at(3), THREESTAR_ROLE); item->setData(entry.convertRatio.at(4), FOURSTAR_ROLE); item->setData(entry.convertRatio.at(5), FIVESTAR_ROLE); } item->setData((int)entry.specialOpts, SPECIALOPTS_ROLE); item->setData(entry.alternativeName, ALTNAME_ROLE); item->setData((int)entry.subspace, SUBSPACE_ROLE); item->setData((int)entry.secondNameOpts, ALTNAMEOPTS_ROLE); item->setData(entry.isDefault, ISDEFAULT_ROLE); item->setCheckable(true); if (!entry.isDisabled) { item->setCheckState(Qt::Checked); } } int AdvancedMetadataTab::getModelIndex() { if (d->unifyReadWrite->isChecked()) { return d->metadataType->currentIndex(); } else { // for 3 metadata types: // read operation = 3*0 + (0, 1, 2) // write operation = 3*1 + (0, 1, 2) = (3, 4 ,5) return (d->metadataTypeSize * d->operationType->currentIndex()) + d->metadataType->currentIndex(); } } QList& AdvancedMetadataTab::getCurrentContainer() { int currentIndex = getModelIndex(); if (currentIndex >= d->metadataTypeSize) { - return d->container.getWriteMapping(QLatin1String(d->metadataType->currentData().toByteArray())); + return d->container.getWriteMapping(QString::fromUtf8(d->metadataType->currentData().toByteArray())); } else { - return d->container.getReadMapping(QLatin1String(d->metadataType->currentData().toByteArray())); + return d->container.getReadMapping(QString::fromUtf8(d->metadataType->currentData().toByteArray())); } } void AdvancedMetadataTab::setModels() { - QList keys = d->container.mappingKeys(); + QList keys = d->container.mappingKeys(); - foreach(const QLatin1String& str, keys) + foreach(const QString& str, keys) { - d->metadataType->addItem(i18n(str.data()), str); + d->metadataType->addItem(str, str); } d->metadataTypeSize = keys.size(); for (int i = 0 ; i < keys.size() * 2; i++) { d->models.append(new QStandardItemModel(this)); } int index = 0; - foreach(const QLatin1String& str, keys) + foreach(const QString& str, keys) { setModelData(d->models.at(index++), d->container.getReadMapping(str)); } - foreach(const QLatin1String& str, keys) + foreach(const QString& str, keys) { setModelData(d->models.at(index++), d->container.getWriteMapping(str)); } slotIndexChanged(); } void AdvancedMetadataTab::saveModelData(QStandardItemModel* model, QList& container) { QStandardItem* const root = model->invisibleRootItem(); if (!root->hasChildren()) { return; } for (int i = 0 ; i < root->rowCount(); i++) { NamespaceEntry ns; QStandardItem* const current = root->child(i); ns.namespaceName = current->data(NAME_ROLE).toString(); ns.tagPaths = (NamespaceEntry::TagType)current->data(ISTAG_ROLE).toInt(); ns.separator = current->data(SEPARATOR_ROLE).toString(); ns.nsType = (NamespaceEntry::NamespaceType)current->data(NSTYPE_ROLE).toInt(); if (ns.nsType == NamespaceEntry::RATING) { ns.convertRatio.append(current->data(ZEROSTAR_ROLE).toInt()); ns.convertRatio.append(current->data(ONESTAR_ROLE).toInt()); ns.convertRatio.append(current->data(TWOSTAR_ROLE).toInt()); ns.convertRatio.append(current->data(THREESTAR_ROLE).toInt()); ns.convertRatio.append(current->data(FOURSTAR_ROLE).toInt()); ns.convertRatio.append(current->data(FIVESTAR_ROLE).toInt()); } ns.specialOpts = (NamespaceEntry::SpecialOptions)current->data(SPECIALOPTS_ROLE).toInt(); ns.alternativeName = current->data(ALTNAME_ROLE).toString(); ns.subspace = (NamespaceEntry::NsSubspace)current->data(SUBSPACE_ROLE).toInt(); ns.secondNameOpts = (NamespaceEntry::SpecialOptions)current->data(ALTNAMEOPTS_ROLE).toInt(); ns.index = i; ns.isDefault = current->data(ISDEFAULT_ROLE).toBool(); if (current->checkState() == Qt::Checked) { ns.isDisabled = false; } else { ns.isDisabled = true; } container.append(ns); } } } // namespace Digikam